@mmerterden/multi-agent-pipeline 20.23.0 → 20.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/docs/facts.json +1 -1
- package/manifest.json +13 -13
- package/package.json +1 -1
- package/pipeline/lib/analysis-jira-write.sh +25 -5
- package/pipeline/multi-agent-refs/features/analysis-jira.md +8 -2
- package/pipeline/schemas/prefs.schema.json +3 -2
- package/pipeline/scripts/_diff-impact.mjs +55 -27
- package/pipeline/scripts/_supertypes.mjs +18 -5
- package/pipeline/scripts/analysis-story-tree.mjs +16 -2
- package/pipeline/scripts/code-graph-rules/ios.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,15 @@ Internal file-layout changes that don't affect the slash-command surface are sti
|
|
|
16
16
|
|
|
17
17
|
## [Unreleased]
|
|
18
18
|
|
|
19
|
+
## [20.23.1] - 2026-10-13
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- `/multi-agent:analysis-jira` finds a site's sub-task types on Jira Data Center 9, which answers only the per-project `createmeta/{project}/issuetypes`; sub-tasks were created under the generic `Sub-task` name there. `issueTree.subtaskIssueType` may now map each role to the site's type (`{"develop": "Development Task", "test": "Test Task"}`, `"*"` for any other role), a site with several sub-task types and no setting is named in the run's output, and the preview shows the type each role gets.
|
|
24
|
+
- The code graph reads Swift imports behind an attribute (`@testable import`, `@_exported import`, `@preconcurrency import`, `@_spi(...) import`).
|
|
25
|
+
- Python type parameters (`class Box[T](Base):`) keep their bases, and a TypeScript base built by a call (`extends mixin(Base)`) adds no edge to the function.
|
|
26
|
+
- A diff hunk that only inserts or only deletes lines is given to the declaration spanning both lines around it, at any context size; at `-U0` it was given to the line before it.
|
|
27
|
+
|
|
19
28
|
## [20.23.0] - 2026-10-13
|
|
20
29
|
|
|
21
30
|
### Added
|
package/docs/facts.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "Generated by pipeline/scripts/gen-facts.mjs. Do not hand-edit: the site reads this, and a number edited here instead of at its source is the drift this file removes.",
|
|
3
3
|
"generatedAt": "2026-10-13",
|
|
4
|
-
"version": "20.23.
|
|
4
|
+
"version": "20.23.1",
|
|
5
5
|
"phaseSchema": 2,
|
|
6
6
|
"phases": [
|
|
7
7
|
{
|
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.1",
|
|
4
|
+
"commit": "202735c51f3f1ba48c52d2faff53b4ebca2ce0e9",
|
|
5
|
+
"generatedAt": "2026-10-13T12:42:14.108Z",
|
|
6
6
|
"algorithm": "sha256",
|
|
7
7
|
"fileCount": 1367,
|
|
8
8
|
"files": {
|
|
9
|
-
"CHANGELOG.md": "
|
|
9
|
+
"CHANGELOG.md": "4989a15706727f4056d2f6008d9fb214369b3d9d9437dfcad867e79bedfcd798",
|
|
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": "0f668242196f9e4ad1fe68871118a8e28da6a9d2598228b2074f1b6760facdcc",
|
|
38
38
|
"docs/features.md": "79a4b5a9722375ea16d7b337148d47bbea2951c8eb7bc8a6e616c9b3ed2c8703",
|
|
39
39
|
"docs/performance.md": "bbf2f3922c25dcba0c1a9e69ef420e3871a729af5b35009a49b48a5d3c5cd151",
|
|
40
40
|
"docs/recovery-guide.md": "59a8f1b66b34cb9ff5507b3d54dac3b9915e184aa044d9c758d18cac818d510e",
|
|
@@ -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": "9378b09fd1cc14fce2d55dfc9d0e607728bcbcf09aee276058e760702b7d3b82",
|
|
69
69
|
"pipeline/agents/android-architect.md": "11fb74543cb0ebcf9525ca6654fe95d53da7229e0bf4a1149b31fd9005abc453",
|
|
70
70
|
"pipeline/agents/backend-architect.md": "b02a606c301e4714131aed70ae6d16eeb77fbc819a001e2752cff2488d4246de",
|
|
71
71
|
"pipeline/agents/bulk-reader.md": "6597052cd7d205bac2b1c7633f5232fe1a40730664a913b8eb77efd5a786dc25",
|
|
@@ -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": "d38a7665c91a4b65b219381184357a723a43c3daff8fae0bace6f55605f8e5bf",
|
|
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": "fe95a8a2435c0118f804f6ad17ab312d04ffa2ea7e0eab9e00d84c684039c93b",
|
|
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",
|
|
@@ -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": "57701b646d651f6ec6bb3a688da1ad2c3c11bd63a28cf0174b78b717ad2cae62",
|
|
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
517
|
"pipeline/scripts/analysis-story-body.mjs": "309cf9f1a9859228d073aff6e7b4a9322bc960d4fe1a21a10ce58521a0c1b641",
|
|
518
|
-
"pipeline/scripts/analysis-story-tree.mjs": "
|
|
518
|
+
"pipeline/scripts/analysis-story-tree.mjs": "5fb7b5978391b1a090db26dae982bb9763177736c2457180a941946d40eb4224",
|
|
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",
|
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.1",
|
|
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",
|
|
@@ -239,6 +239,25 @@ if [ -n "${EPIC:-}" ]; then
|
|
|
239
239
|
fi
|
|
240
240
|
fi
|
|
241
241
|
|
|
242
|
+
# Discover rather than assume: the types this site marks subtask:true, under
|
|
243
|
+
# whatever names it gave them. Jira Data Center 9 answers only the per-project
|
|
244
|
+
# createmeta; older sites answer only the classic one. Read once per run.
|
|
245
|
+
SITE_SUBTASK_TYPES=""
|
|
246
|
+
SITE_SUBTASK_READ=0
|
|
247
|
+
site_subtask_types() {
|
|
248
|
+
SITE_SUBTASK_READ=1
|
|
249
|
+
SITE_SUBTASK_TYPES="$(jira_api GET "/rest/api/2/issue/createmeta/${PROJECT}/issuetypes?maxResults=100" 2>/dev/null \
|
|
250
|
+
| jq -r '.values[]? | select(.subtask == true) | .name' 2>/dev/null || echo "")"
|
|
251
|
+
[ -n "$SITE_SUBTASK_TYPES" ] || SITE_SUBTASK_TYPES="$(jira_api GET \
|
|
252
|
+
"/rest/api/2/issue/createmeta?projectKeys=${PROJECT}&expand=projects.issuetypes" 2>/dev/null \
|
|
253
|
+
| jq -r '.projects[]?.issuetypes[]? | select(.subtask == true) | .name' 2>/dev/null || echo "")"
|
|
254
|
+
local n
|
|
255
|
+
n="$(printf '%s' "$SITE_SUBTASK_TYPES" | grep -c . || true)"
|
|
256
|
+
if [ "${n:-0}" -gt 1 ]; then
|
|
257
|
+
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
|
|
258
|
+
fi
|
|
259
|
+
}
|
|
260
|
+
|
|
242
261
|
RC=0
|
|
243
262
|
NODES="$(jq -c '.nodes[]' "$PLAN")"
|
|
244
263
|
while IFS= read -r node; do
|
|
@@ -272,12 +291,13 @@ while IFS= read -r node; do
|
|
|
272
291
|
[ -n "$sub" ] || continue
|
|
273
292
|
t_label="$(printf '%s' "$sub" | jq -r '.label')"
|
|
274
293
|
t_role="$(printf '%s' "$sub" | jq -r '.role')"
|
|
275
|
-
|
|
294
|
+
# A name, or a map from role to name ("*" for any other role).
|
|
295
|
+
t_type="$(jq -r --arg r "$t_role" '.prefsUsed.subtaskIssueType
|
|
296
|
+
| if type == "object" then (.[$r] // .["*"] // empty)
|
|
297
|
+
elif type == "string" then . else empty end' "$PLAN")"
|
|
276
298
|
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 "")"
|
|
299
|
+
[ "$SITE_SUBTASK_READ" -eq 1 ] || site_subtask_types
|
|
300
|
+
t_type="$(printf '%s\n' "$SITE_SUBTASK_TYPES" | head -n 1)"
|
|
281
301
|
fi
|
|
282
302
|
[ -n "$t_type" ] || t_type="Sub-task"
|
|
283
303
|
t_desc="$(printf '%s' "$sub" | jq -r '.description // empty')"
|
|
@@ -173,9 +173,15 @@ are values the site fills in:
|
|
|
173
173
|
- `epicLinkFieldId`, when set, is the site's own field id; otherwise it is
|
|
174
174
|
discovered
|
|
175
175
|
- `subtaskIssueType: null` means discover it - `createmeta` returns whichever
|
|
176
|
-
type carries `subtask: true`, under whatever name the site gave it
|
|
177
|
-
|
|
176
|
+
type carries `subtask: true`, under whatever name the site gave it (the
|
|
177
|
+
per-project `createmeta/{project}/issuetypes` first, which is all Jira Data
|
|
178
|
+
Center 9 answers, then the classic form). Same rule as
|
|
179
|
+
`features/jira-context.md`: the type travels as it comes from Jira and is
|
|
178
180
|
never a matching criterion in code
|
|
181
|
+
- a site with several sub-task types (Analysis Task, Development Task, Test
|
|
182
|
+
Task) names them per role: `subtaskIssueType` as a map from a `subtaskRoles`
|
|
183
|
+
entry to the type, `"*"` for any other role. Without one the first discovered
|
|
184
|
+
type is used and the run prints the types it found
|
|
179
185
|
|
|
180
186
|
The preview prints every field beside the pref key it came from, so a wrong
|
|
181
187
|
setting is visible before the writes rather than in Jira afterwards.
|
|
@@ -2202,9 +2202,10 @@
|
|
|
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\": \"Development Task\", \"test\": \"Test 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."
|
|
2208
2209
|
},
|
|
2209
2210
|
"subtaskRoles": {
|
|
2210
2211
|
"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;
|
|
@@ -153,7 +153,7 @@ export function identityLabel(prefix, docId, sourceIds) {
|
|
|
153
153
|
* issueTree.projectKey where the tree is created
|
|
154
154
|
* issueTree.epicIssueType the type name for an epic on this site
|
|
155
155
|
* issueTree.storyIssueType the type name for a story
|
|
156
|
-
* issueTree.subtaskIssueType null = discover
|
|
156
|
+
* issueTree.subtaskIssueType a name, a {role: name} map, or null = discover
|
|
157
157
|
* issueTree.subtaskRoles how a story is split; ships empty on purpose
|
|
158
158
|
* issueTree.channelComponents channel -> this site's component name
|
|
159
159
|
* issueTree.channelTeams channel -> this site's team name
|
|
@@ -195,6 +195,17 @@ function readPrefs() {
|
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
/**
|
|
199
|
+
* The sub-task type a role is created as, from issueTree.subtaskIssueType: a
|
|
200
|
+
* name for every role, or a {role: name} map with "*" for any other role. Null
|
|
201
|
+
* means the writer discovers it from the site.
|
|
202
|
+
*/
|
|
203
|
+
export function subtaskTypeFor(setting, role) {
|
|
204
|
+
if (typeof setting === "string" && setting) return setting;
|
|
205
|
+
if (setting && typeof setting === "object") return setting[role] ?? setting["*"] ?? null;
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
198
209
|
/** Every open placeholder that makes a document unfit to become work. */
|
|
199
210
|
export function openItems(text) {
|
|
200
211
|
const found = [];
|
|
@@ -557,7 +568,10 @@ async function main() {
|
|
|
557
568
|
for (const line of s.description.split("\n")) process.stdout.write(` ${line}\n`);
|
|
558
569
|
}
|
|
559
570
|
for (const t of s.subtasks) {
|
|
560
|
-
|
|
571
|
+
const type = subtaskTypeFor(result.prefsUsed.subtaskIssueType, t.role);
|
|
572
|
+
process.stdout.write(
|
|
573
|
+
` subtask ${t.role} type ${type ?? "discovered from createmeta"} [${t.label}]\n`,
|
|
574
|
+
);
|
|
561
575
|
}
|
|
562
576
|
for (const c of s.clones || []) {
|
|
563
577
|
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"],
|