@mmerterden/multi-agent-pipeline 12.1.1 → 12.3.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.
@@ -22,6 +22,7 @@ One fetcher per type. Each fetcher emits a normalized JSON view that the analysi
22
22
  |---|---|---|
23
23
  | `crashlytics` | `~/.claude/lib/fetch-crashlytics.sh <url>` (already invoked in Phase 0 Step 1b.1 for the legacy `state.crashContext` field; Phase 1 reads that field directly) | `state.crashContext` |
24
24
  | `fortify` | `~/.claude/lib/fetch-fortify.sh <url>` (already invoked in Phase 0 Step 1b.2 for the legacy `state.fortifyFinding` field; Phase 1 reads that field, Phase 4 reads the full payload for the security gate) | `state.fortifyFinding` |
25
+ | `graylog` | `~/.claude/lib/fetch-graylog.sh --trx <id>` / `--conv <id>` (already invoked in Phase 0 Step 1b.3 for the `state.graylogContext` field; Phase 1 reads that field directly). Diagnostic logs, **advisory only** - no Phase 4 gate, no generated task | `state.graylogContext` |
25
26
  | `swagger` | `~/.claude/lib/fetch-swagger.sh <url>` → endpoints[], request/response examples | `state.fetchedContext.swagger[]` |
26
27
  | `confluence` | `~/.claude/lib/fetch-confluence.sh <url>` → page body, code blocks, extracted API contracts | `state.fetchedContext.confluence[]` |
27
28
  | `figma` | no standalone fetcher - resolve via the Figma 3-tier chain (Tier 1 MCP tools, Tier 2 `~/.claude/lib/figma-screenshot.sh` + REST) when the task is a component; otherwise advisory only | `state.fetchedContext.figma[]` |
@@ -36,6 +37,8 @@ One fetcher per type. Each fetcher emits a normalized JSON view that the analysi
36
37
 
37
38
  Failures are never fatal at Phase 1 - the agent still runs analysis, just without the referenced source.
38
39
 
40
+ **Graylog specifics.** `fetch-graylog.sh` degrades to empty on any network/VPN failure: it emits a normalized empty object and exits `0`, so an unreachable host reads as `{status:"skipped", reason:"vpn-unreachable"}` and never blocks. Only a genuine auth rejection on a reachable host exits `3` (marked `failed`, still non-fatal here). `state.graylogContext` is prepended to the analysis prompt inside the **Referenced External Sources** section as diagnostic context that is **advisory only** - the agent may use it to orient on a reported error, but code remains ground truth, and there is no Phase 4 review gate for logs.
41
+
39
42
  ## Prompt injection shape
40
43
 
41
44
  Added immediately after the knowledge-injection block, before codebase exploration:
@@ -17,7 +17,7 @@ When a token resolves but the service rejects it (401 / 403), do not silently sk
17
17
 
18
18
  - **Regenerate** - the existing token is dead; open Save Flow Step B to replace `<KEY_NAME>` in place (same key name, clipboard path), then retry the operation.
19
19
  - **Use a different token** - map a new or existing Keychain entry for this service (Save Flow Step B + identity binding), then retry.
20
- - **Skip and continue** - drop this token's contribution. Non-critical services (Confluence enrichment, Fortify scan, Firebase events, Figma fallback tiers) continue with reduced evidence and a one-line warning. A token that is structurally required for the input (e.g. the Jira PAT for a Jira-ID input) halts instead, since the run cannot proceed without it.
20
+ - **Skip and continue** - drop this token's contribution. Non-critical services (Confluence enrichment, Fortify scan, Graylog log fetch, Firebase events, Figma fallback tiers) continue with reduced evidence and a one-line warning. A token that is structurally required for the input (e.g. the Jira PAT for a Jira-ID input) halts instead, since the run cannot proceed without it.
21
21
 
22
22
  The question asks a *choice*; the replacement value still flows through the clipboard, never chat. `smoke-no-token-prompt.sh` greps for value-prompts (`enter token`, `paste token`, `API key:`) - decision labels like `Regenerate` / `Skip and continue` do not trip it.
23
23
 
@@ -64,6 +64,7 @@ The shell driver auto-delegates to `~/.claude/scripts/keychain.py` on macOS / Li
64
64
  | `figma` | Figma | `${USER}_Figma_Access_Token` | PAT |
65
65
  | `figma_mcp` | Figma MCP | `${USER}_Figma_Mcp_Access_Token` | OAuth |
66
66
  | `fortify` | Fortify | `${USER}_Fortify_Access_Token` | API Token |
67
+ | `graylog` | Graylog | `${USER}_Graylog_Access_Token` | API Token |
67
68
  | `firebase` | Firebase | `${USER}_Firebase_Access_Json` | JSON (base64) |
68
69
  | `jenkins` | Jenkins CI | `${USER}_Jenkins_Access_Token` | API Token |
69
70
  | - | Git Identity | Stored in preferences JSON | Not Keychain |
@@ -82,6 +83,7 @@ The shell driver auto-delegates to `~/.claude/scripts/keychain.py` on macOS / Li
82
83
  "figma": "${USER}_Figma_Access_Token",
83
84
  "figma_mcp": "${USER}_Figma_Mcp_Access_Token",
84
85
  "fortify": "${USER}_Fortify_Access_Token",
86
+ "graylog": "${USER}_Graylog_Access_Token",
85
87
  "firebase": "${USER}_Firebase_Access_Json",
86
88
  "jenkins": "${USER}_Jenkins_Access_Token"
87
89
  }
@@ -53,7 +53,7 @@ Then STOP Phase 0 - do not proceed with a broken token state. This prevents th
53
53
 
54
54
  **Key design**: Every field is an **array of all historical values** (most recent first) → "recently used" list.
55
55
 
56
- **Token resolution rule**: ALWAYS check `prefs.global.keychainMapping.<service_id>` first for the key name - this holds the user's actual Keychain key name (which may differ from the standard convention). Fall back to standard key name ONLY when setup has never run (mapping absent). The standard fallback exists as a safety net, not as the primary path. Service IDs: `jira`, `bitbucket_token`, `bitbucket_user`, `github`, `confluence`, `figma`, `figma_mcp`, `fortify`, `firebase`, `jenkins`.
56
+ **Token resolution rule**: ALWAYS check `prefs.global.keychainMapping.<service_id>` first for the key name - this holds the user's actual Keychain key name (which may differ from the standard convention). Fall back to standard key name ONLY when setup has never run (mapping absent). The standard fallback exists as a safety net, not as the primary path. Service IDs: `jira`, `bitbucket_token`, `bitbucket_user`, `github`, `confluence`, `figma`, `figma_mcp`, `fortify`, `firebase`, `jenkins`, `graylog`.
57
57
 
58
58
  **Jira project key resolution** (first match wins):
59
59
 
@@ -196,7 +196,7 @@ Sequential prompts (standard UX pattern with Recent suggestion): Project Key →
196
196
 
197
197
  **Token pre-check** (after parsing): Jira input → resolve key via `prefs.global.keychainMapping.jira`, verify token with a lightweight API call (e.g. `GET /myself`). GitHub input → verify `gh auth status`. On failure (missing key, 401, 403) → run the **Token Save Flow** from `setup.md` inline. This is the same clipboard-based flow used during setup - token never appears in terminal. If user skips and the token is critical for the input type (e.g. Jira token for Jira input), halt Phase 0.
198
198
 
199
- **VPN connectivity check**: Test VPN-dependent services (Jira, Bitbucket, Confluence, Fortify) with `curl --connect-timeout 3`. If unreachable, warn and offer to continue. Fallback: Jira → manual input, Bitbucket → `git push` only, Confluence → skip Phase 7, Fortify → skip scan. Cache in `agent-state.json` → `"vpnServices": {"jira": true, ...}`.
199
+ **VPN connectivity check**: Test VPN-dependent services (Jira, Bitbucket, Confluence, Fortify, Graylog) with `curl --connect-timeout 3`. If unreachable, warn and offer to continue. Fallback: Jira → manual input, Bitbucket → `git push` only, Confluence → skip Phase 7, Fortify → skip scan, Graylog → skip log fetch (advisory only, never blocks). Cache in `agent-state.json` → `"vpnServices": {"jira": true, ...}`.
200
200
 
201
201
  #### Step 1b - URL Enrichment (catalogue + targeted deep fetches)
202
202
 
@@ -225,6 +225,7 @@ Output schema (one array entry per URL - full contract in `~/.claude/lib/conte
225
225
  { "type": "confluence", "url": "<url>", "metadata": { "host": "<host>", "pageId": "<id|null>", "spaceKey": "<key|null>" } },
226
226
  { "type": "crashlytics", "url": "<url>", "metadata": { "projectId": "...", "platform": "ios|android", "bundle": "...", "issueId": "...", "sessionId": "<id|null>" } },
227
227
  { "type": "fortify", "url": "<url>", "metadata": { "host": "<host>", "projectId": "<id|null>" } },
228
+ { "type": "graylog", "url": null, "metadata": { "idType": "trx|conversation", "id": "...", "label": "..." } },
228
229
  { "type": "figma", "url": "<url>", "metadata": { "kind": "design|make|board|slides", "fileKey": "...", "nodeId": "<id|null>" } },
229
230
  { "type": "generic-doc", "url": "<url>", "metadata": { "host": "<host>", "kind": "notion|google-docs|gist|github-doc" } }
230
231
  ]
@@ -258,7 +259,18 @@ URL pattern (caught by the extractor): `https://<prefs.global.hosts.fortify>/ssc
258
259
  - Phase 2 Planning injects `state.fortifyFinding` into the architectural-review prompt under a **Known Security Finding** section, and the generated task breakdown includes a task addressing the finding (naming convention: `sec(fortify-<issueId>): <issueName> at <file>:<line>`).
259
260
  - Soft-fail: 401 → re-run Token Save Flow for `fortify`; 403 → warn and skip; network timeout → respect VPN fallback (skip enrichment, mark `state.fortifyFinding = { skipped: "vpn-unreachable" }`).
260
261
 
261
- ##### Step 1b.3 - Other link types (catalogued only at Phase 0; fetched at Phase 1)
262
+ ##### Step 1b.3 - Graylog deep fetch (runs when `type == "graylog"` present in `state.contextLinks[]`)
263
+
264
+ The extractor emits `graylog` entries from free-text trx / conversation ids (labels like `trx`, `trxId`, `transaction id`, `conversationId`, `convId`, `X-conversationId`), not from a URL - so these entries carry `"url": null`. When at least one is present, pull the matching diagnostic logs.
265
+
266
+ - Host comes from `prefs.global.hosts.graylog` (`GRAYLOG_HOST_OVERRIDE` env forces it for tests/CI); no literal host lives in any source file. Missing host → the fetcher exits `6`; log a setup hint and skip.
267
+ - Token resolution: `prefs.global.keychainMapping.graylog` → `~/.claude/lib/credential-store.sh get "<key>"` → Graylog PAT (fallback key `${USER}_Graylog_Access_Token`). Auth is HTTP Basic with the token as username and the literal `token` as password; the token is fed to `curl` only via a `-K` config file (process substitution), never on argv.
268
+ - Fetch logs (VPN required; respect `vpnServices.graylog`): call `~/.claude/lib/fetch-graylog.sh --trx <id>` (or `--conv <id>` for conversation ids) once per extracted id. The fetcher emits a normalized JSON object of matching log messages.
269
+ - Store as `state.graylogContext = { idType, id, messages[], fetchedAt }` (or an array keyed per id when several were extracted).
270
+ - Phase 1 Analysis prepends `state.graylogContext` to the agent prompt under the **Referenced External Sources** section as **diagnostic context, advisory only** (never contradicts code; no task is generated from it, no Phase 4 gate).
271
+ - Soft-fail (non-blocking, mirrors Crashlytics): unreachable host / VPN down / network timeout → the fetcher emits a normalized empty object and exits `0`; mark `state.graylogContext = { skipped: "vpn-unreachable" }` and continue. Only a genuine auth rejection (4xx on a reachable host, fetcher exit `3`) is logged as `skipped` with the auth reason. A log fetch never blocks the run.
272
+
273
+ ##### Step 1b.4 - Other link types (catalogued only at Phase 0; fetched at Phase 1)
262
274
 
263
275
  For `swagger`, `confluence`, `figma`, `generic-doc` entries in `state.contextLinks[]`, no Phase 0 fetch runs. The entries pass through to Phase 1, which dispatches the matching fetcher (Block B work: `fetch-swagger.sh`, `fetch-confluence.sh`, etc.). Until those fetchers land, the entries are surfaced in Phase 1 context as "referenced but not fetched - agent should treat the URL as an authoritative external source and read it directly when relevant."
264
276
 
@@ -267,11 +279,11 @@ For `swagger`, `confluence`, `figma`, `generic-doc` entries in `state.contextLin
267
279
  **Log line shape** (progress contract):
268
280
 
269
281
  ```
270
- → context links: total=<N>, by-type={swagger:<n>, confluence:<n>, crashlytics:<n>, fortify:<n>, figma:<n>, generic-doc:<n>}
271
- → URL deep fetch: crashlytics=<hit|miss|skipped>, fortify=<hit|miss|skipped>
282
+ → context links: total=<N>, by-type={swagger:<n>, confluence:<n>, crashlytics:<n>, fortify:<n>, graylog:<n>, figma:<n>, generic-doc:<n>}
283
+ → URL deep fetch: crashlytics=<hit|miss|skipped>, fortify=<hit|miss|skipped>, graylog=<hit|miss|skipped>
272
284
  ```
273
285
 
274
- Two lines emit at the end of Step 1b. The first reports the catalogue from 1b.0 (always runs). The second reports the deep-fetch outcomes from 1b.1 / 1b.2 - `miss` = URL type not present in catalogue (normal), `skipped` = URL present but auth/VPN/mismatch skipped the fetch, `hit` = URL-derived context stored in state.
286
+ Two lines emit at the end of Step 1b. The first reports the catalogue from 1b.0 (always runs). The second reports the deep-fetch outcomes from 1b.1 / 1b.2 / 1b.3 - `miss` = type not present in catalogue (normal), `skipped` = present but auth/VPN/mismatch skipped the fetch (for graylog this includes the non-blocking VPN-unreachable degrade), `hit` = context stored in state.
275
287
 
276
288
  #### Step 2 - Project Selection
277
289
 
@@ -484,11 +496,12 @@ Single-repo mode (`projects.length === 1` or scalar-only) uses the legacy single
484
496
  "inputType": "github-issue-url|github-issue-number|jira-url|jira-id|free-text",
485
497
  "jiraId": "PROJ-XXXXX|null", "figmaUrl": "...|null",
486
498
  "contextLinks": [
487
- { "type": "swagger|confluence|crashlytics|fortify|figma|generic-doc",
488
- "url": "<full-url>", "metadata": { } }
499
+ { "type": "swagger|confluence|crashlytics|fortify|graylog|figma|generic-doc",
500
+ "url": "<full-url|null>", "metadata": { } }
489
501
  ],
490
502
  "crashContext": null,
491
503
  "fortifyFinding": null,
504
+ "graylogContext": null,
492
505
  "localMode": false, "instructionDriven": true|false, "instructionFiles": {},
493
506
  "identity": {"name": "...", "email": "...", "keychainKey": "..."},
494
507
  "currentPhase": 0, "status": "in_progress", "startedAt": "{ISO}", "shortId": "#N",
@@ -75,7 +75,7 @@ Progress lines:
75
75
 
76
76
  #### Step 1.5 - External Context Injection (`state.contextLinks[]`)
77
77
 
78
- Phase 0 Step 1b catalogued every typed external link from the task description into `state.contextLinks[]`. Phase 1 dispatches each entry to its matching fetcher (crashlytics, fortify, swagger, confluence, figma, generic-doc) and prepends results under a **Referenced External Sources** section in the analysis prompt - so the agent doesn't re-discover what the ticket already pointed at. Failures never fatal; pending refs are advisories. Full dispatch table, exit-code handling, prompt injection shape, log line shape: `$HOME/.claude/multi-agent-refs/features/external-context-injection.md`.
78
+ Phase 0 Step 1b catalogued every typed external link from the task description into `state.contextLinks[]`. Phase 1 dispatches each entry to its matching fetcher (crashlytics, fortify, graylog, swagger, confluence, figma, generic-doc) and prepends results under a **Referenced External Sources** section in the analysis prompt - so the agent doesn't re-discover what the ticket already pointed at. `state.graylogContext` is injected there too, as diagnostic context (advisory only). Failures never fatal (a non-zero fetcher exit is marked skipped and the analysis still runs, exactly as for crashlytics); pending refs are advisories. Full dispatch table, exit-code handling, prompt injection shape, log line shape: `$HOME/.claude/multi-agent-refs/features/external-context-injection.md`.
79
79
 
80
80
  **Log line shape** (progress contract):
81
81
 
@@ -11,6 +11,7 @@
11
11
  "figma": null,
12
12
  "figma_mcp": null,
13
13
  "fortify": null,
14
+ "graylog": null,
14
15
  "firebase": null,
15
16
  "jenkins": null
16
17
  },
@@ -49,6 +49,24 @@
49
49
  "default": "v3",
50
50
  "description": "Template version emitted. v3 is the platform-agnostic + Pass B render template (Locked 22)."
51
51
  },
52
+ "options": {
53
+ "type": "object",
54
+ "additionalProperties": false,
55
+ "description": "Coverage opt-ins captured at Phase 0 Step 5a. Absent = defaults (no UI tests, basic accessibility).",
56
+ "properties": {
57
+ "uiTests": {
58
+ "type": "boolean",
59
+ "default": false,
60
+ "description": "When true, Section 15.6 renders UI test scenarios (XCUITest / Compose UI test)."
61
+ },
62
+ "a11yDepth": {
63
+ "type": "string",
64
+ "enum": ["basic", "full"],
65
+ "default": "basic",
66
+ "description": "basic = Section 16.1 checklist only; full = also render the 16.2 VoiceOver/TalkBack walkthrough."
67
+ }
68
+ }
69
+ },
52
70
  "evidenceDigest": {
53
71
  "type": ["string", "null"],
54
72
  "pattern": "^sha256:[0-9a-f]{64}$",
@@ -110,6 +128,8 @@
110
128
  "url": { "type": "string", "format": "uri" },
111
129
  "fileKey": { "type": ["string", "null"] },
112
130
  "nodeId": { "type": ["string", "null"] },
131
+ "screenshotUrl": { "type": ["string", "null"], "description": "Local PNG path or signed render URL for this node." },
132
+ "tier": { "type": ["integer", "null"], "enum": [1, 2, 3, null], "description": "Figma access tier used to capture this record (mirrors state.figmaAccess.tier)." },
113
133
  "frames": {
114
134
  "type": "array",
115
135
  "items": {
@@ -134,6 +154,64 @@
134
154
  "repoPath": { "type": ["string", "null"] }
135
155
  }
136
156
  }
157
+ },
158
+ "codeConnectSnippets": {
159
+ "type": "array",
160
+ "description": "Verbatim CodeConnectSnippet blocks captured on Tier 1 (component name + modifier chain).",
161
+ "items": {
162
+ "type": "object",
163
+ "additionalProperties": false,
164
+ "properties": {
165
+ "nodeId": { "type": ["string", "null"] },
166
+ "component": { "type": "string" },
167
+ "snippet": { "type": ["string", "null"] }
168
+ }
169
+ }
170
+ },
171
+ "tokens": {
172
+ "type": "array",
173
+ "items": {
174
+ "type": "object",
175
+ "additionalProperties": false,
176
+ "properties": {
177
+ "name": { "type": "string" },
178
+ "value": { "type": ["string", "null"] }
179
+ }
180
+ }
181
+ },
182
+ "textLayers": {
183
+ "type": "array",
184
+ "description": "Visible text-layer content. Treated as placeholder copy when an annotation exists for the same node (annotation wins).",
185
+ "items": {
186
+ "type": "object",
187
+ "additionalProperties": false,
188
+ "properties": {
189
+ "nodeId": { "type": ["string", "null"] },
190
+ "name": { "type": ["string", "null"] },
191
+ "text": { "type": "string" }
192
+ }
193
+ }
194
+ },
195
+ "annotations": {
196
+ "type": "array",
197
+ "description": "Dev Mode annotations captured as authoritative copy ground truth (Phase 1 annotation ingestion). Empty unless annotations.enabled in the project figma-config.",
198
+ "items": {
199
+ "type": "object",
200
+ "additionalProperties": false,
201
+ "required": ["nodeId", "mode"],
202
+ "properties": {
203
+ "nodeId": { "type": "string" },
204
+ "name": { "type": ["string", "null"] },
205
+ "designText": { "type": ["string", "null"], "description": "The visible-layer text at this node (often a placeholder)." },
206
+ "raw": { "type": ["string", "null"], "description": "The raw annotation label/labelMarkdown before parsing." },
207
+ "parsed": {
208
+ "type": "object",
209
+ "description": "Parsed copy keyed by language code (lowercased), e.g. { tr, en }.",
210
+ "additionalProperties": { "type": "string" }
211
+ },
212
+ "mode": { "type": "string", "enum": ["prefixed", "two-line", "single", "empty"] }
213
+ }
214
+ }
137
215
  }
138
216
  }
139
217
  }
@@ -50,6 +50,55 @@
50
50
  }
51
51
  }
52
52
  },
53
+ "annotations": {
54
+ "type": "object",
55
+ "additionalProperties": false,
56
+ "description": "Figma Dev Mode annotation ingestion. When enabled, /multi-agent:analysis treats annotations as the authoritative copy for a node (the visible text layer is a placeholder). Absent or disabled = current behavior (visible text layers only).",
57
+ "properties": {
58
+ "enabled": { "type": "boolean", "default": false },
59
+ "langPrefixes": {
60
+ "type": "array",
61
+ "items": { "type": "string" },
62
+ "default": ["TR", "EN"],
63
+ "description": "Ordered language-code prefixes the annotation parser recognizes, e.g. [\"TR\",\"EN\"]. A `TR: ...\\nEN: ...` label parses by prefix; a two-line label with no prefix maps line 1 -> langPrefixes[0], line 2 -> langPrefixes[1]; a single line -> langPrefixes[0]."
64
+ },
65
+ "devModeCategory": {
66
+ "type": ["string", "null"],
67
+ "description": "Optional Dev Mode annotation category to filter on (e.g. a copy/UX-writing category). Null = accept all annotations on the node."
68
+ }
69
+ }
70
+ },
71
+ "localization": {
72
+ "type": "object",
73
+ "additionalProperties": false,
74
+ "description": "How this project owns localization. Drives Section 10 of /multi-agent:analysis. Default `in-repo` keeps the historical hand-filled per-locale grid; `externally-owned` defers per-locale values to an authoring system (the analysis names key + status + copy source + base value only).",
75
+ "properties": {
76
+ "ownership": {
77
+ "type": "string",
78
+ "enum": ["in-repo", "externally-owned"],
79
+ "default": "in-repo"
80
+ },
81
+ "authoringPipeline": {
82
+ "type": ["string", "null"],
83
+ "description": "Human-readable reference to the owning localization system when ownership = externally-owned (e.g. a workflow or resource-bot name). Rendered as the Ownership column value; never a secret."
84
+ },
85
+ "locales": {
86
+ "type": "array",
87
+ "items": { "type": "string" },
88
+ "description": "Supported locale codes for this project (e.g. [\"tr\",\"en\",\"ar\",\"de\",\"es\",\"fr\",\"it\",\"ru\"]). Overrides the built-in default locale set used by Section 10.",
89
+ "minItems": 1
90
+ },
91
+ "baseLanguage": {
92
+ "type": "string",
93
+ "default": "en",
94
+ "description": "Source/base language code. A key missing its base-language value is a defect (renders the raw key at runtime)."
95
+ },
96
+ "keyPattern": {
97
+ "type": ["string", "null"],
98
+ "description": "Key naming convention hint, e.g. `Namespace.Case`. Documentation only; not enforced."
99
+ }
100
+ }
101
+ },
53
102
  "github": {
54
103
  "type": "object",
55
104
  "additionalProperties": false,
@@ -157,6 +157,12 @@
157
157
  "null"
158
158
  ]
159
159
  },
160
+ "graylog": {
161
+ "type": [
162
+ "string",
163
+ "null"
164
+ ]
165
+ },
160
166
  "firebase": {
161
167
  "type": [
162
168
  "string",
@@ -482,6 +488,10 @@
482
488
  "type": "string",
483
489
  "description": "Fortify SSC host without scheme, e.g. ssc.example.com. Used by Phase 0 Step 1b URL enrichment to validate pasted Fortify URLs and for direct SSC REST API calls. Resolves {FORTIFY_HOST}."
484
490
  },
491
+ "graylog": {
492
+ "type": "string",
493
+ "description": "Graylog host without scheme, e.g. logs.example.com. Used by the Graylog log-fetch adapter for direct REST API calls. Resolves {GRAYLOG_HOST}."
494
+ },
485
495
  "corpDomain": {
486
496
  "type": "string",
487
497
  "description": "Corporate email / cookie domain, e.g. example.com. Resolves {CORP_DOMAIN}."
@@ -1248,6 +1258,28 @@
1248
1258
  "figmaConfigPath": {
1249
1259
  "type": "string",
1250
1260
  "description": "Path to per-project figma-config.json (for Figma pipeline projects)."
1261
+ },
1262
+ "standardsFile": {
1263
+ "type": "string",
1264
+ "description": "Filename (or absolute/tilde path) of the canonical home-dir Standards reference for this project, used by /multi-agent:analysis Q5 auto-detect (e.g. ~/<project>-Standards.md)."
1265
+ },
1266
+ "editableRelatedRepos": {
1267
+ "type": "array",
1268
+ "items": { "type": "string" },
1269
+ "maxItems": 20,
1270
+ "description": "Extra writable repos to offer in the dev-context picker beyond auto-detected submodules (iOS/Android/Backend)."
1271
+ },
1272
+ "frontendRepos": {
1273
+ "type": "array",
1274
+ "items": { "type": "string" },
1275
+ "maxItems": 20,
1276
+ "description": "Frontend repos for this project, used by /multi-agent:analysis Step 4 (Frontend is rarely in the iOS/Android submodule tree)."
1277
+ },
1278
+ "frontendRoots": {
1279
+ "type": "array",
1280
+ "items": { "type": "string" },
1281
+ "maxItems": 20,
1282
+ "description": "Whitelist source roots for the Frontend repo-evidence scan (overrides the default src/ app/ components/ features/ lib/)."
1251
1283
  }
1252
1284
  }
1253
1285
  }
@@ -2,18 +2,18 @@
2
2
  .claude/CLAUDE.md 1
3
3
  .claude/agents 8
4
4
  .claude/commands 44
5
- .claude/lib 25
5
+ .claude/lib 27
6
6
  .claude/multi-agent-preferences.json 1
7
7
  .claude/multi-agent-refs 51
8
8
  .claude/rules 12
9
9
  .claude/schemas 23
10
- .claude/scripts 193
10
+ .claude/scripts 195
11
11
  .claude/settings.json 1
12
12
  .claude/skills 428
13
13
  .copilot/.pipeline-version 1
14
14
  .copilot/agents 8
15
15
  .copilot/copilot-instructions.md 1
16
- .copilot/lib 25
16
+ .copilot/lib 27
17
17
  .copilot/schemas 23
18
- .copilot/scripts 193
18
+ .copilot/scripts 195
19
19
  .copilot/skills 467
@@ -261,6 +261,30 @@ expect_rc 0 "stub token + offline curl degrades to empty result (exit 0)" \
261
261
  "$SH" "$LIB/fetch-fortify.sh" "$FORTIFY_URL"
262
262
  assert_in_file '"gateOutcome"' "$OUT" "normalized JSON still emitted"
263
263
 
264
+ # -----------------------------------------------------------------------------
265
+ note "4b. fetch-graylog.sh"
266
+ # -----------------------------------------------------------------------------
267
+ expect_rc 6 "no host configured exits 6" \
268
+ env HOME="$FAKE_HOME" USER=smoke \
269
+ "$SH" "$LIB/fetch-graylog.sh" --trx T123
270
+ expect_rc 4 "host override + no ids exits 4 (usage)" \
271
+ env HOME="$FAKE_HOME" USER=smoke GRAYLOG_HOST_OVERRIDE=logs.example.com \
272
+ "$SH" "$LIB/fetch-graylog.sh"
273
+
274
+ expect_rc 2 "host override + id reaches credential stage" \
275
+ env HOME="$FAKE_HOME" USER=smoke GRAYLOG_HOST_OVERRIDE=logs.example.com \
276
+ "$SH" "$LIB/fetch-graylog.sh" --trx T123 --conv C456
277
+ assert_in_file "missing-token" "$ERR" "blocked JSON names missing-token"
278
+
279
+ # Non-blocking contract: with a stub token and a failing curl, the fetch must
280
+ # degrade to an empty normalized result (messages present) and exit 0 so a log
281
+ # fetch never blocks a run.
282
+ expect_rc 0 "stub token + offline curl degrades to empty result (exit 0)" \
283
+ env HOME="$FAKE_HOME" USER=smoke GRAYLOG_HOST_OVERRIDE=logs.example.com \
284
+ FAKE_CRED_VALUE=fake-token PATH="$BIN_CURL_FAIL:$PATH" \
285
+ "$SH" "$LIB/fetch-graylog.sh" --trx T123
286
+ assert_in_file '"messages"' "$OUT" "normalized JSON still emitted"
287
+
264
288
  # -----------------------------------------------------------------------------
265
289
  note "5. figma-screenshot.sh"
266
290
  # -----------------------------------------------------------------------------
@@ -330,6 +354,46 @@ assert_in_file "^-i" "$WORK/security-argv.log" "security invoked in -i stdin mod
330
354
  assert_not_in_file "$SECRET_VALUE" "$WORK/security-argv.log" "secret absent from security argv"
331
355
  assert_in_file "$SECRET_VALUE" "$WORK/security-stdin.log" "secret delivered via stdin"
332
356
 
357
+ # -----------------------------------------------------------------------------
358
+ note "7b. fetch-figma-annotations.sh (Tier-2 annotation ingestion)"
359
+ # -----------------------------------------------------------------------------
360
+ expect_rc 1 "no args exits 1 (usage)" \
361
+ "$SH" "$LIB/fetch-figma-annotations.sh"
362
+
363
+ # Missing token -> blocked JSON + exit 2 (no network).
364
+ expect_rc 2 "missing token exits 2 with blocked JSON" \
365
+ env HOME="$FAKE_HOME" USER=smoke \
366
+ "$SH" "$LIB/fetch-figma-annotations.sh" --file-key ABC --node-id "1:23"
367
+ assert_in_file "missing-token" "$OUT" "blocked JSON names missing-token"
368
+
369
+ # Fake curl returns a /nodes doc carrying a TR:/EN: annotation; with a stub
370
+ # token the parser must reach + parse it (regression guard for the parser).
371
+ BIN_CURL_ANNOT="$WORK/bin-curl-annot"
372
+ mkdir -p "$BIN_CURL_ANNOT"
373
+ cat > "$BIN_CURL_ANNOT/curl" <<'FAKE'
374
+ #!/bin/bash
375
+ out=""; prev=""
376
+ for a in "$@"; do
377
+ [ "$prev" = "-o" ] && out="$a"
378
+ prev="$a"
379
+ done
380
+ if [ -n "$out" ]; then
381
+ cat > "$out" <<'JSON'
382
+ {"nodes":{"1:23":{"document":{"id":"1:23","name":"Title","type":"TEXT","characters":"Giriniz","annotations":[{"label":"TR: Ucusunuzu secin\nEN: Select your flight"}]}}}}
383
+ JSON
384
+ fi
385
+ printf '200'
386
+ exit 0
387
+ FAKE
388
+ chmod +x "$BIN_CURL_ANNOT/curl"
389
+
390
+ expect_rc 0 "stub token + annotation doc parses (exit 0)" \
391
+ env HOME="$FAKE_HOME" USER=smoke FIGMA_PAT=fake-token \
392
+ PATH="$BIN_CURL_ANNOT:$PATH" \
393
+ "$SH" "$LIB/fetch-figma-annotations.sh" --file-key ABC --node-id "1:23" --lang-prefixes TR,EN
394
+ assert_in_file "\"mode\": \"prefixed\"" "$OUT" "annotation parsed as prefixed"
395
+ assert_in_file "Select your flight" "$OUT" "EN copy extracted from annotation"
396
+
333
397
  # -----------------------------------------------------------------------------
334
398
  note "8. static source checks (bash-3.2 compat + no secrets on argv)"
335
399
  # -----------------------------------------------------------------------------
@@ -338,6 +402,8 @@ OWNED_FILES=(
338
402
  "$LIB/fetch-crashlytics.sh"
339
403
  "$LIB/fetch-swagger.sh"
340
404
  "$LIB/fetch-fortify.sh"
405
+ "$LIB/fetch-graylog.sh"
406
+ "$LIB/fetch-figma-annotations.sh"
341
407
  "$LIB/figma-mcp-refresh.sh"
342
408
  "$LIB/figma-screenshot.sh"
343
409
  "$LIB/credential-store.sh"
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env bash
2
+ # smoke-validate-analysis-doc.sh - contract for validate-analysis-doc.mjs,
3
+ # the deterministic gate over an emitted /multi-agent:analysis document.
4
+
5
+ set -uo pipefail
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
8
+ V="$SCRIPT_DIR/validate-analysis-doc.mjs"
9
+
10
+ PASS=0
11
+ FAIL=0
12
+ record_pass() { PASS=$((PASS + 1)); printf ' \342\234\223 %s\n' "$1"; }
13
+ record_fail() { FAIL=$((FAIL + 1)); printf ' \342\234\227 %s\n' "$1"; }
14
+
15
+ printf '\342\206\222 smoke-validate-analysis-doc: emitted-doc gate\n'
16
+
17
+ tmp=$(mktemp -d "${TMPDIR:-/tmp}/vdoc.XXXXXX")
18
+ trap 'rm -rf "$tmp"' EXIT
19
+
20
+ # A well-formed Full-mode doc (neutral UserProfile domain, BR referenced downstream).
21
+ cat > "$tmp/good.md" <<'MD'
22
+ ---
23
+ feature: UserProfile
24
+ platform: ios
25
+ language: tr
26
+ mode: full
27
+ template_version: v3
28
+ ---
29
+ ## 1. Özet
30
+ Profile screen.
31
+ ## 2. Hedefler ve Karşı Hedefler
32
+ goals.
33
+ ## 4. Kullanıcı Hikayeleri
34
+ ### 4.4 İş Kuralları
35
+ | BR-profile-01 | rule | source | Given/When/Then |
36
+ ## 9. API Kontratları
37
+ api.
38
+ ## 13. Mimari Plan
39
+ arch.
40
+ ## 14. Eklenecek Dosyalar
41
+ files.
42
+ ## 15. Test Planı
43
+ BR-profile-01 happy path.
44
+ ## 20. Riskler ve Açık Sorular
45
+ risk.
46
+ ## 21. Referanslar
47
+ refs.
48
+ MD
49
+
50
+ if node "$V" "$tmp/good.md" >/dev/null 2>&1; then
51
+ record_pass "well-formed Full doc passes (exit 0)"
52
+ else
53
+ record_fail "well-formed doc should pass"
54
+ fi
55
+
56
+ # Lite doc without Section 15 must still pass (Locked 31 mode-aware).
57
+ cat > "$tmp/lite.md" <<'MD'
58
+ ---
59
+ feature: UserProfile
60
+ platform: ios
61
+ language: en
62
+ mode: lite
63
+ template_version: v3
64
+ ---
65
+ ## 1. Summary
66
+ s
67
+ ## 2. Goals and Non-Goals
68
+ g
69
+ ## 4. User Stories
70
+ ### 4.4 Business Rules
71
+ | BR-profile-01 | rule | source | Given/When/Then |
72
+ ## 9. API Contracts
73
+ a
74
+ ## 13. Architecture
75
+ x
76
+ ## 14. Files to Add
77
+ f
78
+ ## 20. Risks and Open Questions
79
+ r
80
+ ## 21. References
81
+ ref
82
+ MD
83
+ if node "$V" "$tmp/lite.md" >/dev/null 2>&1; then
84
+ record_pass "Lite doc (no Section 15) passes - mode-aware Locked 31"
85
+ else
86
+ record_fail "Lite doc without Section 15 must pass"
87
+ fi
88
+
89
+ # Missing front-matter -> ERROR.
90
+ printf '## 1. Summary\nno front matter\n' > "$tmp/nofm.md"
91
+ node "$V" "$tmp/nofm.md" >/dev/null 2>&1
92
+ [ "$?" -eq 1 ] && record_pass "missing front-matter fails (exit 1)" || record_fail "missing front-matter should fail"
93
+
94
+ # Banned punctuation (em-dash) -> ERROR.
95
+ cp "$tmp/good.md" "$tmp/emdash.md"
96
+ printf 'A line with an em-dash \342\200\224 here.\n' >> "$tmp/emdash.md"
97
+ # Capture first (pipefail + node's nonzero exit would poison a direct pipe).
98
+ emdash_out=$(node "$V" "$tmp/emdash.md" 2>&1 || true)
99
+ if printf '%s' "$emdash_out" | grep -q "em-dash"; then
100
+ record_pass "em-dash flagged (Locked 7)"
101
+ else
102
+ record_fail "em-dash should be flagged"
103
+ fi
104
+
105
+ # Missing a never-omitted section (References) -> ERROR.
106
+ grep -v "Referanslar\|References" "$tmp/good.md" > "$tmp/noref.md"
107
+ node "$V" "$tmp/noref.md" >/dev/null 2>&1
108
+ [ "$?" -eq 1 ] && record_pass "missing References section fails" || record_fail "missing References should fail"
109
+
110
+ # Define-only BR under --strict -> fail (WARN promoted).
111
+ cat > "$tmp/orphan.md" <<'MD'
112
+ ---
113
+ feature: UserProfile
114
+ platform: ios
115
+ language: en
116
+ mode: full
117
+ template_version: v3
118
+ ---
119
+ ## 1. Summary
120
+ s
121
+ ## 2. Goals
122
+ g
123
+ ## 4. User Stories
124
+ ### 4.4 Business Rules
125
+ | BR-profile-42 | rule | source | Given/When/Then |
126
+ ## 9. API Contracts
127
+ a
128
+ ## 13. Architecture
129
+ x
130
+ ## 14. Files to Add
131
+ f
132
+ ## 15. Test Plan
133
+ no BR reference here
134
+ ## 20. Risks
135
+ r
136
+ ## 21. References
137
+ ref
138
+ MD
139
+ node "$V" "$tmp/orphan.md" >/dev/null 2>&1
140
+ [ "$?" -eq 0 ] && record_pass "define-only BR is WARN, not error (default)" || record_fail "orphan BR should not hard-fail by default"
141
+ node "$V" "$tmp/orphan.md" --strict >/dev/null 2>&1
142
+ [ "$?" -eq 1 ] && record_pass "define-only BR fails under --strict" || record_fail "orphan BR should fail under --strict"
143
+
144
+ # ui_tests: true but no Section 15.6 -> ERROR.
145
+ sed 's/^mode: full/mode: full\nui_tests: true\na11y_depth: basic/' "$tmp/good.md" > "$tmp/uimiss.md"
146
+ node "$V" "$tmp/uimiss.md" >/dev/null 2>&1
147
+ [ "$?" -eq 1 ] && record_pass "ui_tests:true without Section 15.6 fails" || record_fail "ui_tests:true must require 15.6"
148
+
149
+ # ui_tests: true WITH a Section 15.6 heading -> passes.
150
+ cp "$tmp/uimiss.md" "$tmp/uiok.md"
151
+ printf '## 15.6 UI test senaryolari\nUI-profile-01 flow.\n' >> "$tmp/uiok.md"
152
+ node "$V" "$tmp/uiok.md" >/dev/null 2>&1
153
+ [ "$?" -eq 0 ] && record_pass "ui_tests:true with Section 15.6 passes" || record_fail "ui_tests:true + 15.6 should pass"
154
+
155
+ # a11y_depth: full but no Section 16.2 walkthrough -> ERROR.
156
+ sed 's/^mode: full/mode: full\nui_tests: false\na11y_depth: full/' "$tmp/good.md" > "$tmp/a11ymiss.md"
157
+ node "$V" "$tmp/a11ymiss.md" >/dev/null 2>&1
158
+ [ "$?" -eq 1 ] && record_pass "a11y_depth:full without Section 16.2 fails" || record_fail "a11y_depth:full must require 16.2"
159
+
160
+ printf '\n\342\225\220\342\225\220 smoke-validate-analysis-doc: %d passed, %d failed \342\225\220\342\225\220\n' "$PASS" "$FAIL"
161
+ [ "$FAIL" -eq 0 ]