@konductro/cursor-plugin 1.2.0 → 1.3.0

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 CHANGED
@@ -63,7 +63,7 @@ Tools are called automatically by Cursor's agent based on your requests:
63
63
  | `update_work_item` | Change the title, description or acceptance criteria of a work item you own |
64
64
  | `delete_work_item` | Delete a task or bug that has not been started |
65
65
  | `list_tech_analysis_tasks` | List tech analysis assignments |
66
- | `get_task_context` | Load tech analysis context |
66
+ | `get_task_context` | Load tech analysis context, plus the requester's message when a refresh was asked for |
67
67
  | `submit_tech_analysis` | Submit analysis document |
68
68
  | `list_decomposition_tasks` | List story decomposition assignments |
69
69
  | `get_decomposition_context` | Load story context |
package/bin/setup.js CHANGED
@@ -136,6 +136,7 @@ function main() {
136
136
  console.log(' prototype — build a UX prototype');
137
137
  console.log(' find-prototype — find and download prototypes');
138
138
  console.log(' bug-enrich — enrich a bug report');
139
+ console.log(' demand-analysis — size a demand against the real code');
139
140
  console.log('');
140
141
  console.log('Reload Cursor to activate (Cmd+Shift+P → "Developer: Reload Window").');
141
142
  console.log('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@konductro/cursor-plugin",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Cursor IDE plugin for the Konductro platform: technical analysis, delivery tasks, repository profiling, and prototype workflows.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -32,6 +32,24 @@ function loadFileConfig() {
32
32
  const fileConfig = loadFileConfig();
33
33
  const KONDUCTRO_URL = (process.env.KONDUCTRO_URL || fileConfig.url || fileConfig.konductroUrl || '').replace(/\/$/, '');
34
34
  const CLI_TOKEN = process.env.KONDUCTRO_CLI_TOKEN || fileConfig.token || fileConfig.cliToken || '';
35
+
36
+ /*
37
+ * What this plugin tells the server it is, on every request.
38
+ *
39
+ * READ FROM package.json, never a constant. The server's refusal of an outdated plugin is
40
+ * based on this string, so a hand-maintained copy that drifts from the published version
41
+ * is worse than no header at all: it would be believed.
42
+ *
43
+ * `readFileSync` rather than a JSON import assertion, whose syntax differs between Node 18
44
+ * and 20 and would break for whichever developers are on the other one.
45
+ *
46
+ * The shape is `<plugin>/<version>` and is identical across all four plugins, so the
47
+ * server parses one format rather than four.
48
+ */
49
+ const PLUGIN_VERSION = JSON.parse(
50
+ readFileSync(new URL('../package.json', import.meta.url), 'utf8'),
51
+ ).version;
52
+ const PLUGIN_ID = `cursor/${PLUGIN_VERSION}`;
35
53
  const NOT_CONFIGURED_MESSAGE =
36
54
  'Konductro is not configured. Run `konductro-cursor-setup --url <URL> --token <TOKEN>`, or set KONDUCTRO_URL and KONDUCTRO_CLI_TOKEN in the MCP server environment.';
37
55
 
@@ -39,6 +57,7 @@ async function konductroFetch(path, options = {}) {
39
57
  const url = `${KONDUCTRO_URL}${path}`;
40
58
  const headers = {
41
59
  'Authorization': `Bearer ${CLI_TOKEN}`,
60
+ 'X-Konductro-Plugin': PLUGIN_ID,
42
61
  ...options.headers,
43
62
  };
44
63
  // Only set Content-Type and default body for methods that send JSON
@@ -226,7 +245,7 @@ server.tool(
226
245
  }
227
246
 
228
247
  const taskList = tasks.map((t, i) =>
229
- `${i + 1}. **${t.projectName}** — ${t.phaseName}\n Client: ${t.clientName}\n Phase ID: ${t.phaseId}\n ${t.hasExistingDraft ? '(has existing draft)' : '(new analysis)'}`
248
+ `${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)'}`
230
249
  ).join('\n\n');
231
250
 
232
251
  return {
@@ -256,6 +275,25 @@ server.tool(
256
275
  if (context.phase.description) contextText += `**Description:** ${context.phase.description}\n`;
257
276
  contextText += `\n`;
258
277
 
278
+ // KON-321: a refresh was requested, so this is a revision of an approved
279
+ // analysis rather than a first pass. Sits above the repositories and the
280
+ // previous document deliberately — it changes what is being asked for, so
281
+ // it has to be read before the material it applies to. Absent on a normal
282
+ // task, in which case the output is exactly what it was before.
283
+ if (context.refreshContext) {
284
+ const { requestedBy, message } = context.refreshContext;
285
+ contextText += `### Refresh Requested\n\n`;
286
+ contextText += `A refresh of this technical analysis was requested`;
287
+ if (requestedBy) contextText += ` by ${requestedBy}`;
288
+ contextText += `.\n\n`;
289
+ if (message) {
290
+ contextText += `**Context from the requester:**\n\n${message}\n\n`;
291
+ } else {
292
+ contextText += `No further context was given.\n\n`;
293
+ }
294
+ contextText += `Revise the existing analysis below rather than starting over.\n\n`;
295
+ }
296
+
259
297
  if (context.repositories.length > 0) {
260
298
  contextText += `### Repositories\n\n`;
261
299
  for (const repo of context.repositories) {
@@ -282,6 +320,8 @@ server.tool(
282
320
  }
283
321
  }
284
322
 
323
+ contextText += renderDocumentTemplate(context.documentTemplate);
324
+
285
325
  return {
286
326
  content: [{
287
327
  type: 'text',
@@ -291,6 +331,51 @@ server.tool(
291
331
  }
292
332
  );
293
333
 
334
+ /**
335
+ * The document structure this workspace requires, as the agent must read it.
336
+ *
337
+ * APPENDED, never woven into the blob above. Four plugins share that server response and
338
+ * the agent's prompt is sensitive to its shape, so everything before this point is
339
+ * byte-identical to what it has always been. A workspace with no template gets an empty
340
+ * string here and therefore exactly today's context.
341
+ *
342
+ * THE WORDING IS IDENTICAL IN ALL FOUR PLUGINS, deliberately. The story this belongs to
343
+ * requires the same behaviour from Claude, Amazon Q, Codex and Cursor, and four separately
344
+ * worded prompts is precisely how that stops being true. Change it here and copy it, do
345
+ * not improve it in one place.
346
+ *
347
+ * Standing content is reproduced WORD FOR WORD rather than summarised. It is usually a
348
+ * compliance clause the workspace requires verbatim, which is the whole reason the field
349
+ * exists.
350
+ */
351
+ function renderDocumentTemplate(template) {
352
+ if (!template) return '';
353
+
354
+ let text = `### Required Document Structure\n\n`;
355
+ text += `This workspace requires technical analysis documents to follow **${template.name}** `;
356
+ text += `(v${template.version}). Produce these sections, in this order, using these exact headings.\n\n`;
357
+
358
+ for (const [index, section] of template.sections.entries()) {
359
+ text += `${index + 1}. **${section.title}**`;
360
+ if (section.intent) text += ` — ${section.intent}`;
361
+ if (section.mustCover) text += ` _(must cover: do not produce the document until you can answer this)_`;
362
+ text += `\n`;
363
+ if (section.diagram && section.diagram !== 'none') {
364
+ const kind = section.diagram === 'any' ? 'a Mermaid diagram of whichever type fits' : `a Mermaid ${section.diagram} diagram`;
365
+ text += ` Include ${kind} inside this section.\n`;
366
+ }
367
+ if (section.standingContent) {
368
+ text += ` Reproduce this text inside the section, word for word:\n`;
369
+ text += ` > ${section.standingContent.split('\n').join('\n > ')}\n`;
370
+ }
371
+ }
372
+
373
+ text += `\nThis structure replaces any default you would otherwise use. `;
374
+ text += `The sections marked "must cover" are what your exploration and your questions `;
375
+ text += `must be able to answer before you produce the document.\n\n`;
376
+ return text;
377
+ }
378
+
294
379
  // Tool: Submit tech analysis
295
380
  server.tool(
296
381
  'submit_tech_analysis',
@@ -315,6 +400,216 @@ server.tool(
315
400
  }
316
401
  );
317
402
 
403
+ // ══ DEMAND ANALYSIS ════════════════════════════════════════════════════════
404
+ //
405
+ // The demand-level counterpart of the three tools above. A DEMAND has no project yet —
406
+ // which projects it lands in is decided at IT Eval, from what this analysis finds — so
407
+ // the context carries the whole workspace's repositories and the submission reports
408
+ // which of them the change actually touches.
409
+
410
+ // Tool: List pending demand analyses
411
+ server.tool(
412
+ 'list_demand_analyses',
413
+ '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.',
414
+ {},
415
+ async () => {
416
+ if (!KONDUCTRO_URL || !CLI_TOKEN) {
417
+ return {
418
+ content: [{
419
+ type: 'text',
420
+ text: 'Konductro is not configured. Please set your Konductro URL and CLI token in the plugin settings.',
421
+ }],
422
+ };
423
+ }
424
+
425
+ const { tasks } = await konductroFetch('/api/cli/demand-analyses');
426
+
427
+ if (!tasks || tasks.length === 0) {
428
+ return {
429
+ content: [{ type: 'text', text: 'No demand analyses assigned to you.' }],
430
+ };
431
+ }
432
+
433
+ const lines = tasks.map((t) => {
434
+ const parts = [`### ${t.key} — ${t.title}`];
435
+ if (t.businessUnit) parts.push(`**Business unit:** ${t.businessUnit}`);
436
+ if (t.nextSitting) parts.push(`**Back at the forum:** ${new Date(t.nextSitting).toISOString().slice(0, 10)}`);
437
+ if (t.startWith && t.startWith.length) parts.push(`**Start with:** ${t.startWith.join(', ')}`);
438
+ if (t.askedFor) parts.push(`**They asked:** ${t.askedFor}`);
439
+ parts.push(`\`get_demand_analysis_context\` with demandId ${t.key} to load it.`);
440
+ return parts.join('\n');
441
+ });
442
+
443
+ return {
444
+ content: [{
445
+ type: 'text',
446
+ text: `## Demand analyses assigned to you (${tasks.length})\n\n${lines.join('\n\n')}`,
447
+ }],
448
+ };
449
+ }
450
+ );
451
+
452
+ // Tool: Load one demand's analysis context
453
+ server.tool(
454
+ 'get_demand_analysis_context',
455
+ '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.',
456
+ {
457
+ demandId: z.string().describe('The demand key (e.g. DEM-4) or its UUID'),
458
+ },
459
+ async ({ demandId }) => {
460
+ if (!KONDUCTRO_URL || !CLI_TOKEN) {
461
+ return {
462
+ content: [{
463
+ type: 'text',
464
+ text: 'Konductro is not configured. Please set your Konductro URL and CLI token in the plugin settings.',
465
+ }],
466
+ };
467
+ }
468
+
469
+ const ctx = await konductroFetch(`/api/cli/demand-analyses/${demandId}/context`);
470
+ const d = ctx && ctx.demand;
471
+ if (!d) {
472
+ return {
473
+ content: [{
474
+ type: 'text',
475
+ text: `No demand context came back for ${demandId}. Check the key, and that the analysis is assigned to you.`,
476
+ }],
477
+ };
478
+ }
479
+
480
+ const cost = Object.entries(d.cost || {})
481
+ .filter(([, v]) => v)
482
+ .map(([k, v]) => `- **${k}:** ${v}`)
483
+ .join('\n') || '- Not given';
484
+
485
+ // THE PROJECT ID IS THE POINT OF THIS LIST, not decoration. `submit_demand_analysis`
486
+ // takes a `projectId` per proposed phase, and its description says to take it from
487
+ // here. Rendering only the project NAME left the analyst nowhere to get it: either
488
+ // the phase is submitted with no project, so the green light creates no real Phase
489
+ // and IT Eval prefills with nothing, or the agent invents a uuid and the whole
490
+ // submission is refused with PROJECT_NOT_FOUND.
491
+ const repos = (ctx.repositories || [])
492
+ .map((r) => `- \`${r.name}\` (${r.projectName}${r.type ? `, ${r.type}` : ''}) — ${r.url}\n projectId: \`${r.projectId}\``)
493
+ .join('\n') || '- None linked to any project yet';
494
+
495
+ const conversation = (ctx.conversation || [])
496
+ .map((c) => `- **${c.by}**${c.kind === 'amendment' ? ' (amendment)' : ''}: ${c.body}`)
497
+ .join('\n');
498
+
499
+ // THE WORKSPACE'S OWN QUESTIONS, which the block above does not carry.
500
+ //
501
+ // The fields rendered above are the nine Konductro ships. A workspace that added
502
+ // "Which regulator requires this?" has that question and its answer here and
503
+ // NOWHERE ELSE in this payload, so without this the analysis never learns it was
504
+ // asked — the one thing the whole templating feature exists for.
505
+ //
506
+ // Read off the PINNED template, so a question a later template deleted still shows
507
+ // on the demands that answered it. Unanswered questions are kept rather than
508
+ // dropped: that the forum asked and got nothing is itself worth knowing.
509
+ const tpl = ctx.template;
510
+ const templateBlock = tpl
511
+ ? [
512
+ `## The form they filled in — ${tpl.name} v${tpl.version}`,
513
+ '',
514
+ ...(tpl.sections || []).flatMap((section) => [
515
+ `### ${section.name}`,
516
+ ...(section.help ? ['', `_${section.help}_`] : []),
517
+ '',
518
+ ...(section.questions || []).map((q) =>
519
+ `**${q.question}**\n\n${q.answer || '_Not answered._'}\n`
520
+ ),
521
+ ]),
522
+ ].join('\n')
523
+ : '';
524
+
525
+ return {
526
+ content: [{
527
+ type: 'text',
528
+ text: [
529
+ `# ${d.key} — ${d.title}`,
530
+ '',
531
+ `**Raised by:** ${d.raisedBy}${d.businessUnit ? ` (${d.businessUnit})` : ''}`,
532
+ '',
533
+ '## What they need',
534
+ d.need,
535
+ '',
536
+ '## What happens today',
537
+ d.currentState || 'Not given',
538
+ '',
539
+ '## Who it affects',
540
+ [d.affects, d.affectsWho].filter(Boolean).join(' — ') || 'Not given',
541
+ '',
542
+ '## What it costs to not have it',
543
+ cost,
544
+ '',
545
+ '## Tied to a date',
546
+ 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',
547
+ '',
548
+ '## How they would know it worked',
549
+ d.successLooksLike || 'Not given',
550
+ '',
551
+ '## What the forum asked you to answer',
552
+ ctx.askedFor || 'Nothing specific. Answer the pitch.',
553
+ '',
554
+ '## Where to start looking',
555
+ (ctx.startWith && ctx.startWith.length) ? ctx.startWith.map((r) => `\`${r}\``).join(', ') : 'Nothing named — use your judgement.',
556
+ '',
557
+ '## Every repository in this workspace',
558
+ 'The starting point above is a hint, not a boundary. Report what the change ACTUALLY touches.',
559
+ '',
560
+ repos,
561
+ templateBlock ? `\n${templateBlock}` : '',
562
+ conversation ? `\n## Conversation on the demand\n${conversation}` : '',
563
+ ].join('\n'),
564
+ }],
565
+ };
566
+ }
567
+ );
568
+
569
+ // Tool: Submit a demand analysis
570
+ server.tool(
571
+ 'submit_demand_analysis',
572
+ '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.',
573
+ {
574
+ demandId: z.string().describe('The demand key (e.g. DEM-4) or its UUID'),
575
+ 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.'),
576
+ // BOTH OF THESE ARE CLEARED WHEN OMITTED, server-side and deliberately: a
577
+ // resubmission that dropped them used to keep the OLD proposal while the repos
578
+ // beside it reset to empty, leaving an analysis half from one run and half from
579
+ // another — which IT Eval then prefills from. So a second submission has to send
580
+ // all three fields or lose the two it leaves out.
581
+ foundRepos: z.array(z.string()).optional().describe('The repositories the change actually touches, by name. Not the ones you were told to start with. Resubmitting without this CLEARS it.'),
582
+ proposedPhases: z.array(z.object({
583
+ name: z.string().describe('What this phase delivers'),
584
+ projectId: z.string().uuid().optional().describe('The project it lands in, from the context. Omit if it genuinely belongs nowhere yet.'),
585
+ points: z.number().int().min(0).max(1000).describe('Story points for this phase alone. Never send a total; Konductro derives it.'),
586
+ why: z.string().optional().describe('Why it is its own phase rather than part of another'),
587
+ })).optional().describe('The phases you propose. A phase belongs to ONE project; work spanning two projects is two phases. Resubmitting without this CLEARS it.'),
588
+ },
589
+ async ({ demandId, summary, foundRepos, proposedPhases }) => {
590
+ if (!KONDUCTRO_URL || !CLI_TOKEN) {
591
+ return {
592
+ content: [{
593
+ type: 'text',
594
+ text: 'Konductro is not configured. Please set your Konductro URL and CLI token in the plugin settings.',
595
+ }],
596
+ };
597
+ }
598
+
599
+ const result = await konductroFetch(`/api/cli/demand-analyses/${demandId}/submit`, {
600
+ method: 'POST',
601
+ body: JSON.stringify({ summary, foundRepos, proposedPhases }),
602
+ });
603
+
604
+ return {
605
+ content: [{
606
+ type: 'text',
607
+ text: `## Demand analysis submitted\n\n${result.message}\n\n- Demand: ${result.key}\n- Submitted: ${result.submittedAt}\n\nThe demand forum has been notified and can now record the IT Eval.`,
608
+ }],
609
+ };
610
+ }
611
+ );
612
+
318
613
  // Tool: List pending decomposition tasks
319
614
  server.tool(
320
615
  'list_decomposition_tasks',
@@ -0,0 +1,107 @@
1
+ ---
2
+ description: USE WHEN an architect has been assigned a Konductro DEMAND technical analysis — a business request that has passed the demand forum and needs sizing against the real code before the forum will commit to it
3
+ globs:
4
+ alwaysApply: false
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**, which is the workspace's own template rather
40
+ than a fixed set of questions. A workspace that asks "Which regulator requires this?"
41
+ has that question and its answer here and nowhere else, and it is usually the one that
42
+ changes the estimate. Read it.
43
+ - What the forum specifically asked you to answer
44
+ - Every repository in the workspace, with its project
45
+
46
+ **Say the value case back to the user in one line.** An estimate has to be proportionate
47
+ to the thing being bought, and this is the only place that number appears.
48
+
49
+ ### Step 3: Read the code
50
+
51
+ The repositories are on this machine. Use Glob, Grep and Read. Konductro never clones
52
+ anything and neither do you: if a repository named in the context is not present locally,
53
+ say so rather than guessing at it.
54
+
55
+ Start with the repositories you were pointed at, then follow the work wherever it goes.
56
+
57
+ What you are trying to establish, in this order:
58
+
59
+ 1. **What already exists.** Half of every demand turns out to be built. Say what is there
60
+ before you say what is needed.
61
+ 2. **What actually has to change**, per repository, concretely enough that somebody could
62
+ argue with it.
63
+ 3. **What is in the way.** Missing data, an integration nobody has written, a migration
64
+ with no way back.
65
+ 4. **What the estimate rests on.** Every assumption you make is a way the number could be
66
+ wrong, and the forum is told which one when it is.
67
+
68
+ ### Step 4: Propose the phases
69
+
70
+ This is the part the forum needs most, and the part it cannot do itself.
71
+
72
+ - **A phase belongs to ONE project.** Work spanning two projects is two phases. This is
73
+ not a formality: each phase becomes a real Phase in that project when the demand is
74
+ green lit.
75
+ - **Order them by what has to land first.** If phase two cannot start until phase one is
76
+ deployed, say so in its `why`.
77
+ - **Points per phase, never a total.** Konductro derives the total. Sending your own is
78
+ how the headline number and the breakdown behind it end up disagreeing.
79
+ - **A phase with no obvious home** is allowed: omit `projectId` and say why in the `why`.
80
+ A visible gap beats a phase filed in whichever project came first.
81
+
82
+ ### Step 5: Submit
83
+
84
+ Call `submit_demand_analysis` with:
85
+
86
+ - `summary` — what you found. Structure it as: what exists, what has to change, risks,
87
+ assumptions. Markdown.
88
+ - `foundRepos` — the repositories the change actually touches. Not the hint you were given.
89
+ - `proposedPhases` — the breakdown above.
90
+
91
+ The forum is notified and records the IT Eval from your proposal. They can change any of
92
+ it; you are advising, not deciding.
93
+
94
+ **If you submit a second time, send all three again.** `foundRepos` and `proposedPhases`
95
+ are cleared when they are left out, deliberately: a resubmission that kept the old
96
+ proposal beside freshly reset repositories would be half one run and half another, and
97
+ IT Eval prefills from it.
98
+
99
+ ## The bar
100
+
101
+ **Never estimate something you have not read.** An unfamiliar name is a reason to go and
102
+ look, never a reason to assume it is small. If a repository you need is not on this
103
+ machine, the honest output is a summary that says which one and what it blocks — not a
104
+ number with a shrug behind it.
105
+
106
+ **Say what you did not check.** The forum is about to commit money against this. A gap you
107
+ name costs an hour; a gap you paper over costs the quarter.
@@ -20,11 +20,20 @@ Call `get_task_context` with the phase ID. This loads:
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
 
26
29
  ### Step 3: Explore the Codebase
27
30
 
31
+ **If the context carried a "Required Document Structure", that is your brief.** Every section in it is something you must be able to answer by the end of this step, and the ones marked "must cover" are not optional. Explore with those sections in mind rather than working through the generic list below and hoping it covers them — a workspace that asks for a Regulatory Impact section is telling you what to go and find out, and nothing in the generic list will lead you there.
32
+
33
+ Where the structure asks for something the generic list does not cover, go and look for it. Where it omits something, you do not need to chase it.
34
+
35
+ If no structure was supplied, use the list below as-is.
36
+
28
37
  Analyse the local codebase using file reading and search tools:
29
38
  - **Project structure** — folder layout, module organisation
30
39
  - **Tech stack** — frameworks, versions, build tools
@@ -40,6 +49,10 @@ Discuss findings with the user as you go. Ask clarifying questions.
40
49
 
41
50
  ### Step 4: Impact Analysis
42
51
 
52
+ **Before moving on, check yourself against the required structure.** Take each section, and each "must cover" section especially, and ask whether you could write it now from what you have found. Where you could not, that is a question for the user or another pass over the codebase — ask it now rather than discovering the gap while writing the document, when the honest options are guessing or going back.
53
+
54
+ This is the step that makes the structure mean something. Producing the right headings over content that does not answer them is worse than the default document, because it looks like compliance.
55
+
43
56
  Based on the requirements and your codebase exploration, assess:
44
57
  - **What needs to change** — which files, modules, or services
45
58
  - **What needs to be created** — new components, services, APIs
@@ -49,7 +62,12 @@ Based on the requirements and your codebase exploration, assess:
49
62
 
50
63
  ### Step 5: Produce the Document
51
64
 
52
- Produce a structured technical analysis document including:
65
+ **If the context carried a "Required Document Structure", follow it exactly** — those sections, in that order, under those headings. It replaces the default list below rather than supplementing it: do not add the default sections alongside it, and do not reorder it to something you find more natural. Where a section carries standing content, reproduce that text word for word; it is usually a clause the workspace is required to publish, and paraphrasing it defeats the point of it being there. Where a section asks for a diagram, draw one, as Mermaid, inside that section.
66
+
67
+ A document produced here must be indistinguishable from one produced for the same workspace in Konductro itself. That is the whole purpose of the structure being sent.
68
+
69
+ **If no structure was supplied**, use this default list:
70
+
53
71
  1. **Codebase Overview** — what exists today
54
72
  2. **Technical Stack** — frameworks, versions, build tools, deployment
55
73
  3. **Architecture Patterns** — structure, key design decisions
@@ -72,3 +90,4 @@ Ask the user to review the document. When approved, call `submit_tech_analysis`
72
90
  - Be thorough but focused — analyse what's relevant to the requirements
73
91
  - Ask the user questions — they know the codebase context you don't
74
92
  - Produce a complete, standalone document an architect can use without additional context
93
+ - When a required document structure is supplied, it wins over anything in this file. It is the workspace's own standard, and your job is to meet it, not to improve on it