@mmerterden/multi-agent-pipeline 20.23.0 → 20.23.2
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/CHANGELOG.md +16 -0
- package/docs/facts.json +1 -1
- package/install/catalog-history.json +1 -1
- package/manifest.json +19 -19
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/analysis-jira/SKILL.md +7 -0
- package/pipeline/lib/analysis-jira-write.sh +41 -8
- package/pipeline/multi-agent-refs/features/analysis-jira.md +15 -3
- package/pipeline/multi-agent-refs/generate-issue.md +1 -1
- package/pipeline/schemas/prefs.schema.json +23 -2
- package/pipeline/scripts/_diff-impact.mjs +55 -27
- package/pipeline/scripts/_supertypes.mjs +18 -5
- package/pipeline/scripts/analysis-story-body.mjs +26 -10
- package/pipeline/scripts/analysis-story-tree.mjs +73 -3
- package/pipeline/scripts/code-graph-rules/ios.json +1 -1
- package/pipeline/skills/.skill-manifest.json +2 -2
- package/pipeline/skills/shared/core/multi-agent-analysis-jira/SKILL.md +7 -0
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
*
|
|
51
51
|
* Usage:
|
|
52
52
|
* analysis-story-tree.mjs <analysis.md> [--json] [--brief] [--prefs FILE] [--page-url URL]
|
|
53
|
+
* analysis-story-tree.mjs --save-subtasks "<type>[,<type>...]"|none [--prefs FILE]
|
|
53
54
|
*
|
|
54
55
|
* Exit: 0 planned, 2 nothing to plan, 3 usage, 4 the document is not issue-ready.
|
|
55
56
|
*/
|
|
@@ -60,6 +61,7 @@ import { createHash } from "node:crypto";
|
|
|
60
61
|
import { homedir } from "node:os";
|
|
61
62
|
import { dirname, join } from "node:path";
|
|
62
63
|
import { hostCommand } from "../lib/user-facing.mjs";
|
|
64
|
+
import { lockTimeoutFrom, updateJsonFileSync } from "../lib/json-file-lock.mjs";
|
|
63
65
|
import { resolveConstitution } from "./constitution.mjs";
|
|
64
66
|
import { gateContext, recordQuietly } from "./gate-ledger.mjs";
|
|
65
67
|
import {
|
|
@@ -81,7 +83,11 @@ const PREFS_PATH =
|
|
|
81
83
|
: join(homedir(), ".claude", "multi-agent-preferences.json");
|
|
82
84
|
const pageUrlFlag = argv.indexOf("--page-url");
|
|
83
85
|
const PAGE_URL = pageUrlFlag !== -1 ? argv[pageUrlFlag + 1] : null;
|
|
84
|
-
const
|
|
86
|
+
const saveFlag = argv.indexOf("--save-subtasks");
|
|
87
|
+
const SAVE_SUBTASKS = saveFlag !== -1 ? (argv[saveFlag + 1] ?? "") : null;
|
|
88
|
+
const valueIdx = new Set(
|
|
89
|
+
[prefsFlag, pageUrlFlag, saveFlag].filter((i) => i !== -1).map((i) => i + 1),
|
|
90
|
+
);
|
|
85
91
|
const file = argv.find((a, i) => !a.startsWith("--") && !valueIdx.has(i));
|
|
86
92
|
|
|
87
93
|
function die(msg, code) {
|
|
@@ -153,7 +159,7 @@ export function identityLabel(prefix, docId, sourceIds) {
|
|
|
153
159
|
* issueTree.projectKey where the tree is created
|
|
154
160
|
* issueTree.epicIssueType the type name for an epic on this site
|
|
155
161
|
* issueTree.storyIssueType the type name for a story
|
|
156
|
-
* issueTree.subtaskIssueType null = discover
|
|
162
|
+
* issueTree.subtaskIssueType a name, a {role: name} map, or null = discover
|
|
157
163
|
* issueTree.subtaskRoles how a story is split; ships empty on purpose
|
|
158
164
|
* issueTree.channelComponents channel -> this site's component name
|
|
159
165
|
* issueTree.channelTeams channel -> this site's team name
|
|
@@ -162,6 +168,8 @@ export function identityLabel(prefix, docId, sourceIds) {
|
|
|
162
168
|
* issueTree.epicLinkFieldId the epic-link field, when not discovered
|
|
163
169
|
* issueTree.cloneByChannel one linked story per channel in channelComponents
|
|
164
170
|
* issueTree.cloneLinkType the link type between a story and its clones
|
|
171
|
+
* issueTree.storyPanels story section panel colours, or false for none
|
|
172
|
+
* issueTree.subtaskRolesDeclined the person chose no sub-tasks; do not ask again
|
|
165
173
|
*/
|
|
166
174
|
const ISSUE_TREE_SETTINGS = [
|
|
167
175
|
"issueTree.enabled",
|
|
@@ -177,6 +185,8 @@ const ISSUE_TREE_SETTINGS = [
|
|
|
177
185
|
"issueTree.epicLinkFieldId",
|
|
178
186
|
"issueTree.cloneByChannel",
|
|
179
187
|
"issueTree.cloneLinkType",
|
|
188
|
+
"issueTree.storyPanels",
|
|
189
|
+
"issueTree.subtaskRolesDeclined",
|
|
180
190
|
];
|
|
181
191
|
|
|
182
192
|
function readPrefs() {
|
|
@@ -195,6 +205,17 @@ function readPrefs() {
|
|
|
195
205
|
}
|
|
196
206
|
}
|
|
197
207
|
|
|
208
|
+
/**
|
|
209
|
+
* The sub-task type a role is created as, from issueTree.subtaskIssueType: a
|
|
210
|
+
* name for every role, or a {role: name} map with "*" for any other role. Null
|
|
211
|
+
* means the writer discovers it from the site.
|
|
212
|
+
*/
|
|
213
|
+
export function subtaskTypeFor(setting, role) {
|
|
214
|
+
if (typeof setting === "string" && setting) return setting;
|
|
215
|
+
if (setting && typeof setting === "object") return setting[role] ?? setting["*"] ?? null;
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
198
219
|
/** Every open placeholder that makes a document unfit to become work. */
|
|
199
220
|
export function openItems(text) {
|
|
200
221
|
const found = [];
|
|
@@ -377,6 +398,7 @@ export function plan(text, prefs, { pageUrl = null } = {}) {
|
|
|
377
398
|
profile,
|
|
378
399
|
pageUrl: pageUrl || fm.confluence_url || null,
|
|
379
400
|
redesign: String(fm.redesign || "").toLowerCase() === "true",
|
|
401
|
+
panels: prefs.storyPanels,
|
|
380
402
|
};
|
|
381
403
|
for (const story of stories) {
|
|
382
404
|
const b = buildStoryBody(story, bodyCtx);
|
|
@@ -423,6 +445,8 @@ export function plan(text, prefs, { pageUrl = null } = {}) {
|
|
|
423
445
|
epicLinkFieldId: prefs.epicLinkFieldId ?? null,
|
|
424
446
|
cloneByChannel: prefs.cloneByChannel === true,
|
|
425
447
|
cloneLinkType: prefs.cloneLinkType ?? "Relates",
|
|
448
|
+
storyPanels: prefs.storyPanels ?? null,
|
|
449
|
+
subtaskRolesDeclined: prefs.subtaskRolesDeclined === true,
|
|
426
450
|
},
|
|
427
451
|
nodes: stories,
|
|
428
452
|
writeCount: stories.reduce(
|
|
@@ -453,7 +477,50 @@ function consistencyOf(text, nodes) {
|
|
|
453
477
|
});
|
|
454
478
|
}
|
|
455
479
|
|
|
480
|
+
/**
|
|
481
|
+
* Save the sub-tasks a person chose for every story: each chosen site type
|
|
482
|
+
* becomes a role named after it and mapped to it, or "none" records that the
|
|
483
|
+
* question was answered with no sub-tasks.
|
|
484
|
+
*/
|
|
485
|
+
function saveSubtasks(choice) {
|
|
486
|
+
const names = String(choice)
|
|
487
|
+
.split(",")
|
|
488
|
+
.map((n) => n.trim())
|
|
489
|
+
.filter(Boolean);
|
|
490
|
+
const none = names.length === 1 && names[0].toLowerCase() === "none";
|
|
491
|
+
if (!names.length || names.some((n) => n.length > 100 || /[\n{}]/.test(n)))
|
|
492
|
+
return die("analysis-story-tree: --save-subtasks takes type names or none", 3);
|
|
493
|
+
try {
|
|
494
|
+
updateJsonFileSync(
|
|
495
|
+
PREFS_PATH,
|
|
496
|
+
(j) => {
|
|
497
|
+
if (!j || typeof j !== "object" || Array.isArray(j)) return undefined;
|
|
498
|
+
j.global = j.global || {};
|
|
499
|
+
const t = { ...(j.global.issueTree || {}) };
|
|
500
|
+
if (none) {
|
|
501
|
+
t.subtaskRoles = [];
|
|
502
|
+
t.subtaskRolesDeclined = true;
|
|
503
|
+
} else {
|
|
504
|
+
t.subtaskRoles = names;
|
|
505
|
+
t.subtaskIssueType = Object.fromEntries(names.map((n) => [n, n]));
|
|
506
|
+
delete t.subtaskRolesDeclined;
|
|
507
|
+
}
|
|
508
|
+
j.global.issueTree = t;
|
|
509
|
+
return j;
|
|
510
|
+
},
|
|
511
|
+
{ mode: 0o600, followSymlink: true, timeoutMs: lockTimeoutFrom(process.env) },
|
|
512
|
+
);
|
|
513
|
+
} catch (e) {
|
|
514
|
+
return die(`analysis-story-tree: could not write ${PREFS_PATH}: ${e.message}`, 3);
|
|
515
|
+
}
|
|
516
|
+
process.stdout.write(
|
|
517
|
+
none ? "issueTree: no sub-tasks\n" : `issueTree: sub-tasks ${names.join(", ")}\n`,
|
|
518
|
+
);
|
|
519
|
+
return null;
|
|
520
|
+
}
|
|
521
|
+
|
|
456
522
|
async function main() {
|
|
523
|
+
if (SAVE_SUBTASKS !== null) return saveSubtasks(SAVE_SUBTASKS);
|
|
457
524
|
if (!file)
|
|
458
525
|
return die(
|
|
459
526
|
"usage: analysis-story-tree.mjs <analysis.md> [--json] [--brief] [--prefs FILE] [--page-url URL]",
|
|
@@ -557,7 +624,10 @@ async function main() {
|
|
|
557
624
|
for (const line of s.description.split("\n")) process.stdout.write(` ${line}\n`);
|
|
558
625
|
}
|
|
559
626
|
for (const t of s.subtasks) {
|
|
560
|
-
|
|
627
|
+
const type = subtaskTypeFor(result.prefsUsed.subtaskIssueType, t.role);
|
|
628
|
+
process.stdout.write(
|
|
629
|
+
` subtask ${t.role} type ${type ?? "discovered from createmeta"} [${t.label}]\n`,
|
|
630
|
+
);
|
|
561
631
|
}
|
|
562
632
|
for (const c of s.clones || []) {
|
|
563
633
|
process.stdout.write(
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"importPatterns": [
|
|
48
48
|
{
|
|
49
49
|
"id": "import",
|
|
50
|
-
"regex": "^\\s*import\\s+(?:class|struct|enum|protocol|func|var|let|typealias)\\s+([A-Za-z_][A-Za-z0-9_]*)|^\\s*import\\s+([A-Za-z_][A-Za-z0-9_]*)"
|
|
50
|
+
"regex": "^\\s*(?:@[A-Za-z_]\\w*(?:\\([^)\\n]*\\))?\\s+)*import\\s+(?:class|struct|enum|protocol|func|var|let|typealias)\\s+([A-Za-z_][A-Za-z0-9_]*)|^\\s*(?:@[A-Za-z_]\\w*(?:\\([^)\\n]*\\))?\\s+)*import\\s+([A-Za-z_][A-Za-z0-9_]*)"
|
|
51
51
|
}
|
|
52
52
|
],
|
|
53
53
|
"referenceKinds": ["class", "struct", "enum", "protocol", "actor", "typealias"],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
|
-
"generatedAt": "2026-10-
|
|
3
|
+
"generatedAt": "2026-10-13T19:19:55Z",
|
|
4
4
|
"skillCount": 228,
|
|
5
5
|
"entries": [
|
|
6
6
|
{
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
"path": "shared/core/multi-agent-analysis-jira/SKILL.md",
|
|
16
|
-
"sha256": "
|
|
16
|
+
"sha256": "798061199762b141d0312b56f9e8e10af1324fb867393929565ebe4e61fcbc81"
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"path": "shared/core/multi-agent-analysis-resolve/SKILL.md",
|
|
@@ -69,6 +69,13 @@ Then ask for the epic, once. `AskUserQuestion`:
|
|
|
69
69
|
- **Link to an existing epic** - the user types its key (`--epic <KEY>`)
|
|
70
70
|
- **No epic** - the stories are created unlinked
|
|
71
71
|
|
|
72
|
+
When the plan's `prefsUsed.subtaskRoles` is empty and `prefsUsed.subtaskRolesDeclined`
|
|
73
|
+
is false, attended runs ask once which sub-tasks every story gets. List the site's types
|
|
74
|
+
with `bash "$HOME/.claude/lib/analysis-jira-write.sh" --subtask-types --project "<KEY>"`,
|
|
75
|
+
offer them as a multi-select `AskUserQuestion` plus **No sub-tasks**, save the answer with
|
|
76
|
+
`node "$HOME/.claude/scripts/analysis-story-tree.mjs" --save-subtasks "<type>,<type>"` (or
|
|
77
|
+
`none`), and re-run Phase 1. Unattended runs never ask.
|
|
78
|
+
|
|
72
79
|
Then the dry run, which is what proves the writer agrees with the plan:
|
|
73
80
|
|
|
74
81
|
```bash
|