@mmerterden/multi-agent-pipeline 20.11.1 → 20.13.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.
Files changed (71) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +1 -1
  3. package/README.tr.md +1 -1
  4. package/docs/facts.json +2 -2
  5. package/install/_common.mjs +1 -1
  6. package/install/_plugin-skills.mjs +13 -9
  7. package/install/catalog-history.json +1 -1
  8. package/install/claude.mjs +9 -0
  9. package/install/codex.mjs +12 -12
  10. package/manifest.json +74 -67
  11. package/package.json +5 -5
  12. package/pipeline/agents/code-reviewer.md +1 -1
  13. package/pipeline/commands/multi-agent/SKILL.md +1 -0
  14. package/pipeline/commands/multi-agent/analysis/SKILL.md +7 -3
  15. package/pipeline/commands/multi-agent/analysis-jira/SKILL.md +57 -7
  16. package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +2 -2
  17. package/pipeline/commands/multi-agent/autopilot/SKILL.md +6 -0
  18. package/pipeline/commands/multi-agent/autopilot-off/SKILL.md +1 -0
  19. package/pipeline/commands/multi-agent/doctor/SKILL.md +6 -0
  20. package/pipeline/commands/multi-agent/feedback/SKILL.md +2 -2
  21. package/pipeline/commands/multi-agent/forget/SKILL.md +1 -0
  22. package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +1 -0
  23. package/pipeline/commands/multi-agent/kill/SKILL.md +1 -0
  24. package/pipeline/commands/multi-agent/model/SKILL.md +7 -0
  25. package/pipeline/commands/multi-agent/prune-logs/SKILL.md +1 -0
  26. package/pipeline/commands/multi-agent/prune-prompts/SKILL.md +1 -0
  27. package/pipeline/commands/multi-agent/review/SKILL.md +5 -0
  28. package/pipeline/commands/multi-agent/review-analysis/SKILL.md +7 -2
  29. package/pipeline/commands/multi-agent/sync/SKILL.md +5 -6
  30. package/pipeline/lib/agent-model-sync.mjs +95 -0
  31. package/pipeline/lib/analysis-jira-write.sh +81 -27
  32. package/pipeline/lib/analysis-quality.mjs +261 -0
  33. package/pipeline/lib/analysis-sections.mjs +105 -0
  34. package/pipeline/lib/jira-epic-link.sh +54 -0
  35. package/pipeline/lib/model-rung.sh +7 -0
  36. package/pipeline/multi-agent-refs/analysis/locked.md +4 -4
  37. package/pipeline/multi-agent-refs/analysis/redesign.md +3 -1
  38. package/pipeline/multi-agent-refs/analysis/render.md +20 -21
  39. package/pipeline/multi-agent-refs/analysis/resolve.md +1 -1
  40. package/pipeline/multi-agent-refs/analysis/review.md +16 -3
  41. package/pipeline/multi-agent-refs/analysis/synthesis.md +10 -2
  42. package/pipeline/multi-agent-refs/analysis-template-corporate.md +44 -9
  43. package/pipeline/multi-agent-refs/analysis-template.md +21 -14
  44. package/pipeline/multi-agent-refs/cross-cli-contract.md +4 -0
  45. package/pipeline/multi-agent-refs/features/analysis-jira.md +69 -19
  46. package/pipeline/multi-agent-refs/features/doctor.md +10 -0
  47. package/pipeline/multi-agent-refs/features/model-fallback.md +10 -0
  48. package/pipeline/multi-agent-refs/features/phone-api.md +1 -1
  49. package/pipeline/multi-agent-refs/features/unattended-security.md +2 -2
  50. package/pipeline/multi-agent-refs/features/usage-reporting.md +13 -0
  51. package/pipeline/schemas/analysis-spec.schema.json +22 -0
  52. package/pipeline/schemas/prefs.schema.json +22 -1
  53. package/pipeline/scripts/analysis-story-body.mjs +210 -0
  54. package/pipeline/scripts/analysis-story-tree.mjs +73 -12
  55. package/pipeline/scripts/analysis-tickets-writeback.mjs +101 -0
  56. package/pipeline/scripts/build-references.mjs +14 -1
  57. package/pipeline/scripts/confluence-readback.mjs +148 -0
  58. package/pipeline/scripts/doctor.mjs +48 -0
  59. package/pipeline/scripts/jira-wiki-escape.mjs +40 -0
  60. package/pipeline/scripts/migrate-prefs.mjs +14 -0
  61. package/pipeline/scripts/unattended_policy.py +12 -5
  62. package/pipeline/scripts/usage-report.mjs +84 -9
  63. package/pipeline/scripts/validate-analysis-doc.mjs +120 -70
  64. package/pipeline/skills/.skill-manifest.json +8 -8
  65. package/pipeline/skills/shared/core/multi-agent-analysis/SKILL.md +6 -1
  66. package/pipeline/skills/shared/core/multi-agent-analysis-jira/SKILL.md +56 -6
  67. package/pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md +6 -0
  68. package/pipeline/skills/shared/core/multi-agent-doctor/SKILL.md +6 -0
  69. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +5 -0
  70. package/pipeline/skills/shared/core/multi-agent-review-analysis/SKILL.md +5 -0
  71. package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +1 -0
@@ -102,14 +102,27 @@ The verdict names the profile it judged against, the counts per severity, and wh
102
102
  ```
103
103
  Verdict: 2 Blocker, 3 Important, 1 Suggestion (profile: global, mode: full)
104
104
 
105
- A evidence 2 D altitude none
106
- B backbone none E admitted gaps 1
107
- C ... F contradiction 3
105
+ A buildability 2 D altitude none
106
+ B evidence none E gaps 1
107
+ C spine none F contradiction 3
108
108
 
109
109
  Not checked: references coverage (no state file passed)
110
110
  Provenance: evidence_digest sha256:1f3a9c2 base_commit 4c1b2de
111
+
112
+ Publish ready: no
113
+ Fix first:
114
+ 1. [A] 4.4 BR-login-03 has no acceptance criterion
115
+ 2. [A] 13.2 names no file for the session store
116
+ 3. [F] 9.1 and 4.2 disagree on the code lifetime (3 min vs 5 min)
117
+ Verified OK: front-matter, required sections, traceability, references (12 of 12)
111
118
  ```
112
119
 
120
+ **Publish ready** is `yes` only with zero Blocker findings and a clean
121
+ validator run. `Fix first` lists the Blocker and Important findings in the
122
+ order they should be fixed, each with its class and section, so the author has
123
+ a work list rather than a report. `Verified OK` names what was checked and
124
+ held, with counts, so a clean area is distinguishable from an unexamined one.
125
+
113
126
  ## Phase 4 - Output
114
127
 
115
128
  Default is the chat report. Nothing is written anywhere without an explicit choice.
@@ -32,6 +32,14 @@ synthesizedSections = {
32
32
  | 6. Business + Tests | If sections 2 and 4 are both `null` AND `evidence.firebase[]` is empty → null. Otherwise produce use-case + mock stubs + per-platform test skeletons + shared Firebase events table. Reuse Red-Green-Refactor naming from `$HOME/.claude/rules/tdd.md`. |
33
33
  | 7. Development Plan | Always present. Tasks for the current platform only. Architecture standards come from `evidence.standards[]` filtered by platform (see Pass B step 1). **Reuse-first rule (Locked 11)**: when an item has `direct-match` in `evidence.repoEvidence[<repo>].buckets.<X>`, emit `Reuse existing <FQN> (<file>:<line>)` instead of `Add new <FQN>`. New-write task with a `direct-match` competitor becomes a Risk row. |
34
34
 
35
+ #### Pass A - content rules
36
+
37
+ - **Edge cases are proposals.** Failure paths the evidence states (refusal, error code, timeout, empty or expired state) go to `state.analysisSpec.edgeCandidates[]` (`{target, candidate, source}`) and the Pass B preview; only accepted ones enter the document. No source, no candidate.
38
+ - **Nothing invented:** no event name, sample value or threshold without a source. Show the type; a missing event name is a Section 20 question.
39
+ - **Unreadable is not absent.** A source that failed is listed in the missing-inputs panel and References; the body never infers absence from it.
40
+ - **One question, one Section 20 row**, listing every section it affects.
41
+ - Validator-enforced: an item is never both goal and non-goal (`scope-overlap`); every cited id is defined (`cited-undefined`); Part A carries no file path or endpoint outside `<details>` (`business-layer-technical`).
42
+
35
43
  #### Phase 2a - Pass B preview (Locked 25)
36
44
 
37
45
  Before Pass B renders any file, present the resolved convention table to the user via `AskUserQuestion` (`picker-contract.md`). The table is one row per concept, one column per selected platform.
@@ -86,7 +94,7 @@ For each `platform` in `state.analysisSpec.platforms[]`:
86
94
  - `android` → `~/.claude/rules/kotlin-android.md` first → `evidence.standards[]` entries whose path contains `android` or `kotlin`
87
95
  - `backend` → `evidence.standards[]` entries matching language hints (`python`, `go`, `node`, `fastapi`) → fall back to `~/.claude/rules/security.md` + `code-style.md`
88
96
  - `web` → `evidence.standards[]` entries matching `react`, `vue`, `next`, `sveltekit` → `~/.claude/rules/code-style.md`
89
- 2. **Apply per-platform omission rules.** Backend-only file drops Sections 5, 6, 7, 8, 16. Web with no UI inventory still keeps 5 (UI exists in code). Sections 1, 2, 4, 9, 13, 14, 20, 21 always present per Locked decision 2 + 13; their numbers are canonical and never re-flowed.
97
+ 2. **Apply per-platform omission rules.** Backend-only file drops Sections 5, 6, 7, 8, 16. Web with no UI inventory still keeps 5 (UI exists in code). Sections 1, 2, 4, 9, 13, 14, 20, 21 always present per Locked decision 2 + 13; their numbers are canonical and never renumbered.
90
98
  3. **Resolve the section set from evidence, not from a mode.** There is one
91
99
  pipeline. A section renders when it has evidence and is dropped when it does
92
100
  not, per Locked 2. A fixed section list chosen by a size score would fight
@@ -95,7 +103,7 @@ For each `platform` in `state.analysisSpec.platforms[]`:
95
103
  would keep Section 9 for being on it.
96
104
  4. **Produce YAML front-matter header** (see `$HOME/.claude/multi-agent-refs/analysis-template.md`). Include `profile: <state.analysisSpec.profile | global>` and `platform: <platform | none>` so the validator applies the right contract per profile (Locked 31) and recognises the stack-optional render (Locked 34), plus `ui_tests: <state.analysisSpec.options.uiTests | false>`, `a11y_depth: <state.analysisSpec.options.a11yDepth | basic>` and `redesign: <state.analysisSpec.options.redesign | false>` so the pre-dispatch validator can enforce the opt-in coverage (15.6 present when ui_tests, 16.2 walkthrough present when a11y_depth is full, 4.5 / 4.6 / 9.5 present when redesign). `status` is written only by `/multi-agent:analysis-resolve`; a rendered document is a draft.
97
105
  5. **Read conventions for this platform's repo.** For each cell Pass B fills in Section 13 and in any per-platform projection (Sections 5, 6, 7, 8, 10, 11, 13, 14, 15, 16, 17), read `state.analysisSpec.evidence.conventions[<repo>].<field>` and emit the value with a footnote (Locked 24). If `conventionOverrides` has an entry for that field, use the override and footnote with `^[user-override: <reason>]` instead of evidence path.
98
- 6. **Concatenate non-null sections in canonical order.** Each rendered section keeps its canonical template number, so omitted sections DO leave gaps - `1, 2, 4, 9, 13, 14, 21` is a correct rendered document. Locked 30 cites sections by number across the whole document; re-flowing them would break every one of those references.
106
+ 6. **Concatenate non-null sections in canonical order.** Each rendered section keeps its canonical template number, so omitted sections DO leave gaps - `1, 2, 4, 9, 13, 14, 21` is a correct rendered document. Locked 30 cites sections by number across the whole document; renumbering them would break every one of those references.
99
107
  7. **Schema validation** on the per-platform spec object:
100
108
  ```bash
101
109
  python3 -c "import json,jsonschema; jsonschema.validate(json.load(open('state/<feature>-<platform>.json')), json.load(open('$HOME/.claude/schemas/analysis-spec.schema.json')))"
@@ -20,14 +20,14 @@ This is a sibling of `analysis-template.md` (the global profile), not a replacem
20
20
  | Altitude | What to build and how | What is required and why; the how follows in Parts B and C |
21
21
  | Current state | Legacy findings are blockquotes inside forward-looking sections (Locked 4) | Section 2.1 is a first-class current-state analysis, and 2.3 is the diff between 2.1 and 2.2 |
22
22
  | Empty section | Dropped entirely (Locked 2) | Backbone sections always render, with `N/A` or `EKLENECEK` (Locked 32) |
23
- | Version history | Section 22 Changelog at the bottom | `DOKÜMAN TARİHÇESİ` table at the top |
23
+ | Version history | Section 23 Changelog at the bottom | `DOKÜMAN TARİHÇESİ` table at the top |
24
24
  | Use cases | Gherkin scenarios in Section 4 | UC tables with Aktör / Ön Koşul / Ana Akış / Alternatif Akış |
25
25
 
26
26
  Everything else is shared: citation discipline (Locked 3), forward-looking spec for Part B and C (Locked 4), the Figma 3-tier chain (Locked 12), Pass B footnotes (Locked 24), and References at the bottom (Locked 21).
27
27
 
28
28
  ## Section map
29
29
 
30
- Numbering is fixed for Parts A and B and does not re-flow, because the corporate backbone never drops (Locked 32). Section 5 sub-numbering shifts with the use-case count, exactly as the source documents do.
30
+ Numbering is fixed for Parts A and B, because the corporate backbone never drops (Locked 32). Section 5 sub-numbering shifts with the use-case count, exactly as the source documents do.
31
31
 
32
32
  | Layer | Sections | Carries |
33
33
  |---|---|---|
@@ -127,6 +127,16 @@ rather than a footnote.>
127
127
  |---|---|---|---|
128
128
  | 1 | <capability> | Var / Yok / Kısmi | <detail> |
129
129
 
130
+ #### 2.1.1 Mevcut Davranış / Current Behaviour
131
+
132
+ <Only when the front-matter carries `redesign: true` (Locked 36, `analysis/redesign.md`);
133
+ omitted otherwise. One `CB-<slug>-NN` row per v1 behaviour a reader could only find by
134
+ reading v1, each with the evidence that proves it.>
135
+
136
+ | Id | Davranış | Kanıt | Kesinlik |
137
+ |---|---|---|---|
138
+ | CB-<slug>-01 | <behaviour> | <path:line> | confirmed / uncertain |
139
+
130
140
  ### 2.2 Hedeflenen Durum Analizi
131
141
 
132
142
  <What the product will do once this work ships. Sourced from the design and the
@@ -150,6 +160,16 @@ reason 2.1 and 2.2 are separate; if it is empty, one of them was not done.>
150
160
  |---|---|---|---|
151
161
  | <area> | <from 2.1> | <from 2.2> | <what has to change> |
152
162
 
163
+ #### 2.3.1 Fark Listesi / Difference List
164
+
165
+ <Only under `redesign: true`. Every `CB-` row from 2.1.1 appears here once with a status:
166
+ Moved / Taşındı, Partial / Kısmi, Missing / Yok, New / Yeni, Out of scope / Kapsam dışı.
167
+ Partial and Missing rows name the `AS-NN` that decides them.>
168
+
169
+ | Id | Durum | Nerede | AS-NN |
170
+ |---|---|---|---|
171
+ | CB-<slug>-01 | Moved / Taşındı | <section> | - |
172
+
153
173
  ### 2.4 Kısıtlar, Varsayımlar, Bağımlılıklar
154
174
 
155
175
  | Tür | Madde | Kaynak |
@@ -230,6 +250,7 @@ Sub-numbering shifts with the use-case count. For `N` use cases:
230
250
  | **Kullanım Sıklığı** | <how often> |
231
251
  | **Ana Akış** | 1. <step> 2. <step> 3. <step> |
232
252
  | **Alternatif Akış** | 2a. <alternative to step 2> 4a. <alternative to step 4> |
253
+ | **İstisna Akışı** | E1. <what happens when step N fails: refusal, error, timeout> |
233
254
  | **İş Kuralları / Referans** | IG-01, IG-03 |
234
255
 
235
256
  ### Ekranlar
@@ -245,12 +266,14 @@ Rules the renderer enforces:
245
266
 
246
267
  - **Ana Akış is numbered and single-directional.** A nested option takes a second-level number under its step.
247
268
  - **Alternatif Akış always binds to a main-flow step**: `2a.`, `2b.`, `4a.`. A free-floating alternative sentence is rejected, because a reader cannot tell which step it replaces.
269
+ - **İstisna Akışı is never left out.** It is the failure path: what the user sees when the system refuses, errors or times out, and what state the flow is left in. A use case with genuinely no failure path writes `Yok: <reason>`. `validate-analysis-doc.mjs` (`exception-flow`) warns on a draft with no row or an empty one and fails a `status: final` document with no row. Edge-case candidates the synthesis proposes are written here only after the analyst accepts them.
270
+ - **Every screen belongs to a use case.** A design frame is listed in the `Ekranlar` block of the use case it serves; a frame linked anywhere else (outside Section 21) is a warning (`design-frame-unmapped`), because a screen no flow reaches is either missing a use case or out of scope.
248
271
  - **Concrete limits appear only when a source states them.** A threshold nobody wrote down is invented (Locked 3), and an invented threshold in a requirements document reaches production as a bug.
249
272
  - **FG references inside main-flow steps are optional but must be consistent.** Either every use case cites them or none does; half a document doing it reads as an omission.
250
273
 
251
274
  ### 5.(N+1) Kullanım Senaryosu Diyagramları
252
275
 
253
- One mermaid diagram per use case, under a `### UC-00<i>: <name>` heading. The diagram shows the flow, including the alternative branches named in the table.
276
+ One mermaid diagram per use case, under a `### UC-00<i>: <name>` heading. The diagram shows the flow, including the alternative and exception branches named in the table. A use case that calls more than one service also gets a `sequenceDiagram`, so the call order and the failure point of each call are visible.
254
277
 
255
278
  ### 5.(N+2) Kullanım Senaryosu - İş Gereksinimi Eşleştirme
256
279
 
@@ -300,6 +323,14 @@ renders; when the whole contract is unknown, write one line instead of six `EKLE
300
323
  cells: `Bu servisin sozlesmesi tanimli degil (AS-NN).` Render the table only once
301
324
  `Name` and `Path` are both known.
302
325
 
326
+ #### 5.(N+4).1 Endpoint Eşlemesi / Endpoint Mapping
327
+
328
+ <Only under `redesign: true`. One row per v1 endpoint and the v2 endpoint that replaces it.>
329
+
330
+ | v1 | v2 | Değişiklik |
331
+ |---|---|---|
332
+ | <METHOD /v1/path> | <METHOD /v2/path> | unchanged / renamed / split / removed |
333
+
303
334
  ### 5.(N+5) Servis - Fonksiyonel Gereksinim Eşleştirmesi
304
335
 
305
336
  ```markdown
@@ -311,9 +342,9 @@ cells: `Bu servisin sozlesmesi tanimli degil (AS-NN).` Render the table only onc
311
342
  ### 5.(N+6) Gereksinim İzlenebilirlik Matrisi
312
343
 
313
344
  ```markdown
314
- | İş Gereksinimi | Kullanım Senaryosu | Fonksiyonel Gereksinim | Servis |
315
- |---|---|---|---|
316
- | IG-01 | UC-001 | FG-01, FG-02 | <operation name> |
345
+ | İş Gereksinimi | Kullanım Senaryosu | Fonksiyonel Gereksinim | Servis | Ekran | Test |
346
+ |---|---|---|---|---|---|
347
+ | IG-01 | UC-001 | FG-01, FG-02 | <operation name> | <frame name> | <test row id> |
317
348
  ```
318
349
 
319
350
  **This matrix is the most expensive thing in the document to get wrong**, because every downstream reader trusts it instead of re-deriving the chain.
@@ -325,6 +356,8 @@ The rest is the renderer's obligation, checked by reading rather than by a scrip
325
356
  - every `IG` appears in at least one `UC`, and every `UC` cites at least one `IG`
326
357
  - every `FG` names a source `UC` and a source `IG` that exist
327
358
  - a struck-through requirement is struck through in all four places it appears
359
+ - `Ekran` names the frame from the use case's `Ekranlar` block; `Test` names the Section 19 row that proves the requirement, or `-` when Part C is not rendered
360
+ - tickets are not a matrix column: `/multi-agent:analysis-jira` writes them to a `## Tickets` table at the end of the document, keyed by the same ids
328
361
 
329
362
  ## 6. Donanım ve Altyapı / Hardware and Infrastructure
330
363
 
@@ -433,11 +466,13 @@ Never omitted.
433
466
  ## 20. Riskler ve Açık Sorular <!-- TR -->
434
467
  ## 20. Risks and Open Questions <!-- EN -->
435
468
 
436
- | Id | Konu | Neden açık | Kime sorulacak | Etkilediği bölüm |
437
- |---|---|---|---|---|
438
- | AS-01 | <question> | <what evidence is missing> | <role or team> | 2.3 |
469
+ | Id | Konu | Neden açık | Kime sorulacak | Etkilediği bölüm | Kategori | Karar |
470
+ |---|---|---|---|---|---|---|
471
+ | AS-01 | <question> | <what evidence is missing> | <role or team> | 2.3, 5.2 | <business / design / service / content / legal> | <empty until answered> |
439
472
  ```
440
473
 
474
+ One question, one row: a question that affects several sections is written once and lists every section it affects, rather than repeated per section. `Karar` records the answer once it arrives, and `/multi-agent:analysis-resolve` folds it back into those sections.
475
+
441
476
  The source corporate documents keep open questions out of the page and raise them in conversation instead. This profile keeps them in the document deliberately: an `EKLENECEK` with no matching `AS-NN` row here is an unanswered question nobody owns. Every `EKLENECEK` and every unverified assumption in 2.4 emits a row (Locked 32).
442
477
 
443
478
  ## 21. Referanslar / References
@@ -67,7 +67,7 @@ Phase 1 compares `evidence_digest` against an existing document to decide whethe
67
67
 
68
68
  ## Layer headings (A / B / C)
69
69
 
70
- The 23 sections render under three named layers. This is an **additive heading level**, not a renumbering: section numbers are referenced in 172 places across the repo including two deterministic validators, so they do not move. Layer headings are `#` (h1); sections stay `##`. `validate-analysis-doc.mjs` collects `^#{1,3}` and `md2confluence-v3.py` maps `#{1,6}` to `<h{level}>`, so the extra level is safe in both and gives the Confluence page a two-level table of contents.
70
+ The 23 sections render under three named layers, an **additive heading level** that never renumbers: layer headings are `#` (h1), sections stay `##`, and both the validator and `md2confluence-v3.py` read the extra level, which gives the Confluence page a two-level table of contents.
71
71
 
72
72
  | Layer | Sections | What is lost if it is missing |
73
73
  |---|---|---|
@@ -76,7 +76,9 @@ The 23 sections render under three named layers. This is an **additive heading l
76
76
  | `# Bölüm C - Geliştirme Analizi` / `# Part C - Development Analysis` | 13, 14, 15 | **how** to build it: class name, file path, reuse, test row |
77
77
  | (footer, no heading) | 21, 22, 23 | |
78
78
 
79
- **Boundary rule.** Which layer a row belongs to is settled by one question: remove it, what becomes unclear? A row that is unclear in two layers at once is two rows. `A carries no technology name; C carries no business rationale.`
79
+ **Boundary rule.** Which layer a row belongs to is settled by one question: remove it, what becomes unclear? A row that is unclear in two layers at once is two rows. `A carries no technology name; C carries no business rationale.` Evidence that must stay in A goes in `<details>`.
80
+
81
+ **Document head**, above Section 1: the missing-inputs panel (if any), then one line of counts, e.g. `At a glance: 6 rules, 3 screens, 4 services, 2 open questions`.
80
82
 
81
83
  A layer whose sections all drop for lack of evidence drops its heading too (Locked 2). Numbering still flows 1..N over the rendered set.
82
84
 
@@ -84,7 +86,7 @@ A layer whose sections all drop for lack of evidence drops its heading too (Lock
84
86
 
85
87
  ## Omission rules
86
88
 
87
- Locked 2 - no `TBD` / `Not applicable` placeholder. Sections with zero evidence are omitted entirely. Numbering remains sequential `1..N` over the rendered set; numbers re-flow when sections drop. The dispatch report logs the omission list.
89
+ Locked 2 - no `TBD` / `Not applicable` placeholder. Sections with zero evidence are omitted entirely. Each rendered section keeps its canonical number, so an omitted section leaves a gap (`1, 2, 4, 9, ...` is correct); section numbers are cited across the document and must not move. The dispatch report logs the omission list.
88
90
 
89
91
  | Section | Omission condition |
90
92
  |---|---|
@@ -472,15 +474,17 @@ Authorization: Bearer <token>
472
474
 
473
475
  \`\`\`json
474
476
  {
475
- "field1": "value",
476
- "field2": 0
477
+ "field1": "<string>",
478
+ "field2": "<integer>"
477
479
  }
478
480
  \`\`\`
479
481
 
480
- | Alan / Field | Tip / Type | Zorunlu / Required | Validation |
481
- |---|---|---|---|
482
- | field1 | string | yes | <rule> |
483
- | field2 | integer | no | <rule> |
482
+ | Alan / Field | Tip / Type | Zorunlu / Required | Validation | Kaynak / Source |
483
+ |---|---|---|---|---|
484
+ | field1 | string | yes | <rule> | <spec path or page that defines it> |
485
+ | field2 | integer | no | <rule> | <spec path or page that defines it> |
486
+
487
+ Types, never sample values; `Kaynak / Source` names where each field is defined.
484
488
 
485
489
  ### 9.3 Response varyantları / Response variants
486
490
 
@@ -933,13 +937,16 @@ Never omitted.
933
937
  ## 20. Riskler ve Açık Sorular <!-- TR -->
934
938
  ## 20. Risks and Open Questions <!-- EN -->
935
939
 
936
- | Risk / Soru / Risk / Question | Sahibi / Owner | Durum / Status |
937
- |---|---|---|
938
- | <risk or question> | <name or role> | Açık / Open |
939
- | <convention fallback applied> | Pass B (auto) | Açık / Open |
940
- | <existing X candidate found; reuse or document why a new one is needed> | <name> | Açık / Open |
940
+ | ID | Risk / Soru / Risk / Question | Sahibi / Owner | Durum / Status |
941
+ |---|---|---|---|
942
+ | AS-01 | <risk or question> | <name or role> | Açık / Open |
943
+ | AS-02 | <convention fallback applied> | Pass B (auto) | Açık / Open |
944
+ | AS-03 | <existing X candidate found; reuse or document why a new one is needed> | <name> | Açık / Open |
941
945
  ```
942
946
 
947
+ Every row carries an `AS-NN` id: a gap marker elsewhere in the document names the row
948
+ it is answered by, and the closure gate pairs them by that id.
949
+
943
950
  Auto-populated rows (Locked 11, 23): repo-evidence direct-match candidates that conflict with planned new-write; convention fallbacks where Phase 1c confidence was none.
944
951
 
945
952
  ## 21. Referanslar / References
@@ -288,6 +288,10 @@ argument-hint: "<input hint>"
288
288
 
289
289
  **`not-for`** is optional and lists sibling commands this one must not be chosen for, as bare names (`not-for: jira, review-jira`). It is written for the model reading the routing surface, so it belongs beside the description rather than in prose further down. `lint-skills.mjs` also consumes it: a trigger-vocabulary collision either side has named drops off the warning list and is reported as settled instead, and a name that resolves to no sibling on the same surface is an error - a typo would otherwise leave the pair unanswered while the author believes it is handled. Both trees spell the value the same way and the lint reads it from either place; the `multi-agent-` prefix on the Copilot side is stripped before matching.
290
290
 
291
+ **Command-only keys stay top-level.** `parameters`, `gui`, `destructive`, `confirm` and `description-tr` sit at the top level of a command's frontmatter, not under `metadata:`. Claude Code ignores a key it does not recognize, command files are never packaged for claude.ai or the Skills API (where unknown keys are rejected), and these keys are read by the command contract, the GUI catalog and the installer's localizer, which all parse the top level. A shared skill keeps every key no host reads under `metadata:`, because shared skills are the files that do get packaged.
292
+
293
+ **`disable-model-invocation: true`** marks every command whose `destructive` is true: such a command runs only when the user types it. It is set on the Claude command only; the Copilot mirror does not carry it. Commands that only confirm (`confirm: required` without `destructive`), such as `sync`, stay model-invocable because they are routinely run on request and confirm inside their own flow. `test/command-contract.test.mjs` holds the rule.
294
+
291
295
  **Invariant**: the English `description` must have the same meaning on both sides, and a `description-tr` line (when present) must be a faithful translation of it. Semantic drift is not allowed. `description-en` sidecars are an installed-tree artifact and must never appear in repo or Copilot files.
292
296
 
293
297
  ---
@@ -7,6 +7,10 @@
7
7
  - [Identity is a label, not a title](#identity-is-a-label-not-a-title)
8
8
  - [The write is ledgered](#the-write-is-ledgered)
9
9
  - [An existing node is skipped, never updated](#an-existing-node-is-skipped-never-updated)
10
+ - [The story body is document text, escaped](#the-story-body-is-document-text-escaped)
11
+ - [An existing epic, linked the way the site links](#an-existing-epic-linked-the-way-the-site-links)
12
+ - [Channel clones are opt-in](#channel-clones-are-opt-in)
13
+ - [The document lists its tickets](#the-document-lists-its-tickets)
10
14
  - [Every site-specific name is a VALUE, never a schema key](#every-site-specific-name-is-a-value-never-a-schema-key)
11
15
  - [Auth](#auth)
12
16
  <!-- /toc -->
@@ -17,16 +21,18 @@ Two files do it, and the split is the design:
17
21
  | File | Does | Network |
18
22
  |---|---|---|
19
23
  | `scripts/analysis-story-tree.mjs` | decides WHAT to create, checks coverage, assigns identity | none |
24
+ | `scripts/analysis-story-body.mjs` | builds each story's description from the document | none |
20
25
  | `lib/analysis-jira-write.sh` | creates it | yes |
26
+ | `lib/jira-epic-link.sh` | decides which field links a story to its epic | none (the writer passes the site's answers in) |
27
+ | `scripts/analysis-tickets-writeback.mjs` | writes the created keys back into the document | none |
21
28
 
22
29
  Planning is deterministic and testable offline; creating issues is neither. Kept
23
30
  apart, the file that can write to a tracker is small enough to read in one
24
31
  sitting.
25
32
 
26
- `jira-publish.sh` could not be extended to do this: it writes a comment or a
27
- description on an issue that already exists. The only creation path in the repo
28
- was a curl hand-written inside a markdown instruction, which is what this
29
- replaces.
33
+ `jira-publish.sh` writes a comment or a description on an issue that already
34
+ exists; creating issues is a different contract, so it is a different file, and
35
+ the only one that creates issues.
30
36
 
31
37
  ## The marker gate runs first, before any network call
32
38
 
@@ -69,10 +75,9 @@ verdict is `unverifiable`, never `ok`. It appears:
69
75
  - as a separate fourth approval option, not folded into `Approve`
70
76
  - in the writer's own output, and beside every key in the ledger
71
77
 
72
- Such a document still plans work, from its user-story sub-sections. That is not
73
- a convenience: without it the no-atom document produced no stories, exited
74
- "nothing to plan", and the `unverifiable` branch was unreachable - a case the
75
- code claimed to handle and never could.
78
+ Such a document still plans work, from its user-story sub-sections, so a
79
+ document without atoms gets stories and an honest `unverifiable` verdict rather
80
+ than "nothing to plan".
76
81
 
77
82
  ## Identity is a label, not a title
78
83
 
@@ -99,8 +104,55 @@ answerable from the tree's own trail.
99
104
  ## An existing node is skipped, never updated
100
105
 
101
106
  Jira has no backup path for fields other than description. Rewriting a body an
102
- engineer has since edited would repeat, at tree scale, the defect that made
103
- `jira-publish.sh` take backups in the first place.
107
+ engineer has since edited would lose that edit with no way back. A node found by
108
+ its label is recorded in the ledger as `exists`, beside its key.
109
+
110
+ ## The story body is document text, escaped
111
+
112
+ `buildStoryBody()` assembles the description from the document alone:
113
+
114
+ | Part | Global profile | Corporate profile |
115
+ |---|---|---|
116
+ | User story | the "As a ..." line of the story's own section; the user-story section only when it holds exactly one | the use case's actor and short description |
117
+ | Acceptance criteria | the G/W/T column of each rule, else the rule | each functional requirement, plus the use case's alternative and exception flows |
118
+ | Current vs target | redesign rows that point at the story's section | the same, from the corporate redesign sub-sections |
119
+ | Links | the analysis page, Figma URLs in the story's sections, Swagger/OpenAPI URLs in References | same |
120
+
121
+ At most 8 criteria. What is missing stays missing and becomes a body warning
122
+ (no user story, fewer than 2 criteria, no negative criterion); nothing is filled
123
+ in. Every piece of document text passes `escapeJiraWiki()` before it is placed
124
+ in wiki markup, and links pass `wikiLink()`, which accepts http and https only,
125
+ so document text cannot become a macro, a mention, an image or a link.
126
+
127
+ ## An existing epic, linked the way the site links
128
+
129
+ `--epic KEY` links every story to an epic that already exists; the tree never
130
+ creates one. Cloud links through the `parent` field. Server and Data Center use
131
+ an Epic Link custom field whose id differs per site, so `issueTree.epicLinkMode:
132
+ auto` reads `serverInfo` and discovers the field by its schema type
133
+ (`com.pyxis.greenhopper.jira:gh-epic-link`). No field found means the stories
134
+ are created unlinked, with a warning; a field id is never guessed. `parent`,
135
+ `epicLinkField` (with `epicLinkFieldId`) and `none` force one path. A story that
136
+ already exists is not relinked.
137
+
138
+ ## Channel clones are opt-in
139
+
140
+ With `issueTree.cloneByChannel: true` and `channelComponents` filled, each story
141
+ gets one clone per channel: its own label (the story's identity plus
142
+ `ch:<channel>`), the channel's component, and an issue link of
143
+ `cloneLinkType` (default `Relates`) back to the story. Clone labels are part of
144
+ the label search, and a link is checked before it is sent, so a re-run doubles
145
+ neither.
146
+
147
+ ## The document lists its tickets
148
+
149
+ After the write, `analysis-tickets-writeback.mjs` puts a `## Tickets` table at
150
+ the end of the local document, between `<!-- tickets:start -->` and
151
+ `<!-- tickets:end -->`: story, key, source ids and channel, keys taken from the
152
+ ledger. It carries no section number, so the canonical numbering is untouched,
153
+ and a re-run replaces the block. An unverifiable tree is titled
154
+ `## Tickets (coverage unverified)`. Publishing the updated document to its
155
+ Confluence page is a separate approval.
104
156
 
105
157
  ## Every site-specific name is a VALUE, never a schema key
106
158
 
@@ -113,6 +165,8 @@ are values the site fills in:
113
165
  - `subtaskRoles` ships **empty**. An empty list is an instruction to look at how
114
166
  this board actually splits work; a ready-made list would be the guess most
115
167
  worth avoiding
168
+ - `epicLinkFieldId`, when set, is the site's own field id; otherwise it is
169
+ discovered
116
170
  - `subtaskIssueType: null` means discover it - `createmeta` returns whichever
117
171
  type carries `subtask: true`, under whatever name the site gave it. Same rule
118
172
  as `features/jira-context.md`: the type travels as it comes from Jira and is
@@ -126,14 +180,10 @@ setting is visible before the writes rather than in Jira afterwards.
126
180
  `lib/_jira-auth.sh` holds one host-and-token resolution and one curl idiom: the
127
181
  token reaches curl through a `-K` config on process substitution and never
128
182
  touches argv, a log, or `ps`. That idiom is the part most easily retyped badly,
129
- which is why the third writer got a shared copy instead of a third hand-written
130
- one.
131
-
132
- **Only this writer consumes it today.** `jira-publish.sh` and `issue-fetcher.sh`
133
- still carry their own resolution, and retrofitting them is real work rather than
134
- a rename - `issue-fetcher.sh` resolves per-account token keys that this helper
135
- does not model yet. So the file is the shared copy going forward, not a
136
- consolidation that has already happened, and saying otherwise would describe a
137
- cleanup nobody did. The leak property itself is asserted on all three callers
183
+ so it lives in one file.
184
+
185
+ **Only this writer consumes it.** `jira-publish.sh` and `issue-fetcher.sh`
186
+ carry their own resolution; `issue-fetcher.sh` resolves per-account token keys
187
+ that this helper does not model. The file is the shared copy for new callers. The leak property itself is asserted on all three callers
138
188
  independently in `smoke-analysis-jira.sh`, which is the part that must hold
139
189
  whether or not they ever share code.
@@ -293,6 +293,16 @@ readiness, not correctness: a machine that fails all four still runs the
293
293
  pipeline perfectly well with someone watching it. What they catch is the
294
294
  failure mode of an unwatched run - stopping without saying so.
295
295
 
296
+
297
+ ### usage-report
298
+
299
+ The outcome `usage-report.mjs` recorded for its last send attempt, read from
300
+ `logs/multi-agent/usage-last-send.json`. SKIP when `usageLog.enabled` is off or
301
+ nothing has been sent yet; OK when the last send was accepted; WARN for every
302
+ other outcome (`redirected`, `rejected`, `not-accepted`, `failed`, `skipped`),
303
+ with the status code or reason and the step that fixes it. The emitter runs
304
+ detached from phase-tracker with its output discarded, so this record is the
305
+ only surface on which a reporting path that stopped working shows up.
296
306
  ### unattended-contract
297
307
 
298
308
  Is `multi-agent-refs/unattended-contract.md` installed. It is the only place
@@ -132,6 +132,16 @@ evidence, and there is now one fewer of them. Triage also runs on `opus`, which
132
132
  makes it the same model as Reviewer 1; the Step 3 anonymisation requirement
133
133
  already covers that case and is not optional here.
134
134
 
135
+ **Installed agents follow the switch.** Five personas declare
136
+ `preferredModel: fable` (code-reviewer, plan-critic, and the iOS, Android and
137
+ backend architects). A dispatch the router resolves passes its model explicitly,
138
+ but an agent spawned without one runs on its frontmatter `model:`. So the
139
+ installed copies are written from `fableEnabled`: `fable` while it is on, `opus`
140
+ while it is off. `model-rung.sh` re-applies them on every flip and the installer
141
+ on every install; sources keep `model: fable`, so `lint-personas.mjs` still holds
142
+ `model === preferredModel` there. A `--symlink` install links the source tree and
143
+ is left alone.
144
+
135
145
  **Commands without Phase 0 resolve it themselves.** `/multi-agent:review`,
136
146
  `/multi-agent:review-analysis` and the untracked-branch tail of
137
147
  `/multi-agent:resume` dispatch reviewers and triage without a Phase 0 Step 0, so
@@ -303,4 +303,4 @@ Scripts: `contract-server.mjs` (phone branch, `isPhonePath`), `_phone-auth.mjs`
303
303
  `unattended-policy.json` (`publisherScripts`). Tests:
304
304
  `test/phone-api.test.mjs`, `test/contract-server.test.mjs`,
305
305
  `test/launch-request.test.mjs`, `test/redact.test.mjs`,
306
- `test/contract-kit.test.mjs`, `test/unattended-guard.test.mjs`.
306
+ `test/contract-kit.test.mjs`, `test/unattended-guard.serial.mjs`.
@@ -35,7 +35,7 @@ the variable the same way.
35
35
 
36
36
  With the variable unset every hook behaves as it did without this feature:
37
37
  `smoke-unattended-redteam.sh` runs the same inputs both ways, and
38
- `test/unattended-guard.test.mjs` holds the attended verdicts.
38
+ `test/unattended-guard.serial.mjs` holds the attended verdicts.
39
39
 
40
40
  ## The guard
41
41
 
@@ -722,7 +722,7 @@ Scripts: `agent-guard.sh`, `agent-guard.py`, `unattended_policy.py`,
722
722
  `lib/outbound-gate.mjs`, `lib/unattended-settings-location.mjs`,
723
723
  `install/_unattended-profile.mjs`. Data:
724
724
  `schemas/unattended-policy.json`, `schemas/unattended-profile.json`,
725
- `schemas/pr-request.schema.json`. Tests: `test/unattended-guard.test.mjs`,
725
+ `schemas/pr-request.schema.json`. Tests: `test/unattended-guard.serial.mjs`,
726
726
  `test/pr-request.test.mjs`, `test/autopilot-publish.test.mjs`,
727
727
  the runner's tests, `test/outbound-gate.test.mjs`,
728
728
  `test/unattended-profile.test.mjs`, `test/agent-guard.test.mjs`; smokes
@@ -147,6 +147,19 @@ reason, `unavailable` with the cause, `declined`, `registered`, `enabled`, `dry-
147
147
  "you turned it off" and "we could not reach the endpoint" are different facts
148
148
  about the same empty panel.
149
149
 
150
+ Every send attempt past the on-switch overwrites
151
+ `~/.claude/logs/multi-agent/usage-last-send.json` with its outcome: `accepted`,
152
+ `not-accepted` (the server answered but did not keep the record), `rejected`
153
+ (with the status code), `redirected` (with the host it pointed to; a redirect is
154
+ never followed, because the token would ride along), `failed` (network or
155
+ timeout), or `skipped` (`no-token`, `no-login`, `endpoint-not-allowed`). It
156
+ carries the run id, the event and the endpoint host, never the token. The
157
+ phase-tracker ping discards the emitter's output, so `/multi-agent:doctor`
158
+ reads this file (check `usage-report`) and turns anything but `accepted` into a
159
+ WARN with one step. `migrate-prefs.mjs` rewrites an endpoint on the platform
160
+ host to the site's domain, so a stored redirecting endpoint does not outlive an
161
+ update.
162
+
150
163
  `prefs.global.usageLog.endpoint` overrides where both calls go - the register URL
151
164
  is derived from it, so a self-hosted ingest gets its own registration rather than
152
165
  this one's. Absent means the shipped default, and every shipped default names the
@@ -57,6 +57,28 @@
57
57
  "default": "global",
58
58
  "description": "Analysis profile chosen at Phase 0 Step 1b (Locked 31). 'global' renders analysis-template.md (23-section development handoff); 'corporate' renders analysis-template-corporate.md (IG/UC/FG requirements document). Both read the same evidence; only the projection differs."
59
59
  },
60
+ "edgeCandidates": {
61
+ "type": "array",
62
+ "description": "Failure paths proposed from the evidence (refusals, error codes, timeouts, empty or expired states), shown at the Pass B preview. Only accepted ones enter the document; a candidate needs a source.",
63
+ "items": {
64
+ "type": "object",
65
+ "additionalProperties": false,
66
+ "required": ["target", "candidate", "source"],
67
+ "properties": {
68
+ "target": {
69
+ "type": "string",
70
+ "description": "Use case or rule group the candidate belongs to, e.g. UC-001 or BR-login."
71
+ },
72
+ "candidate": { "type": "string", "minLength": 1 },
73
+ "source": {
74
+ "type": "string",
75
+ "minLength": 1,
76
+ "description": "Where the evidence states it: a spec path, a page, a file:line."
77
+ },
78
+ "accepted": { "type": "boolean" }
79
+ }
80
+ }
81
+ },
60
82
  "options": {
61
83
  "type": "object",
62
84
  "additionalProperties": false,
@@ -948,7 +948,7 @@
948
948
  },
949
949
  "titleFormat": {
950
950
  "type": "string",
951
- "description": "Page-title pattern, e.g. \"{prefix}{module}_{flow}_{suffix}\". Placeholders are resolved at emit time."
951
+ "description": "Page-title pattern, e.g. \"{status}{prefix}{module}_{flow}_{suffix}\". Placeholders are resolved at emit time; {status} is the draft marker while the document is not final, and empty once it is. Without {status} the marker is prepended."
952
952
  },
953
953
  "titlePrefix": {
954
954
  "type": "string",
@@ -2107,6 +2107,27 @@
2107
2107
  "type": "string",
2108
2108
  "default": "ma-analysis",
2109
2109
  "description": "Prefix of the identity label a created issue carries. The label is how a second run, on a second machine or by a second analyst, finds the tree it already made instead of opening a duplicate."
2110
+ },
2111
+ "epicLinkMode": {
2112
+ "type": "string",
2113
+ "enum": ["auto", "parent", "epicLinkField", "none"],
2114
+ "default": "auto",
2115
+ "description": "How a story joins an existing epic. auto: parent on Cloud, the epic-link field (found by its schema type) on Server and Data Center, nothing when neither exists."
2116
+ },
2117
+ "epicLinkFieldId": {
2118
+ "type": ["string", "null"],
2119
+ "default": null,
2120
+ "description": "The epic-link custom field id, for epicLinkField mode or to skip discovery. Null means discover."
2121
+ },
2122
+ "cloneByChannel": {
2123
+ "type": "boolean",
2124
+ "default": false,
2125
+ "description": "Create one linked story per channel in channelComponents, each carrying that channel's component."
2126
+ },
2127
+ "cloneLinkType": {
2128
+ "type": "string",
2129
+ "default": "Relates",
2130
+ "description": "The link type between a story and its channel clones, by this site's name."
2110
2131
  }
2111
2132
  }
2112
2133
  },