@mmerterden/multi-agent-pipeline 20.30.0 → 20.34.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 (195) hide show
  1. package/CHANGELOG.md +123 -0
  2. package/README.md +47 -1
  3. package/README.tr.md +61 -1
  4. package/docs/adr/0018-cross-repo-links-not-a-global-graph.md +43 -0
  5. package/docs/adr/README.md +2 -1
  6. package/docs/facts.json +6 -5
  7. package/docs/features.md +1 -1
  8. package/install/_dev-only-files.mjs +1 -0
  9. package/install/_unattended-profile.mjs +61 -0
  10. package/install/catalog-history.json +1 -1
  11. package/install/claude.mjs +16 -2
  12. package/install/index.mjs +10 -1
  13. package/install/templates/claude-hooks.json +23 -1
  14. package/manifest.json +198 -134
  15. package/package.json +4 -1
  16. package/pipeline/agents/code-reviewer.md +9 -0
  17. package/pipeline/agents/explorer.md +4 -0
  18. package/pipeline/agents/plan-critic.md +4 -2
  19. package/pipeline/commands/multi-agent/SKILL.md +17 -136
  20. package/pipeline/commands/multi-agent/analysis/SKILL.md +4 -4
  21. package/pipeline/commands/multi-agent/channels/SKILL.md +15 -297
  22. package/pipeline/commands/multi-agent/graph/SKILL.md +5 -2
  23. package/pipeline/commands/multi-agent/language/SKILL.md +3 -3
  24. package/pipeline/commands/multi-agent/office-on/SKILL.md +8 -7
  25. package/pipeline/commands/multi-agent/office-status/SKILL.md +1 -1
  26. package/pipeline/commands/multi-agent/prune-logs/SKILL.md +4 -1
  27. package/pipeline/commands/multi-agent/resume/SKILL.md +8 -22
  28. package/pipeline/commands/multi-agent/review/SKILL.md +11 -134
  29. package/pipeline/commands/multi-agent/scenario-audit/SKILL.md +4 -2
  30. package/pipeline/commands/multi-agent/setup/SKILL.md +19 -224
  31. package/pipeline/commands/multi-agent/sync/SKILL.md +12 -0
  32. package/pipeline/commands/multi-agent/test-gen/SKILL.md +29 -3
  33. package/pipeline/contract/CHANGELOG.md +46 -0
  34. package/pipeline/contract/README.md +27 -10
  35. package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
  36. package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
  37. package/pipeline/contract/fixtures/error-unsigned.json +1 -1
  38. package/pipeline/contract/fixtures/launch-plan.json +1 -1
  39. package/pipeline/contract/fixtures/runs-awaiting-question.json +6 -1
  40. package/pipeline/contract/fixtures/runs-empty.json +1 -1
  41. package/pipeline/contract/fixtures/runs-failed.json +6 -1
  42. package/pipeline/contract/fixtures/runs-old-schema.json +6 -1
  43. package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +5 -1
  44. package/pipeline/contract/fixtures/runs-pr-opened.json +6 -1
  45. package/pipeline/contract/fixtures/runs-running.json +6 -1
  46. package/pipeline/contract/manifest.json +153 -19
  47. package/pipeline/contract/types/index.d.ts +304 -6
  48. package/pipeline/lib/bg-launch.mjs +106 -0
  49. package/pipeline/lib/code-digest.mjs +49 -0
  50. package/pipeline/lib/credential-resolve.mjs +42 -2
  51. package/pipeline/multi-agent-refs/channels/generator.md +146 -0
  52. package/pipeline/multi-agent-refs/channels/menu.md +55 -0
  53. package/pipeline/multi-agent-refs/channels/multi-repo.md +6 -0
  54. package/pipeline/multi-agent-refs/channels/preview.md +25 -0
  55. package/pipeline/multi-agent-refs/channels/reference.md +98 -0
  56. package/pipeline/multi-agent-refs/features/autopilot-circuit-breaker.md +1 -1
  57. package/pipeline/multi-agent-refs/features/code-graph.md +27 -0
  58. package/pipeline/multi-agent-refs/features/office-threat-model.md +24 -6
  59. package/pipeline/multi-agent-refs/features/plan-critic.md +5 -2
  60. package/pipeline/multi-agent-refs/features/review-delta.md +4 -1
  61. package/pipeline/multi-agent-refs/features/scope-check.md +10 -1
  62. package/pipeline/multi-agent-refs/features/unattended-security.md +1 -1
  63. package/pipeline/multi-agent-refs/help/en.md +2 -2
  64. package/pipeline/multi-agent-refs/help/tr.md +2 -2
  65. package/pipeline/multi-agent-refs/knowledge.md +9 -0
  66. package/pipeline/multi-agent-refs/orchestrator/dispatcher.md +163 -0
  67. package/pipeline/multi-agent-refs/phases/modes.md +7 -7
  68. package/pipeline/multi-agent-refs/phases/phase-1-plan.md +4 -4
  69. package/pipeline/multi-agent-refs/phases/phase-2-dev.md +9 -8
  70. package/pipeline/multi-agent-refs/phases/phase-3-review.md +12 -7
  71. package/pipeline/multi-agent-refs/phases/phase-4-commit.md +7 -3
  72. package/pipeline/multi-agent-refs/phases/phase-5-report.md +19 -10
  73. package/pipeline/multi-agent-refs/picker-contract.md +1 -1
  74. package/pipeline/multi-agent-refs/resume/details.md +35 -0
  75. package/pipeline/multi-agent-refs/review/inputs.md +87 -0
  76. package/pipeline/multi-agent-refs/review/module-guides.md +25 -0
  77. package/pipeline/multi-agent-refs/review/valven.md +42 -0
  78. package/pipeline/multi-agent-refs/review-standalone-tracker.md +37 -0
  79. package/pipeline/multi-agent-refs/setup/accounts.md +27 -0
  80. package/pipeline/multi-agent-refs/setup/app-store-connect.md +5 -0
  81. package/pipeline/multi-agent-refs/setup/firebase.md +5 -0
  82. package/pipeline/multi-agent-refs/setup/google-play.md +4 -0
  83. package/pipeline/multi-agent-refs/setup/hooks-and-plugins.md +21 -0
  84. package/pipeline/multi-agent-refs/setup/language.md +46 -0
  85. package/pipeline/multi-agent-refs/setup/multi-repo-group.md +51 -0
  86. package/pipeline/multi-agent-refs/setup/summaries.md +63 -0
  87. package/pipeline/multi-agent-refs/setup/token-save-flow.md +51 -0
  88. package/pipeline/multi-agent-refs/tracker-contract.md +8 -3
  89. package/pipeline/office/NOTICE.md +4 -1
  90. package/pipeline/office/assets/ARCHIVE_BOXES.png +0 -0
  91. package/pipeline/office/assets/COFFEE_MACHINE.png +0 -0
  92. package/pipeline/office/assets/COUNTER_SINK.png +0 -0
  93. package/pipeline/office/assets/DEVICE_STAND.png +0 -0
  94. package/pipeline/office/assets/FILING_CABINET.png +0 -0
  95. package/pipeline/office/assets/FRIDGE.png +0 -0
  96. package/pipeline/office/assets/MAILBOX.png +0 -0
  97. package/pipeline/office/assets/MOP_BUCKET.png +0 -0
  98. package/pipeline/office/assets/PRINTER.png +0 -0
  99. package/pipeline/office/assets/RECEPTION_DESK.png +0 -0
  100. package/pipeline/office/assets/SERVER_RACK.png +0 -0
  101. package/pipeline/office/assets/SERVER_RACK_2.png +0 -0
  102. package/pipeline/office/assets/TOILET_STALL.png +0 -0
  103. package/pipeline/office/assets/VENDING.png +0 -0
  104. package/pipeline/office/assets/WASHBASIN.png +0 -0
  105. package/pipeline/office/assets/WATER_COOLER.png +0 -0
  106. package/pipeline/office/index.html +3936 -819
  107. package/pipeline/office/toolkit.json +3 -0
  108. package/pipeline/rules/response-shape.md +6 -7
  109. package/pipeline/schemas/activity.schema.json +188 -4
  110. package/pipeline/schemas/agent-state.schema.json +511 -115
  111. package/pipeline/schemas/contract-error.schema.json +12 -1
  112. package/pipeline/schemas/job-change.schema.json +72 -0
  113. package/pipeline/schemas/job-request.schema.json +38 -0
  114. package/pipeline/schemas/launch.json +4 -4
  115. package/pipeline/schemas/office-credentials.schema.json +71 -0
  116. package/pipeline/schemas/office-jobs.json +253 -0
  117. package/pipeline/schemas/office-jobs.schema.json +62 -0
  118. package/pipeline/schemas/office-memory.schema.json +105 -0
  119. package/pipeline/schemas/office-settings-request.schema.json +30 -0
  120. package/pipeline/schemas/office-settings.schema.json +67 -0
  121. package/pipeline/schemas/phases.json +14 -6
  122. package/pipeline/schemas/phases.schema.json +86 -19
  123. package/pipeline/schemas/plan-critique.schema.json +9 -2
  124. package/pipeline/schemas/planning-output.schema.json +25 -2
  125. package/pipeline/schemas/prefs.schema.json +24 -1
  126. package/pipeline/schemas/prs.schema.json +5 -0
  127. package/pipeline/schemas/prune-logs-request.schema.json +26 -0
  128. package/pipeline/schemas/prune-logs.schema.json +52 -0
  129. package/pipeline/schemas/repo-change.schema.json +54 -0
  130. package/pipeline/schemas/repo-request.schema.json +29 -0
  131. package/pipeline/schemas/repos.schema.json +49 -1
  132. package/pipeline/schemas/reviewer-output.schema.json +20 -2
  133. package/pipeline/schemas/runs-index.schema.json +257 -46
  134. package/pipeline/schemas/system-status.schema.json +111 -0
  135. package/pipeline/schemas/token-budget.json +4 -4
  136. package/pipeline/schemas/tools.schema.json +5 -0
  137. package/pipeline/schemas/triage-output.schema.json +20 -2
  138. package/pipeline/schemas/unattended-profile.json +7 -2
  139. package/pipeline/scripts/_active-run.mjs +2 -1
  140. package/pipeline/scripts/_graph-analysis.mjs +270 -0
  141. package/pipeline/scripts/_memory-policy.mjs +165 -0
  142. package/pipeline/scripts/_secret-scan.mjs +105 -0
  143. package/pipeline/scripts/_spawn-async.mjs +9 -5
  144. package/pipeline/scripts/_unified-diff.mjs +2 -2
  145. package/pipeline/scripts/activity-snapshot.mjs +219 -23
  146. package/pipeline/scripts/autopilot-runner.mjs +7 -5
  147. package/pipeline/scripts/capture-flush.sh +12 -2
  148. package/pipeline/scripts/check-read-size.sh +9 -0
  149. package/pipeline/scripts/code-graph-rules/android.json +8 -3
  150. package/pipeline/scripts/contract-server.mjs +575 -28
  151. package/pipeline/scripts/debt-ledger.mjs +323 -0
  152. package/pipeline/scripts/file-context.mjs +261 -0
  153. package/pipeline/scripts/file-context.sh +20 -0
  154. package/pipeline/scripts/gen-mode-dispatch.mjs +3 -1
  155. package/pipeline/scripts/graph-links.mjs +230 -0
  156. package/pipeline/scripts/graph-query.mjs +133 -1
  157. package/pipeline/scripts/graph-report.mjs +65 -0
  158. package/pipeline/scripts/job-launch.mjs +477 -0
  159. package/pipeline/scripts/launch-request.mjs +94 -9
  160. package/pipeline/scripts/learn-from-transcripts.mjs +12 -48
  161. package/pipeline/scripts/learnings-ledger.mjs +143 -24
  162. package/pipeline/scripts/office-build.mjs +148 -11
  163. package/pipeline/scripts/office-control.mjs +34 -7
  164. package/pipeline/scripts/office-credentials.mjs +106 -0
  165. package/pipeline/scripts/office-memory.mjs +200 -0
  166. package/pipeline/scripts/office-prune.mjs +125 -0
  167. package/pipeline/scripts/office-settings.mjs +145 -0
  168. package/pipeline/scripts/phase-tracker.sh +12 -5
  169. package/pipeline/scripts/plan-critique-gate.mjs +11 -2
  170. package/pipeline/scripts/prune-logs.sh +53 -2
  171. package/pipeline/scripts/rework-rulings.mjs +106 -0
  172. package/pipeline/scripts/runs-index.mjs +62 -2
  173. package/pipeline/scripts/scope-check-gate.mjs +149 -2
  174. package/pipeline/scripts/system-status.mjs +196 -0
  175. package/pipeline/scripts/task-mirror.mjs +150 -0
  176. package/pipeline/scripts/task-mirror.sh +20 -0
  177. package/pipeline/scripts/toolkit-tools.mjs +33 -7
  178. package/pipeline/scripts/transcript-index.mjs +4 -1
  179. package/pipeline/scripts/triage-memory.mjs +64 -37
  180. package/pipeline/scripts/validate-planning.mjs +68 -4
  181. package/pipeline/scripts/validate-reviewer.mjs +20 -0
  182. package/pipeline/scripts/validate-triage.mjs +107 -2
  183. package/pipeline/scripts/work-items.mjs +191 -22
  184. package/pipeline/skills/.skill-manifest.json +9 -9
  185. package/pipeline/skills/.skills-index.json +1 -1
  186. package/pipeline/skills/shared/README.md +1 -1
  187. package/pipeline/skills/shared/core/multi-agent-graph/SKILL.md +5 -2
  188. package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +4 -4
  189. package/pipeline/skills/shared/core/multi-agent-office-on/SKILL.md +3 -4
  190. package/pipeline/skills/shared/core/multi-agent-prune-logs/SKILL.md +4 -1
  191. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +21 -0
  192. package/pipeline/skills/shared/core/multi-agent-scenario-audit/SKILL.md +4 -2
  193. package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +4 -4
  194. package/pipeline/skills/shared/core/multi-agent-test-gen/SKILL.md +26 -3
  195. package/pipeline/skills/skills-index.md +1 -1
@@ -0,0 +1,146 @@
1
+ # Channels - Generator (Step 5 section bodies)
2
+
3
+ > Loaded by `channels/SKILL.md` Step 5. The SKILL keeps the Jira-vs-PR body rule, the section-order rule and the target length rule; this file carries the content-option table, the aggregated output template, the store-compliance augmentation, and the work-summary and cost-summary generators. Read it before generating any section body.
4
+
5
+ ## Content options
6
+
7
+ | Content option | Generated section |
8
+ |---|---|
9
+ | Normal analiz | `### Analysis` - impact summary from Phase 1, risks from Phase 4 (pipeline-log source, high-level only) |
10
+ | Teknik analiz | `### Technical Details` - Changes (what/why per file group), Architecture (structural decisions, pattern changes), Dependencies (new imports/frameworks/packages). Source: Phase 1 planning + Phase 2 dev log + `git diff --stat`. **PR and Confluence only** - never rendered into a Jira comment. |
11
+ | Test senaryoları | `### Test Scenarios` - precondition / steps / expected table (4-8 rows), user perspective |
12
+ | Test Koşumu (Xray) | `### Test Koşumu` - Xray Test Execution of the task's UI scenario run (`channels/test-run.md`) |
13
+ | Auto-diff | Old enrich Output Template verbatim - Root Cause / Solution / Changed Files / Test Scenarios |
14
+ | Manuel not | `### Notes` - user-provided paragraph, or LLM-split into subsections if `--message` is plain text and long |
15
+ | Cost özeti | `### Cost Summary` - per-phase token tally + est. USD table. Source: `phase-tracker.sh` phase status + optional OTel spans. See "Cost summary generation" below. |
16
+ | Yapılan iş özeti | `### Work Summary` - executive one-screen summary: task + branch + base + PR, scope delivered (done/[pending] per Phase 2 task), changed files with +/- counts (capped at 20 rows), review outcome (accepted/deferred/rejected + approved), phase tick strip. Source: `agent-state.json` + `phase-tracker.json` + `git diff --numstat base...HEAD`. See "Work summary generation" below. |
17
+
18
+ ## Output template
19
+
20
+ **Output template (aggregated across selected content options):**
21
+
22
+ ```markdown
23
+ ### Analysis ← if "Normal analiz" selected
24
+ {impact summary, risks - from pipeline log, high-level}
25
+
26
+ ### Technical Details ← if "Teknik analiz" selected
27
+ #### Changes
28
+ {Per changed-file-group, what changed + why - from Phase 2 dev log}
29
+ #### Architecture
30
+ {Structural decisions, pattern changes, new file layouts - from Phase 1 plan}
31
+ #### Dependencies
32
+ {New imports / frameworks / packages - from git diff --stat, or "None"}
33
+
34
+ ### Root Cause ← if "Auto-diff" selected
35
+ {single paragraph - from diff}
36
+
37
+ - `path/to/File.ext → funcName`: {why it broke}
38
+
39
+ ### Solution ← if "Auto-diff" selected
40
+ - `path/to/File.ext:LINE-LINE` - {change, one line}
41
+
42
+ ### Changed Files ← if "Auto-diff" selected
43
+ | File | +/- |
44
+ |------|-----|
45
+ | `path/to/File.ext` | +N / -M |
46
+
47
+ ### Test Scenarios ← if "Test senaryoları" OR "Auto-diff" selected
48
+ **1. {what this scenario exercises}**
49
+ 1. {step the tester performs}
50
+ 2. **Expected:** {what they should see}
51
+
52
+ **2. {regression scenario}**
53
+ 1. {step}
54
+ 2. **Expected:** {what should still behave as before}
55
+
56
+ ### Test Koşumu ← if "Test Koşumu (Xray)" selected
57
+
58
+ ### Notes ← if "Manuel not" selected
59
+ {user message}
60
+
61
+ ### Work Summary ← if "Yapılan iş özeti" selected
62
+ {executive scope + files + review + phases block - see "Work summary generation" below}
63
+
64
+ ### Cost Summary ← if "Cost özeti" selected
65
+ {per-phase token + USD table - see "Cost summary generation" below}
66
+
67
+ ### Store compliance ← auto-appended if cached compliance report exists
68
+ {summary line - see below}
69
+
70
+ Ref: {jiraId}
71
+ ```
72
+
73
+ **Store compliance auto-augmentation:**
74
+
75
+ After the content-selected sections are generated, check for a recent compliance report cached from `/multi-agent:test "store-ready"`:
76
+
77
+ ```bash
78
+ # Cache locations (newest wins; expire after 6h to avoid stale cross-branch reports)
79
+ find /tmp -maxdepth 1 -name 'archiveguard-*.json' -o -name 'play-compliance-*.json' \
80
+ -newermt "-6 hours" -print 2>/dev/null | head -1
81
+ ```
82
+
83
+ If a cached report exists AND any of the selected channels is PR / Jira / Confluence, append an auto-summary section:
84
+
85
+ | Blocker count | Appended line |
86
+ |---|---|
87
+ | `errors > 0` | `### Store compliance` + `{N} blocker(s), {M} warning(s) - run /multi-agent:test "store-ready" for the full report.` - per-blocker: `- \`{ruleID}\` - {Apple ITMS / Play ref}: {message}` (capped at 5 rows, rest collapsed to `+N more`) |
88
+ | `errors == 0, warnings > 0` | `### Store compliance` + `[done] No blockers. {M} warning(s) - see /multi-agent:test "store-ready" for detail.` |
89
+ | `errors == 0, warnings == 0` | Skip section entirely - don't pollute PR body |
90
+
91
+ The section references `ruleID` + policy ref from the compliance skill catalogs (`apple-archive-compliance` / `google-play-compliance`) verbatim. No translation - global identifiers stay as-is.
92
+
93
+ If no cached report exists, skip silently - this is augmentation, not a gate.
94
+
95
+ **Work summary generation:**
96
+
97
+ Emitted only when `reportContent.workSummary === true` and at least one of (`agent-state.json`, `--branch` flag) is available. The shell adapter is `$HOME/.claude/scripts/render-work-summary.sh <taskId>`, and that script's own header is the spec for what it emits - task header, scope delivered, changed files, review outcome, phase strip - including the caps and the hidden-when-empty rules. Exit 2 means the state was unreadable: skip the section, do not substitute a hand-written one.
98
+
99
+ **Output template:**
100
+
101
+ ```markdown
102
+ ### Work Summary
103
+ **Task**: `PROJ-1099` | **Branch**: `bugfix/PROJ-1099-dark-mode` | **Base**: `develop` | **PR**: #104
104
+
105
+ #### Scope delivered
106
+ - [done] T1 Add failing dark-mode snapshot test
107
+ - [done] T2 Replace Color.white with design token
108
+
109
+ #### Changed files (3 files · +48 / -12)
110
+ - `Sources/Auth/LoginView.swift` (+24 / -8)
111
+ - `Sources/Auth/LoginViewTests.swift` (+22 / -4)
112
+ - `Sources/DesignSystem/Color+Tokens.swift` (+2 / -0)
113
+
114
+ #### Review outcome
115
+ - 1 accepted · 0 deferred · 0 rejected · approved=true
116
+
117
+ #### Phases
118
+ - 0 Init [done] · 1 Plan [done] · 2 Dev [done] · 3 Review [done] · 4 Commit [done] · 5 Report active
119
+ ```
120
+
121
+ **Post-hoc invocation** (task already finished, agent-state.json may be archived): pass `--branch` + `--base-branch` explicitly - the script still emits task header + changed-files + phases sections (scope + review outcome are state-dependent and will be absent if state is gone).
122
+
123
+ **Cost summary generation:**
124
+
125
+ Emitted only when `reportContent.costSummary === true` and tracker data is available. Source of truth is `phase-tracker.sh`:
126
+
127
+ 1. **Token tallies per phase** - `render-cost-summary.sh <taskId>` resolves the tracker JSON itself: worktree candidates (`.worktrees/{taskPath}/phase-tracker.json`) first, then the standard location `$HOME/.claude/logs/multi-agent/{taskId}/tracker-state.json` that `phase-tracker.sh` actually writes. Each phase row carries `{tokens_in, tokens_out}`.
128
+ 2. **Fallback to OTel spans** - if tracker JSON has no token columns but `otel-spans.jsonl` exists (set `MULTI_AGENT_OTEL_SPANS=1` beforehand), aggregate `phase.tokens` spans by phase ID.
129
+ 3. **USD estimate** - uses a static price table in `$HOME/.claude/scripts/cost-table.json` (model → $/Mtok_in, $/Mtok_out). Missing model → show ` - ` for USD, never block.
130
+
131
+ **Output template:**
132
+
133
+ ```markdown
134
+ ### Cost Summary
135
+ | Phase | Model | Tokens in | Tokens out | Est. USD |
136
+ |-------|-------|-----------|------------|----------|
137
+ | 1. Analysis | opus | 12,480 | 3,210 | $0.42 |
138
+ | 2. Planning | opus | 18,904 | 5,120 | $0.66 |
139
+ | 3. Dev | sonnet | 42,310 | 9,880 | $0.27 |
140
+ | 4. Review | opus | 21,050 | 4,720 | $0.66 |
141
+ | **Total** | | **94,744** | **22,930** | **$2.04** |
142
+ ```
143
+
144
+ Generated by `$HOME/.claude/scripts/render-cost-summary.sh <task-id>` using prices from `$HOME/.claude/scripts/cost-table.json`. The channels adapter shells out to this renderer - doc table is illustrative only, runtime is authoritative.
145
+
146
+ **Precision rules:** tokens as thousand-separated integers. USD rounded to 2 dp (`printf '$%.2f'`). Rows with zero tokens dropped. If total USD is ` - ` (no price for any model), replace the total row USD cell with ` - ` and add a one-line footnote: `*USD unavailable - add model to cost-table.json*`.
@@ -0,0 +1,55 @@
1
+ # Channels - Menu (Step 2 sources, Step 3a preview, Step 3b picker rules)
2
+
3
+ > Loaded by `channels/SKILL.md` Step 2. The SKILL keeps the step order and the autopilot rule; this file carries the context-source table, the summary-first preview logic, the greyed-out rules and the first-run defaults. Read it before Step 2.
4
+
5
+ ## Step 2 - Context sources
6
+
7
+ Automatic. The generator picks the best-available source:
8
+
9
+ | Source | Available when | Used for |
10
+ |---|---|---|
11
+ | **Pipeline log** | `$HOME/.claude/logs/multi-agent/<project>/<task-id>/agent-log.md` exists AND matches `jiraId` | "Normal analiz" + "Test senaryoları" content options - uses Phase 1/2/4 output verbatim |
12
+ | **PR diff** | PR exists, diff fetchable (`gh pr diff` or Bitbucket `/diff?contextLines=5`) | "Auto-diff summary" content option - old enrich behavior |
13
+ | **Manual message** | `--message` / `--message-file` provided | Replaces both sources - user text is canonical |
14
+
15
+ ## Step 3a - Work Summary preview (summary-first flow)
16
+
17
+ **Runs only when** (a) `agent-state.json` exists for the task AND (b) a Work Summary can be generated (same availability rule as the "Yapılan iş özeti" content row). Purpose: give the user an instant "what did this ship?" answer BEFORE they pick channels. They see the outcome first, then decide where to route it - instead of the reverse.
18
+
19
+ ```bash
20
+ preview=$(bash $HOME/.claude/scripts/render-work-summary.sh "$TASK_ID" 2>/dev/null || true)
21
+ [ -n "$preview" ] && {
22
+ printf '%s\n\n' "$preview"
23
+ # Auto-tick workSummary content row when preview was shown.
24
+ # Users can still untick it in 3b if they don't want to re-send the same
25
+ # block to remote channels (useful when preview was enough for a look).
26
+ content_defaults.workSummary=true
27
+ }
28
+ ```
29
+
30
+ When the preview renders:
31
+ - **workSummary** row in 3b is **auto-ticked** (user can still uncheck).
32
+ - Previous `prefs.global.reportContent.workSummary` value is respected - if the user had explicitly unticked in a prior run, the auto-tick is **suppressed** and the ticked state matches the pref.
33
+
34
+ When the preview does NOT render (no state, or pref explicitly off at global level with no override):
35
+ - Skip 3a silently, go straight to the channels picker. The workSummary row in 3b stays at its pref default (`false`).
36
+
37
+ After the preview prints, 3b renders the two pickers: **Channels** (PR description, Jira comment, Confluence page, Wiki, Board; each unavailable one greyed with its reason) and **Content** (normal analysis, technical analysis, test scenarios, test run, PR auto-diff, manual note, cost summary, work summary).
38
+
39
+ **Why auto-tick:** the user just saw the summary and is most likely sending it; the explicit pref still wins.
40
+
41
+ ## Step 3b - Channel + content picker
42
+
43
+ **Greyed-out rules:**
44
+ - "Wiki" unselectable unless `state.taskType === "component"` AND `figmaConfig.wiki.enabled === true`. If greyed, show reason: `"(no figma-config or taskType != component)"`.
45
+ - "Board" unselectable unless `figmaConfig.board.enabled === true` AND `figmaConfig.board.provider === "github-projects-v2"` AND `figmaConfig.github.projectV2Id` set. If greyed, show reason: `"(no figma-config.board or projectV2Id missing)"`. Default target column: `inReview`.
46
+ - "Normal analiz" + "Test senaryoları" unselectable if no pipeline log found. Reason: `"(no pipeline log for PROJ-12345)"`.
47
+ - "Teknik analiz" unselectable if no Phase 1 planning log AND no PR diff available. Reason: `"(no technical source - need Phase 1 plan or PR diff)"`.
48
+ - "Auto-diff" unselectable if no PR found. Reason: `"(no PR linked to this target)"`.
49
+ - "Manuel not" always available. "Test Koşumu (Xray)": `channels/test-run.md`.
50
+ - "Cost özeti" unselectable if `phase-tracker.sh cost total` prints `-` (no priced token tallies; there is no `status` action) AND no `otel-spans.jsonl` exists for the task. Reason: `"(no tracker data - set MULTI_AGENT_OTEL_SPANS=1 or enable token tracking)"`.
51
+ - "Yapılan iş özeti" unselectable if no `agent-state.json` exists for the task (post-hoc call with `--branch` + `--base-branch` flags restores partial availability). Reason: `"(no agent-state.json for {taskId})"`. **Auto-ticked** when Step 3a preview was shown - user can still uncheck before confirming the menu.
52
+
53
+ **Defaults (first run):** PR + Jira ticked, Confluence + Wiki unticked. Content: auto-diff if pipeline log missing; otherwise normal-analiz + test-senaryoları.
54
+
55
+ Saved to `prefs.global.reportChannels` and `prefs.global.reportContent` - next run uses these as defaults.
@@ -13,3 +13,9 @@
13
13
  **Target enumeration:** `channels-multi-repo.sh targets <state>` returns a JSON array `[{repoName, prUrl, prNumber, isPrimary, crossLinks}]` for the dispatch loop. Single-repo tasks return one entry with empty `crossLinks` - every render-* call falls through to the body unchanged, so the loop is uniform across single-repo and multi-repo paths.
14
14
 
15
15
  **Logging hint:** `channels-multi-repo.sh summary <state>` produces a one-line autopilot log entry (`1 primary (foo) + 2 extras: <url> <url>` or `single-repo task`).
16
+
17
+ ## Target resolution (Step 1)
18
+
19
+ **Multi-repo:** when `agent-state.json` `projects[].length > 1`, Step 1 builds **one target per project**. Each project's PR is resolved via `pr` field in its projects[] entry; if missing, the Jira/Issue link query returns multiple PRs (one per repo) - all are added to `targets[]`. The project at `projects[0]` (or whose name matches `state.project`) is marked `isPrimary: true`; others are `isPrimary: false`.
20
+
21
+ For single-repo tasks, `targets` has exactly one entry. The rest of the flow is target-aware: Step 5 generates one body, Step 7 dispatches to each target, with cross-links (see the dispatch table above).
@@ -0,0 +1,25 @@
1
+ # Channels - Preview render (Step 6)
2
+
3
+ > Loaded by `channels/SKILL.md` Step 6. The SKILL keeps the approval gate, its skip cases, its options and the byte-identical rule; this file carries the per-channel markup conversion, the preview block layout and the secondary-artifact intents. Read it before rendering the Step 6 preview.
4
+
5
+ Conversion is per-channel; two channels have none:
6
+
7
+ | Channel | Conversion after approval | Table |
8
+ |---|---|---|
9
+ | PR description | **none** - posted as Markdown, verbatim | GitHub / Bitbucket / GitLab all render Markdown; see `channels/pr.md` "Markup dialect" |
10
+ | GitHub issue comment | **none** - posted as Markdown, verbatim | - |
11
+ | Jira comment | markdown → Jira wiki markup | `channels/jira.md` "Wiki markup conversion" |
12
+ | Confluence page | markdown → storage format | `channels/confluence.md` "Body conversion" |
13
+
14
+ The Jira table is the only *text* markup table in the doc set and never applies to the PR body: `h2.`, `{{code}}` or `#`-numbered lists render there as raw wiki markup.
15
+
16
+ ```
17
+ ── PR description ({repoName} #{prNumber}, replace) ─────────
18
+ {body}
19
+ ── Jira comment ({jiraId}) ──────────────────────────────────
20
+ {body}
21
+ ── Confluence page ("{title}" under {parentUrl}) ────────────
22
+ {body}
23
+ ```
24
+
25
+ Secondary artifacts ride along as one-line intents at the bottom of the preview: Wiki page list, wiki → Jira triad comment (when Wiki is selected and the triad is active per `$HOME/.claude/multi-agent-refs/issue-jira-triad.md`), Board status move (`{from} → {to}`), GitHub issue comment + Progress-flag sync (Phase 5 Step 1.5 - same gate, see `$HOME/.claude/multi-agent-refs/phases/phase-5-report.md`), test-run candidates to untick (`channels/test-run.md`), the Jira body's `--measure` (`render-jira-evidence.mjs`).
@@ -0,0 +1,98 @@
1
+ # Channels - Input forms, flags, summary format, error handling, cross-CLI parity, examples
2
+
3
+ > Loaded by `channels/SKILL.md`. Read the flag table and the error-handling table before Step 1 and whenever a target, a token or a precondition fails; the parity notes and examples are for orientation.
4
+
5
+ ## Input forms
6
+
7
+ ```
8
+ /multi-agent:channels # cwd + current branch
9
+ /multi-agent:channels PROJ-12345 # Jira ID
10
+ /multi-agent:channels #42 # GitHub issue/PR number
11
+ /multi-agent:channels https://github.com/<org>/<repo>/pull/N
12
+ /multi-agent:channels https://<bitbucket-host>/projects/.../pull-requests/N
13
+ /multi-agent:channels https://<jira-host>/browse/PROJ-12345
14
+
15
+ /multi-agent:channels PROJ-12345 --channels jira,confluence # non-interactive channel
16
+ /multi-agent:channels PROJ-12345 --content auto-diff # non-interactive content
17
+ /multi-agent:channels PROJ-12345 --message "root: ..." # manual source
18
+ /multi-agent:channels PROJ-12345 --message-file notes.md # manual, long
19
+ /multi-agent:channels PROJ-12345 --dry-run # preview, no POST
20
+ ```
21
+
22
+ ## Flags
23
+
24
+ | Flag | Effect |
25
+ |------|--------|
26
+ | `--channels pr,jira,confluence,wiki` | Non-interactive channel selection (comma list). Skips channel menu. |
27
+ | `--content normal,technical,test,auto-diff,note,test-run` | Non-interactive content selection. Skips content menu. |
28
+ | `--flow-run <runId>[,<runId>...]` | Runs for the test run block (`channels/test-run.md`). |
29
+ | `--message "..."` | Manual mode - use user text as source, no diff read. Forces `content=note`. |
30
+ | `--message-file path` | Manual mode, long message from file. |
31
+ | `--append` | PR description: append instead of replace. |
32
+ | `--dry-run` | Generate, print to stdout, do not POST. |
33
+ | `--lang=tr\|en\|both` | Output language override (default: `en`; `promptLanguage` is locked to `en`). |
34
+ | `--ready` | If PR draft, promote to READY after channels dispatch. |
35
+
36
+ ## Step 8 summary format
37
+
38
+ ```
39
+ channels dispatch complete
40
+ PR description ✓ version 3→4, reviewers 23 preserved
41
+ Jira comment ✓ posted → <jira-url>?focusedCommentId=2484780
42
+ Confluence - skipped: user declined
43
+ Wiki ✓ 3 files via github-wiki adapter
44
+ Board ✓ In Develop → In Review (project #4)
45
+ ```
46
+
47
+ ## Error handling
48
+
49
+ | Condition | Behavior |
50
+ |-----------|----------|
51
+ | No target resolvable | Error, exit 1 |
52
+ | PR not found + Jira selected | Proceed Jira-only, log reason |
53
+ | PR merged/closed | Warn, confirm continue |
54
+ | Author bot / dependabot | Skip entire run |
55
+ | Diff > 2 MB | Ask: skip / top-20-files / cancel |
56
+ | No pipeline log + "Normal analiz" selected | Greyed out at menu; if forced via `--content`, error |
57
+ | Bitbucket PR version mismatch | Refetch once, retry; fail on second mismatch |
58
+ | Jira token missing/expired | Token Save Flow inline; skip on user-skip |
59
+ | Wiki preconditions not met | Case B menu per `$HOME/.claude/multi-agent-refs/wiki-capture.md` |
60
+
61
+ ## Cross-CLI parity
62
+
63
+ | CLI | Command name | Invocation |
64
+ |---|---|---|
65
+ | Claude Code | `/multi-agent:channels` | Slash namespace (colon) |
66
+ | Copilot CLI | `multi-agent-channels` | Top-level command (dash-separated, no namespace support) |
67
+
68
+ Both installations read the same source file (`$HOME/.claude/commands/multi-agent/channels/SKILL.md`) - `install.js --all` copies to:
69
+ - `~/.claude/commands/multi-agent/channels/SKILL.md`
70
+ - `~/.copilot/commands/multi-agent-channels.md`
71
+
72
+ Prompts MUST be byte-identical. `smoke-cross-cli-behavior.sh` asserts this - spacing, brackets, numbering, log-line format.
73
+
74
+ Progress emission on Copilot (no native TaskCreate UI): `bash ~/.copilot/scripts/phase-tracker.sh sub 5 <N> "<step>" <status>` - same shape as Phase 5.
75
+
76
+ ## Examples
77
+
78
+ ```
79
+ /multi-agent:channels
80
+ → resolves from cwd+branch → PROJ-12345 + PR #3433
81
+ → menu: PR ✓ Jira ✓ / Normal analiz ✓ Test senaryoları ✓
82
+ → PR description replaced (v3→4, 23 reviewers preserved)
83
+ → Jira comment posted (id 2484780)
84
+ → Confluence - Wiki -
85
+
86
+ /multi-agent:channels PROJ-12345 --channels confluence --content note --message-file notes.md
87
+ → non-interactive: only Confluence, content from notes.md
88
+ → asks: parent page URL (or recent [1] / [2])
89
+ → page created → https://<host>/pages/12345
90
+
91
+ /multi-agent:channels https://<bitbucket>/.../pull-requests/42 --dry-run
92
+ → resolves → PROJ-67890
93
+ → prints Jira + PR body preview to stdout, no POST
94
+
95
+ /multi-agent:channels --message "root: order id empty, UI silent. fix: length>0 guard in HomeVC.m"
96
+ → auto-diff from cwd+branch PR + user note combined
97
+ → posts to default channels (PR + Jira)
98
+ ```
@@ -32,7 +32,7 @@ Trigger 2 is the key addition over the plain build-retry cap: a build can "fail
32
32
  | Trigger | Evaluated by | Status |
33
33
  |---|---|---|
34
34
  | 2, finding half | `review-delta.mjs` exit 3 at Phase 3 Step 3.8: a blocking/important finding whose `fingerprint` (finding-fingerprint.mjs) stays in the accepted set for `identicalFindingCycles` consecutive rounds | **code** (v16.20.0) |
35
- | 3 | Phase 3 re-entry item 6: the `retryCount === 3` hard-kill records the trip | **code** (v16.20.0) |
35
+ | 3 | Phase 3 re-entry item 6: at `retryCount === 3`, `rework-rulings.mjs` rules on every open finding; the hard-kill, and the trip, happen only when a ruling is `escalate`. All parked: no trip, the run continues to Phase 4 with them as follow-ups | **code** (v16.20.0, rulings 20.34.0) |
36
36
  | 2, build-error half | needs a build-log signature normaliser | documented behaviour, no script yet |
37
37
  | 1 | needs checkpoint-to-checkpoint artifact diffing | documented behaviour, no script yet |
38
38
  | 4 | belongs to `cost-budget-check.mjs` | documented behaviour, no script yet |
@@ -4,6 +4,7 @@
4
4
  - [Phase 1 Step 2.6 - query before dispatching Explore](#phase-1-step-26---query-before-dispatching-explore)
5
5
  - [Impact of a change, not of a name](#impact-of-a-change-not-of-a-name)
6
6
  - [Who implements this](#who-implements-this)
7
+ - [How two symbols connect](#how-two-symbols-connect)
7
8
  - [Confirming with a language server](#confirming-with-a-language-server)
8
9
  - [Phase 5 Step 3 - refresh after the branch changed code](#phase-5-step-3---refresh-after-the-branch-changed-code)
9
10
  - [What the report answers that a file-level view cannot](#what-the-report-answers-that-a-file-level-view-cannot)
@@ -93,6 +94,17 @@ them with every other dependency. Production code never gains an edge to a
93
94
  type a test file declares, so a fake named after a framework type cannot turn
94
95
  into a hub.
95
96
 
97
+ ### How two symbols connect
98
+
99
+ `graph-query.mjs --path <from> <to>` answers with the shortest chain of edges
100
+ between two symbols or files, given by id or by name. An ambiguous name is
101
+ refused with its candidate ids and an unknown one gets its nearest names (exit
102
+ 3), so the answer is never a guess. A module node (an import the repository
103
+ does not declare) can end a path but is never walked through, since it joins
104
+ every file that imports it. On Android, a capitalised `fun` (in practice a
105
+ Compose UI function) is a symbol of kind `composable`, so screens and
106
+ components take part like types.
107
+
96
108
  ### Confirming with a language server
97
109
 
98
110
  The graph joins by name, so a direct dependent can be a file that only uses
@@ -134,6 +146,21 @@ node $HOME/.claude/scripts/validate-code-graph.mjs "$GRAPH_PATH"
134
146
  node $HOME/.claude/scripts/graph-report.mjs --graph "$GRAPH_PATH"
135
147
  ```
136
148
 
149
+ The report adds three whole-graph views (`_graph-analysis.mjs`): import cycles
150
+ (files that import each other, directly or around a loop), clusters of files
151
+ more connected to each other than to the rest (label propagation over imports
152
+ and references, with cohesion = the share of a cluster's edges that stay
153
+ inside), and a short list of questions worth asking before a change, drawn
154
+ from the hubs, the cycles and the strongest links between clusters. All three
155
+ are deterministic, so a changed report means changed code. `graph-report.mjs
156
+ --diff <older graph>` lists what changed between two builds.
157
+
158
+ A run across several repositories (dev-context extras, submodules) joins their
159
+ graphs with `graph-links.mjs build --projects <a,b,...>`: an import of a name
160
+ one repository does not declare is matched against what the others export,
161
+ and `graph-links.mjs affected --project <b>` lists the files elsewhere that
162
+ import b. The graphs are not merged (ADR-0018).
163
+
137
164
  The rebuild costs no API tokens, so it runs every task rather than on a staleness
138
165
  heuristic. A non-zero validator exit keeps the previous graph and logs
139
166
  `knowledge.graph_invalid`; it never fails the run - a stale graph is a degraded
@@ -1,5 +1,13 @@
1
1
  # Office routes: threat model
2
2
 
3
+ <!-- toc -->
4
+ - [Assets](#assets)
5
+ - [Attackers](#attackers)
6
+ - [Threats and controls](#threats-and-controls)
7
+ - [Residual risks](#residual-risks)
8
+ - [Later, with a remote carrier](#later-with-a-remote-carrier)
9
+ <!-- /toc -->
10
+
3
11
  The office routes of `scripts/contract-server.mjs` let a client watch the
4
12
  agents on this machine, read what they said, answer and steer runs, and change
5
13
  what runs here (autopilot, stacks, routines). A browser panel may be served at
@@ -20,6 +28,8 @@ not mitigated here.
20
28
  | Run state | `agent-state.json`, `tracker-state.json` | an answer or steer written here changes what a run does |
21
29
  | Machine configuration | autopilot `config.json` and launchd jobs, a repo's `.claude/settings.json`, routine commands | widening any of them changes what runs unattended |
22
30
  | PR verdicts | posted to GitHub or Bitbucket with the user's own credential | an approval in the user's name |
31
+ | Learning stores | `~/.claude/memory/multi-agent/<repo>/` and `~/.claude/knowledge/<repo>/` | statements a run wrote down, which can quote code or a pasted secret |
32
+ | Run logs | `~/.claude/logs/multi-agent/<project>/<task>/` | deleting the wrong folder loses a live run's state |
23
33
 
24
34
  ## Attackers
25
35
 
@@ -36,20 +46,28 @@ not mitigated here.
36
46
  |---|---|---|---|
37
47
  | T1 | DNS rebinding: a page on an attacker host resolves to 127.0.0.1 and calls the API under its own name | Every request, `/office` and the event stream included, must carry `Host: 127.0.0.1:<port>` or `localhost:<port>`; anything else is 403 before routing | `smoke-office-security.sh` T1, `test/office-routes.test.mjs` "403 under a rebound Host" |
38
48
  | T2 | CSRF: a page posts to a write route | Writes (POST, DELETE) accept no `Origin` but the server's own, even an origin allowed with `--allow-origin`; a foreign preflight is 403 and no CORS header is ever sent to a foreign page; writes that widen what runs here take a second request with a single-use, route-bound confirm token | `smoke-office-security.sh` T2, `office-routes.test.mjs` "a write from a foreign page is refused" |
39
- | T3 | Token theft or replay | The token is read from the `Authorization` header only (never a cookie or a query parameter), compared in constant time over digests, never written into a response, an error or a log line. It never appears in a URL: `office-on` opens the page with a one-time code in the fragment (10 seconds, one use, minted by the bearer holder at `POST /office/code` with no Origin), and the page trades it at `POST /office/exchange`, which accepts only the server's own Origin; a second use, an expired or an unknown code is 401. The code never reaches a command line, which every local user can read (`ps`): the browser is opened on a 0600 launcher page in the 0700 state directory that carries the code and forwards with `location.replace`, under a CSP that allows only its own script by hash; the launcher is removed after 15 seconds. An Origin header can be forged by a non-browser client, so the code's secrecy and its 10 second, single-use life are what protect the exchange. The page keeps the token in memory and strips the fragment. The server exits after 8 hours however busy it is, so a token cannot outlive a working day | `office-control.test.mjs` "the opener's argv carries no code", "the launcher page has a strict CSP", "the launcher is deleted"; `office-routes.test.mjs` "a panel code trades for the token once", "a code is refused from a foreign page", "an expired code is 401", "exits at its absolute lifetime"; `smoke-office-page.sh` "the code trades once" |
49
+ | T3 | Token theft or replay | The token is read from the `Authorization` header only (never a cookie or a query parameter), compared in constant time over digests, never written into a response, an error or a log line. It never appears in a URL: `office-on` opens the page with a one-time code in the fragment (10 seconds, one use, minted by the bearer holder at `POST /office/code` with no Origin), and the page trades it at `POST /office/exchange`, which accepts only the server's own Origin; a second use, an expired or an unknown code is 401. The code never reaches a command line, which every local user can read (`ps`): the browser is opened on a 0600 launcher page in the 0700 state directory that carries the code and forwards with `location.replace`, under a CSP that allows only its own script by hash; the launcher is removed after 15 seconds. An Origin header can be forged by a non-browser client, so the code's secrecy and its 10 second, single-use life are what protect the exchange. The page keeps the token in memory and strips the fragment. A server started by `contract-server.mjs` alone exits after 8 hours however busy it is; the panel's own server (`office-on`) runs until `office-off`, because a person keeps it open beside their work. Its token lives only in the server's memory, the page's memory and a 0600 file, is never on a command line or in a URL, and is replaced each time `office-on` starts a server | `office-control.test.mjs` "the opener's argv carries no code", "the launcher page has a strict CSP", "the launcher is deleted"; `office-routes.test.mjs` "a panel code trades for the token once", "a code is refused from a foreign page", "an expired code is 401", "exits at its absolute lifetime"; `smoke-office-page.sh` "the code trades once" |
40
50
  | T4 | Secret exposure through a response | Transcripts are read through `_transcript-tail.mjs`, which never parses api-request records and drops thinking blocks; every string passes the credential redactor and the scrubber; the finished office and chat documents are checked by the outbound gate; seat labels come from allow-listed fields (tool name, file basename, sidecar description), never a command line; credentials are read only by the service scripts and never returned | `smoke-office-privacy.sh`, `smoke-office-security.sh` T4, `test/office-activity.test.mjs` "carries no credential" |
41
51
  | T5 | Path traversal into arbitrary files | Session, agent, run, routine, tool and evidence ids are matched against safe character sets after decoding; transcripts are looked up by name under the projects root only; a screenshot must resolve (real path) inside the run's evidence directory; an analysis document named in run state is read only when its real path is inside the run's log directory, its artifacts or a worktree git lists under the state's projectRoot, and only when it is Markdown or HTML | `smoke-office-security.sh` T5, `test/office-work.test.mjs` "nothing outside its directory", "analysis documents are read only inside the run's roots", "a worktreePath counts only when git lists it" |
42
52
  | T6 | Oversized or malformed input | Bodies are capped at 64 KiB and validated against the route's schema; tool arguments are checked against the tool's inputSchema; launch input goes through `launch-request.mjs` validation only; chat paging is capped at 500 | `smoke-office-security.sh` T6, `office-work.test.mjs` "refuses an unknown tool, invalid arguments and an oversized body" |
43
- | T7 | Resource exhaustion | At most four event streams and 64 connections; a phone request has five seconds to send its body before any signature work; at most 256 confirm tokens, the oldest dropped first; service clients, the work list and autopilot control run as child processes, never blocking the event loop, one at a time per service (429 `busy` for a second one); one snapshot is computed per poll from bounded tail reads; the session list is cached for ten seconds; the server exits after its idle time, after 8 hours in any case, and on SIGTERM | `smoke-office-security.sh` T7; `office-routes.test.mjs` "connections past the cap are dropped", "never sends its body", "confirm tokens are capped"; `office-work.test.mjs` "a slow service read neither blocks other routes nor runs twice at once" |
53
+ | T7 | Resource exhaustion | At most four event streams and 64 connections; a phone request has five seconds to send its body before any signature work; at most 256 confirm tokens, the oldest dropped first; service clients, the work list and autopilot control run as child processes, never blocking the event loop, one at a time per service (429 `busy` for a second write or service read; a second `GET /v1/prs`, `GET /v1/autopilot` or maturity read of the same issue shares the run in flight, and at most two maturity reads for different issues run at once); one snapshot is computed per poll from bounded tail reads; the session list is cached for ten seconds; a server started on its own exits after its idle time and after 8 hours in any case, the one `office-on` starts runs until `office-off`, and both stop on SIGTERM | `smoke-office-security.sh` T7; `office-routes.test.mjs` "connections past the cap are dropped", "never sends its body", "confirm tokens are capped"; `office-work.test.mjs` "a slow service read neither blocks other routes nor runs twice at once", "maturity checks for different issues run at most two at a time" |
44
54
  | T8 | XSS from hostile transcript or issue text | The panel renders every server string with `textContent` or canvas text; markdown is shown as plain text; server images only as `data:` URLs of image types; the office sprites are same-origin PNGs from `/office/assets/`, which answers only a plain file name (no separator or escape) whose real path is a PNG directly in the installed assets directory, and 404 otherwise; the page language the server stamps comes from a two-value set (`en`, `tr`), whatever the preferences file holds; the page's CSP allows only its per-response nonce for script and style, `connect-src 'self'`, no framing, no base URI, no form action | `smoke-office-page.sh`, `office-routes.test.mjs` "stamps the page language from outputLanguage", "serves a sprite and nothing else under /office/assets/", `office-build.test.mjs`, `smoke-office-runtime.sh` |
45
55
  | T9 | Clickjacking | `frame-ancestors 'none'` on the page and on every API response | `office-routes.test.mjs` "serves the page without a token, with a nonce CSP" |
46
56
  | T10 | An action nobody can account for | Every write leaves one line (who, route, step, status; never a body or token) in `office-actions.jsonl` | `office-routes.test.mjs` "every write leaves a line" |
47
- | T11 | The panel widening the machine silently | Autopilot on, stack changes and routine removal are tagged `local-only` and `high`, take two requests, and run the same scripts as the terminal commands | `smoke-office-routes.sh`, `smoke-office-single-source.sh` |
57
+ | T11 | The panel widening the machine silently | Autopilot on, stack changes, routine removal and adding or removing a launch repository are tagged `local-only` and `high`, take two requests, and run the same scripts as the terminal commands where one exists (removing a launch repository has none). A repository is added only as a git checkout that is neither the home directory nor the root; a suggestion is named by an opaque id and resolved again on the server, so a page never sends or sees a path it did not type | `smoke-office-routes.sh`, `smoke-office-single-source.sh`, `office-repos.test.mjs`, `smoke-office-ui.sh` |
48
58
  | T12 | A toolkit tool used as a reader of local files or services (a path, a `file://` or loopback URL, a shared browser page, an index of files) | Tool calls are denied by default: only a tool that declares readOnlyHint, is not destructive, is not in the `web` or `context` family and whose inputSchema takes no location (a property named for a path, URL, file, directory, output, project, root and similar, or a uri format, at any depth) is callable; everything else is 403 `tool-not-allowed` and never reaches the server. A `confirm` argument is refused, so a tool's own preview is never skipped. The toolkit child always runs with `MULTI_AGENT_UNATTENDED=1` and `MCP_TOOLKIT_URL_POLICY=strict`, applied after the registration's own environment. An `@latest` registration is pinned to a configured version or refused. The route is `local-only`, and its audit line names the tool and carries a SHA-256 of the arguments, never the arguments | `office-work.test.mjs` "refuses every tool outside the allowlist with 403 and never runs it", "refuses a confirm argument", "runs unattended with the strict URL policy", "pinned to a known version or refused", "the audit line names the tool and hashes the arguments" |
49
59
  | T13 | A steer instruction injected into an unattended run | Steering is tagged `launch` and `high` and takes two requests: the first shows the text, the run's phase and the unread instruction it replaces, and returns a token bound to that run and exactly that text; the second carries only the token. A finished run cannot be steered | `office-routes.test.mjs` "previews first, then queues on confirm", "a steer token is single use, bound to its run and cannot carry new text" |
50
60
  | T14 | A review verdict posted from a stale or unfinished review | A verdict is previewed only for a finished review whose every finding is accepted, rejected or deferred (409 `review-incomplete` otherwise); the confirm token is bound to the decision and a digest of the findings, and a confirmation after either changed is 409 `confirm-mismatch` | `office-work.test.mjs` "a running review or one with an untriaged finding cannot be previewed", "refused when the findings changed after the preview" |
51
61
  | T15 | A service read pointed at another host | A Confluence URL is accepted only on the configured Confluence host, and with no host configured only a page id is accepted (409 `host-not-allowed`), before the client script runs; Crashlytics takes only a Firebase console URL | `office-work.test.mjs` "a Confluence URL on another host is refused before the client runs", "only a page id is accepted" |
52
62
  | T16 | Machine paths leaking through errors, or `office-off` signalling a reused pid | Error bodies name no absolute path (the launchd refusal and the unreadable settings message say what failed, not where); `office-off` sends SIGTERM only when the recorded pid's token file still names it, its command is `contract-server.mjs` and its start time matches the token file's | `office-routes.test.mjs` "refused without naming its path", "reported without the plist path"; `smoke-office-control.sh` "off never signals a pid that is not its server" |
63
+ | T17 | A reload code minted for another page, or a stale server outliving an update | `POST /office/reload` and `GET /office/version` need the bearer token and refuse any Origin other than the panel's own (a request with no Origin, a terminal client, is accepted); the reload code is the same 10 second, single-use code `office-on` uses, so a page that already holds the token gains nothing it did not have. A server whose code changed on disk is reported stale and `office-on` replaces it instead of reusing it | `office-repos.test.mjs` "/office/version reports the installed build", "/office/reload mints a one-time code"; `smoke-office-ui.sh` |
64
+ | T18 | The panel starting work nobody asked for, or work wider than asked | `POST /v1/jobs` takes only a job named in `schemas/office-jobs.json`, each parameter checked by its kind (a pull request URL or `repo#N`, a Jira key, a run id the index knows, a branch name, a path that stays inside the repository, an enumerated value; a pipeline run's work item goes through the launch request rules). Two requests: the preview names the role, the repository, the exact prompt and what the job writes outside the machine (a branch and PR for a pipeline run; review and clarify jobs report locally, since agent-guard keeps GitHub writes to the runner), and the token is bound to that payload. At the confirmation the repository must still be allowed, the unattended profile installed, the workspace trusted, agent-guard.sh registered for every tool and the autopilot runner's sandbox check passing; the session runs `claude --bg` with no permission prompts under that profile, without a shell, and claude's own output stays in the server's log, not the page. The unattended profile denies reading the server's token directory, so a session it starts cannot pick up the bearer. A team job is refused while a run is live in the same repository, from the moment its session starts. Tagged `local-only`/`high`: no phone route reaches it. Every start is recorded under `logs/multi-agent/office/jobs/` and in `office-actions.jsonl` | `office-jobs.test.mjs`, `smoke-office-ui.sh` |
65
+ | T19 | The key cabinet leaking a credential, or the panel becoming a place to enter one | `GET /v1/credentials` runs `lib/credential-inventory.sh`, whose probe reads each credential through the credential store with its output discarded, so no value reaches the server process. The route keeps the logical name, a state and a probe verdict from fixed vocabularies and a capability line cut to 160 characters; anything else in the script's output is dropped. Tagged `local-only`: not even the state goes to a phone. There is no write route: renewing a credential stays in the terminal (`/multi-agent:setup`), so a secret never passes through the browser | `office-routes.test.mjs` "GET /v1/credentials names each credential and its verdict, never a value" |
66
+ | T20 | A learned statement carrying a secret or a machine path to the page | `GET /v1/memory` returns counts, the code graph's age and size, and the five newest learning statements per repository, each passed through the transcript redactor (provider credential rules, paths and addresses) and cut to 120 characters. Repository names are matched against a safe character set before any file is opened. Read only; `local-only` | `office-routes.test.mjs` "GET /v1/memory reports every shelf, redacted" |
67
+ | T21 | Machine details through the resource panel | `GET /v1/system` returns numbers only (cores, load, memory, free disk, process counts, log folder size) and two fixed warnings; no path, process argument, user or host name. The schema allows nothing else. The measurement runs as child processes off the event loop, concurrent requests share one measurement, and the log folder size is reused for a minute. Readable from a phone, since it names nothing | `office-routes.test.mjs` "GET /v1/system returns numbers only: no path, user or host name" |
68
+ | T22 | Old run logs deleted wider than asked, or a live run's log removed | `POST /v1/prune-logs` takes only an age from a fixed set (7, 30 or 90 days) and runs `prune-logs.sh`, the script `/multi-agent:prune-logs` runs: the dry run is the preview; the confirmed step lists the folders again, refuses with `changed` when they differ, and then prunes only the previewed list through `--only-from`, under a token bound to that age and list. The script never touches the audit trail, the metrics corpus or the task counters, refuses a log root that is `/`, the home folder or a git checkout, and spares a folder written in the last minutes and a run that can still be resumed. Tagged `local-only`/`high` | `office-routes.test.mjs` "POST /v1/prune-logs previews, then prunes only on the token" |
69
+ | T23 | Pull request listing exposing a token, or the native task mirror writing into another run | Each GitHub account gh is logged in to is asked with `gh auth token` for that host and login; the token travels in `GH_TOKEN` (and `GH_ENTERPRISE_TOKEN` for an Enterprise Server host) for the child process only, never on a command line, in a response or a log. Bitbucket Server is read over https only (a plain-http host is refused unless it is the loopback), redirects are not followed, and the token, read from the credential store with a deadline, travels in the `Authorization` header only; an unreachable host is a reason on that source, not a failure of the list. A redacted answer drops URLs and logins, numbering the accounts instead. The task-mirror hook (PostToolUse on TaskCreate and TaskUpdate) writes only into the run whose recorded session is the calling session, skips phase tiles, never prints, and exits 0 on any input it cannot read | `work-items-prs.test.mjs`, `task-mirror.test.mjs` |
70
+ | T24 | The panel changing a preference wider than the terminal would, or posting addresses to a phone | `POST /v1/settings` takes one of three settings and one of its two values, previews the change and runs, under a token bound to that setting and value, what `/multi-agent:language`, `/multi-agent:model` or `/multi-agent:route-on`/`route-off` runs; route on passes the saved strategy back so only the switch changes, nothing the panel sends can add rules or a free provider, and two changes never run at once. Tagged `local-only`/`high`. Report links: `channelResults` URLs must be https, travel only in the run record's `channelUrls` (local, dropped from a redacted answer) and are shown as text, never followed | `office-routes.test.mjs` "POST /v1/settings changes a switch only on the token, through its script", `runs-index.test.mjs` |
53
71
 
54
72
  ## Residual risks
55
73
 
@@ -64,9 +82,9 @@ not mitigated here.
64
82
  nothing local does.
65
83
  - **A page on the server's own origin** is trusted. Only the page this server
66
84
  serves can be there, and its CSP keeps other code out.
67
- - **Starting a session**: the server returns the launch plan and never starts
68
- a host process, so a panel without a local process to run the plan cannot
69
- start work by itself. That is a deliberate boundary of the contract.
85
+ - **Starting a session**: `POST /v1/launch` returns the launch plan for a
86
+ client to start. `POST /v1/jobs` starts a session itself, because the office
87
+ is that client; it is bounded by T18 and is never reachable from a phone.
70
88
  - **Host permission prompts** are not forwarded: the host records none where
71
89
  another process can read them (`GET /v1/permissions` says `available:
72
90
  false`).
@@ -53,8 +53,11 @@ CRITIC_RUNG=$(bash "$HOME/.claude/lib/model-dispatch.sh" subagent --persona plan
53
53
  ```
54
54
 
55
55
  It reads the plan, the analysis document(s), the constitution and the worktree,
56
- and writes objections under four lenses: `scope`, `feasibility`, `security`,
57
- `alternative` (a cheaper plan with the same result). Each objection has an id
56
+ and writes objections under six lenses: `scope`, `feasibility`, `security`,
57
+ `alternative` (a cheaper plan with the same result), `unstated-input` (an
58
+ input the spec implies that no task's tests exercise) and `underspecified` (a
59
+ decision a task leaves to its implementer that the analysis or the user should
60
+ make). Each objection has an id
58
61
  (`OBJ-NN`), a lens, a claim, at least one evidence anchor and, when the plan
59
62
  breaks one, a constitution rule id. The orchestrator writes the output to
60
63
  `$WORKTREE/.pipeline/plan-critique.json` (`schemas/plan-critique.schema.json`)
@@ -39,7 +39,8 @@ Phase 2 Step 3.7 wrote `$WORKTREE/.pipeline/scope-check.json` (contract: `featur
39
39
  ```bash
40
40
  SCOPE_JSON="$WORKTREE/.pipeline/scope-check.json"
41
41
  SCOPE_GATE=$(git -C "$WORKTREE" diff --name-only "origin/$BASE_BRANCH"...HEAD \
42
- | node $HOME/.claude/scripts/scope-check-gate.mjs --check "$SCOPE_JSON" --diff-files - --advisory 2>/dev/null)
42
+ | node $HOME/.claude/scripts/scope-check-gate.mjs --check "$SCOPE_JSON" --diff-files - --advisory \
43
+ --repo "$WORKTREE" --patch-range "origin/$BASE_BRANCH...HEAD" 2>/dev/null)
43
44
  ```
44
45
 
45
46
  ```
@@ -48,6 +49,8 @@ Files and the reason the dev gave for touching each:
48
49
  {jq -r '.files[] | "- \(.path): \(.reason)"' "$SCOPE_JSON"}
49
50
  Files in the diff with no stated reason (flag as scope drift if the change is not obviously required):
50
51
  {jq -r '.unjustified[]' <<< "$SCOPE_GATE"}
52
+ Layout-only hunks (whitespace the task did not need; a suggestion at most):
53
+ {jq -r '.formatOnly[]? | "- \(.file) \(.hunk)"' <<< "$SCOPE_GATE"}
51
54
  Deliberately not done (do not raise these as findings; they are known):
52
55
  {jq -r '.notDone[] | "- \(.what) (\(.why))"' "$SCOPE_JSON"}
53
56
  </scope-self-check>
@@ -29,7 +29,16 @@ git -C "$WORKTREE" diff --name-only "origin/$BASE_BRANCH"...HEAD \
29
29
  | node $HOME/.claude/scripts/scope-check-gate.mjs --check "$WORKTREE/.pipeline/scope-check.json" --diff-files -
30
30
  ```
31
31
 
32
- Output `{ok, justified, unjustified[], unlisted[], notDone}`. Exit 1 lists `unjustified[]` (in the diff, no reason or an empty one); `unlisted[]` (in the record, not in the diff) is reported and never fatal. Complete the record once and re-run. A second exit 1 does not block: log `dev.scope_check=incomplete unjustified=<n>` and hand off; Phase 4 receives the gate output inside `<scope-self-check>`, so reviewers see which files arrived without a stated reason. `--advisory` (used by Phase 4) always exits 0. Progress line: ` → checking scope self-check ({n} files, {m} not done)`.
32
+ Output `{ok, justified, unjustified[], unlisted[], notDone}`, plus `formatOnly[]` with `--patch` / `--patch-range` (`patchError` when the diff could not be read). With `--patch <unified diff>` (or `--repo <dir> --patch-range <a>...<b>`, which reads that diff itself), `formatOnly[]` lists hunks whose removed and added lines differ only in whitespace: a reformat of lines the task did not need to touch. It is reported, never fatal, and reviewers see it in `<scope-self-check>`. Exit 1 lists `unjustified[]` (in the diff, no reason or an empty one); `unlisted[]` (in the record, not in the diff) is reported and never fatal. Complete the record once and re-run. A second exit 1 does not block: log `dev.scope_check=incomplete unjustified=<n>` and hand off; Phase 4 receives the gate output inside `<scope-self-check>`, so reviewers see which files arrived without a stated reason. `--advisory` (used by Phase 4) always exits 0. Progress line: ` → checking scope self-check ({n} files, {m} not done)`.
33
+
34
+ ## Known limits in the code
35
+
36
+ A limit accepted on purpose is marked at the line, in the file's comment
37
+ style: `shortcut: <the limit>, <when to replace it>`. `notDone[]` says what the
38
+ change left out; the marker says what the code that shipped does not handle
39
+ yet. `debt-ledger.mjs` collects the markers a diff adds into the repo's debt
40
+ list, reviewers do not re-raise a marked limit unless it breaks a requirement
41
+ or a security rule, and the PR description lists them.
33
42
 
34
43
  ## Consumers
35
44
 
@@ -566,7 +566,7 @@ protected paths (`~/.claude/**`, `~/.copilot/**`, `~/.codex/**`,
566
566
  `~/.config/gh`, `~/.config/git`, `~/.gitconfig`, `~/.npmrc`, `~/.netrc`,
567
567
  `~/.git-credentials`, `.git/hooks/**`, `.git/config`, `**/.github/workflows/**`),
568
568
  and the toolkit env `MCP_TOOLKIT_URL_POLICY=strict` and
569
- `MCP_TOOLKIT_INDEX_DENY=~/.ssh,~/.aws,~/.gnupg,~/Library/Keychains`.
569
+ `MCP_TOOLKIT_INDEX_DENY=~/.ssh,~/.aws,~/.gnupg,~/Library/Keychains,~/.claude/logs/multi-agent/contract-server,~/.claude/logs/multi-agent/office`.
570
570
 
571
571
  The runner hands the file to each child with `claude --settings`, so only the
572
572
  unattended session loads it. `~/.claude/settings.json` is the attended posture
@@ -86,9 +86,9 @@ Status & Resume:
86
86
  /multi-agent:autopilot-status Running, queued, awaiting an answer, today's PRs
87
87
  /multi-agent:autopilot-off Stop picking work up (running work finishes; --now stops it)
88
88
  /multi-agent:serve Contract server for a client: 127.0.0.1, token, stops when idle
89
- /multi-agent:office-on Open the office panel in your browser: agents at work, chats, ledger, runs
89
+ /multi-agent:office-on Open the office in your browser: live runs and agents, PRs, keys, memory, system, jobs; its orientation room explains the rest
90
90
  /multi-agent:office-status Is the panel running, its URL (never the token), its last actions
91
- /multi-agent:office-off Close the panel now (it also stops by itself when idle)
91
+ /multi-agent:office-off Stop the panel's server (it runs until you do)
92
92
 
93
93
  Post-Hoc & Side-Channel:
94
94
 
@@ -85,9 +85,9 @@ Status & Resume:
85
85
  /multi-agent:autopilot-status Koşan, sıradaki, cevap bekleyen, bugünkü PR'lar
86
86
  /multi-agent:autopilot-off Yeni madde alma (koşan iş biter; --now onu da durdurur)
87
87
  /multi-agent:serve İstemci için sözleşme sunucusu: 127.0.0.1, token, boşta durur
88
- /multi-agent:office-on Ofis panelini tarayıcıda aç: çalışan ajanlar, sohbetler, defter, koşular
88
+ /multi-agent:office-on Ofisi tarayıcıda aç: canlı koşular ve ajanlar, PR'lar, anahtarlar, hafıza, sistem, işler; gerisini oryantasyon odası anlatır
89
89
  /multi-agent:office-status Panel çalışıyor mu, adresi (token asla), son işlemleri
90
- /multi-agent:office-off Paneli hemen kapat (boşta kalınca kendisi de kapanır)
90
+ /multi-agent:office-off Panelin sunucusunu durdur (sen durdurana kadar çalışır)
91
91
 
92
92
  Post-Hoc & Side-Channel:
93
93
 
@@ -81,6 +81,8 @@ Knowledge files grow over time. Maintenance rules:
81
81
  and a rebuild is seconds, so age is the wrong question to ask of it.
82
82
  - `product-context.jsonl` is written by runs of OTHER repositories of the same product (`features/product-context.md`) and keyed by the remote sha it was read at; `product-context.mjs recall` returns a row only while that sha is current, so it needs no age rule
83
83
  - Stale entries are added to the prompt with a "STALE - verify before relying" tag
84
+ - A ledger entry earns or loses weight from the outcomes recorded against it: Phase 5 runs `learnings-ledger.mjs feedback --id <L:id> --outcome useful|dead_end|corrected` for each injected entry the run relied on or found wrong. Corrected, or a dead end at least twice and more often than useful, drops it from `brief` and `profile`; useful at least twice adds a strength that fades with age (`_memory-policy.mjs`). A new entry that repeats an existing one in other words (TF-IDF cosine 0.9 or more, same numbers, same negations) is not added
85
+ - `prefs.global.memory.excludeProjects` lists repo slugs whose ledger, triage corpus and debt list are never written
84
86
  - `prune-logs` does not touch knowledge - only deletes logs and state
85
87
  - `purge` does not touch knowledge either. Today the only removal path is `/multi-agent:uninstall --all-data`, which drops every project's knowledge at once; there is no per-project clear yet
86
88
 
@@ -113,6 +115,13 @@ node "$HOME/.claude/scripts/triage-memory.mjs" timeline --anchor T:<id> --bef
113
115
  node "$HOME/.claude/scripts/learnings-ledger.mjs" timeline --anchor L:<id> --before 3 --after 3
114
116
  ```
115
117
 
118
+ A store too large to rank in one pass (thousands of triage rows) goes through
119
+ the toolkit's full-text index instead of a whole-file read: `context_index` on
120
+ the store's path (`learnings-ledger.mjs path`, `triage-memory.mjs path`), then
121
+ `context_search` for ranked snippets and `context_get` for one passage. The
122
+ index is owner-only and re-indexing an unchanged file is a no-op, so this is
123
+ layer 1 and 3 again over the same rows, not a second copy to keep in step.
124
+
116
125
  ## Skill Injection Strategy
117
126
 
118
127
  When calling sub-agents, inject task-specific context from previous phases: