@mmerterden/multi-agent-pipeline 20.0.0 → 20.2.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 (90) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +5 -5
  3. package/README.tr.md +5 -5
  4. package/SECURITY.md +3 -3
  5. package/docs/adr/0011-dormant-ci.md +10 -1
  6. package/docs/architecture.md +2 -2
  7. package/docs/ecosystem.md +5 -5
  8. package/docs/facts.json +8 -7
  9. package/install/_codex-agents.mjs +1 -1
  10. package/manifest.json +92 -64
  11. package/package.json +1 -1
  12. package/pipeline/agents/code-reviewer.md +2 -2
  13. package/pipeline/agents/dev-critic.md +5 -5
  14. package/pipeline/agents/security-auditor.md +80 -72
  15. package/pipeline/commands/figma-to-swiftui.md +1 -1
  16. package/pipeline/commands/multi-agent/SKILL.md +1 -1
  17. package/pipeline/commands/multi-agent/channels/SKILL.md +1 -1
  18. package/pipeline/commands/multi-agent/diff-explain/SKILL.md +1 -1
  19. package/pipeline/commands/multi-agent/help/SKILL.md +2 -0
  20. package/pipeline/commands/multi-agent/scan/SKILL.md +2 -2
  21. package/pipeline/commands/multi-agent/security-review/SKILL.md +52 -0
  22. package/pipeline/commands/multi-agent/sync/SKILL.md +3 -3
  23. package/pipeline/multi-agent-refs/component-dispatch.md +5 -5
  24. package/pipeline/multi-agent-refs/cross-cli-contract.md +6 -6
  25. package/pipeline/multi-agent-refs/features/security-audit.md +55 -0
  26. package/pipeline/multi-agent-refs/phases/modes.md +1 -1
  27. package/pipeline/multi-agent-refs/phases/phase-3-review.md +9 -15
  28. package/pipeline/multi-agent-refs/phases/phase-5-report.md +1 -1
  29. package/pipeline/multi-agent-refs/threat-model.md +39 -0
  30. package/pipeline/schemas/agent-state.schema.json +23 -0
  31. package/pipeline/schemas/phases.json +1 -2
  32. package/pipeline/schemas/prefs.schema.json +0 -4
  33. package/pipeline/schemas/reviewer-output.schema.json +99 -2
  34. package/pipeline/schemas/security-finding.schema.json +144 -0
  35. package/pipeline/scripts/_stack-routing.mjs +1 -0
  36. package/pipeline/scripts/gc-abandoned.sh +16 -9
  37. package/pipeline/scripts/render-work-summary.sh +7 -4
  38. package/pipeline/skills/.skill-manifest.json +47 -23
  39. package/pipeline/skills/.skills-index.json +75 -9
  40. package/pipeline/skills/shared/README.md +13 -7
  41. package/pipeline/skills/shared/core/multi-agent/SKILL.md +3 -4
  42. package/pipeline/skills/shared/core/multi-agent-scan/SKILL.md +2 -2
  43. package/pipeline/skills/shared/core/multi-agent-security-review/SKILL.md +29 -0
  44. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +3 -3
  45. package/pipeline/skills/shared/external/android-architecture/SKILL.md +71 -0
  46. package/pipeline/skills/shared/external/android-architecture/references/patterns.md +142 -0
  47. package/pipeline/skills/shared/external/android-build-quality-gates/SKILL.md +314 -0
  48. package/pipeline/skills/shared/external/android-build-quality-gates/references/patterns.md +432 -0
  49. package/pipeline/skills/shared/external/android-datastore/SKILL.md +236 -0
  50. package/pipeline/skills/shared/external/android-datastore/references/patterns.md +297 -0
  51. package/pipeline/skills/shared/external/android-design-tokens-codegen/SKILL.md +249 -0
  52. package/pipeline/skills/shared/external/android-design-tokens-codegen/references/patterns.md +270 -0
  53. package/pipeline/skills/shared/external/android-jetpack-compose-expert/SKILL.md +62 -0
  54. package/pipeline/skills/shared/external/android-mvi-viewmodel/SKILL.md +255 -0
  55. package/pipeline/skills/shared/external/android-mvi-viewmodel/references/patterns.md +257 -0
  56. package/pipeline/skills/shared/external/android-performance/SKILL.md +86 -602
  57. package/pipeline/skills/shared/external/android-performance/references/patterns.md +659 -0
  58. package/pipeline/skills/shared/external/android-security/SKILL.md +117 -430
  59. package/pipeline/skills/shared/external/android-security/references/patterns.md +690 -0
  60. package/pipeline/skills/shared/external/{android_ui_verification → android-ui-verification}/SKILL.md +1 -1
  61. package/pipeline/skills/shared/external/api-security-best-practices/SKILL.md +35 -733
  62. package/pipeline/skills/shared/external/api-security-best-practices/references/auth.md +299 -0
  63. package/pipeline/skills/shared/external/api-security-best-practices/references/input-validation.md +255 -0
  64. package/pipeline/skills/shared/external/api-security-best-practices/references/rate-limiting.md +167 -0
  65. package/pipeline/skills/shared/external/app-intents/SKILL.md +39 -174
  66. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +178 -0
  67. package/pipeline/skills/shared/external/compose-components/SKILL.md +48 -0
  68. package/pipeline/skills/shared/external/compose-components/references/patterns.md +200 -0
  69. package/pipeline/skills/shared/external/compose-navigation/SKILL.md +66 -3
  70. package/pipeline/skills/shared/external/compose-navigation/references/patterns.md +191 -0
  71. package/pipeline/skills/shared/external/compose-testing/SKILL.md +107 -397
  72. package/pipeline/skills/shared/external/compose-testing/references/patterns.md +631 -0
  73. package/pipeline/skills/shared/external/gradle-kotlin-dsl/SKILL.md +121 -449
  74. package/pipeline/skills/shared/external/gradle-kotlin-dsl/references/patterns.md +715 -0
  75. package/pipeline/skills/shared/external/kotlin-coroutines-expert/SKILL.md +143 -0
  76. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +27 -102
  77. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +42 -0
  78. package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +94 -383
  79. package/pipeline/skills/shared/external/retrofit-networking/references/patterns.md +640 -0
  80. package/pipeline/skills/shared/external/room-database/SKILL.md +101 -440
  81. package/pipeline/skills/shared/external/room-database/references/patterns.md +614 -0
  82. package/pipeline/skills/shared/external/security-review/SKILL.md +64 -0
  83. package/pipeline/skills/shared/external/security-review/references/owasp-mobile-top10-2024.md +53 -0
  84. package/pipeline/skills/shared/external/security-review/references/owasp-web-api-top10-2021.md +56 -0
  85. package/pipeline/skills/shared/external/storekit/SKILL.md +69 -343
  86. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +371 -0
  87. package/pipeline/skills/shared/external/widgetkit/SKILL.md +25 -101
  88. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +107 -0
  89. package/pipeline/skills/skills-index.md +8 -2
  90. package/pipeline/commands/security-review.md +0 -6
@@ -857,6 +857,29 @@
857
857
  }
858
858
  }
859
859
  },
860
+ "threatModel": {
861
+ "type": "object",
862
+ "additionalProperties": true,
863
+ "description": "The run-scoped threat model produced at Phase 3 Step 2.7 (or by /multi-agent:security-review), mirrored here so a resume reuses it instead of re-deriving it. Contract: multi-agent-refs/threat-model.md.",
864
+ "properties": {
865
+ "path": {
866
+ "type": "string",
867
+ "description": "Path to .pipeline/threat-model.md in the worktree."
868
+ },
869
+ "sha": {
870
+ "type": "string",
871
+ "description": "Content hash, so a later step can tell whether the model changed."
872
+ },
873
+ "producedAt": {
874
+ "type": "string",
875
+ "format": "date-time"
876
+ },
877
+ "producedBy": {
878
+ "type": "string",
879
+ "description": "Which step or command wrote it (e.g. 'phase-3-step-2.7', 'security-review')."
880
+ }
881
+ }
882
+ },
860
883
  "reviewIterations": {
861
884
  "type": "array",
862
885
  "items": {
@@ -78,7 +78,6 @@
78
78
  "thresholds": {
79
79
  "waitingFromPhase": 4,
80
80
  "mcpAllowedThroughPhase": 1,
81
- "shortRunFromPhase": 2,
82
- "note": "Phase numbers other code compares against, named here so a renumbering moves them with the contract. waitingFromPhase: runs-index.mjs groups a run as 'waiting on you' from Commit on. mcpAllowedThroughPhase: Figma MCP is reachable only through Plan; smoke-no-mcp-in-dev-phases.sh fails any recorded call at a higher phase. Its literal threshold is unchanged from the eight-phase contract on purpose - Analysis was 1 and is now inside Plan, also 1, so the permitted set {0,1} is identical. shortRunFromPhase: the depth picker's Short set starts here."
81
+ "note": "Phase numbers other code compares against, named here so a renumbering moves them with the contract. waitingFromPhase: runs-index.mjs groups a run as 'waiting on you' from Commit on. mcpAllowedThroughPhase: Figma MCP is reachable only through Plan; smoke-no-mcp-in-dev-phases.sh fails any recorded call at a higher phase. Its literal threshold is unchanged from the eight-phase contract on purpose - Analysis was 1 and is now inside Plan, also 1, so the permitted set {0,1} is identical."
83
82
  }
84
83
  }
@@ -2140,10 +2140,6 @@
2140
2140
  "format": "date-time",
2141
2141
  "description": "Timestamp of last task start against this project."
2142
2142
  },
2143
- "componentDevWorkflow": {
2144
- "type": "boolean",
2145
- "description": "Project follows the component (Configuration / View / Modifiers) development workflow, so Phase 2 dispatches the component skills instead of the standard TDD flow."
2146
- },
2147
2143
  "figmaConfigPath": {
2148
2144
  "type": "string",
2149
2145
  "description": "Path to per-project figma-config.json (for Figma pipeline projects)."
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://github.com/mmerterden/multi-agent-pipeline/pipeline/schemas/reviewer-output.schema.json",
4
- "version": "1.3.0",
4
+ "version": "1.4.0",
5
5
  "title": "Multi-Agent Pipeline - Phase 3 reviewer output",
6
- "description": "Contract for a single code-reviewer subagent's JSON output in Phase 4 Step 2. Every host dispatches 3 parallel reviewers; the middle slot is CLI-aware: Claude Code (Fable, Opus, Sonnet); Copilot CLI (Opus, GPT-5.4, Sonnet); Codex CLI dispatches 3 (gpt-5.6 at xhigh, gpt-5.4, gpt-5.6 at medium). Every reviewer must return an object matching this shape before Opus triage merges them. v1.1.0 adds the rule-ID conformance checklist: when the orchestrator supplies a ${CRITERIA} block (Phase 3 Step 1.78), the reviewer must return one conformance row per selected rule ID. Findings alone cannot answer 'was this applied completely' - a reviewer that opened nothing returns the same empty findings array as one that checked everything. v1.2.0 adds the optional per-finding fingerprint (Phase 3 Step 2.1): the stable id a finding keeps across review rounds.",
6
+ "description": "Contract for a single code-reviewer subagent's JSON output in Phase 4 Step 2. Every host dispatches 3 parallel reviewers; the middle slot is CLI-aware: Claude Code (Fable, Opus, Sonnet); Copilot CLI (Opus, GPT-5.4, Sonnet); Codex CLI dispatches 3 (gpt-5.6 at xhigh, gpt-5.4, gpt-5.6 at medium). Every reviewer must return an object matching this shape before Opus triage merges them. v1.1.0 adds the rule-ID conformance checklist: when the orchestrator supplies a ${CRITERIA} block (Phase 3 Step 1.78), the reviewer must return one conformance row per selected rule ID. Findings alone cannot answer 'was this applied completely' - a reviewer that opened nothing returns the same empty findings array as one that checked everything. v1.2.0 adds the optional per-finding fingerprint (Phase 3 Step 2.1): the stable id a finding keeps across review rounds. v1.4.0 adds the optional per-finding `security` envelope (OWASP + CWE + CVSS + evidence + remediation): the security-auditor emits reviewer-output objects whose findings carry it, so a blocking security finding merges at Step 3.0 and blocks Phase 4 like any reviewer blocker. General reviewers omit it. Its shape mirrors security-finding.schema.json, kept in step by smoke-security-schema-parity.sh.",
7
7
  "type": "object",
8
8
  "additionalProperties": false,
9
9
  "required": ["findings", "approved"],
@@ -128,6 +128,103 @@
128
128
  "type": "string",
129
129
  "pattern": "^F:[0-9a-f]{8}$",
130
130
  "description": "Stable cross-round identity of the finding, computed by finding-fingerprint.mjs from (file, ruleId or normalized issue text). Never includes the line. On iteration >= 2 a reviewer that recognises an entry from <previous-round-findings> echoes its fingerprint; otherwise leave it unset and the script fills it in."
131
+ },
132
+ "security": {
133
+ "type": "object",
134
+ "additionalProperties": false,
135
+ "required": ["owaspCategory", "cwe", "cvss", "evidence", "confidence", "remediation"],
136
+ "description": "OPTIONAL security envelope. Present when the finding comes from the security-auditor (Phase 3 Step 3.0 merge) or /multi-agent:security-review; absent on a general code-reviewer finding. Its shape is the security block of security-finding.schema.json, kept in step by smoke-security-schema-parity.sh (self-contained, no cross-file $ref). validate-reviewer.mjs ignores it; triage carries it through unchanged so Phase 5 can report CVSS + CWE + remediation.",
137
+ "properties": {
138
+ "owaspCategory": {
139
+ "type": "string",
140
+ "pattern": "^(A(0[1-9]|10):2021|M[1-9]:2024|M10:2024)( .+)?$",
141
+ "description": "OWASP Top 10 2021 id (web/API) or OWASP Mobile Top 10 2024 id, optionally followed by a human title."
142
+ },
143
+ "cwe": {
144
+ "type": "string",
145
+ "pattern": "^CWE-[0-9]{1,5}$",
146
+ "description": "The specific CWE weakness id. One per finding."
147
+ },
148
+ "cve": {
149
+ "type": "string",
150
+ "pattern": "^CVE-[0-9]{4}-[0-9]{4,}$",
151
+ "description": "A published CVE, for a known-vulnerable dependency finding."
152
+ },
153
+ "cvss": {
154
+ "type": "object",
155
+ "additionalProperties": false,
156
+ "required": ["vector", "baseScore", "band"],
157
+ "description": "CVSS 3.1 base metrics; baseScore and band are computed from the vector by security_cvss_score.",
158
+ "properties": {
159
+ "vector": {
160
+ "type": "string",
161
+ "pattern": "^CVSS:3[.]1/AV:[NALP]/AC:[LH]/PR:[NLH]/UI:[NR]/S:[UC]/C:[NLH]/I:[NLH]/A:[NLH]$",
162
+ "description": "Full CVSS 3.1 base vector."
163
+ },
164
+ "baseScore": {
165
+ "type": "number",
166
+ "minimum": 0,
167
+ "maximum": 10,
168
+ "description": "0.0 .. 10.0 base score."
169
+ },
170
+ "band": {
171
+ "type": "string",
172
+ "enum": ["none", "low", "medium", "high", "critical"],
173
+ "description": "Qualitative band; maps to the finding severity (critical/high -> blocking, medium -> important, low/none -> suggestion)."
174
+ }
175
+ }
176
+ },
177
+ "evidence": {
178
+ "type": "string",
179
+ "minLength": 8,
180
+ "description": "What in the code proves the finding, cited by file:line."
181
+ },
182
+ "counterevidence": {
183
+ "type": "string",
184
+ "description": "What would disprove it, or the condition under which it is a false positive."
185
+ },
186
+ "confidence": {
187
+ "type": "string",
188
+ "enum": ["high", "medium", "low"],
189
+ "description": "How sure the auditor is the finding is real."
190
+ },
191
+ "confidenceRationale": {
192
+ "type": "string",
193
+ "description": "Why the confidence is what it is."
194
+ },
195
+ "severityChangeConditions": {
196
+ "type": "string",
197
+ "description": "The fact that, if learned, would move the severity."
198
+ },
199
+ "remediation": {
200
+ "type": "string",
201
+ "minLength": 8,
202
+ "description": "Remediation steps in prose."
203
+ },
204
+ "remediationDiff": {
205
+ "type": "object",
206
+ "additionalProperties": false,
207
+ "required": ["before", "after"],
208
+ "description": "The fix as a before/after pair.",
209
+ "properties": {
210
+ "before": { "type": "string" },
211
+ "after": { "type": "string" }
212
+ }
213
+ },
214
+ "endpoint": {
215
+ "type": "string",
216
+ "description": "The HTTP route or RPC method for a service-layer finding."
217
+ },
218
+ "method": {
219
+ "type": "string",
220
+ "enum": ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
221
+ "description": "HTTP method for an endpoint finding."
222
+ },
223
+ "fixVerification": {
224
+ "type": "string",
225
+ "description": "How to confirm the fix worked - the test to add or check to run."
226
+ }
227
+ }
131
228
  }
132
229
  }
133
230
  }
@@ -0,0 +1,144 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/mmerterden/multi-agent-pipeline/pipeline/schemas/security-finding.schema.json",
4
+ "version": "1.0.0",
5
+ "title": "Multi-Agent Pipeline - security finding",
6
+ "description": "Contract for one finding produced by the security-auditor subagent (Phase 3) and the /multi-agent:security-review command. A security finding IS a reviewer-output.schema.json finding - same core fields (severity, file, line, issue, fix) - carrying one extra `security` block. That is exactly why it works: the auditor emits reviewer-output objects whose findings have the block, they merge into the Phase 3 reviewer set at Step 3.0, and a blocking one blocks Phase 4 like any reviewer blocker. reviewer-output.schema.json declares the same `security` property as OPTIONAL on its finding; here it is REQUIRED, which is the whole difference between a general finding and a security finding. The severity enum is the reviewer's (blocking|important|suggestion), never a private Critical/High/Medium scale: severity is derived from security.cvss.band by the rule on the `severity` property, and a severity that contradicts the band is the one inconsistency this contract forbids. The two schemas are kept in step by smoke-security-schema-parity.sh, not by a cross-file $ref, matching the self-contained style of the other pipeline schemas.",
7
+ "type": "object",
8
+ "additionalProperties": false,
9
+ "required": ["severity", "file", "line", "issue", "fix", "security"],
10
+ "properties": {
11
+ "severity": {
12
+ "type": "string",
13
+ "enum": ["blocking", "important", "suggestion"],
14
+ "description": "The reviewer severity, derived from security.cvss.band so this finding filters through triage like any other: critical or high band -> blocking; medium -> important; low or none -> suggestion. It is not a free choice - a severity that disagrees with the band is the one inconsistency this schema exists to forbid."
15
+ },
16
+ "file": {
17
+ "type": "string",
18
+ "minLength": 1,
19
+ "description": "Path relative to repo root. The auditor must not invent a file that is not in the tree."
20
+ },
21
+ "line": {
22
+ "type": "integer",
23
+ "minimum": 0,
24
+ "description": "Line number. 0 = whole-file, configuration-level, or dependency-manifest finding."
25
+ },
26
+ "issue": {
27
+ "type": "string",
28
+ "minLength": 4,
29
+ "description": "What is wrong, in one sentence. The vulnerability, not the fix."
30
+ },
31
+ "fix": {
32
+ "type": "string",
33
+ "minLength": 4,
34
+ "description": "Concrete remediation in one line, for the reviewer-shaped view. The full before/after diff lives in security.remediationDiff."
35
+ },
36
+ "ruleId": {
37
+ "type": "string",
38
+ "minLength": 1,
39
+ "description": "Stable id of a cited rule (e.g. SEC-03) when the finding comes from a standards registry supplied to the auditor. The author can look the rule up and argue with it rather than with the auditor."
40
+ },
41
+ "criteriaSource": {
42
+ "type": "string",
43
+ "minLength": 1,
44
+ "description": "Which source the rule came from: a registry name, a compliance catalog (apple-archive-compliance / google-play-compliance), a reference path, or 'threat-model' when the finding is derived from the run-scoped threat model rather than a fixed rule."
45
+ },
46
+ "security": {
47
+ "type": "object",
48
+ "additionalProperties": false,
49
+ "required": ["owaspCategory", "cwe", "cvss", "evidence", "confidence", "remediation"],
50
+ "description": "The security envelope. Present on every security finding; optional on a general reviewer finding.",
51
+ "properties": {
52
+ "owaspCategory": {
53
+ "type": "string",
54
+ "pattern": "^(A(0[1-9]|10):2021|M[1-9]:2024|M10:2024)( .+)?$",
55
+ "description": "The OWASP category: web/API uses OWASP Top 10 2021 ids (A01:2021 .. A10:2021), mobile uses OWASP Mobile Top 10 2024 ids (M1:2024 .. M10:2024). An optional human title may follow the id, e.g. 'A01:2021 Broken Access Control'."
56
+ },
57
+ "cwe": {
58
+ "type": "string",
59
+ "pattern": "^CWE-[0-9]{1,5}$",
60
+ "description": "The specific weakness, as a validated CWE id (CWE-89, CWE-798, ...). One weakness per finding; if a line has two, it is two findings."
61
+ },
62
+ "cve": {
63
+ "type": "string",
64
+ "pattern": "^CVE-[0-9]{4}-[0-9]{4,}$",
65
+ "description": "A published CVE, when the finding is a known-vulnerable dependency rather than first-party code. Set by the dependency-audit path, not by static code review."
66
+ },
67
+ "cvss": {
68
+ "type": "object",
69
+ "additionalProperties": false,
70
+ "required": ["vector", "baseScore", "band"],
71
+ "description": "CVSS 3.1 base metrics. The auditor writes the vector; security_cvss_score (toolkit) computes baseScore and band from it, so the two cannot drift from the vector by hand.",
72
+ "properties": {
73
+ "vector": {
74
+ "type": "string",
75
+ "pattern": "^CVSS:3[.]1/AV:[NALP]/AC:[LH]/PR:[NLH]/UI:[NR]/S:[UC]/C:[NLH]/I:[NLH]/A:[NLH]$",
76
+ "description": "A full CVSS 3.1 base vector string. All eight base metrics are required; temporal and environmental metrics are out of scope for a static review."
77
+ },
78
+ "baseScore": {
79
+ "type": "number",
80
+ "minimum": 0,
81
+ "maximum": 10,
82
+ "description": "The CVSS 3.1 base score computed from the vector by security_cvss_score. 0.0 .. 10.0, one decimal."
83
+ },
84
+ "band": {
85
+ "type": "string",
86
+ "enum": ["none", "low", "medium", "high", "critical"],
87
+ "description": "The qualitative band of baseScore: none 0.0, low 0.1-3.9, medium 4.0-6.9, high 7.0-8.9, critical 9.0-10.0. This is what maps to `severity`."
88
+ }
89
+ }
90
+ },
91
+ "evidence": {
92
+ "type": "string",
93
+ "minLength": 8,
94
+ "description": "What in the code proves the finding, quoted or cited by file:line. A static finding with no evidence is a guess; this is what a reader checks before agreeing."
95
+ },
96
+ "counterevidence": {
97
+ "type": "string",
98
+ "description": "What in the code would DISPROVE the finding, or the condition under which it is a false positive - a guard elsewhere, a framework default, an unreachable path. Stating it keeps confidence honest; leaving it blank asserts there is none."
99
+ },
100
+ "confidence": {
101
+ "type": "string",
102
+ "enum": ["high", "medium", "low"],
103
+ "description": "How sure the auditor is that this is real. Low confidence does not mean do-not-report; it means report with the counterevidence and let triage weigh it."
104
+ },
105
+ "confidenceRationale": {
106
+ "type": "string",
107
+ "description": "One line on why the confidence is what it is - what was and was not verifiable from the code alone."
108
+ },
109
+ "severityChangeConditions": {
110
+ "type": "string",
111
+ "description": "The fact that, if learned, would move the severity: 'critical if this endpoint is unauthenticated in production; medium if it is admin-only'. Names the assumption the score rests on."
112
+ },
113
+ "remediation": {
114
+ "type": "string",
115
+ "minLength": 8,
116
+ "description": "The remediation steps in prose - what to change and why it closes the weakness. Actionable, not 'sanitize input'."
117
+ },
118
+ "remediationDiff": {
119
+ "type": "object",
120
+ "additionalProperties": false,
121
+ "required": ["before", "after"],
122
+ "description": "The fix as a before/after pair the author can read as a diff. Optional - some findings are configuration or process changes with no single code hunk - but preferred for first-party code.",
123
+ "properties": {
124
+ "before": { "type": "string", "description": "The vulnerable code as it stands." },
125
+ "after": { "type": "string", "description": "The same region after the fix." }
126
+ }
127
+ },
128
+ "endpoint": {
129
+ "type": "string",
130
+ "description": "The HTTP route or RPC method the finding sits on, when it is a service-layer issue. Empty for non-network findings."
131
+ },
132
+ "method": {
133
+ "type": "string",
134
+ "enum": ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
135
+ "description": "The HTTP method for an endpoint finding."
136
+ },
137
+ "fixVerification": {
138
+ "type": "string",
139
+ "description": "How to confirm the fix worked - the test to add or the check to run. Static review cannot fire an exploit, so this is the empirical step a human or a later dynamic pass takes."
140
+ }
141
+ }
142
+ }
143
+ }
144
+ }
@@ -28,6 +28,7 @@ export const COMMON_SKILLS = [
28
28
  "skill-creator",
29
29
  "backlog",
30
30
  "localization-reuse-map",
31
+ "security-review",
31
32
  ];
32
33
 
33
34
  // The analyst plugin is the second always-enabled toolkit. It carries outside
@@ -11,9 +11,9 @@
11
11
  # Three things keep this from being a foot-gun, and each is a rule rather than a
12
12
  # heuristic:
13
13
  #
14
- # 1. A run WAITING FOR YOU is never reaped. An open PR, Phase 4 or 7, or
15
- # `status: awaiting_input` means the work landed and the pipeline is
16
- # holding for an answer by design. That is finished work, not residue.
14
+ # 1. A run WAITING FOR YOU is never reaped. An open PR, the Commit or Report
15
+ # phase, or `status: awaiting_input` means the work landed and the pipeline
16
+ # is holding for an answer by design. That is finished work, not residue.
17
17
  # 2. A path that is not strictly inside `<repo>/.worktrees/` is never removed.
18
18
  # This is not theoretical: four state files on that machine record
19
19
  # `worktreePath` as the REPO ROOT, so a sweep that trusted the field would
@@ -96,6 +96,13 @@ case "$DAYS$PHASE0_DAYS" in *[!0-9]*) echo "gc-abandoned: --days and --phase0-da
96
96
  command -v jq >/dev/null 2>&1 || { echo "gc-abandoned: jq is required"; exit 0; }
97
97
  [ -d "$LOGS" ] || { echo "gc-abandoned: nothing to do (no state at $LOGS)"; exit 0; }
98
98
 
99
+ # The phase from which a stopped run is "waiting on you", not residue, is the
100
+ # contract's threshold - Commit onward - not a literal pinned to one numbering.
101
+ # runs-index.mjs groups the same way from the same field.
102
+ SELF_DIR=$(cd "$(dirname "$0")" && pwd)
103
+ WAITING_FROM=$(jq -r '.thresholds.waitingFromPhase' "$SELF_DIR/../schemas/phases.json" 2>/dev/null || echo 4)
104
+ case "$WAITING_FROM" in *[!0-9]*|"") WAITING_FROM=4 ;; esac
105
+
99
106
  # GNU form FIRST. `stat -f` is a valid GNU flag (--file-system) that succeeds and
100
107
  # prints a mount point, so BSD-first silently returns a number that is not a
101
108
  # timestamp. ADR-0012 lists this among the constructs that look cross-platform
@@ -142,7 +149,7 @@ while IFS= read -r state; do
142
149
  wt=$(jq -r '.worktreePath // ""' "$state" 2>/dev/null || true)
143
150
 
144
151
  # Rule 1: waiting for you is not residue.
145
- if [ "$status" = "awaiting_input" ] || [ -n "$pr" ] || [ "$phase" = "6" ] || [ "$phase" = "7" ]; then
152
+ if [ "$status" = "awaiting_input" ] || [ -n "$pr" ] || { [ "$phase" != "?" ] && [ "$phase" -ge "$WAITING_FROM" ] 2>/dev/null; }; then
146
153
  skipped_waiting=$((skipped_waiting + 1))
147
154
  continue
148
155
  fi
@@ -271,10 +278,10 @@ if [ "$STATE_ONLY" -eq 0 ] && [ -d "$REPOS" ]; then
271
278
  ph=$(printf '%s' "$row" | cut -f5)
272
279
 
273
280
  # Rule 1 again: landed work is not residue, whichever pass finds it. Order
274
- # matters here - the phase-4/5 test is a proxy for "still waiting", and a
275
- # run whose status already says it FINISHED is not waiting for anyone. A
276
- # complete run sitting at phase 7 was read as waiting and never reaped,
277
- # which is how three finished worktrees held 5.1 GB indefinitely.
281
+ # matters here - the waiting-phase test is a proxy for "still waiting", and
282
+ # a run whose status already says it FINISHED is not waiting for anyone. A
283
+ # complete run sitting at the report phase was read as waiting and never
284
+ # reaped, which is how three finished worktrees held 5.1 GB indefinitely.
278
285
  case "$st" in
279
286
  complete | completed | failed) ;;
280
287
  awaiting_input)
@@ -282,7 +289,7 @@ if [ "$STATE_ONLY" -eq 0 ] && [ -d "$REPOS" ]; then
282
289
  in_progress | paused)
283
290
  continue ;; # pass 1 owns these; do not report them twice
284
291
  *)
285
- if [ -n "$pr" ] || [ "$ph" = "6" ] || [ "$ph" = "7" ]; then
292
+ if [ -n "$pr" ] || { [ "$ph" != "?" ] && [ "$ph" -ge "$WAITING_FROM" ] 2>/dev/null; }; then
286
293
  skipped_waiting=$((skipped_waiting + 1)); continue
287
294
  fi ;;
288
295
  esac
@@ -34,7 +34,7 @@
34
34
  # - <A> accepted · <D> deferred · <R> rejected · approved=<bool>
35
35
  #
36
36
  # #### Phases
37
- # - 0 Init done · 1 Analysis done · 2 Planning done · 3 Dev done · 4 Review done · 5 Test done · 6 Commit done · 7 Report active
37
+ # - 0 Init done · 1 Plan done · 2 Dev done · 3 Review done · 4 Commit done · 5 Report active
38
38
  #
39
39
  # Exit codes: 0 = rendered, 2 = missing state (caller should skip section).
40
40
 
@@ -171,7 +171,11 @@ fi
171
171
  # Phase tick marks
172
172
  phase_ticks=""
173
173
  if [ -n "$TRACKER_FILE" ]; then
174
- phase_ticks=$(jq -r '
174
+ # The phase vocabulary is the contract's, not a literal pinned to one
175
+ # numbering: "<id> <name>" per phase, in id order, straight from phases.json.
176
+ PHASES_JSON="$_MA_RP_HERE/../schemas/phases.json"
177
+ LABELS=$(jq -c '[.phases | sort_by(.id) | .[] | "\(.id) \(.name)"]' "$PHASES_JSON" 2>/dev/null || echo '[]')
178
+ phase_ticks=$(jq -r --argjson labels "$LABELS" '
175
179
  # Normalize both tracker shapes: phase-tracker.sh writes phases as an
176
180
  # array of {id,...}; older fixtures keyed an object by phase id. The
177
181
  # string-index below errors on an array and 2>/dev/null would swallow
@@ -179,8 +183,7 @@ if [ -n "$TRACKER_FILE" ]; then
179
183
  (if (.phases | type) == "array"
180
184
  then (.phases | map({key: (.id | tostring), value: .}) | from_entries)
181
185
  else (.phases // {}) end) as $ph |
182
- ["0 Init","1 Analysis","2 Planning","3 Dev","4 Review","5 Test","6 Commit","7 Report"] as $labels |
183
- [range(0;8) | tostring] as $ids |
186
+ [range(0; ($labels | length)) | tostring] as $ids |
184
187
  $ids
185
188
  | map(. as $i |
186
189
  ($labels[$i | tonumber]) as $label |
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-09-21T11:25:15Z",
4
- "skillCount": 213,
3
+ "generatedAt": "2026-09-21T19:14:20Z",
4
+ "skillCount": 219,
5
5
  "entries": [
6
6
  {
7
7
  "path": "shared/core/apple-archive-compliance/SKILL.md",
@@ -177,12 +177,16 @@
177
177
  },
178
178
  {
179
179
  "path": "shared/core/multi-agent-scan/SKILL.md",
180
- "sha256": "544b29d001e677c579c89705c81c8bc7fb9e8037297e512a2bfad379bd07ee55"
180
+ "sha256": "71cdbf40a40edf80b61985ea4cd6b7d43511e91ad3d7c4121512134a9e94f2a8"
181
181
  },
182
182
  {
183
183
  "path": "shared/core/multi-agent-search/SKILL.md",
184
184
  "sha256": "32541ceca20c489814f9d948763d5237d4a71473a05683d9da262b8f6dea815b"
185
185
  },
186
+ {
187
+ "path": "shared/core/multi-agent-security-review/SKILL.md",
188
+ "sha256": "a3d4d1e99a8be67f179b08d79ee421108f3e62c8d4190cf4e9af7dd4391a8c11"
189
+ },
186
190
  {
187
191
  "path": "shared/core/multi-agent-setup/SKILL.md",
188
192
  "sha256": "d2c167082f40af3c2a7cbb0be55a8cdf8076a4f06a3df54c89b1318759421a29"
@@ -205,7 +209,7 @@
205
209
  },
206
210
  {
207
211
  "path": "shared/core/multi-agent-sync/SKILL.md",
208
- "sha256": "34aea52bbbe86018011edc1c96a986d1ee2aba8bdb82b19212714bac80bea076"
212
+ "sha256": "70b81f275fa430eb7382d0fa16f0a07ec29d92d9476655c496ce38565833fa13"
209
213
  },
210
214
  {
211
215
  "path": "shared/core/multi-agent-test-accessibility/SKILL.md",
@@ -241,7 +245,7 @@
241
245
  },
242
246
  {
243
247
  "path": "shared/core/multi-agent/SKILL.md",
244
- "sha256": "ca1286f3f17d76b75547320ec9b39af575a88828da14b4ed4ffe79932c7406a3"
248
+ "sha256": "7a8a1e51d7e1f9e6cf00e09a5b1d680db4609d79cb629ab4b80e7a27bee5ef5e"
245
249
  },
246
250
  {
247
251
  "path": "shared/external/accessibility-compliance-accessibility-audit/SKILL.md",
@@ -261,23 +265,39 @@
261
265
  },
262
266
  {
263
267
  "path": "shared/external/android-architecture/SKILL.md",
264
- "sha256": "c7bd92eb894b764d60788dd254f4f1379146df0a675451c9fb753e752c58a409"
268
+ "sha256": "29e2b383eda2b9c2ef9ef66901ed812a78428e9e24393814bac21af8e99be7e6"
269
+ },
270
+ {
271
+ "path": "shared/external/android-build-quality-gates/SKILL.md",
272
+ "sha256": "0aea8498b9c1eca98a5ef6e9d5dbc15fbd2fc3946eeb4997306ce4fc4040acc5"
273
+ },
274
+ {
275
+ "path": "shared/external/android-datastore/SKILL.md",
276
+ "sha256": "f858ce6d1b23f1ecd38a403a8095dc6d08b342677ea6c34fb06cf298ba67699f"
277
+ },
278
+ {
279
+ "path": "shared/external/android-design-tokens-codegen/SKILL.md",
280
+ "sha256": "39be5f1e9f70d3d6c22010c4d8b6e831c2ae4bfcb78aff36623595f45ff118c4"
265
281
  },
266
282
  {
267
283
  "path": "shared/external/android-jetpack-compose-expert/SKILL.md",
268
- "sha256": "e52d57212d41b582a05b9d6a631a90fae228070a7d44c2bac66d69a2e7b19329"
284
+ "sha256": "7b76b0e82eca0692d06595098b14cc788c3720df8a1fb36fb38b0ddf92ef1d0a"
285
+ },
286
+ {
287
+ "path": "shared/external/android-mvi-viewmodel/SKILL.md",
288
+ "sha256": "13dbbb3401d5ae9dd2fb38c0826517ce4e0c8632bfabadcb2d9e28187f71b331"
269
289
  },
270
290
  {
271
291
  "path": "shared/external/android-performance/SKILL.md",
272
- "sha256": "6514470492465a6f710166fd8fb7002591ef1be9fe044cb8da1c0e5271e67f74"
292
+ "sha256": "179e51eb020161e05072541eab7b7ee8c97ee6047bb15731b70986547486def3"
273
293
  },
274
294
  {
275
295
  "path": "shared/external/android-security/SKILL.md",
276
- "sha256": "8c46926e05fb3b13c4b912c48762729486c300b0538aa03d0fcc81ce2672a986"
296
+ "sha256": "9d528fbed56fb1b40a34015bbd16b818a4ccccae61e0ab667ad9436efc4ccafa"
277
297
  },
278
298
  {
279
- "path": "shared/external/android_ui_verification/SKILL.md",
280
- "sha256": "775db5a7505c9ede8c3feda4ab592720bd8a5f0ea635ac823795221c98e7d784"
299
+ "path": "shared/external/android-ui-verification/SKILL.md",
300
+ "sha256": "a2653691a3c4ddfddfbc3e6469738932783e8949fc413dfdb6f5a6231f284985"
281
301
  },
282
302
  {
283
303
  "path": "shared/external/api-patterns/SKILL.md",
@@ -285,7 +305,7 @@
285
305
  },
286
306
  {
287
307
  "path": "shared/external/api-security-best-practices/SKILL.md",
288
- "sha256": "ded33c7f6a1174b6abfb8392e3b53712d67b8230178b565e5927885fce6c85f7"
308
+ "sha256": "7560d73037d606de630d67df292d3de499930e25d5a83bb00b543a09fc407b59"
289
309
  },
290
310
  {
291
311
  "path": "shared/external/app-clips/SKILL.md",
@@ -293,7 +313,7 @@
293
313
  },
294
314
  {
295
315
  "path": "shared/external/app-intents/SKILL.md",
296
- "sha256": "18153f28385c034496cb4c7c6e23776bfab8b56b35c8d6f483b32bd49b3181c6"
316
+ "sha256": "1fdde59ad0106f1fad452c8009980118dd907cc7c3a9c1b6a5d84593c4602968"
297
317
  },
298
318
  {
299
319
  "path": "shared/external/app-store-changelog/SKILL.md",
@@ -353,15 +373,15 @@
353
373
  },
354
374
  {
355
375
  "path": "shared/external/compose-components/SKILL.md",
356
- "sha256": "7066b3caf8dafc81a8c03522dd7406fac36307e999c2457558f969023862c2d8"
376
+ "sha256": "b53a55d6235cfed6160caac29d39c7cbc7018019d27183622e9f87dc975ae708"
357
377
  },
358
378
  {
359
379
  "path": "shared/external/compose-navigation/SKILL.md",
360
- "sha256": "cb2c7f31b58c9497a22854c53084775c20472a1778377b54f58b800d7242cd80"
380
+ "sha256": "90a2a1f4a1f4a0fc8a6be1232eb0805dd6a3f3b4a10465964404d784e8d8b94b"
361
381
  },
362
382
  {
363
383
  "path": "shared/external/compose-testing/SKILL.md",
364
- "sha256": "82ff73c20e253194ddff730da75cc01abe2e06a9f13292f1b35f4580c78ac82e"
384
+ "sha256": "e61d74511d688b472f3ad27f9182dcd12a014cff81e98072978ae945f5484196"
365
385
  },
366
386
  {
367
387
  "path": "shared/external/contacts-framework/SKILL.md",
@@ -453,7 +473,7 @@
453
473
  },
454
474
  {
455
475
  "path": "shared/external/gradle-kotlin-dsl/SKILL.md",
456
- "sha256": "e866442b20177bff44745a090401d620dedb006d21d6f70f9bc36c48b0745bde"
476
+ "sha256": "b0eb32ef4fece08d58e89eebb20e906646be9525ae86261f4c02dd8e4acfb9c3"
457
477
  },
458
478
  {
459
479
  "path": "shared/external/healthkit/SKILL.md",
@@ -545,7 +565,7 @@
545
565
  },
546
566
  {
547
567
  "path": "shared/external/kotlin-coroutines-expert/SKILL.md",
548
- "sha256": "c38c1a3a09a615d8cad6bc4f5f433ddf752f9a68b7df5499339b9ad2f9e6f484"
568
+ "sha256": "b2cd2c2b3dc0d0469122080b7fccedb6af01e5aee8fac4ce9cddca6d38f09996"
549
569
  },
550
570
  {
551
571
  "path": "shared/external/live-activities/SKILL.md",
@@ -565,7 +585,7 @@
565
585
  },
566
586
  {
567
587
  "path": "shared/external/mapkit-location/SKILL.md",
568
- "sha256": "449ca065d5e8c1b44450f1b61ea6bc3bae06c1806bee150c3f5eef868a1cb981"
588
+ "sha256": "9c03186a4e7b7bcc06ac67e4d0a00645453b74b26e0be6730e700ba3816254aa"
569
589
  },
570
590
  {
571
591
  "path": "shared/external/metrickit-diagnostics/SKILL.md",
@@ -641,16 +661,20 @@
641
661
  },
642
662
  {
643
663
  "path": "shared/external/retrofit-networking/SKILL.md",
644
- "sha256": "364a13c739105be702bfcd84e9a5711edaf5be5de734918ffcc9544044aee3b2"
664
+ "sha256": "393ae91706d9d810283f4face45a09987f04056833ed81ee3e74e4315fd0d626"
645
665
  },
646
666
  {
647
667
  "path": "shared/external/room-database/SKILL.md",
648
- "sha256": "95be733ec77acc8cbabccc072d59f3faf4f6e083410bf179cc0a6632b746e93e"
668
+ "sha256": "0cb50a75676df1e3e5e3f68781d9d5bede06d4ae8156266dac41ad7ed94705fd"
649
669
  },
650
670
  {
651
671
  "path": "shared/external/search-first/SKILL.md",
652
672
  "sha256": "4730d95babebecb3070a23e09cfce57f687467e0d45bbc332ae612cf01066e10"
653
673
  },
674
+ {
675
+ "path": "shared/external/security-review/SKILL.md",
676
+ "sha256": "6bf82c336b7067d3e8a874e09916fcdbad4867634881f0a58d721b78fc9e83b2"
677
+ },
654
678
  {
655
679
  "path": "shared/external/shareplay-activities/SKILL.md",
656
680
  "sha256": "508e3123039bc114e4239e66d86f8d550fe94fc259d827e4985e0aed3ff54090"
@@ -673,7 +697,7 @@
673
697
  },
674
698
  {
675
699
  "path": "shared/external/storekit/SKILL.md",
676
- "sha256": "749de0129649c043887d2e9c9a751967ffe8852d150c3ae466b4b1319e70f32c"
700
+ "sha256": "45dd3b9a9bfa1c9cbf07da0a104588af9f3bf28d0aeeebbb95abe153a37d02d6"
677
701
  },
678
702
  {
679
703
  "path": "shared/external/swift-api-design-guidelines/SKILL.md",
@@ -833,7 +857,7 @@
833
857
  },
834
858
  {
835
859
  "path": "shared/external/widgetkit/SKILL.md",
836
- "sha256": "b04def841ca62bbb7744f962f8f7d953772e535ec863a14629c6d124f7636397"
860
+ "sha256": "38f1386b076cd9ba8675da5c913a3ddd25424f27fbb34f7f1d33c360f349d059"
837
861
  },
838
862
  {
839
863
  "path": "shared/external/xcode-build-benchmark/SKILL.md",