@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
package/manifest.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mmerterden/multi-agent-pipeline",
|
|
3
|
-
"version": "20.23.
|
|
4
|
-
"commit": "
|
|
5
|
-
"generatedAt": "2026-10-
|
|
3
|
+
"version": "20.23.2",
|
|
4
|
+
"commit": "25bf3bd432f8e4da861b031ad8d61deaa2c56e05",
|
|
5
|
+
"generatedAt": "2026-10-13T21:30:35.928Z",
|
|
6
6
|
"algorithm": "sha256",
|
|
7
7
|
"fileCount": 1367,
|
|
8
8
|
"files": {
|
|
9
|
-
"CHANGELOG.md": "
|
|
9
|
+
"CHANGELOG.md": "7b1276a438d21099c9052c81943cf96b9f075282b7f96834eaac9a414456ab7d",
|
|
10
10
|
"LICENSE": "8d8c51aaf1e1538b180c69965fa038db71be86798cc3e26a143bebfefe16a43e",
|
|
11
11
|
"README.md": "4cb9fee0aed62bd1467bdc09954e011612b0de6e7895d663f297bc8778e00b2e",
|
|
12
12
|
"README.tr.md": "f30edbc26e51e311077fbb5b64234e70285db92340e6dadc259cfd673f104326",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"docs/best-practices.md": "f8b75b0d38b5896a6a852bb30a1edddeb24dab8b5b7b1d9313290e1241c72024",
|
|
35
35
|
"docs/ecosystem.md": "328a56a9f48514bca380d311a8e62246d85ccb18668f873047a883596153395f",
|
|
36
36
|
"docs/engineering.md": "550528cd47c7d2c921729c68d0782a00ac8e77a4bdfec479f6749f9c3235d188",
|
|
37
|
-
"docs/facts.json": "
|
|
37
|
+
"docs/facts.json": "df24755e012fbea4106bddd3a268b9ba98d27ceae910cc2e31ade9bc241e0f01",
|
|
38
38
|
"docs/features.md": "79a4b5a9722375ea16d7b337148d47bbea2951c8eb7bc8a6e616c9b3ed2c8703",
|
|
39
39
|
"docs/performance.md": "bbf2f3922c25dcba0c1a9e69ef420e3871a729af5b35009a49b48a5d3c5cd151",
|
|
40
40
|
"docs/recovery-guide.md": "59a8f1b66b34cb9ff5507b3d54dac3b9915e184aa044d9c758d18cac818d510e",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"install/_unattended-profile.mjs": "e1ed82bde35d19105ee12c2a551eb778912b0e5c93ab227fd15c993098d98de0",
|
|
55
55
|
"install/_usage-lifecycle.mjs": "4b010c4e75d6f8e13b58d80a93cc9407f42ccde781c9ed12dcb8e7faf05e526c",
|
|
56
56
|
"install/_xcode-skills.mjs": "1a7e20f1e6743324b941c5d3ecac9f94c65fff1534ffd9c63b39cbea4f8d101a",
|
|
57
|
-
"install/catalog-history.json": "
|
|
57
|
+
"install/catalog-history.json": "51bba941b447217b999080c9ae31905286e625a35e400fc89a376b92ba6073a1",
|
|
58
58
|
"install/claude.mjs": "106ce353ebc19c8319d057ce342e1f989519510465c798399076b5f3d8727904",
|
|
59
59
|
"install/codex.mjs": "08925fe9f3ecdf509b37f01105652c847475d20fcde2db0d679b9a7d663ca145",
|
|
60
60
|
"install/copilot.mjs": "41dc394b8a3ae77cfd194ce0ca9bbcafce9bdce349f1f72eeb86569f993e15fe",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"install/templates/multi-agent-autopilot-awake.plist.template": "ddd25c76c56fcbb38a89de8e35637372d70ace993d6bc487a88ca9cfb702fe1f",
|
|
66
66
|
"install/templates/multi-agent-autopilot.plist.template": "122c8a61cd927edd99375d6e3da40f1d42bcceed5b6641785c8b28bb930bc77e",
|
|
67
67
|
"install/unattended-profile-legacy.json": "f52e86b998956f09ad306b1735a6c10b58580d7082d8a55665a4c6bb15c3aa1f",
|
|
68
|
-
"package.json": "
|
|
68
|
+
"package.json": "364c6375f94f3da95979a320685e97790e4e7f13a68388d270527f291a41d26e",
|
|
69
69
|
"pipeline/agents/android-architect.md": "11fb74543cb0ebcf9525ca6654fe95d53da7229e0bf4a1149b31fd9005abc453",
|
|
70
70
|
"pipeline/agents/backend-architect.md": "b02a606c301e4714131aed70ae6d16eeb77fbc819a001e2752cff2488d4246de",
|
|
71
71
|
"pipeline/agents/bulk-reader.md": "6597052cd7d205bac2b1c7633f5232fe1a40730664a913b8eb77efd5a786dc25",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"pipeline/commands/deploy.md": "d20f95f3b1d2357c8c92f3047e8fe11eadf539ff13cab9b54f072f0ad697242a",
|
|
82
82
|
"pipeline/commands/figma-to-swiftui.md": "cb198d279d36c6c34648ab9834433d00f81c9d427934cc1641c0e09f1878b0a6",
|
|
83
83
|
"pipeline/commands/multi-agent/SKILL.md": "20e3d9af469af2e347700d1ff4a600e44a69a18f15f374ba107c011e52a1c31a",
|
|
84
|
-
"pipeline/commands/multi-agent/analysis-jira/SKILL.md": "
|
|
84
|
+
"pipeline/commands/multi-agent/analysis-jira/SKILL.md": "90e41e01318fc3c0feb984fd119cc61b64b0dfa7a65d918b50bb592a0aafe87a",
|
|
85
85
|
"pipeline/commands/multi-agent/analysis-resolve/SKILL.md": "48b72f59775ebfaab916afaeb2609e5694c0f7a3ce2bf2689cf25623d9755682",
|
|
86
86
|
"pipeline/commands/multi-agent/analysis/SKILL.md": "8b92d8832f72e195408d6e8e137956df3a4920074ccc0a4a130f36995aa0f6d9",
|
|
87
87
|
"pipeline/commands/multi-agent/autopilot-off/SKILL.md": "1574f9801993b8ac301de8d60f49df0036175ca7bf95e1b4682b76b56ae6479f",
|
|
@@ -169,7 +169,7 @@
|
|
|
169
169
|
"pipeline/lib/_jira-auth.sh": "fc468736057f8841d824eb1a4db527442562f8979fea333ebaef5527f4369e9f",
|
|
170
170
|
"pipeline/lib/account-resolver.sh": "f7bef9beba3f839268509361651be0b1e1f319f2f23821f90ba466fdf185a8f6",
|
|
171
171
|
"pipeline/lib/agent-model-sync.mjs": "2e0264ca8d8fef761563db219d588fed9067847043f960f68f055c186c20b668",
|
|
172
|
-
"pipeline/lib/analysis-jira-write.sh": "
|
|
172
|
+
"pipeline/lib/analysis-jira-write.sh": "e1b7399b23bf9a9a7cb935d2f89f85e613a9f6f54231839f732243b0ede63d0a",
|
|
173
173
|
"pipeline/lib/analysis-quality.mjs": "58dec3447178ff05dc0687c256fa5134c909cd485dd22a0932eafc05ba5c9ad8",
|
|
174
174
|
"pipeline/lib/analysis-sections.mjs": "e31ddf128ef7a6d1b9061896fc2d7cb4b5165202d8e03a46a1e0f9ec906ed01d",
|
|
175
175
|
"pipeline/lib/ask-choice.sh": "b77282645cf64d016cdebf43d1ee299874a1411f5c51b6ae3b6c580d1bbfe78a",
|
|
@@ -282,7 +282,7 @@
|
|
|
282
282
|
"pipeline/multi-agent-refs/design-check/figma-mapping.md": "731b7f19ddee2e67ea5d318e95e3a0d6840fa62c4ad682a25315509ac9ddcbc3",
|
|
283
283
|
"pipeline/multi-agent-refs/design-check/init-inventory.md": "62defc5ea804f6c8f059a8d512b023791daac59274c0def28cefd7709a7fe862",
|
|
284
284
|
"pipeline/multi-agent-refs/design-check/mcp-currency-gate.md": "70a96d3135af905989cd1819e6fb37fc44d25df9817b1efcd20121ec6db76d74",
|
|
285
|
-
"pipeline/multi-agent-refs/features/analysis-jira.md": "
|
|
285
|
+
"pipeline/multi-agent-refs/features/analysis-jira.md": "79d6f0784d2ee68181f65aa63c34e8c4152f63373079fdbd2aa4f329e791c7c5",
|
|
286
286
|
"pipeline/multi-agent-refs/features/analysis-outline.md": "36a917343a7c07ae115aa1e89f825304e5878db931915aefef614d251b167e92",
|
|
287
287
|
"pipeline/multi-agent-refs/features/analysis-sources.md": "12c8035e6aabf8e8a973b7264edc9a943e6758e79dbc53ae3e9b43c7f998000c",
|
|
288
288
|
"pipeline/multi-agent-refs/features/assertion-judge.md": "ee9d1f32a5edfc35bf7f97126fa285eaddad6c1ba99724149bcf414c6e318e00",
|
|
@@ -331,7 +331,7 @@
|
|
|
331
331
|
"pipeline/multi-agent-refs/features/verify.md": "1a6db5e7239eac01e51100353013abd16bf785a7fce88b40139fca27f4166be4",
|
|
332
332
|
"pipeline/multi-agent-refs/features/visual-evidence.md": "2b6aa2e4ff05c7a1fbe9e0fedfa4b28b9ad01c93a64f1ca6f6cd3e02293866aa",
|
|
333
333
|
"pipeline/multi-agent-refs/features/worktree-finalize.md": "628f627b94927a9c4a5b0b261fed1f0ffa42d4155390d57b5e56566a26ed8193",
|
|
334
|
-
"pipeline/multi-agent-refs/generate-issue.md": "
|
|
334
|
+
"pipeline/multi-agent-refs/generate-issue.md": "7a57f920ad72078fe9d75f561b5dac9aa3b504eb0c539a4598cb7080dc6bdbd7",
|
|
335
335
|
"pipeline/multi-agent-refs/help/en.md": "e47435607e503f9042a06a815bc005d9e0d0965e21d3f50f6fd221b3f14453d8",
|
|
336
336
|
"pipeline/multi-agent-refs/help/tr.md": "227fc73ff4e6cbc164072b17eee8c1212dc5e17dac841bbbca689c583d739df8",
|
|
337
337
|
"pipeline/multi-agent-refs/issue-jira-triad.md": "a12f4e3b75badf8a466e0c3531a5f0e12e44a1dd7799522fb03ff11068ce391d",
|
|
@@ -458,7 +458,7 @@
|
|
|
458
458
|
"pipeline/schemas/plan-todos.schema.json": "f1dcf8df338b29bbc1d874addd3408c0841a5f869c0b6075efd9242e3e8552d6",
|
|
459
459
|
"pipeline/schemas/planning-output.schema.json": "bc6eda32ecd435cf84d846078f9d38347f8f741f8a427cff15913dd6c380d4b8",
|
|
460
460
|
"pipeline/schemas/pr-request.schema.json": "ae75c4856349d62834802b1189af516bb6fb3d36646831b7ff2fa61d938afcd0",
|
|
461
|
-
"pipeline/schemas/prefs.schema.json": "
|
|
461
|
+
"pipeline/schemas/prefs.schema.json": "836f7ad7ef5932758dc38b8c2ce970b58f1ce568cec49885260e56e515f069d2",
|
|
462
462
|
"pipeline/schemas/repo-profile.schema.json": "1a74ef8330da9a26a5a70955f6f9cbb20eda54a8615efaf86c7993d420a7f04e",
|
|
463
463
|
"pipeline/schemas/repos.schema.json": "a9bf56f25c66201c170912e3389bd73e4a3f61922410f83e31a6c6ba47aee861",
|
|
464
464
|
"pipeline/schemas/research-output.schema.json": "54e3ba1b04fdc20fbaa84333b943a8ab95d1395c7b0e0aedc67929184cb49523",
|
|
@@ -496,7 +496,7 @@
|
|
|
496
496
|
"pipeline/scripts/_code-graph.mjs": "6b1848d78cef2000c11fcaaa6f1faaae89365a86a37642aa458db9ad422a4cc1",
|
|
497
497
|
"pipeline/scripts/_command-contract.mjs": "e97a5c624e0768769da54d4175e9f7655f1de1dcd0832468bc66bf140b0f5a6f",
|
|
498
498
|
"pipeline/scripts/_cost.mjs": "bdf03835af4719b72e4574f2d5ca827e94f9d80e49527a8e9e200a0aa4ecd7a9",
|
|
499
|
-
"pipeline/scripts/_diff-impact.mjs": "
|
|
499
|
+
"pipeline/scripts/_diff-impact.mjs": "cda016ca6071c741a680a3dbc35bb21926465b296defa4fa3677ce550704198a",
|
|
500
500
|
"pipeline/scripts/_fingerprint.mjs": "d8c63b062a3be0e138b6675aa031824fcd96f5517cf0f7b477378913c1db7d73",
|
|
501
501
|
"pipeline/scripts/_notices.mjs": "1f78037b0849ab3cf8a85ca353ed1610c2895250ac21a1d50ac1fb9732be4300",
|
|
502
502
|
"pipeline/scripts/_path-match.mjs": "880b27d90351441b4f3c05e1a87ace62cd1caab3ec7a286663efb2176cb69a69",
|
|
@@ -508,14 +508,14 @@
|
|
|
508
508
|
"pipeline/scripts/_smoke-root.sh": "dad129959a4e37a0cd0f4f6c04f6d66c5a43752b2bf33989db9c002eb106b832",
|
|
509
509
|
"pipeline/scripts/_stack-adapter.mjs": "a754c042d237cd03abf11c6909099a7a3ea798cdd13d49b03787797fd3496cd7",
|
|
510
510
|
"pipeline/scripts/_stack-routing.mjs": "d1f804d457295e5cc8ad7039e8c29431f0e9f52f4396cc54c1305eb82b9b61e9",
|
|
511
|
-
"pipeline/scripts/_supertypes.mjs": "
|
|
511
|
+
"pipeline/scripts/_supertypes.mjs": "7bf4bf8fca087ad93a19316fd24bfaae19d05f8a0907edda4c17946fcaf375d8",
|
|
512
512
|
"pipeline/scripts/agent-guard.py": "ba8f0c08a80d953d339ea69492efb1fbe8db5d364c2bcb2094a9839cf6dcc081",
|
|
513
513
|
"pipeline/scripts/agent-guard.sh": "5b57cea687754533c673b66a9dd2281ca06d12ef86bdc0d03fa285fc9e7debf8",
|
|
514
514
|
"pipeline/scripts/aggregate-metrics.mjs": "c324e349993fb154a885263cf6665af0ff656e9c987b455a263dad8b96cca667",
|
|
515
515
|
"pipeline/scripts/analysis-conform-coverage.mjs": "2b7de99855d1901b1b7803f47d4bb5f75ed5df1b5f4840d55bfc3537e3f65afe",
|
|
516
516
|
"pipeline/scripts/analysis-sources.mjs": "53d843b0504a6cadd7d4a047331ebc34a2868304fcf791617ad0a215396e35a6",
|
|
517
|
-
"pipeline/scripts/analysis-story-body.mjs": "
|
|
518
|
-
"pipeline/scripts/analysis-story-tree.mjs": "
|
|
517
|
+
"pipeline/scripts/analysis-story-body.mjs": "34ffda565696d99827f1e63692417364f5bebd127e80a377d19bbbcff67b4926",
|
|
518
|
+
"pipeline/scripts/analysis-story-tree.mjs": "5e36db18bb78c987734142edc3a0c9acf38ec2c3b6fddf22f5b994f379f2ec77",
|
|
519
519
|
"pipeline/scripts/analysis-tickets-writeback.mjs": "8c1f117ac5c6516385e3458462de6bde28c81768853c2d9524118d0155befe8b",
|
|
520
520
|
"pipeline/scripts/anonymize-findings.mjs": "5d7e042793790c1b924d4d952b4f79b7e5f1dd0a3fe936f1ee16a4fa2ba820dd",
|
|
521
521
|
"pipeline/scripts/answer-question.mjs": "b948df9d063f623a7ccd9cc65903c4388018b6b968228f77eb2e229223a61b1c",
|
|
@@ -545,7 +545,7 @@
|
|
|
545
545
|
"pipeline/scripts/classify-plan-safety.mjs": "d25f21da21dd4ee1ea507fb0df89301715071121b1bd96aca9ef140a4eee896b",
|
|
546
546
|
"pipeline/scripts/code-graph-rules/android.json": "6ba2cc0d496f5c6cc40cca9931e0903da3a64596d939fe2e86367615ec6241c2",
|
|
547
547
|
"pipeline/scripts/code-graph-rules/go.json": "7d794157675c0d4c71d7ba3d6e538de0e20de6711c652d55844eac11b10e0bdf",
|
|
548
|
-
"pipeline/scripts/code-graph-rules/ios.json": "
|
|
548
|
+
"pipeline/scripts/code-graph-rules/ios.json": "b2c8acc464158b29c669050e7f13cf1f3445fcc2e8b8fb5c5da80feea78f9d2d",
|
|
549
549
|
"pipeline/scripts/code-graph-rules/node.json": "062d7337519f5a743da6953d2748e95560729715502a665d19025108ceba4845",
|
|
550
550
|
"pipeline/scripts/code-graph-rules/python.json": "91394dc31a9392e8d02cf46797bf39551fbe483aa2e6b34145cd33aa2460eca9",
|
|
551
551
|
"pipeline/scripts/commands.mjs": "5b20a7d01aa0f205f2605fcfcbdb197bdfeafbdfc1e5164b5ee083ac6ff39cc2",
|
|
@@ -701,14 +701,14 @@
|
|
|
701
701
|
"pipeline/scripts/worktrees.mjs": "90dfd09b70fffe9b56c331a4ae2692cc248f4898f4ee6aee4239ef0b20888a90",
|
|
702
702
|
"pipeline/scripts/write-state.mjs": "e1198bf970eace07d3eff22b394808a526e473df65d3d58aa2d5a12cfdac8b49",
|
|
703
703
|
"pipeline/scripts/write-store-config.mjs": "b994a061f6febb2ca3d396074d8b51d2c1c1f991d3072d321703380da3639a56",
|
|
704
|
-
"pipeline/skills/.skill-manifest.json": "
|
|
704
|
+
"pipeline/skills/.skill-manifest.json": "784b52afc412806ef44d0f5691d7f55d6b4c7f3a3b983410f2d9ad726ffc8fa4",
|
|
705
705
|
"pipeline/skills/.skills-index.json": "b2917fa44e273b939549f1c3ab3dd81c26ba8168772bc7644c9fa076116d8a4c",
|
|
706
706
|
"pipeline/skills/shared/README.md": "928fe4292be729c3b7d7d0898cade8be0dfa868f208e0c6d1fe7b2c6bd6223c5",
|
|
707
707
|
"pipeline/skills/shared/core/apple-archive-compliance/SKILL.md": "7239fef717ea45b79276bfcc2c3903acd57950b6e00dd6a2a66399c4b09cbd51",
|
|
708
708
|
"pipeline/skills/shared/core/apple-archive-compliance/references/rules.yml": "25129ce6d416242ac04c4032f7561e0c2761ab8bbadf08d9c9f49b573a663389",
|
|
709
709
|
"pipeline/skills/shared/core/google-play-compliance/SKILL.md": "3089e8c5f99c1ec7c1358394c398335ccb5ebc20ca05d60d870127da4592de34",
|
|
710
710
|
"pipeline/skills/shared/core/google-play-compliance/references/rules.yml": "21fc058762e7341a8917442f1ab4dc50def59aa3145eea120c474b0d0de6a5c1",
|
|
711
|
-
"pipeline/skills/shared/core/multi-agent-analysis-jira/SKILL.md": "
|
|
711
|
+
"pipeline/skills/shared/core/multi-agent-analysis-jira/SKILL.md": "798061199762b141d0312b56f9e8e10af1324fb867393929565ebe4e61fcbc81",
|
|
712
712
|
"pipeline/skills/shared/core/multi-agent-analysis-resolve/SKILL.md": "9e405b0f7403c72b00a7ddf1e4b8518cfe37feb31d91d8e5ba70f0341efa5d0c",
|
|
713
713
|
"pipeline/skills/shared/core/multi-agent-analysis/SKILL.md": "a9c1b44208271ae2d0845cde7565542e2502324abc2c1aac0397d92f908d37db",
|
|
714
714
|
"pipeline/skills/shared/core/multi-agent-autopilot-off/SKILL.md": "a7efde03189cebf6ff81a1233eddd59dbcfd1b2ebdb30175503e37468905afcb",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mmerterden/multi-agent-pipeline",
|
|
3
|
-
"version": "20.23.
|
|
3
|
+
"version": "20.23.2",
|
|
4
4
|
"description": "6-phase AI development pipeline with full orchestration on Claude Code, Copilot CLI and Codex CLI. Analysis, planning, TDD, CLI-aware parallel review with consensus surfacing + Fable triage, default-FAIL evidence gates, secret + intent guards, per-phase cost ledger, persistent learnings memory, wiki generation, commit automation. Token-preserving uninstall.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -73,6 +73,13 @@ Then ask for the epic, once. `AskUserQuestion`:
|
|
|
73
73
|
- **Link to an existing epic** - the user types its key (`--epic <KEY>`)
|
|
74
74
|
- **No epic** - the stories are created unlinked
|
|
75
75
|
|
|
76
|
+
When the plan's `prefsUsed.subtaskRoles` is empty and `prefsUsed.subtaskRolesDeclined`
|
|
77
|
+
is false, attended runs ask once which sub-tasks every story gets. List the site's types
|
|
78
|
+
with `bash "$HOME/.claude/lib/analysis-jira-write.sh" --subtask-types --project "<KEY>"`,
|
|
79
|
+
offer them as a multi-select `AskUserQuestion` plus **No sub-tasks**, save the answer with
|
|
80
|
+
`node "$HOME/.claude/scripts/analysis-story-tree.mjs" --save-subtasks "<type>,<type>"` (or
|
|
81
|
+
`none`), and re-run Phase 1. Unattended runs never ask.
|
|
82
|
+
|
|
76
83
|
Then the dry run, which is what proves the writer agrees with the plan:
|
|
77
84
|
|
|
78
85
|
```bash
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
#
|
|
37
37
|
# Usage:
|
|
38
38
|
# analysis-jira-write.sh --plan plan.json --project KEY [--dry-run] [--ledger FILE] [--epic KEY]
|
|
39
|
+
# analysis-jira-write.sh --subtask-types --project KEY the site's sub-task type names, one per line
|
|
39
40
|
#
|
|
40
41
|
# Exit: 0 written (or previewed), 3 the plan does not parse, 4 auth, 5 a Jira
|
|
41
42
|
# call failed, 64 usage.
|
|
@@ -46,7 +47,7 @@ SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
46
47
|
# shellcheck source=/dev/null
|
|
47
48
|
. "$SELF_DIR/_jira-auth.sh"
|
|
48
49
|
|
|
49
|
-
PLAN=""; PROJECT=""; DRY=0; LEDGER=""; EPIC=""
|
|
50
|
+
PLAN=""; PROJECT=""; DRY=0; LEDGER=""; EPIC=""; LIST_TYPES=0
|
|
50
51
|
while [ $# -gt 0 ]; do
|
|
51
52
|
case "$1" in
|
|
52
53
|
--plan) PLAN="${2:-}"; shift 2 ;;
|
|
@@ -54,13 +55,44 @@ while [ $# -gt 0 ]; do
|
|
|
54
55
|
--ledger) LEDGER="${2:-}"; shift 2 ;;
|
|
55
56
|
--dry-run) DRY=1; shift ;;
|
|
56
57
|
--epic) EPIC="${2:-}"; shift 2 ;;
|
|
57
|
-
|
|
58
|
+
--subtask-types) LIST_TYPES=1; shift ;;
|
|
59
|
+
*) echo "usage: analysis-jira-write.sh --plan FILE --project KEY [--dry-run] [--ledger FILE] [--epic KEY] | --subtask-types --project KEY" >&2; exit 64 ;;
|
|
58
60
|
esac
|
|
59
61
|
done
|
|
60
|
-
[ -n "$PLAN" ] && [ -f "$PLAN" ] || { echo "ERR: --plan FILE is required and must exist" >&2; exit 64; }
|
|
61
62
|
[ -n "$PROJECT" ] || { echo "ERR: --project KEY is required" >&2; exit 64; }
|
|
63
|
+
if [ "$LIST_TYPES" -eq 0 ] && { [ -z "$PLAN" ] || [ ! -f "$PLAN" ]; }; then
|
|
64
|
+
echo "ERR: --plan FILE is required and must exist" >&2; exit 64
|
|
65
|
+
fi
|
|
62
66
|
|
|
63
67
|
command -v jq >/dev/null 2>&1 || { echo "ERR: jq is required" >&2; exit 3; }
|
|
68
|
+
|
|
69
|
+
# Discover rather than assume: the types this site marks subtask:true, under
|
|
70
|
+
# whatever names it gave them. Jira Data Center 9 answers only the per-project
|
|
71
|
+
# createmeta; older sites answer only the classic one. Read once per run.
|
|
72
|
+
SITE_SUBTASK_TYPES=""
|
|
73
|
+
SITE_SUBTASK_READ=0
|
|
74
|
+
site_subtask_types() {
|
|
75
|
+
SITE_SUBTASK_READ=1
|
|
76
|
+
SITE_SUBTASK_TYPES="$(jira_api GET "/rest/api/2/issue/createmeta/${PROJECT}/issuetypes?maxResults=100" 2>/dev/null \
|
|
77
|
+
| jq -r '.values[]? | select(.subtask == true) | .name' 2>/dev/null || echo "")"
|
|
78
|
+
[ -n "$SITE_SUBTASK_TYPES" ] || SITE_SUBTASK_TYPES="$(jira_api GET \
|
|
79
|
+
"/rest/api/2/issue/createmeta?projectKeys=${PROJECT}&expand=projects.issuetypes" 2>/dev/null \
|
|
80
|
+
| jq -r '.projects[]?.issuetypes[]? | select(.subtask == true) | .name' 2>/dev/null || echo "")"
|
|
81
|
+
local n
|
|
82
|
+
n="$(printf '%s' "$SITE_SUBTASK_TYPES" | grep -c . || true)"
|
|
83
|
+
if [ "${n:-0}" -gt 1 ]; then
|
|
84
|
+
echo " note ${PROJECT} has ${n} sub-task types ($(printf '%s' "$SITE_SUBTASK_TYPES" | paste -sd, - | sed 's/,/, /g')); using the first. Set issueTree.subtaskIssueType to a name, or to a {role: type} map, to choose." >&2
|
|
85
|
+
fi
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if [ "$LIST_TYPES" -eq 1 ]; then
|
|
89
|
+
jira_auth_resolve || exit 4
|
|
90
|
+
site_subtask_types 2>/dev/null
|
|
91
|
+
[ -n "$SITE_SUBTASK_TYPES" ] || { echo "ERR: ${PROJECT} answered no sub-task types" >&2; exit 5; }
|
|
92
|
+
printf '%s\n' "$SITE_SUBTASK_TYPES"
|
|
93
|
+
exit 0
|
|
94
|
+
fi
|
|
95
|
+
|
|
64
96
|
jq -e . "$PLAN" >/dev/null 2>&1 || { echo "ERR: the plan does not parse as JSON: $PLAN" >&2; exit 3; }
|
|
65
97
|
|
|
66
98
|
DOC_ID="$(jq -r '.document.id // "unidentified"' "$PLAN")"
|
|
@@ -272,12 +304,13 @@ while IFS= read -r node; do
|
|
|
272
304
|
[ -n "$sub" ] || continue
|
|
273
305
|
t_label="$(printf '%s' "$sub" | jq -r '.label')"
|
|
274
306
|
t_role="$(printf '%s' "$sub" | jq -r '.role')"
|
|
275
|
-
|
|
307
|
+
# A name, or a map from role to name ("*" for any other role).
|
|
308
|
+
t_type="$(jq -r --arg r "$t_role" '.prefsUsed.subtaskIssueType
|
|
309
|
+
| if type == "object" then (.[$r] // .["*"] // empty)
|
|
310
|
+
elif type == "string" then . else empty end' "$PLAN")"
|
|
276
311
|
if [ -z "$t_type" ] && [ "$DRY" -eq 0 ]; then
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
t_type="$(jira_api GET "/rest/api/2/issue/createmeta?projectKeys=${PROJECT}&expand=projects.issuetypes" \
|
|
280
|
-
| jq -r '[.projects[]?.issuetypes[]? | select(.subtask==true) | .name] | first // empty' 2>/dev/null || echo "")"
|
|
312
|
+
[ "$SITE_SUBTASK_READ" -eq 1 ] || site_subtask_types
|
|
313
|
+
t_type="$(printf '%s\n' "$SITE_SUBTASK_TYPES" | head -n 1)"
|
|
281
314
|
fi
|
|
282
315
|
[ -n "$t_type" ] || t_type="Sub-task"
|
|
283
316
|
t_desc="$(printf '%s' "$sub" | jq -r '.description // empty')"
|
|
@@ -122,6 +122,9 @@ Each part sits in a coloured wiki panel with its `h3.` heading first: the user
|
|
|
122
122
|
story in `{panel:borderColor=#d4d39e}`, the acceptance criteria in
|
|
123
123
|
`{panel:borderColor=#d4a19e}`, current vs target and links in
|
|
124
124
|
`{panel:borderColor=#9eb6d4}`. The `Sources:` line stays outside the panels.
|
|
125
|
+
`issueTree.storyPanels` replaces any of the three colours (`story`, `criteria`,
|
|
126
|
+
`other`), or turns the panels off with `false`; `/multi-agent:create-jira`
|
|
127
|
+
reads the same setting.
|
|
125
128
|
|
|
126
129
|
At most 8 criteria. What is missing stays missing and becomes a body warning
|
|
127
130
|
(no user story, fewer than 2 criteria, no negative criterion); nothing is filled
|
|
@@ -169,13 +172,22 @@ are values the site fills in:
|
|
|
169
172
|
channel, the value is the site's own name for it
|
|
170
173
|
- `subtaskRoles` ships **empty**. An empty list is an instruction to look at how
|
|
171
174
|
this board actually splits work; a ready-made list would be the guess most
|
|
172
|
-
worth avoiding
|
|
175
|
+
worth avoiding. An attended run asks once, offering the site's own sub-task
|
|
176
|
+
types (`analysis-jira-write.sh --subtask-types`), and saves the answer
|
|
177
|
+
(`analysis-story-tree.mjs --save-subtasks`): each chosen type becomes a role
|
|
178
|
+
mapped to itself, and "no sub-tasks" sets `subtaskRolesDeclined`
|
|
173
179
|
- `epicLinkFieldId`, when set, is the site's own field id; otherwise it is
|
|
174
180
|
discovered
|
|
175
181
|
- `subtaskIssueType: null` means discover it - `createmeta` returns whichever
|
|
176
|
-
type carries `subtask: true`, under whatever name the site gave it
|
|
177
|
-
|
|
182
|
+
type carries `subtask: true`, under whatever name the site gave it (the
|
|
183
|
+
per-project `createmeta/{project}/issuetypes` first, which is all Jira Data
|
|
184
|
+
Center 9 answers, then the classic form). Same rule as
|
|
185
|
+
`features/jira-context.md`: the type travels as it comes from Jira and is
|
|
178
186
|
never a matching criterion in code
|
|
187
|
+
- a site with several sub-task types (research, build and QA sub-tasks)
|
|
188
|
+
names them per role: `subtaskIssueType` as a map from a `subtaskRoles`
|
|
189
|
+
entry to the type, `"*"` for any other role. Without one the first discovered
|
|
190
|
+
type is used and the run prints the types it found
|
|
179
191
|
|
|
180
192
|
The preview prints every field beside the pref key it came from, so a wrong
|
|
181
193
|
setting is visible before the writes rather than in Jira afterwards.
|
|
@@ -66,7 +66,7 @@ Each type has a standard section skeleton. `A` = always present. `C` = condition
|
|
|
66
66
|
| Acceptance Criteria | `{panel:borderColor=#d4a19e}` |
|
|
67
67
|
| Every other section | `{panel:borderColor=#9eb6d4}` |
|
|
68
68
|
|
|
69
|
-
Each panel closes with `{panel}` on its own line. Task and Bug keep plain `h3.` sections.
|
|
69
|
+
Each panel closes with `{panel}` on its own line. Task and Bug keep plain `h3.` sections. The colours are defaults: `prefs.global.issueTree.storyPanels` (`{story, criteria, other}`, `#rrggbb` each) replaces any of them, and `false` writes the Story with plain `h3.` sections. A value that is not a `#rrggbb` colour keeps the default. The `/multi-agent:analysis-jira` stories read the same setting.
|
|
70
70
|
|
|
71
71
|
Section headings render in `outputLanguage` (e.g. tr: "Detaylı Açıklama", "Kapsam", "Kabul Kriterleri", "Test Senaryoları", "Adımlar", "Beklenen Sonuç", "Gerçekleşen Sonuç", "Ortam", "Ekran Görüntüleri", "API Contract", "Design Reference", "Notlar").
|
|
72
72
|
|
|
@@ -2202,9 +2202,30 @@
|
|
|
2202
2202
|
"default": "Story"
|
|
2203
2203
|
},
|
|
2204
2204
|
"subtaskIssueType": {
|
|
2205
|
-
"type": ["string", "null"],
|
|
2205
|
+
"type": ["string", "null", "object"],
|
|
2206
|
+
"additionalProperties": { "type": "string" },
|
|
2206
2207
|
"default": null,
|
|
2207
|
-
"description": "Null means discover from createmeta rather than assume a name."
|
|
2208
|
+
"description": "The sub-task type name, or a map from a subtaskRoles entry to this site's type name (\"*\" for any other role), e.g. {\"develop\": \"Build Sub-task\", \"test\": \"QA Sub-task\"}. Null means discover from createmeta (the per-project endpoint, else the classic one) rather than assume a name; when the site has several sub-task types the first is used and the run says so."
|
|
2209
|
+
},
|
|
2210
|
+
"subtaskRolesDeclined": {
|
|
2211
|
+
"type": "boolean",
|
|
2212
|
+
"default": false,
|
|
2213
|
+
"description": "true once the person answered the sub-task question with no sub-tasks, so /multi-agent:analysis-jira does not ask again. Written by analysis-story-tree.mjs --save-subtasks none."
|
|
2214
|
+
},
|
|
2215
|
+
"storyPanels": {
|
|
2216
|
+
"oneOf": [
|
|
2217
|
+
{ "const": false },
|
|
2218
|
+
{
|
|
2219
|
+
"type": "object",
|
|
2220
|
+
"additionalProperties": false,
|
|
2221
|
+
"properties": {
|
|
2222
|
+
"story": { "type": "string", "pattern": "^#[0-9a-fA-F]{6}$" },
|
|
2223
|
+
"criteria": { "type": "string", "pattern": "^#[0-9a-fA-F]{6}$" },
|
|
2224
|
+
"other": { "type": "string", "pattern": "^#[0-9a-fA-F]{6}$" }
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
],
|
|
2228
|
+
"description": "Border colours of a Story's wiki panels, as written by /multi-agent:create-jira and /multi-agent:analysis-jira: story (the user story), criteria (the acceptance criteria), other (every other section). Unset means #d4d39e, #d4a19e and #9eb6d4; a colour left out keeps its default; false writes plain h3. sections."
|
|
2208
2229
|
},
|
|
2209
2230
|
"subtaskRoles": {
|
|
2210
2231
|
"type": "array",
|
|
@@ -38,7 +38,7 @@ function addLine(set, line) {
|
|
|
38
38
|
set.add(Math.max(1, line));
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function toRanges(lines) {
|
|
41
|
+
function toRanges(lines, gaps = new Set()) {
|
|
42
42
|
const sorted = [...lines].sort((a, b) => a - b);
|
|
43
43
|
const out = [];
|
|
44
44
|
for (const l of sorted) {
|
|
@@ -46,21 +46,28 @@ function toRanges(lines) {
|
|
|
46
46
|
if (last && l === last.end + 1) last.end = l;
|
|
47
47
|
else if (!last || l > last.end) out.push({ start: l, end: l });
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
// A gap is a point between two lines where lines went in (or, on the new
|
|
50
|
+
// side, came out): `{start: before, end: before + 1, gap: true}`.
|
|
51
|
+
for (const before of gaps)
|
|
52
|
+
if (!sorted.includes(before) && !sorted.includes(before + 1))
|
|
53
|
+
out.push({ start: before, end: before + 1, gap: true });
|
|
54
|
+
return out.sort((a, b) => a.start - b.start);
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
/**
|
|
53
58
|
* Every file of a unified diff with the lines it touched on each side.
|
|
54
59
|
*
|
|
55
|
-
* New side: an added line counts as itself, and a deletion as the
|
|
56
|
-
*
|
|
57
|
-
* itself, and
|
|
58
|
-
*
|
|
60
|
+
* New side: an added line counts as itself, and a pure deletion as the gap
|
|
61
|
+
* between the two new lines it fell between. Old side: a removed line counts as
|
|
62
|
+
* itself, and a pure insertion as the gap between the two old lines it went in
|
|
63
|
+
* between. A gap belongs to the innermost declaration spanning both of its
|
|
64
|
+
* lines, so a hunk that only adds or only removes lands inside the declaration
|
|
65
|
+
* it changed, whatever the context size.
|
|
59
66
|
* Hunks are counted from their headers, so concatenated `diff -u` output with
|
|
60
67
|
* no `diff --git` lines parses too, CRLF included, at any context size.
|
|
61
68
|
*
|
|
62
69
|
* @param {string} diffText - unified diff
|
|
63
|
-
* @returns {Array<{oldPath: string|null, newPath: string|null, oldRanges: Array<{start: number, end: number}>, newRanges: Array<{start: number, end: number}>}>}
|
|
70
|
+
* @returns {Array<{oldPath: string|null, newPath: string|null, oldRanges: Array<{start: number, end: number, gap?: boolean}>, newRanges: Array<{start: number, end: number, gap?: boolean}>}>}
|
|
64
71
|
*/
|
|
65
72
|
export function parseDiffFiles(diffText) {
|
|
66
73
|
const files = [];
|
|
@@ -70,23 +77,36 @@ export function parseDiffFiles(diffText) {
|
|
|
70
77
|
let newLine = 0;
|
|
71
78
|
let oldLeft = 0;
|
|
72
79
|
let newLeft = 0;
|
|
80
|
+
// One run of -/+ lines. Its removed lines stand for it on the old side and
|
|
81
|
+
// its added lines on the new side; a run with nothing on one side is a gap
|
|
82
|
+
// there, between the lines either side of it.
|
|
83
|
+
let run = null;
|
|
84
|
+
const endRun = () => {
|
|
85
|
+
if (run && !run.removed) cur.oldGaps.add(run.oldBefore);
|
|
86
|
+
if (run && !run.added) cur.newGaps.add(run.newBefore);
|
|
87
|
+
run = null;
|
|
88
|
+
};
|
|
73
89
|
for (const line of String(diffText).split("\n")) {
|
|
74
90
|
const raw = line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
75
91
|
if (oldLeft > 0 || newLeft > 0) {
|
|
76
|
-
if (raw.startsWith("+")) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
92
|
+
if (raw.startsWith("+") || raw.startsWith("-")) {
|
|
93
|
+
run = run || { oldBefore: oldLine - 1, newBefore: newLine - 1, removed: 0, added: 0 };
|
|
94
|
+
if (raw.startsWith("+")) {
|
|
95
|
+
addLine(cur.newLines, newLine);
|
|
96
|
+
run.added++;
|
|
97
|
+
newLine++;
|
|
98
|
+
newLeft--;
|
|
99
|
+
} else {
|
|
100
|
+
addLine(cur.oldLines, oldLine);
|
|
101
|
+
run.removed++;
|
|
102
|
+
oldLine++;
|
|
103
|
+
oldLeft--;
|
|
104
|
+
}
|
|
105
|
+
if (oldLeft <= 0 && newLeft <= 0) endRun();
|
|
88
106
|
continue;
|
|
89
107
|
}
|
|
108
|
+
if (raw.startsWith("\\")) continue;
|
|
109
|
+
endRun();
|
|
90
110
|
if (raw.startsWith(" ") || raw === "") {
|
|
91
111
|
oldLine++;
|
|
92
112
|
newLine++;
|
|
@@ -94,7 +114,6 @@ export function parseDiffFiles(diffText) {
|
|
|
94
114
|
newLeft--;
|
|
95
115
|
continue;
|
|
96
116
|
}
|
|
97
|
-
if (raw.startsWith("\\")) continue;
|
|
98
117
|
oldLeft = 0;
|
|
99
118
|
newLeft = 0;
|
|
100
119
|
}
|
|
@@ -112,6 +131,8 @@ export function parseDiffFiles(diffText) {
|
|
|
112
131
|
newPath: sidePath(raw.slice(4), "b/"),
|
|
113
132
|
oldLines: new Set(),
|
|
114
133
|
newLines: new Set(),
|
|
134
|
+
oldGaps: new Set(),
|
|
135
|
+
newGaps: new Set(),
|
|
115
136
|
};
|
|
116
137
|
files.push(cur);
|
|
117
138
|
pendingOld = undefined;
|
|
@@ -121,12 +142,14 @@ export function parseDiffFiles(diffText) {
|
|
|
121
142
|
if (hunk && cur) {
|
|
122
143
|
const oldCount = hunk[2] === undefined ? 1 : Number(hunk[2]);
|
|
123
144
|
const newCount = hunk[4] === undefined ? 1 : Number(hunk[4]);
|
|
124
|
-
|
|
125
|
-
|
|
145
|
+
// An empty side names the line BEFORE the change; any other side names
|
|
146
|
+
// the change's first line.
|
|
147
|
+
oldLine = Number(hunk[1]) + (oldCount === 0 ? 1 : 0);
|
|
148
|
+
newLine = Number(hunk[3]) + (newCount === 0 ? 1 : 0);
|
|
126
149
|
oldLeft = oldCount;
|
|
127
150
|
newLeft = newCount;
|
|
128
|
-
if (
|
|
129
|
-
if (
|
|
151
|
+
if (oldCount === 0) cur.oldGaps.add(oldLine - 1);
|
|
152
|
+
if (newCount === 0) cur.newGaps.add(newLine - 1);
|
|
130
153
|
}
|
|
131
154
|
}
|
|
132
155
|
return files
|
|
@@ -134,8 +157,8 @@ export function parseDiffFiles(diffText) {
|
|
|
134
157
|
.map((f) => ({
|
|
135
158
|
oldPath: f.oldPath,
|
|
136
159
|
newPath: f.newPath,
|
|
137
|
-
oldRanges: toRanges(f.oldLines),
|
|
138
|
-
newRanges: toRanges(f.newLines),
|
|
160
|
+
oldRanges: toRanges(f.oldLines, f.oldGaps),
|
|
161
|
+
newRanges: toRanges(f.newLines, f.newGaps),
|
|
139
162
|
}));
|
|
140
163
|
}
|
|
141
164
|
|
|
@@ -202,8 +225,13 @@ export function changedSymbols(graph, ranges) {
|
|
|
202
225
|
continue;
|
|
203
226
|
}
|
|
204
227
|
for (const r of list) {
|
|
228
|
+
// A gap belongs to a declaration only when the lines on both sides do.
|
|
205
229
|
const containing = spanned
|
|
206
|
-
.filter((s) =>
|
|
230
|
+
.filter((s) =>
|
|
231
|
+
r.gap
|
|
232
|
+
? (s.startLine ?? s.line) <= r.start && s.endLine >= r.end
|
|
233
|
+
: (s.startLine ?? s.line) <= r.end && s.endLine >= r.start,
|
|
234
|
+
)
|
|
207
235
|
.sort((a, b) => b.line - a.line);
|
|
208
236
|
if (!containing.length) {
|
|
209
237
|
seeds.set(fileNode.id, fileNode);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* protocols or interfaces. Three syntaxes cover the stacks the graph indexes:
|
|
7
7
|
*
|
|
8
8
|
* colon Swift and Kotlin: `Name<T>(ctor): A, B<X>() where ... {`
|
|
9
|
-
* parens Python: `Name(A, B, metaclass=M):`
|
|
9
|
+
* parens Python: `Name(A, B, metaclass=M):`, `Name[T](A):`
|
|
10
10
|
* extends TypeScript / JavaScript: `Name<T> extends A implements B, C {`
|
|
11
11
|
*
|
|
12
12
|
* Only names that start with a letter or underscore are kept, qualified names
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
const HEADER_MAX = 4000;
|
|
23
|
+
const CALL_RE = /^\s*[A-Za-z_$][\w$.]*\s*(?:<[^()]*>)?\s*\(/;
|
|
23
24
|
const OPEN = { "(": ")", "<": ">", "[": "]" };
|
|
24
25
|
const CLOSE = new Set([")", ">", "]"]);
|
|
25
26
|
|
|
@@ -99,10 +100,14 @@ function names(list) {
|
|
|
99
100
|
export function supertypesAt(body, start, syntax) {
|
|
100
101
|
const header = headerText(body, start, syntax);
|
|
101
102
|
if (syntax === "parens") {
|
|
102
|
-
|
|
103
|
+
let k = 0;
|
|
104
|
+
while (k < header.length && /\s/.test(header[k])) k++;
|
|
105
|
+
// PEP 695 type parameters come before the bases: `class Box[T](Base):`.
|
|
106
|
+
const rest = header.slice(skipGroup(header, k, "["));
|
|
107
|
+
const m = /^\s*\(/.exec(rest);
|
|
103
108
|
if (!m) return [];
|
|
104
|
-
const end = skipGroup(
|
|
105
|
-
return names(
|
|
109
|
+
const end = skipGroup(rest, m[0].length - 1, "(");
|
|
110
|
+
return names(rest.slice(m[0].length, end - 1));
|
|
106
111
|
}
|
|
107
112
|
if (syntax === "extends") {
|
|
108
113
|
let k = 0;
|
|
@@ -111,8 +116,16 @@ export function supertypesAt(body, start, syntax) {
|
|
|
111
116
|
const out = [];
|
|
112
117
|
const ext = /\bextends\s+([\s\S]*?)(?=\bimplements\b|$)/.exec(rest);
|
|
113
118
|
const imp = /\bimplements\s+([\s\S]*)$/.exec(rest);
|
|
119
|
+
// A base built by a call (`extends mixin(Base)`) is the call's result,
|
|
120
|
+
// which names no type the graph can point at.
|
|
114
121
|
for (const part of [ext?.[1], imp?.[1]])
|
|
115
|
-
if (part)
|
|
122
|
+
if (part)
|
|
123
|
+
for (const n of names(
|
|
124
|
+
splitTop(part)
|
|
125
|
+
.filter((p) => !CALL_RE.test(p))
|
|
126
|
+
.join(","),
|
|
127
|
+
))
|
|
128
|
+
if (!out.includes(n)) out.push(n);
|
|
116
129
|
return out;
|
|
117
130
|
}
|
|
118
131
|
let i = 0;
|
|
@@ -17,10 +17,22 @@ import { redesignSections } from "../lib/analysis-sections.mjs";
|
|
|
17
17
|
|
|
18
18
|
const MAX_CRITERIA = 8;
|
|
19
19
|
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
const
|
|
20
|
+
// Default panel border colours of a story section: the user story, the
|
|
21
|
+
// acceptance criteria, and every other section. issueTree.storyPanels
|
|
22
|
+
// overrides any of them, or turns panels off with false.
|
|
23
|
+
const PANEL_DEFAULTS = { story: "#d4d39e", criteria: "#d4a19e", other: "#9eb6d4" };
|
|
24
|
+
const HEX_COLOUR = /^#[0-9a-fA-F]{6}$/;
|
|
25
|
+
|
|
26
|
+
/** The colours to draw with, or null for plain sections. */
|
|
27
|
+
export function panelColours(setting) {
|
|
28
|
+
if (setting === false) return null;
|
|
29
|
+
const own = setting && typeof setting === "object" ? setting : {};
|
|
30
|
+
const pick = (k) => (HEX_COLOUR.test(own[k] ?? "") ? own[k] : PANEL_DEFAULTS[k]);
|
|
31
|
+
return { story: pick("story"), criteria: pick("criteria"), other: pick("other") };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const section = (colours, kind, lines) =>
|
|
35
|
+
colours ? [`{panel:borderColor=${colours[kind]}}`, ...lines, "{panel}", ""] : [...lines, ""];
|
|
24
36
|
const MAX_DESIGN_LINKS = 5;
|
|
25
37
|
|
|
26
38
|
// A criterion that describes a refusal, an error or an alternative path. One is
|
|
@@ -155,12 +167,13 @@ function linksOf(story, lines, ctx) {
|
|
|
155
167
|
return links;
|
|
156
168
|
}
|
|
157
169
|
|
|
158
|
-
function render({ userStory, acceptance, currentVsTarget, links }, sourceIds) {
|
|
170
|
+
function render({ userStory, acceptance, currentVsTarget, links }, sourceIds, colours) {
|
|
159
171
|
const out = [];
|
|
160
|
-
if (userStory)
|
|
172
|
+
if (userStory)
|
|
173
|
+
out.push(...section(colours, "story", ["h3. User story", escapeJiraWiki(userStory)]));
|
|
161
174
|
if (acceptance.length) {
|
|
162
175
|
out.push(
|
|
163
|
-
...
|
|
176
|
+
...section(colours, "criteria", [
|
|
164
177
|
"h3. Acceptance criteria",
|
|
165
178
|
...acceptance.map((a) => `# ${escapeJiraWiki(a)}`),
|
|
166
179
|
]),
|
|
@@ -168,7 +181,7 @@ function render({ userStory, acceptance, currentVsTarget, links }, sourceIds) {
|
|
|
168
181
|
}
|
|
169
182
|
if (currentVsTarget.length) {
|
|
170
183
|
out.push(
|
|
171
|
-
...
|
|
184
|
+
...section(colours, "other", [
|
|
172
185
|
"h3. Current vs target",
|
|
173
186
|
"||Id||Status||Where||",
|
|
174
187
|
...currentVsTarget.map(
|
|
@@ -179,7 +192,10 @@ function render({ userStory, acceptance, currentVsTarget, links }, sourceIds) {
|
|
|
179
192
|
}
|
|
180
193
|
if (links.length) {
|
|
181
194
|
out.push(
|
|
182
|
-
...
|
|
195
|
+
...section(colours, "other", [
|
|
196
|
+
"h3. Links",
|
|
197
|
+
...links.map((l) => `* ${wikiLink(l.label, l.url)}`),
|
|
198
|
+
]),
|
|
183
199
|
);
|
|
184
200
|
}
|
|
185
201
|
out.push(
|
|
@@ -208,7 +224,7 @@ export function buildStoryBody(story, ctx) {
|
|
|
208
224
|
if (!acceptance.some((a) => NEGATIVE.test(a))) warnings.push("no negative acceptance criterion");
|
|
209
225
|
|
|
210
226
|
const body = { userStory, acceptance, currentVsTarget, links };
|
|
211
|
-
return { ...body, warnings, wiki: render(body, story.sourceIds || []) };
|
|
227
|
+
return { ...body, warnings, wiki: render(body, story.sourceIds || [], panelColours(ctx.panels)) };
|
|
212
228
|
}
|
|
213
229
|
|
|
214
230
|
/**
|