@mmerterden/multi-agent-pipeline 20.25.1 → 20.26.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/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/README.tr.md +1 -1
- package/docs/facts.json +1 -1
- package/install/catalog-history.json +1 -1
- package/manifest.json +24 -21
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/channels/SKILL.md +4 -2
- package/pipeline/lib/_jira-auth.sh +3 -1
- package/pipeline/lib/jira-family-write.sh +339 -0
- package/pipeline/lib/jira-publish.sh +28 -1
- package/pipeline/multi-agent-refs/channels/jira.md +2 -0
- package/pipeline/multi-agent-refs/features/jira-family.md +175 -0
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +3 -1
- package/pipeline/schemas/agent-state.schema.json +36 -0
- package/pipeline/schemas/prefs.schema.json +23 -0
- package/pipeline/scripts/jira-family.mjs +947 -0
- package/pipeline/scripts/jira-wiki-escape.mjs +3 -3
- package/pipeline/skills/.skill-manifest.json +3 -3
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +5 -0
- package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +7 -0
package/manifest.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mmerterden/multi-agent-pipeline",
|
|
3
|
-
"version": "20.
|
|
4
|
-
"commit": "
|
|
5
|
-
"generatedAt": "2026-10-
|
|
3
|
+
"version": "20.26.0",
|
|
4
|
+
"commit": "35ac932f54214466d6a63effea9d3a5fdd3f90bb",
|
|
5
|
+
"generatedAt": "2026-10-14T20:14:41.247Z",
|
|
6
6
|
"algorithm": "sha256",
|
|
7
|
-
"fileCount":
|
|
7
|
+
"fileCount": 1394,
|
|
8
8
|
"files": {
|
|
9
|
-
"CHANGELOG.md": "
|
|
9
|
+
"CHANGELOG.md": "bc477ff23f828f3302ddd5f79cfa52bbbe792b1decbbbce270f46b19740257d1",
|
|
10
10
|
"LICENSE": "8d8c51aaf1e1538b180c69965fa038db71be86798cc3e26a143bebfefe16a43e",
|
|
11
|
-
"README.md": "
|
|
12
|
-
"README.tr.md": "
|
|
11
|
+
"README.md": "c90b8d15713750c621fa8083531cdeb8158e9b67e995192874b6ac9da1dedc45",
|
|
12
|
+
"README.tr.md": "ce7978c53c733cf359c21423fbfed640f20644584ff153aaab473958fdccfdc3",
|
|
13
13
|
"SECURITY.md": "0eb89553ddcfc56bfe9712315b55a6630ee2f579631aaeccd6ad24f8ac769a0b",
|
|
14
14
|
"docs/FIGMA_PIPELINE.md": "ea01f3ea5fef1a9cb316040a2d416fcf87dd4f2a14b5497aa00994a0b5dc7e6a",
|
|
15
15
|
"docs/adr/0001-three-model-triage.md": "79a6f1025469f607a5859add21854052d7433bc213f4aceee2e159b613eeb6a1",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"docs/best-practices.md": "f8b75b0d38b5896a6a852bb30a1edddeb24dab8b5b7b1d9313290e1241c72024",
|
|
35
35
|
"docs/ecosystem.md": "9b23467fca1bcdc0275e7eccd3428c36181ba9f8cd866778159edd7d65a4d563",
|
|
36
36
|
"docs/engineering.md": "550528cd47c7d2c921729c68d0782a00ac8e77a4bdfec479f6749f9c3235d188",
|
|
37
|
-
"docs/facts.json": "
|
|
37
|
+
"docs/facts.json": "7020352c8c1f73a6f9b616d1e3d8060b76d138beb8c68e85e4edc3c4a0cfb8a7",
|
|
38
38
|
"docs/features.md": "d5fcbe3acc05e054efdf0c43ddfc1a82ef3d463f349ea2ec5b4842fa26d9d49d",
|
|
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": "ae53e8c4bbf1d1f17a91f247f53a5ba79d881ec0251bf25273b68c1482df30ee",
|
|
58
58
|
"install/claude.mjs": "106ce353ebc19c8319d057ce342e1f989519510465c798399076b5f3d8727904",
|
|
59
59
|
"install/codex.mjs": "08925fe9f3ecdf509b37f01105652c847475d20fcde2db0d679b9a7d663ca145",
|
|
60
60
|
"install/copilot.mjs": "41dc394b8a3ae77cfd194ce0ca9bbcafce9bdce349f1f72eeb86569f993e15fe",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"install/templates/multi-agent-autopilot-awake.plist.template": "ddd25c76c56fcbb38a89de8e35637372d70ace993d6bc487a88ca9cfb702fe1f",
|
|
67
67
|
"install/templates/multi-agent-autopilot.plist.template": "122c8a61cd927edd99375d6e3da40f1d42bcceed5b6641785c8b28bb930bc77e",
|
|
68
68
|
"install/unattended-profile-legacy.json": "f52e86b998956f09ad306b1735a6c10b58580d7082d8a55665a4c6bb15c3aa1f",
|
|
69
|
-
"package.json": "
|
|
69
|
+
"package.json": "e2aa2bd877d2f106314385ca681a67071158c9089a5a05e74b4f8a3039c2c39e",
|
|
70
70
|
"pipeline/agents/android-architect.md": "11fb74543cb0ebcf9525ca6654fe95d53da7229e0bf4a1149b31fd9005abc453",
|
|
71
71
|
"pipeline/agents/backend-architect.md": "b02a606c301e4714131aed70ae6d16eeb77fbc819a001e2752cff2488d4246de",
|
|
72
72
|
"pipeline/agents/bulk-reader.md": "6597052cd7d205bac2b1c7633f5232fe1a40730664a913b8eb77efd5a786dc25",
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"pipeline/commands/multi-agent/autopilot/SKILL.md": "ae95364c28e21a5d12b8a579180d59963bad3738fcfd978180c5dca6e55a0d02",
|
|
92
92
|
"pipeline/commands/multi-agent/bug-bash/SKILL.md": "0ae5883b72239c74f4f64fc2d60526091e471afa6a2886f90f70a469a85cc832",
|
|
93
93
|
"pipeline/commands/multi-agent/build-optimize/SKILL.md": "e74d6f5be703acef1704092d5fe53485d95fe6c220bb768accaf68ebd212b7c1",
|
|
94
|
-
"pipeline/commands/multi-agent/channels/SKILL.md": "
|
|
94
|
+
"pipeline/commands/multi-agent/channels/SKILL.md": "4ccd28e5eaa8bd0b618fc23dbf49f007cf65150a4796aabafdb51d1dcffd6752",
|
|
95
95
|
"pipeline/commands/multi-agent/complaint-analysis/SKILL.md": "b9ab7b9444cf99f25868d90f050ed96f04ac8cd8ea18d0999376184b067fc2bc",
|
|
96
96
|
"pipeline/commands/multi-agent/create-jira/SKILL.md": "ac3d3e0ab2fb079b7c4107928573912699f68600f922b2f453be49c65317665a",
|
|
97
97
|
"pipeline/commands/multi-agent/design-check/SKILL.md": "d3cb1b50f2d14cf22a38f4c0db98c852e8ed0586e969f7e6da7c1d65f36554eb",
|
|
@@ -168,7 +168,7 @@
|
|
|
168
168
|
"pipeline/contract/frozen/toolbox.json": "d52f94ca81f62e4c0d67f0718ce6361f332f289b09fe7cc25c8f5f40feb0ffff",
|
|
169
169
|
"pipeline/contract/manifest.json": "e9163ba923619447f872416d7f57ea48d67dc13c356c9544f24957f4131946bf",
|
|
170
170
|
"pipeline/contract/types/index.d.ts": "1434e70701023a7274fb13c9dbdf901b2a0d7259e46f3fcf40fe2454300c32a3",
|
|
171
|
-
"pipeline/lib/_jira-auth.sh": "
|
|
171
|
+
"pipeline/lib/_jira-auth.sh": "b5fd733ac26727eda299976c688456e95611ca40acafd3947c66c445d77f2237",
|
|
172
172
|
"pipeline/lib/account-resolver.sh": "f7bef9beba3f839268509361651be0b1e1f319f2f23821f90ba466fdf185a8f6",
|
|
173
173
|
"pipeline/lib/agent-model-sync.mjs": "2e0264ca8d8fef761563db219d588fed9067847043f960f68f055c186c20b668",
|
|
174
174
|
"pipeline/lib/analysis-jira-write.sh": "26e17b8a972819a1649ead8cc4e6f0ff8548dd686c2572107e1777fb384f639d",
|
|
@@ -205,7 +205,8 @@
|
|
|
205
205
|
"pipeline/lib/invoked-directly.mjs": "a7014f252174414c267cc303a992aa9f3986541c5aad188c87cc6809ba3b69bd",
|
|
206
206
|
"pipeline/lib/issue-fetcher.sh": "36f6eb41935fa44072cf3c9a92a5698d4ffd762103eb57f733e65687ab47479e",
|
|
207
207
|
"pipeline/lib/jira-epic-link.sh": "b5f9ce1067cd8e9f0e547da4e681b71e4ff8c145e9a759fc98f83703dfca4d35",
|
|
208
|
-
"pipeline/lib/jira-
|
|
208
|
+
"pipeline/lib/jira-family-write.sh": "b29674fd2dc6993636d0c8b6b674a069f37d76775420372a755a1ce3b0f94b95",
|
|
209
|
+
"pipeline/lib/jira-publish.sh": "d80c7c128fb16e67962d087f0e12ecbb396d8c7367bbb4f6d7eecb7cedf60fb3",
|
|
209
210
|
"pipeline/lib/json-file-lock.mjs": "d8a9075e6fa4fd10d4fb84efd438f92906fb3181455d661ca5fe604c57441524",
|
|
210
211
|
"pipeline/lib/md2confluence-v3.py": "3677ca47931e4b3d0fd2ce3e11299fc5f6d24799eb8332e37dd60b6c3c4653a2",
|
|
211
212
|
"pipeline/lib/model-dispatch.sh": "6500a5a7cb6a4c099cf1d01678bc9465a3d4eb1001e1f516dcc5eacd69577158",
|
|
@@ -270,7 +271,7 @@
|
|
|
270
271
|
"pipeline/multi-agent-refs/channels/board.md": "895edfd4a26f7f5fbe13f0eb5865d30975ed86ee3dbd4bd3bd12340f48001fcb",
|
|
271
272
|
"pipeline/multi-agent-refs/channels/confluence.md": "1e1d731e9f57efdfe77efab3281d74b7f16e28d3d49699da516225492c551ffc",
|
|
272
273
|
"pipeline/multi-agent-refs/channels/issue-comment.md": "19df6d8d6c23b5ea88687250dc10dd5fa35ba2b2e279efcb000e83622a0664f6",
|
|
273
|
-
"pipeline/multi-agent-refs/channels/jira.md": "
|
|
274
|
+
"pipeline/multi-agent-refs/channels/jira.md": "193152cb85396dcea312785b2a630c13a9403b9b9b1ae91c1fca719e8b37747e",
|
|
274
275
|
"pipeline/multi-agent-refs/channels/multi-repo.md": "a309478d8ba253c797904b1ad174be958e4d365b5ff008634ad1d5fc87969474",
|
|
275
276
|
"pipeline/multi-agent-refs/channels/pr-review-actions.md": "21bf53796ae4ecd59769f9944ad8c7d1cd3a5915b0956be99ec875b847b33c00",
|
|
276
277
|
"pipeline/multi-agent-refs/channels/pr.md": "abe0101d4f2a7cb1ecd88c4260e9ae9e7d6dd65dd52afbf6c44c573c48d11f20",
|
|
@@ -306,6 +307,7 @@
|
|
|
306
307
|
"pipeline/multi-agent-refs/features/external-context-injection.md": "be9a0e904efd52535fa18a254567c9d0c3fc2ed12fcbc7a116f7879048303df6",
|
|
307
308
|
"pipeline/multi-agent-refs/features/free-models.md": "64afaba7e7a77575f4ed6a10905a01330a83f573907fb1a5d5356ae6c80999a0",
|
|
308
309
|
"pipeline/multi-agent-refs/features/jira-context.md": "7d14802ed9ad1747e1a06dec26f22ff9e8959e03c47d7a018af45b8274aea47c",
|
|
310
|
+
"pipeline/multi-agent-refs/features/jira-family.md": "4be05f296e6a2050355c218ed1cd33b489cd8c89ab798e1e44c7ba45fb9517b8",
|
|
309
311
|
"pipeline/multi-agent-refs/features/maturity-followup.md": "e663e5bcf0b3b4d4d2a67f4516dbb9fb12520dfbdedb47a9c5f57c13a9a0633e",
|
|
310
312
|
"pipeline/multi-agent-refs/features/model-fallback.md": "5647b4dbeed6fea21d264f03a85796727a83a001710a8bcbb3932c5a3914d6c5",
|
|
311
313
|
"pipeline/multi-agent-refs/features/mutation-probe.md": "e6d622ab061f6cdcc16da40311e8e7aeae3e6f3c1d4b76d3849859a48a8ae31a",
|
|
@@ -358,7 +360,7 @@
|
|
|
358
360
|
"pipeline/multi-agent-refs/phases/log-format.md": "62d51e57cca8445ab40f3c0dc29615e4b5c8e4743286bf9ef0173610e81397aa",
|
|
359
361
|
"pipeline/multi-agent-refs/phases/modes.md": "0733e20dce458c09f30343db9149fc93bc5a4c9531670b93952f223811dcb1aa",
|
|
360
362
|
"pipeline/multi-agent-refs/phases/operations.md": "9506a26684abfff1e332d92c2321e840544fd4f049aff9f118731e198dabca94",
|
|
361
|
-
"pipeline/multi-agent-refs/phases/phase-0-init.md": "
|
|
363
|
+
"pipeline/multi-agent-refs/phases/phase-0-init.md": "c83e485b2be0461fdefe796d37788fcb11360616d07ab8e13c266d039ecd4e28",
|
|
362
364
|
"pipeline/multi-agent-refs/phases/phase-1-plan.md": "de3f3dc16ae8853740540caa5497d2f18f016dcb7ea0bfc280ca5c136259a078",
|
|
363
365
|
"pipeline/multi-agent-refs/phases/phase-2-dev.md": "7a87dcf07fc36d877eea92a34c1137dba8cd1f7865c8de83959fb2929bd4c248",
|
|
364
366
|
"pipeline/multi-agent-refs/phases/phase-3-review.md": "60d3bff0370e3a31cbc7824560bd8598c19da0c4025be71e1df60661202d2a38",
|
|
@@ -415,7 +417,7 @@
|
|
|
415
417
|
"pipeline/rules/swiftui-qa.md": "8c4afe9e72413e7bdab655f3d17588469e80ac34cb7ad2a690901fc5b6534818",
|
|
416
418
|
"pipeline/rules/tdd.md": "4c1b3114506aa9794c9bb1e1aee1b1bbb4a993d192342e5697cc00bc2f6ea4a2",
|
|
417
419
|
"pipeline/rules/testing.md": "695be5520b65451f0aa300a9643df2c05e607ff6ad372201ea34798dc5998bd5",
|
|
418
|
-
"pipeline/schemas/agent-state.schema.json": "
|
|
420
|
+
"pipeline/schemas/agent-state.schema.json": "85d9ebfb75f89adf9359320702b07742dff7ecc38b1b7801dbf3de9d4447ede4",
|
|
419
421
|
"pipeline/schemas/analysis-output.schema.json": "41826f4bf7c96778f8cdbcc68531e984087897d438b7b49ac5a93fc29db47916",
|
|
420
422
|
"pipeline/schemas/analysis-spec.schema.json": "43770223266be1c94e2cdea585efb6783043353bc5a374613b554596f46e1bcd",
|
|
421
423
|
"pipeline/schemas/answer-request.schema.json": "29d4211a2a05a230e0707b171b73341d2e56f6b83edb178b731905211aab4e23",
|
|
@@ -469,7 +471,7 @@
|
|
|
469
471
|
"pipeline/schemas/plan-todos.schema.json": "be4374b7eeb513fb8a7663374defa5b35ae084f72e112faa41b07c46532c50de",
|
|
470
472
|
"pipeline/schemas/planning-output.schema.json": "6aea9b5b8bbf7f9a227215c29cc30a5b642981a02fe2a81eaf9e8a113f7c3947",
|
|
471
473
|
"pipeline/schemas/pr-request.schema.json": "ae75c4856349d62834802b1189af516bb6fb3d36646831b7ff2fa61d938afcd0",
|
|
472
|
-
"pipeline/schemas/prefs.schema.json": "
|
|
474
|
+
"pipeline/schemas/prefs.schema.json": "e3b983744852a8572d92344cdb88367a892afb3e3ee63ce84e798b92b764b8d7",
|
|
473
475
|
"pipeline/schemas/repo-profile.schema.json": "1a74ef8330da9a26a5a70955f6f9cbb20eda54a8615efaf86c7993d420a7f04e",
|
|
474
476
|
"pipeline/schemas/repos.schema.json": "a9bf56f25c66201c170912e3389bd73e4a3f61922410f83e31a6c6ba47aee861",
|
|
475
477
|
"pipeline/schemas/research-output.schema.json": "54e3ba1b04fdc20fbaa84333b943a8ab95d1395c7b0e0aedc67929184cb49523",
|
|
@@ -606,8 +608,9 @@
|
|
|
606
608
|
"pipeline/scripts/host-hooks.mjs": "19fa3afc4c5caead2905144fe7727eacaa7c8e1fee5db20cfdfb8879e5aa7704",
|
|
607
609
|
"pipeline/scripts/issues.mjs": "66c787f1d6be924a61cb8b1504354dd7fb0de89a2afee744aa31435b834ef383",
|
|
608
610
|
"pipeline/scripts/jira-attach.sh": "300b6eb76ae2179324f70a2131388c0f56ca8bf49397cc858937b4d7f47372db",
|
|
611
|
+
"pipeline/scripts/jira-family.mjs": "b5860c5702ec8c03d0a9ac222fe9728c147445eab32c72173fa077ef82dcf632",
|
|
609
612
|
"pipeline/scripts/jira-search.sh": "afa2e552eec83bf7ab8bc1aa43b99c7480a82bfbfc808ce22c89df1b57e90563",
|
|
610
|
-
"pipeline/scripts/jira-wiki-escape.mjs": "
|
|
613
|
+
"pipeline/scripts/jira-wiki-escape.mjs": "655a170c1ea0d366a7eac9eb25fbf80ddcd19d1d3315bbf75b210908d14237f9",
|
|
611
614
|
"pipeline/scripts/keychain-save.sh": "7cc4d81acd88d84d710fa733d5c9604a0acb3d924738216ac7d34040bad7ae29",
|
|
612
615
|
"pipeline/scripts/keychain.py": "727f55c6e2b87c6078d4a54cc9470b16fc4610abb3a24c60fdd226ae0bf10391",
|
|
613
616
|
"pipeline/scripts/launch-request.mjs": "826c5ec100cd7def4d55c94894582959c1ce325ffc9527e4b3fe43c89fdd8b10",
|
|
@@ -724,7 +727,7 @@
|
|
|
724
727
|
"pipeline/scripts/worktrees.mjs": "90dfd09b70fffe9b56c331a4ae2692cc248f4898f4ee6aee4239ef0b20888a90",
|
|
725
728
|
"pipeline/scripts/write-state.mjs": "e1198bf970eace07d3eff22b394808a526e473df65d3d58aa2d5a12cfdac8b49",
|
|
726
729
|
"pipeline/scripts/write-store-config.mjs": "b994a061f6febb2ca3d396074d8b51d2c1c1f991d3072d321703380da3639a56",
|
|
727
|
-
"pipeline/skills/.skill-manifest.json": "
|
|
730
|
+
"pipeline/skills/.skill-manifest.json": "df49c99c3f308b8d5dab153e2ed22b301de7417e52065146d11eb754adf75326",
|
|
728
731
|
"pipeline/skills/.skills-index.json": "bfb04a72333aec2c25e21c8edce4a76e2128e5e7f1e88b0cd780e7f1cc6bf370",
|
|
729
732
|
"pipeline/skills/shared/README.md": "47f5c77556bf1676857fe6a803fc5c85da5cd32e1b0332a6f3728b6da610cc58",
|
|
730
733
|
"pipeline/skills/shared/core/apple-archive-compliance/SKILL.md": "7239fef717ea45b79276bfcc2c3903acd57950b6e00dd6a2a66399c4b09cbd51",
|
|
@@ -740,7 +743,7 @@
|
|
|
740
743
|
"pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md": "a9731771fbc1f45260f214f069b976eff16e7b25e2f1375b0d9a213575271bc6",
|
|
741
744
|
"pipeline/skills/shared/core/multi-agent-bug-bash/SKILL.md": "a44ad2520ef24444b68d9040519f82498abda3ebb6467890bb6b5e1460299400",
|
|
742
745
|
"pipeline/skills/shared/core/multi-agent-build-optimize/SKILL.md": "8534891d1198597a0893d131e6b944d15443f62b24cb5584c486a235845fd281",
|
|
743
|
-
"pipeline/skills/shared/core/multi-agent-channels/SKILL.md": "
|
|
746
|
+
"pipeline/skills/shared/core/multi-agent-channels/SKILL.md": "cb5e1f9e82f3fd3e7d9ad20bff7a066cafff75323044a08726e3af368738b123",
|
|
744
747
|
"pipeline/skills/shared/core/multi-agent-complaint-analysis/SKILL.md": "dd30708491303902aa8629130809e9601c2096dc12aa9fc671f798142cea3485",
|
|
745
748
|
"pipeline/skills/shared/core/multi-agent-create-jira/SKILL.md": "5aad0d841e9e5b7140bb441e07819209ca64c5b6fc209d6ad586b5fbb353e377",
|
|
746
749
|
"pipeline/skills/shared/core/multi-agent-design-check/SKILL.md": "8888744fa52e935f53db9a1068e1b18c636b6c9d55b084dabfc19b9ae68bf6f8",
|
|
@@ -796,7 +799,7 @@
|
|
|
796
799
|
"pipeline/skills/shared/core/multi-agent-testflight-validation/SKILL.md": "b6c360c330ef0094cd7e7d9624e89624446b048951e43a180766bbe67d1b725c",
|
|
797
800
|
"pipeline/skills/shared/core/multi-agent-uninstall/SKILL.md": "9dccf78ffc2fb014d83f67d683d395f56f4fad8b5a36b9aa84e6cbd075b58818",
|
|
798
801
|
"pipeline/skills/shared/core/multi-agent-update/SKILL.md": "2f9e224e240e1c9053a59066678f869ae36f18c088482488f3bda8fd207148ea",
|
|
799
|
-
"pipeline/skills/shared/core/multi-agent/SKILL.md": "
|
|
802
|
+
"pipeline/skills/shared/core/multi-agent/SKILL.md": "079b49a1981c63d90798089a343b7f324571c4f7cc6ad3fb757e82719c35eb0a",
|
|
800
803
|
"pipeline/skills/shared/external/NOTICE-avdlee-swiftui-agent-skill.md": "400842e4653f038fad22509fe3f3d152151120758f5f9b170e6dba59afaf4a48",
|
|
801
804
|
"pipeline/skills/shared/external/NOTICE-dimillian-skills.md": "f9c3d783fd3563418c18291b90899251e890fd3fa8ee353c8c1fbbe61078526e",
|
|
802
805
|
"pipeline/skills/shared/external/NOTICE-paul-hudson-skills.md": "bca5fe8779df70fb0f234483d65ff3562c67521e719b69b22aac260803b75244",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mmerterden/multi-agent-pipeline",
|
|
3
|
-
"version": "20.
|
|
3
|
+
"version": "20.26.0",
|
|
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",
|
|
@@ -352,7 +352,7 @@ Aggregated Markdown from Step 5 → PR description. GitHub uses `gh pr edit --bo
|
|
|
352
352
|
Full contract: [`$HOME/.claude/multi-agent-refs/channels/pr.md`]($HOME/.claude/multi-agent-refs/channels/pr.md) - Bitbucket payload assembly snippet, version-mismatch retry, `--ready` promotion, multi-repo cross-link block.
|
|
353
353
|
|
|
354
354
|
#### Adapter: Jira comment
|
|
355
|
-
Sections: `summary` + `test_scenarios` + `impact` + `context_refs` only - the technical ones go to the PR and Confluence (`channels/jira.md` fixes the order). The PR URL goes on line 1, above the first heading. The wiki-markup conversion table lives in that same ref and only there
|
|
355
|
+
Sections: `summary` + `test_scenarios` + `impact` + `context_refs` only - the technical ones go to the PR and Confluence (`channels/jira.md` fixes the order). The PR URL goes on line 1, above the first heading. The wiki-markup conversion table lives in that same ref and only there.
|
|
356
356
|
|
|
357
357
|
Then post it - do not assemble the request:
|
|
358
358
|
|
|
@@ -360,10 +360,12 @@ Then post it - do not assemble the request:
|
|
|
360
360
|
bash "$HOME/.claude/lib/jira-publish.sh" --issue "$JIRA_ID" --body-file "$F" --target comment
|
|
361
361
|
```
|
|
362
362
|
|
|
363
|
-
The publisher escapes every body (Jira renders `:)` `(x)` `(!)` `(/)` as emoticon images, and a Swift selector like `login(source:input:)` ends in `:)`) and resolves the token from `keychainMapping.jira` without putting it on argv.
|
|
363
|
+
The publisher escapes every body (Jira renders `:)` `(x)` `(!)` `(/)` as emoticon images, and a Swift selector like `login(source:input:)` ends in `:)`) and resolves the token from `keychainMapping.jira` without putting it on argv.
|
|
364
364
|
|
|
365
365
|
Full contract: [`$HOME/.claude/multi-agent-refs/channels/jira.md`]($HOME/.claude/multi-agent-refs/channels/jira.md) - full conversion table, multi-repo PR-list prepend, Wiki→Jira triad interaction.
|
|
366
366
|
|
|
367
|
+
**Issue family** (`state.jiraFamily` set): instead, `bash $HOME/.claude/lib/jira-family-write.sh plan --state "$STATE_FILE" --dev-body "$F"`, the Step 6 preview as one multi-select under `plan.confirm`, then `apply --state "$STATE_FILE"`: `$HOME/.claude/multi-agent-refs/features/jira-family.md`.
|
|
368
|
+
|
|
367
369
|
#### Adapter: Confluence page
|
|
368
370
|
Token from `keychainMapping.confluence`; missing/expired triggers the inline Save Flow from `setup.md`. Parent page picked from `prefs.projects[<project>].confluenceUrls` recents (LRU, max 5) or paste new URL. Auto-suggest title `"{jiraId} - {taskTitle}"`. Body converted to Confluence storage format (`<h3>`, `<table>`, `<ac:structured-macro>` code blocks). POST `/rest/api/content`; URL saved back to recents.
|
|
369
371
|
|
|
@@ -38,7 +38,9 @@
|
|
|
38
38
|
|
|
39
39
|
# shellcheck shell=bash
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
# MULTI_AGENT_PREFS is the same override jira-publish.sh reads, so a read and
|
|
42
|
+
# the write after it resolve the same host and token.
|
|
43
|
+
JIRA_AUTH_PREFS="${JIRA_AUTH_PREFS:-${MULTI_AGENT_PREFS:-$HOME/.claude/multi-agent-preferences.json}}"
|
|
42
44
|
|
|
43
45
|
_jira_auth_pref() { # _jira_auth_pref <jq path> -> value or empty
|
|
44
46
|
[ -f "$JIRA_AUTH_PREFS" ] || { printf ''; return 0; }
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# jira-family-write.sh - fetch the issue family of a Jira-started run, and
|
|
4
|
+
# apply the writes scripts/jira-family.mjs planned for it.
|
|
5
|
+
#
|
|
6
|
+
# The planning half is offline and testable; this half is the one that talks
|
|
7
|
+
# to Jira, so it stays small. Every write goes through jira-publish.sh, which
|
|
8
|
+
# escapes the body, runs the outbound gate and backs a description up before it
|
|
9
|
+
# replaces one. A write the plan marks `raw` (the story reformat: the author's
|
|
10
|
+
# lines plus escaped macros) is sent with --raw, so the author's lines reach
|
|
11
|
+
# Jira unchanged. Reads go through jira_api in _jira-auth.sh.
|
|
12
|
+
#
|
|
13
|
+
# A write is checked against the site right before it is sent:
|
|
14
|
+
# comment skipped when a comment by the authenticated user on the issue
|
|
15
|
+
# already carries its marker
|
|
16
|
+
# description skipped when the story already conforms, or when it changed
|
|
17
|
+
# since the plan read it (jira-publish.sh --expect-file)
|
|
18
|
+
# so running the same plan twice writes nothing the second time.
|
|
19
|
+
#
|
|
20
|
+
# Usage, from a run (the state file is agent-state.json; the family's files go
|
|
21
|
+
# to jira-family/ beside it):
|
|
22
|
+
# jira-family-write.sh resolve --state FILE
|
|
23
|
+
# fetch and resolve the family, record state.jiraFamily; no Jira write
|
|
24
|
+
# jira-family-write.sh plan --state FILE --dev-body FILE [--skip id,id]
|
|
25
|
+
# [--dev-task KEY] [--test-task KEY] [--build TEXT]
|
|
26
|
+
# re-resolve and plan; run id, PR URL and mode come from the state
|
|
27
|
+
# jira-family-write.sh apply --state FILE [--dry-run]
|
|
28
|
+
# apply jira-family/plan.json, write jira-family/writes.json, merge it
|
|
29
|
+
# into state.jiraFamily.writes by write id
|
|
30
|
+
#
|
|
31
|
+
# Lower level:
|
|
32
|
+
# jira-family-write.sh fetch --issue KEY --out DIR
|
|
33
|
+
# DIR/input.json, DIR/story.json (the parent when KEY is a sub-task, else
|
|
34
|
+
# the same issue), DIR/site-types.txt (empty when unreadable)
|
|
35
|
+
# jira-family-write.sh apply --plan FILE [--state FILE] [--result FILE] [--dry-run]
|
|
36
|
+
# --state the plan's autopilot decisions are recorded there
|
|
37
|
+
# (decisions-log.mjs) once their write has gone out
|
|
38
|
+
# --result the per-write outcome as JSON, written on every exit
|
|
39
|
+
#
|
|
40
|
+
# Exit: 0 done, 3 the input does not parse or the plan still has questions,
|
|
41
|
+
# 4 auth (also when a write failed on auth), 5 a write failed (the others
|
|
42
|
+
# went out; --result lists which, and a re-run sends only what is missing),
|
|
43
|
+
# 6 unattended (nothing read or written; the runner owns Jira),
|
|
44
|
+
# 64 usage or a malformed issue key.
|
|
45
|
+
|
|
46
|
+
set -uo pipefail
|
|
47
|
+
|
|
48
|
+
SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
49
|
+
# shellcheck source=/dev/null
|
|
50
|
+
. "$SELF_DIR/_jira-auth.sh"
|
|
51
|
+
# shellcheck source=/dev/null
|
|
52
|
+
. "$SELF_DIR/unattended.sh"
|
|
53
|
+
|
|
54
|
+
USAGE="usage:
|
|
55
|
+
jira-family-write.sh resolve --state FILE
|
|
56
|
+
jira-family-write.sh plan --state FILE --dev-body FILE [--skip id,id] [--dev-task KEY] [--test-task KEY] [--build TEXT]
|
|
57
|
+
jira-family-write.sh apply --state FILE [--plan FILE] [--result FILE] [--dry-run]
|
|
58
|
+
jira-family-write.sh fetch --issue KEY --out DIR
|
|
59
|
+
jira-family-write.sh apply --plan FILE [--state FILE] [--result FILE] [--dry-run]"
|
|
60
|
+
usage() { echo "$USAGE" >&2; exit 64; }
|
|
61
|
+
|
|
62
|
+
CMD="${1:-}"; [ -n "$CMD" ] || usage; shift
|
|
63
|
+
case "$CMD" in -h|--help) sed -n '2,45p' "$0"; exit 0 ;; esac
|
|
64
|
+
ISSUE=""; OUT=""; PLAN=""; STATE=""; RESULT=""; DRY=0
|
|
65
|
+
DEV_BODY=""; SKIP=""; DEV_TASK=""; TEST_TASK=""; BUILD=""
|
|
66
|
+
while [ $# -gt 0 ]; do
|
|
67
|
+
case "$1" in
|
|
68
|
+
-h|--help) sed -n '2,45p' "$0"; exit 0 ;;
|
|
69
|
+
--dev-body) DEV_BODY="${2:-}"; shift 2 ;;
|
|
70
|
+
--skip) SKIP="${2:-}"; shift 2 ;;
|
|
71
|
+
--dev-task) DEV_TASK="${2:-}"; shift 2 ;;
|
|
72
|
+
--test-task) TEST_TASK="${2:-}"; shift 2 ;;
|
|
73
|
+
--build) BUILD="${2:-}"; shift 2 ;;
|
|
74
|
+
--issue) ISSUE="${2:-}"; shift 2 ;;
|
|
75
|
+
--out) OUT="${2:-}"; shift 2 ;;
|
|
76
|
+
--plan) PLAN="${2:-}"; shift 2 ;;
|
|
77
|
+
--state) STATE="${2:-}"; shift 2 ;;
|
|
78
|
+
--result) RESULT="${2:-}"; shift 2 ;;
|
|
79
|
+
--dry-run) DRY=1; shift ;;
|
|
80
|
+
*) usage ;;
|
|
81
|
+
esac
|
|
82
|
+
done
|
|
83
|
+
|
|
84
|
+
command -v jq >/dev/null 2>&1 || { echo "ERR: jq is required" >&2; exit 3; }
|
|
85
|
+
|
|
86
|
+
FAMILY_MJS="$SELF_DIR/../scripts/jira-family.mjs"
|
|
87
|
+
DECISIONS_MJS="$SELF_DIR/../scripts/decisions-log.mjs"
|
|
88
|
+
PUBLISH="$SELF_DIR/jira-publish.sh"
|
|
89
|
+
FIELDS="summary,issuetype,parent,subtasks,description,project,status"
|
|
90
|
+
|
|
91
|
+
# A key goes into a REST path, so anything but a key is refused before use.
|
|
92
|
+
valid_key() { printf '%s' "$1" | grep -Eq '^[A-Z][A-Z0-9_]+-[0-9]+$'; }
|
|
93
|
+
|
|
94
|
+
# What a jira-publish.sh exit code means, for the result line.
|
|
95
|
+
publish_reason() {
|
|
96
|
+
case "$1" in
|
|
97
|
+
4) echo "Jira host or token could not be resolved (exit 4)" ;;
|
|
98
|
+
5) echo "the Jira call failed (exit 5)" ;;
|
|
99
|
+
64) echo "refused before sending, e.g. by the outbound gate (exit 64): $2" ;;
|
|
100
|
+
*) echo "jira-publish.sh exit $1: $2" ;;
|
|
101
|
+
esac
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
fetch_issue() { # fetch_issue <key> <file>
|
|
105
|
+
jira_api GET "/rest/api/2/issue/$1?fields=$FIELDS" > "$2" 2>/dev/null || return 5
|
|
106
|
+
jq -e '.key' "$2" >/dev/null 2>&1 || return 5
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
do_fetch() { # do_fetch <key> <dir>
|
|
110
|
+
local issue="$1" out="$2" parent project
|
|
111
|
+
valid_key "$issue" || { echo "ERR: not an issue key: $issue" >&2; return 64; }
|
|
112
|
+
if ma_unattended; then
|
|
113
|
+
echo "unattended run: the family is not read here; the runner owns Jira" >&2
|
|
114
|
+
return 6
|
|
115
|
+
fi
|
|
116
|
+
jira_auth_resolve || return 4
|
|
117
|
+
mkdir -p "$out"
|
|
118
|
+
fetch_issue "$issue" "$out/input.json" || { echo "ERR: could not read $issue" >&2; return 5; }
|
|
119
|
+
parent="$(jq -r 'if .fields.issuetype.subtask == true then .fields.parent.key // empty else empty end' "$out/input.json")"
|
|
120
|
+
if [ -n "$parent" ] && ! valid_key "$parent"; then
|
|
121
|
+
echo "WARN: the parent key does not look like a key: $parent" >&2; parent=""
|
|
122
|
+
fi
|
|
123
|
+
if [ -n "$parent" ]; then
|
|
124
|
+
fetch_issue "$parent" "$out/story.json" || { echo "WARN: could not read the parent $parent" >&2; rm -f "$out/story.json"; }
|
|
125
|
+
else
|
|
126
|
+
cp "$out/input.json" "$out/story.json"
|
|
127
|
+
fi
|
|
128
|
+
project="$(jq -r '.fields.project.key // empty' "$out/input.json")"
|
|
129
|
+
[ -n "$project" ] || project="${issue%%-*}"
|
|
130
|
+
bash "$SELF_DIR/analysis-jira-write.sh" --subtask-types --project "$project" \
|
|
131
|
+
> "$out/site-types.txt" 2>/dev/null || : > "$out/site-types.txt"
|
|
132
|
+
echo "fetched $issue${parent:+ (story $parent)} into $out"
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# The run's own files: the issue key and the family directory beside agent-state.json.
|
|
136
|
+
state_key() { jq -r '.jiraId // .taskId // empty' "$STATE"; }
|
|
137
|
+
family_dir() { printf '%s/jira-family' "$(cd "$(dirname "$STATE")" && pwd)"; }
|
|
138
|
+
|
|
139
|
+
do_resolve() { # fetch, resolve, and record state.jiraFamily; asks and writes nothing in Jira
|
|
140
|
+
local key dir story=""
|
|
141
|
+
key="$(state_key)"; dir="$(family_dir)"
|
|
142
|
+
do_fetch "$key" "$dir" >/dev/null || return $?
|
|
143
|
+
[ -f "$dir/story.json" ] && story="--story $dir/story.json"
|
|
144
|
+
# shellcheck disable=SC2086
|
|
145
|
+
node "$FAMILY_MJS" resolve --input "$dir/input.json" $story \
|
|
146
|
+
--site-types "$dir/site-types.txt" > "$dir/family.json" || return 3
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if [ "$CMD" = "fetch" ]; then
|
|
150
|
+
[ -n "$ISSUE" ] && [ -n "$OUT" ] || usage
|
|
151
|
+
do_fetch "$ISSUE" "$OUT"
|
|
152
|
+
exit $?
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
WRITE_STATE="$SELF_DIR/../scripts/write-state.mjs"
|
|
156
|
+
|
|
157
|
+
if [ "$CMD" = "resolve" ]; then
|
|
158
|
+
[ -n "$STATE" ] && [ -f "$STATE" ] || usage
|
|
159
|
+
do_resolve || exit $?
|
|
160
|
+
DIR="$(family_dir)"
|
|
161
|
+
jq -n --slurpfile f "$DIR/family.json" --slurpfile s "$STATE" \
|
|
162
|
+
'{jiraFamily: ($f[0].stateEntry + {writes: ($s[0].jiraFamily.writes // [])})}' \
|
|
163
|
+
| node "$WRITE_STATE" "$STATE" || exit 3
|
|
164
|
+
jq -r '"family: story \(.stateEntry.storyKey), dev \(.stateEntry.devTaskKey // "-"), test \(.stateEntry.testTaskKey // "-"), story format \(.stateEntry.storyFormat.status)"' "$DIR/family.json"
|
|
165
|
+
exit 0
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
if [ "$CMD" = "plan" ]; then
|
|
169
|
+
[ -n "$STATE" ] && [ -f "$STATE" ] && [ -n "$DEV_BODY" ] && [ -f "$DEV_BODY" ] || usage
|
|
170
|
+
do_resolve || exit $?
|
|
171
|
+
DIR="$(family_dir)"
|
|
172
|
+
RUN_ID="$(jq -r '"\(.taskId)-\(.shortId // 0)"' "$STATE")"
|
|
173
|
+
PR_URL="$(jq -r '.pr.url // .projects[0].pr.url // empty' "$STATE")"
|
|
174
|
+
MODE="$(jq -r 'if .autopilot == true then "autopilot" else "interactive" end' "$STATE")"
|
|
175
|
+
set -- --family "$DIR/family.json" --dev-body "$DEV_BODY" --run-id "$RUN_ID" --mode "$MODE"
|
|
176
|
+
[ -z "$PR_URL" ] || set -- "$@" --pr-url "$PR_URL"
|
|
177
|
+
[ -z "$BUILD" ] || set -- "$@" --build "$BUILD"
|
|
178
|
+
[ -z "$SKIP" ] || set -- "$@" --skip "$SKIP"
|
|
179
|
+
[ -z "$DEV_TASK" ] || set -- "$@" --dev-task "$DEV_TASK"
|
|
180
|
+
[ -z "$TEST_TASK" ] || set -- "$@" --test-task "$TEST_TASK"
|
|
181
|
+
node "$FAMILY_MJS" plan "$@" > "$DIR/plan.json" || exit 3
|
|
182
|
+
jq -r '.confirm, (.skipped[] | " not written: \(.id) (\(.reason))"), (.questions[] | " question \(.id): \(.question) [\(.options | join(", "))]")' "$DIR/plan.json"
|
|
183
|
+
echo "plan: $DIR/plan.json"
|
|
184
|
+
exit 0
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
[ "$CMD" = "apply" ] || usage
|
|
188
|
+
MERGE=0
|
|
189
|
+
if [ -z "$PLAN" ] && [ -n "$STATE" ] && [ -f "$STATE" ]; then
|
|
190
|
+
PLAN="$(family_dir)/plan.json"
|
|
191
|
+
[ -n "$RESULT" ] || RESULT="$(family_dir)/writes.json"
|
|
192
|
+
MERGE=1
|
|
193
|
+
fi
|
|
194
|
+
[ -n "$PLAN" ] && [ -f "$PLAN" ] || usage
|
|
195
|
+
|
|
196
|
+
# Every exit leaves a result file, so the caller's follow-up never reads nothing.
|
|
197
|
+
early_exit() { # early_exit <code> <status> <reason>
|
|
198
|
+
if [ -n "$RESULT" ]; then
|
|
199
|
+
jq -c --arg s "$2" --arg r "$3" '[.writes[]? | {id, issue, status: $s, reason: $r}]' "$PLAN" \
|
|
200
|
+
> "$RESULT" 2>/dev/null || echo '[]' > "$RESULT"
|
|
201
|
+
fi
|
|
202
|
+
exit "$1"
|
|
203
|
+
}
|
|
204
|
+
if ma_unattended; then
|
|
205
|
+
echo "unattended run: the family writes are left to the runner" >&2
|
|
206
|
+
early_exit 6 skipped "unattended run: left to the runner"
|
|
207
|
+
fi
|
|
208
|
+
jq -e . "$PLAN" >/dev/null 2>&1 || { echo "ERR: the plan does not parse: $PLAN" >&2; early_exit 3 failed "the plan does not parse"; }
|
|
209
|
+
if [ "$(jq -r '.ready' "$PLAN")" != "true" ]; then
|
|
210
|
+
echo "ERR: the plan still has open questions ($(jq -r '[.questions[].id] | join(", ")' "$PLAN")); answer them and re-plan" >&2
|
|
211
|
+
early_exit 3 skipped "the plan still has open questions"
|
|
212
|
+
fi
|
|
213
|
+
[ "$DRY" -eq 1 ] || jira_auth_resolve || exit 4
|
|
214
|
+
|
|
215
|
+
WORK="$(mktemp -d)"
|
|
216
|
+
trap 'rm -rf "$WORK"' EXIT
|
|
217
|
+
RESULTS="$WORK/results.jsonl"; : > "$RESULTS"
|
|
218
|
+
ME=""
|
|
219
|
+
RC=0
|
|
220
|
+
|
|
221
|
+
record() { # record <id> <issue> <status> <reason>
|
|
222
|
+
jq -cn --arg i "$1" --arg k "$2" --arg s "$3" --arg r "$4" \
|
|
223
|
+
'{id: $i, issue: $k, status: $s, reason: $r}' >> "$RESULTS"
|
|
224
|
+
printf ' %-8s %-12s %s%s\n' "$3" "$1" "$2" "${4:+ ($4)}"
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
fail_rc() { # an auth failure (4) outranks any other failure (5) in the exit code
|
|
228
|
+
if [ "$1" -eq 4 ]; then RC=4; elif [ "$RC" -ne 4 ]; then RC=5; fi
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
record_decisions() { # record_decisions <writeId>, or "" for the decisions that made no write
|
|
232
|
+
[ -n "$STATE" ] || return 0
|
|
233
|
+
jq -c --arg w "$1" '.decisions[]? | select(if $w == "" then .writeId == null else .writeId == $w end)' "$PLAN" | while IFS= read -r d; do
|
|
234
|
+
node "$DECISIONS_MJS" add --state "$STATE" \
|
|
235
|
+
--what "$(printf '%s' "$d" | jq -r .what)" \
|
|
236
|
+
--why "$(printf '%s' "$d" | jq -r .why)" \
|
|
237
|
+
--cost-if-wrong "$(printf '%s' "$d" | jq -r .costIfWrong)" \
|
|
238
|
+
--phase "$(printf '%s' "$d" | jq -r .phase)" >/dev/null 2>&1 \
|
|
239
|
+
|| echo " WARN: could not record the decision for $1" >&2
|
|
240
|
+
done
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
echo "jira-family: $(jq -r '.confirm' "$PLAN")$([ "$DRY" -eq 1 ] && echo " (dry run)")"
|
|
244
|
+
N="$(jq '.writes | length' "$PLAN")"
|
|
245
|
+
i=0
|
|
246
|
+
while [ "$i" -lt "$N" ]; do
|
|
247
|
+
W="$(jq -c ".writes[$i]" "$PLAN")"
|
|
248
|
+
i=$((i + 1))
|
|
249
|
+
id="$(printf '%s' "$W" | jq -r .id)"
|
|
250
|
+
kind="$(printf '%s' "$W" | jq -r .kind)"
|
|
251
|
+
issue="$(printf '%s' "$W" | jq -r .issue)"
|
|
252
|
+
printf '%s' "$W" | jq -j .body > "$WORK/body.txt"
|
|
253
|
+
if ! valid_key "$issue"; then
|
|
254
|
+
record "$id" "$issue" "failed" "not an issue key"; RC=5; continue
|
|
255
|
+
fi
|
|
256
|
+
if [ "$DRY" -eq 1 ]; then
|
|
257
|
+
record "$id" "$issue" "dry-run" "$kind"
|
|
258
|
+
continue
|
|
259
|
+
fi
|
|
260
|
+
if [ "$kind" = "comment" ]; then
|
|
261
|
+
mark="$(printf '%s' "$W" | jq -r .marker)"
|
|
262
|
+
if ! jira_api GET "/rest/api/2/issue/$issue/comment?maxResults=1000" > "$WORK/comments.json" 2>/dev/null \
|
|
263
|
+
|| ! jq -e '.comments' "$WORK/comments.json" >/dev/null 2>&1; then
|
|
264
|
+
record "$id" "$issue" "failed" "could not read the comments"; RC=5; continue
|
|
265
|
+
fi
|
|
266
|
+
if [ -z "$ME" ]; then
|
|
267
|
+
jira_api GET "/rest/api/2/myself" > "$WORK/me.json" 2>/dev/null || :
|
|
268
|
+
ME="$(jq -c '{name, key, accountId} | with_entries(select(.value != null))' "$WORK/me.json" 2>/dev/null || echo "")"
|
|
269
|
+
fi
|
|
270
|
+
if [ -z "$ME" ] || [ "$ME" = "{}" ]; then
|
|
271
|
+
ME=""; record "$id" "$issue" "failed" "could not read the authenticated user"; RC=5; continue
|
|
272
|
+
fi
|
|
273
|
+
# Only our own comments count: another user can paste a marker, and it
|
|
274
|
+
# must not be able to stop this one from being posted.
|
|
275
|
+
seen="$(jq --arg m "$mark" --argjson me "$ME" '[.comments[]?
|
|
276
|
+
| select((.author // {}) as $a | any($me | to_entries[]; $a[.key] == .value))
|
|
277
|
+
| .body // "" | select(contains($m))] | length' "$WORK/comments.json")"
|
|
278
|
+
if [ "${seen:-0}" -gt 0 ]; then
|
|
279
|
+
record "$id" "$issue" "exists" "already posted by this run"; continue
|
|
280
|
+
fi
|
|
281
|
+
bash "$PUBLISH" --issue "$issue" --body-file "$WORK/body.txt" --target comment >/dev/null 2>"$WORK/err.txt"
|
|
282
|
+
prc=$?
|
|
283
|
+
if [ "$prc" -eq 0 ]; then
|
|
284
|
+
record "$id" "$issue" "written" ""
|
|
285
|
+
record_decisions "$id"
|
|
286
|
+
else
|
|
287
|
+
record "$id" "$issue" "failed" "$(publish_reason "$prc" "$(head -1 "$WORK/err.txt")")"
|
|
288
|
+
fail_rc "$prc"
|
|
289
|
+
fi
|
|
290
|
+
continue
|
|
291
|
+
fi
|
|
292
|
+
# description
|
|
293
|
+
if ! jira_api GET "/rest/api/2/issue/$issue?fields=description" > "$WORK/current.json" 2>/dev/null \
|
|
294
|
+
|| ! jq -e '.fields' "$WORK/current.json" >/dev/null 2>&1; then
|
|
295
|
+
record "$id" "$issue" "failed" "could not read the description"; RC=5; continue
|
|
296
|
+
fi
|
|
297
|
+
jq -j '.fields.description // ""' "$WORK/current.json" > "$WORK/current.txt"
|
|
298
|
+
printf '%s' "$W" | jq -j '.original // ""' > "$WORK/original.txt"
|
|
299
|
+
jq '{global: {issueTree: (if .storyPanels == null then {} else {storyPanels: .storyPanels} end)}}' \
|
|
300
|
+
"$PLAN" > "$WORK/plan-prefs.json"
|
|
301
|
+
if node "$FAMILY_MJS" check --description "$WORK/current.txt" --prefs "$WORK/plan-prefs.json" >/dev/null 2>&1; then
|
|
302
|
+
record "$id" "$issue" "exists" "already in the story format"; continue
|
|
303
|
+
fi
|
|
304
|
+
if ! cmp -s "$WORK/current.txt" "$WORK/original.txt"; then
|
|
305
|
+
record "$id" "$issue" "skipped" "the description changed since the preview"; continue
|
|
306
|
+
fi
|
|
307
|
+
raw=""
|
|
308
|
+
[ "$(printf '%s' "$W" | jq -r '.raw == true')" = "true" ] && raw="--raw"
|
|
309
|
+
bash "$PUBLISH" --issue "$issue" --body-file "$WORK/body.txt" --target description \
|
|
310
|
+
--mode replace --confirm-overwrite --expect-file "$WORK/original.txt" $raw >/dev/null 2>"$WORK/err.txt"
|
|
311
|
+
prc=$?
|
|
312
|
+
if [ "$prc" -eq 0 ]; then
|
|
313
|
+
record "$id" "$issue" "written" "previous text backed up"
|
|
314
|
+
record_decisions "$id"
|
|
315
|
+
elif [ "$prc" -eq 6 ]; then
|
|
316
|
+
record "$id" "$issue" "skipped" "the description changed since the preview"
|
|
317
|
+
else
|
|
318
|
+
record "$id" "$issue" "failed" "$(publish_reason "$prc" "$(head -1 "$WORK/err.txt")")"
|
|
319
|
+
fail_rc "$prc"
|
|
320
|
+
fi
|
|
321
|
+
done
|
|
322
|
+
|
|
323
|
+
[ "$DRY" -eq 1 ] || record_decisions ""
|
|
324
|
+
[ -z "$RESULT" ] || jq -s '.' "$RESULTS" > "$RESULT"
|
|
325
|
+
# Merged by write id: a re-run that finds a comment already there ("exists")
|
|
326
|
+
# keeps the earlier "written" record instead of replacing it.
|
|
327
|
+
if [ "$MERGE" -eq 1 ] && [ "$DRY" -eq 0 ]; then
|
|
328
|
+
jq -n --slurpfile s "$STATE" --slurpfile n "$RESULT" '
|
|
329
|
+
($s[0].jiraFamily // {}) as $f
|
|
330
|
+
| ($f.writes // []) as $old
|
|
331
|
+
| ($n[0] | map({(.id): .}) | add // {}) as $new
|
|
332
|
+
| ($old | map(.id)) as $oldIds
|
|
333
|
+
| {jiraFamily: ($f + {writes: (
|
|
334
|
+
[$old[] | . as $o | ($new[$o.id] // null) as $x
|
|
335
|
+
| if $x == null or ($o.status == "written" and $x.status == "exists") then $o else $x end]
|
|
336
|
+
+ [$n[0][] | select(.id as $i | $oldIds | index($i) | not)])})}' \
|
|
337
|
+
| node "$WRITE_STATE" "$STATE" >/dev/null || echo " WARN: could not record the writes in $STATE" >&2
|
|
338
|
+
fi
|
|
339
|
+
exit "$RC"
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
# [--mode append|replace] (description only, default: append)
|
|
20
20
|
# [--confirm-overwrite] (required for mode=replace on a non-empty field)
|
|
21
21
|
# [--backup-dir DIR]
|
|
22
|
+
# [--raw] (body is already Jira wiki markup; no emoticon escape)
|
|
23
|
+
# [--expect-file FILE] (description only: write only if it still equals FILE)
|
|
22
24
|
# [--dry-run]
|
|
23
25
|
#
|
|
24
26
|
# Resolution:
|
|
@@ -29,6 +31,9 @@
|
|
|
29
31
|
#
|
|
30
32
|
# Every body is run through scripts/jira-wiki-escape.mjs before it is sent, so
|
|
31
33
|
# Jira cannot manufacture an emoticon out of a Swift selector or a table cell.
|
|
34
|
+
# --raw skips that one step for a body built only from text the author already
|
|
35
|
+
# wrote in Jira markup plus macros the caller escaped itself (the story reformat
|
|
36
|
+
# of lib/jira-family-write.sh); the outbound gate and the backup still apply.
|
|
32
37
|
#
|
|
33
38
|
# Exit codes:
|
|
34
39
|
# 0 published (or, with --dry-run, previewed)
|
|
@@ -36,6 +41,8 @@
|
|
|
36
41
|
# --confirm-overwrite was given. The backup is already on disk.
|
|
37
42
|
# 4 host or token could not be resolved
|
|
38
43
|
# 5 a Jira API call failed
|
|
44
|
+
# 6 refused: --expect-file no longer matches the description (someone
|
|
45
|
+
# edited it after the caller read it). Nothing was written.
|
|
39
46
|
# 64 usage error
|
|
40
47
|
|
|
41
48
|
set -euo pipefail
|
|
@@ -91,6 +98,8 @@ TARGET="comment"
|
|
|
91
98
|
MODE="append"
|
|
92
99
|
CONFIRM=0
|
|
93
100
|
DRY_RUN=0
|
|
101
|
+
RAW=0
|
|
102
|
+
EXPECT=""
|
|
94
103
|
BACKUP_DIR="${JIRA_BACKUP_DIR:-$HOME/.claude/logs/multi-agent/jira-backups}"
|
|
95
104
|
|
|
96
105
|
die() { echo "$1" >&2; exit "${2:-64}"; }
|
|
@@ -106,6 +115,8 @@ while [ "$#" -gt 0 ]; do
|
|
|
106
115
|
--backup-dir) need_value "$@"; BACKUP_DIR="$2"; shift 2 ;;
|
|
107
116
|
--confirm-overwrite) CONFIRM=1; shift ;;
|
|
108
117
|
--dry-run) DRY_RUN=1; shift ;;
|
|
118
|
+
--raw) RAW=1; shift ;;
|
|
119
|
+
--expect-file) need_value "$@"; EXPECT="$2"; shift 2 ;;
|
|
109
120
|
-h|--help) sed -n '2,40p' "$0"; exit 0 ;;
|
|
110
121
|
*) die "unknown argument: $1" ;;
|
|
111
122
|
esac
|
|
@@ -148,7 +159,16 @@ api() { "$CURL" -sS -m 30 -K <(auth_cfg "$TOKEN") -H "Content-Type: application/
|
|
|
148
159
|
WORK="$(mktemp -d)"
|
|
149
160
|
trap 'rm -rf "$WORK"' EXIT
|
|
150
161
|
|
|
151
|
-
|
|
162
|
+
if [ "$RAW" = "1" ]; then
|
|
163
|
+
cp "$BODY_FILE" "$WORK/new.txt"
|
|
164
|
+
else
|
|
165
|
+
node "$ESCAPER" "$BODY_FILE" > "$WORK/new.txt"
|
|
166
|
+
fi
|
|
167
|
+
# An escaper that printed nothing for a body that has text would post an empty
|
|
168
|
+
# comment or blank a description; that is refused, never sent.
|
|
169
|
+
if [ -s "$BODY_FILE" ] && [ ! -s "$WORK/new.txt" ]; then
|
|
170
|
+
die "the escaped body came out empty; nothing was posted" 5
|
|
171
|
+
fi
|
|
152
172
|
|
|
153
173
|
if [ "$TARGET" = "comment" ]; then
|
|
154
174
|
if [ "$DRY_RUN" = "1" ]; then
|
|
@@ -175,6 +195,13 @@ case "$http" in
|
|
|
175
195
|
*) echo "could not read the current description of $ISSUE (HTTP $http)" >&2; exit 5 ;;
|
|
176
196
|
esac
|
|
177
197
|
jq -r '.fields.description // ""' "$WORK/issue.json" > "$WORK/existing.txt"
|
|
198
|
+
if [ -n "$EXPECT" ]; then
|
|
199
|
+
jq -j '.fields.description // ""' "$WORK/issue.json" > "$WORK/existing-exact.txt"
|
|
200
|
+
if ! cmp -s "$WORK/existing-exact.txt" "$EXPECT"; then
|
|
201
|
+
echo "REFUSED: the description of $ISSUE changed since it was read; nothing was written." >&2
|
|
202
|
+
exit 6
|
|
203
|
+
fi
|
|
204
|
+
fi
|
|
178
205
|
|
|
179
206
|
existing_bytes=$(wc -c < "$WORK/existing.txt" | tr -d ' ')
|
|
180
207
|
existing_lines=$(grep -c . "$WORK/existing.txt" || true)
|
|
@@ -232,6 +232,8 @@ Jira's wiki renderer turned it into an emoticon, which is what section
|
|
|
232
232
|
*Emoticon escaping* below is about. A defence that has to be invoked by hand is
|
|
233
233
|
a defence that is eventually not invoked.
|
|
234
234
|
|
|
235
|
+
A run with `state.jiraFamily` posts this body on the development sub-task, plus a test comment on the test sub-task: `features/jira-family.md`.
|
|
236
|
+
|
|
235
237
|
The dispatch summary line includes the comment URL with `?focusedCommentId=...` so the user can paste it into Slack/Teams.
|
|
236
238
|
|
|
237
239
|
### Writing the issue description (not a comment)
|