@mmerterden/multi-agent-pipeline 13.5.0 → 14.0.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.
- package/CHANGELOG.md +243 -0
- package/README.md +3 -3
- package/docs/features.md +1 -1
- package/install/_common.mjs +73 -0
- package/install/_mcp-register.mjs +70 -31
- package/install/_plugin-skills.mjs +73 -14
- package/install/claude.mjs +28 -4
- package/install/codex.mjs +33 -2
- package/install/copilot.mjs +145 -9
- package/install/index.mjs +10 -6
- package/install/templates/copilot-instructions.md +1 -1
- package/package.json +1 -1
- package/pipeline/agents/code-reviewer.md +58 -1
- package/pipeline/commands/multi-agent/SKILL.md +7 -5
- package/pipeline/commands/multi-agent/analysis/SKILL.md +7 -7
- package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/build-optimize/SKILL.md +7 -7
- package/pipeline/commands/multi-agent/channels/SKILL.md +5 -5
- package/pipeline/commands/multi-agent/dev/SKILL.md +23 -18
- package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +19 -13
- package/pipeline/commands/multi-agent/dev-local/SKILL.md +14 -12
- package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +17 -12
- package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/help/SKILL.md +4 -4
- package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +4 -4
- package/pipeline/commands/multi-agent/resume/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/review/SKILL.md +5 -5
- package/pipeline/commands/multi-agent/scan/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/search/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/setup/SKILL.md +6 -6
- package/pipeline/commands/multi-agent/{finish → ship}/SKILL.md +12 -12
- package/pipeline/commands/multi-agent/testflight-validation/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/update/SKILL.md +5 -2
- package/pipeline/commands/sim-test.md +2 -2
- package/pipeline/lib/credential-store-resolver.sh +16 -0
- package/pipeline/lib/credential-store.sh +47 -4
- package/pipeline/lib/fetch-figma-annotations.sh +26 -28
- package/pipeline/lib/figma-screenshot.sh +28 -39
- package/pipeline/lib/figma-token.sh +63 -0
- package/pipeline/multi-agent-refs/analysis-template.md +1 -1
- package/pipeline/multi-agent-refs/android-guide.md +1 -1
- package/pipeline/multi-agent-refs/channels/issue-comment.md +1 -1
- package/pipeline/multi-agent-refs/component-dispatch.md +2 -2
- package/pipeline/multi-agent-refs/cross-cli-contract.md +4 -4
- package/pipeline/multi-agent-refs/features/dev-critic.md +2 -2
- package/pipeline/multi-agent-refs/features/model-fallback.md +35 -2
- package/pipeline/multi-agent-refs/features/plan-todos.md +1 -1
- package/pipeline/multi-agent-refs/features/repo-map.md +1 -1
- package/pipeline/multi-agent-refs/features/review-multi-repo.md +3 -3
- package/pipeline/multi-agent-refs/features/shadow-git.md +1 -1
- package/pipeline/multi-agent-refs/features/skill-conformance.md +116 -0
- package/pipeline/multi-agent-refs/features/verify-by-test.md +1 -1
- package/pipeline/multi-agent-refs/generate-issue.md +1 -1
- package/pipeline/multi-agent-refs/multi-repo-integration-build.md +1 -1
- package/pipeline/multi-agent-refs/phases/log-format.md +4 -4
- package/pipeline/multi-agent-refs/phases/modes.md +7 -7
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +13 -11
- package/pipeline/multi-agent-refs/phases/phase-1-analysis.md +17 -15
- package/pipeline/multi-agent-refs/phases/phase-2-planning.md +7 -7
- package/pipeline/multi-agent-refs/phases/phase-3-dev.md +28 -13
- package/pipeline/multi-agent-refs/phases/phase-4-review.md +90 -58
- package/pipeline/multi-agent-refs/phases/phase-5-test.md +7 -7
- package/pipeline/multi-agent-refs/phases/phase-6-commit.md +8 -8
- package/pipeline/multi-agent-refs/phases/phase-7-report.md +8 -8
- package/pipeline/multi-agent-refs/phases.md +13 -13
- package/pipeline/multi-agent-refs/progress-contract.md +2 -2
- package/pipeline/multi-agent-refs/rules.md +7 -5
- package/pipeline/multi-agent-refs/swiftui-guide.md +1 -1
- package/pipeline/multi-agent-refs/tracker-contract.md +16 -15
- package/pipeline/preferences-template.json +7 -1
- package/pipeline/rules/figma-pipeline.md +2 -2
- package/pipeline/schemas/agent-state.schema.json +333 -79
- package/pipeline/schemas/criteria-manifest.schema.json +228 -0
- package/pipeline/schemas/migrations/prefs-2.4.0-to-2.5.0.mjs +64 -0
- package/pipeline/schemas/prefs.schema.json +118 -262
- package/pipeline/schemas/reviewer-output.schema.json +48 -3
- package/pipeline/schemas/token-budget.json +34 -10
- package/pipeline/schemas/triage-output.schema.json +112 -27
- package/pipeline/scripts/cost-table.json +7 -4
- package/pipeline/scripts/gc-worktrees.sh +1 -1
- package/pipeline/scripts/gen-mode-dispatch.mjs +6 -6
- package/pipeline/scripts/match-skills.mjs +37 -4
- package/pipeline/scripts/migrate-prefs.mjs +88 -17
- package/pipeline/scripts/phase-tracker.sh +14 -3
- package/pipeline/scripts/pre-commit-check.sh +49 -2
- package/pipeline/scripts/skill-conformance.mjs +960 -0
- package/pipeline/scripts/smoke-schema-validation.sh +17 -4
- package/pipeline/scripts/uninstall.mjs +35 -9
- package/pipeline/scripts/validate-reviewer.mjs +108 -1
- package/pipeline/skills/.skill-manifest.json +1 -1
- package/pipeline/skills/.skills-index.json +36 -9
- package/pipeline/skills/shared/README.md +15 -12
- package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +1 -0
- package/pipeline/skills/shared/core/apple-archive-compliance/references/rules.yml +167 -0
- package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +1 -0
- package/pipeline/skills/shared/core/google-play-compliance/references/rules.yml +184 -0
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +10 -10
- package/pipeline/skills/shared/core/multi-agent-analysis/SKILL.md +4 -4
- package/pipeline/skills/shared/core/multi-agent-analysis-resolve/SKILL.md +3 -3
- package/pipeline/skills/shared/core/multi-agent-build-optimize/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-create-jira/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-dev/SKILL.md +6 -5
- package/pipeline/skills/shared/core/multi-agent-dev-autopilot/SKILL.md +7 -6
- package/pipeline/skills/shared/core/multi-agent-dev-local/SKILL.md +4 -3
- package/pipeline/skills/shared/core/multi-agent-dev-local-autopilot/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +4 -4
- package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +5 -5
- package/pipeline/skills/shared/core/multi-agent-scan/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-search/SKILL.md +1 -1
- package/pipeline/skills/shared/core/{multi-agent-finish → multi-agent-ship}/SKILL.md +8 -8
- package/pipeline/skills/shared/external/ios-coding-standard/SKILL.md +44 -5
- package/pipeline/skills/shared/external/ios-coding-standard/modules/_TEMPLATE.yml +82 -0
- package/pipeline/skills/shared/external/ios-coding-standard/references/STANDARD.md +169 -10
- package/pipeline/skills/shared/external/ios-coding-standard/references/lint-local.sh +13 -1
- package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +335 -16
- package/pipeline/skills/skills-index.md +11 -8
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,249 @@ Internal file-layout changes that don't affect the slash-command surface are sti
|
|
|
16
16
|
|
|
17
17
|
## [Unreleased]
|
|
18
18
|
|
|
19
|
+
## [14.0.0] - 2026-07-29
|
|
20
|
+
|
|
21
|
+
The `--dev` family reviews its own work, and review now checks the code against the
|
|
22
|
+
criteria it was supposed to be built to instead of general good taste.
|
|
23
|
+
|
|
24
|
+
Major because a command was renamed: `/multi-agent:finish` is now `/multi-agent:ship`.
|
|
25
|
+
No alias is kept - `installCommands()` replaces the command tree wholesale, so a clean
|
|
26
|
+
cut leaves nothing half-migrated.
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- **Phase 4 Review runs in `dev`, `dev-autopilot`, `dev-local` and `dev-local-autopilot`.**
|
|
31
|
+
Phase sets become `0/3/4/5/6/7` for `dev` and `0/3/4/6/7` for the three variants, in
|
|
32
|
+
`gen-mode-dispatch.mjs` (the generator is the source of truth; the tracker sections are
|
|
33
|
+
regenerated from it, never hand-edited). Analysis and planning are still skipped: they
|
|
34
|
+
shape work that has not happened yet, so a task the user has already scoped can do
|
|
35
|
+
without them, while review judges work that now exists and has no substitute. Accepted
|
|
36
|
+
blocking findings return to Phase 3 under the existing 3-iteration hard kill; the
|
|
37
|
+
autopilot variants auto-fix without prompting and halt on the existing rework-storm
|
|
38
|
+
circuit breaker rather than committing. No new machinery for either - both were already
|
|
39
|
+
built and became reachable the moment Phase 4 entered the phase set.
|
|
40
|
+
- **Phase 4 Step 1.78, criteria resolution** (`skill-conformance.mjs`, zero LLM). Resolves
|
|
41
|
+
which rule registries apply to this diff, scoped to its languages and paths, and writes
|
|
42
|
+
`criteria-manifest.json` BEFORE the reviewers run. That file is the denominator: reviewers
|
|
43
|
+
return one verdict per selected rule ID, so "did it apply this completely" is answerable
|
|
44
|
+
rather than inferred. A reviewer that opened nothing and one that checked everything
|
|
45
|
+
produce identical `findings` arrays, which is why the checklist exists.
|
|
46
|
+
- **Registry discovery is declared, never sniffed by name.** A skill opts in with
|
|
47
|
+
`standards-registry: <path>` in its frontmatter, so the pipeline names no stack-specific
|
|
48
|
+
skill and a future UIKit, Objective-C, Kotlin or backend registry drops in with zero
|
|
49
|
+
pipeline change.
|
|
50
|
+
- **Every registry declares its own `scope`** (`languages`, `paths`, `excludePaths`,
|
|
51
|
+
`notCovered`), and per-rule `scope` narrows it further. Measured before this landed: only
|
|
52
|
+
2 of the iOS registry's 99 rules carried any applicability field, and the rest wrote their
|
|
53
|
+
scope as English prose inside `mechanism` - so an Objective-C or UIKit diff would have
|
|
54
|
+
collected all 99 SwiftUI-shaped rules, manufacturing findings and burying the real ones.
|
|
55
|
+
- `references/rules.yml` for `apple-archive-compliance` (18 rules) and
|
|
56
|
+
`google-play-compliance` (21), converted from their existing SKILL.md tables with IDs and
|
|
57
|
+
severities preserved. Registry count goes 1 -> 3, so discovery is genuinely
|
|
58
|
+
capability-based rather than one skill with extra steps.
|
|
59
|
+
- **Exception-marker audit**: expired, reason-less, expiry-less, or unknown-ID
|
|
60
|
+
`standard:exception(...)` markers become rule-ID-bearing findings. The marker template is
|
|
61
|
+
read from the registry, never hardcoded, so a registry with different comment syntax works.
|
|
62
|
+
- `state.telemetry.skillCalls[]` - Phase 3 records each skill, plugin skill and guide it
|
|
63
|
+
consulted, with the files it applied them to.
|
|
64
|
+
- `prefs.global.skillConformance.blockOnCoverageGap` (default **false**) and
|
|
65
|
+
`prefs.global.ship.autoFix`, the latter referenced by the tail command's spec since it
|
|
66
|
+
shipped but never actually declared. Prefs schema 2.4.0 -> 2.5.0 with a migration that
|
|
67
|
+
carries any existing `finish.autoFix` value across the rename.
|
|
68
|
+
- Gates: `smoke-dev-mode-review.sh`, `smoke-skill-conformance.sh` (26 checks),
|
|
69
|
+
`smoke-skills-root-resolution.sh`. `smoke-subagent-validators.sh` grew 10 checks for the
|
|
70
|
+
conformance contract; `smoke-mode-dispatch-drift.sh` grew a step that asserts the negative,
|
|
71
|
+
because its per-phase `grep -Fq` could not catch an ABSENT phase.
|
|
72
|
+
|
|
73
|
+
### Changed
|
|
74
|
+
|
|
75
|
+
- **`/multi-agent:finish` -> `/multi-agent:ship`.** "Finish" never said what it did. The
|
|
76
|
+
command takes work already sitting on a branch through review, a build+test gate, PR and
|
|
77
|
+
report; `ship` says that. With review now inside the dev modes, its remaining job is work
|
|
78
|
+
that had no pipeline run behind it, rather than a patch for a mode that skipped review.
|
|
79
|
+
- **Reviewers cite rule IDs.** `reviewer-output.schema.json` 1.0.0 -> 1.1.0 adds `ruleId`,
|
|
80
|
+
`criteriaSource` and the per-rule `conformance[]` array; `triage-output.schema.json`
|
|
81
|
+
3.2.0 -> 3.3.0 carries both through triage. `code-reviewer.md` gains a `${CRITERIA}`
|
|
82
|
+
injection slot - the phase doc had claimed for some time that "skills are injected into
|
|
83
|
+
reviewer prompt context" while the agent definition had no slot for them, which is exactly
|
|
84
|
+
why review could not cite a rule.
|
|
85
|
+
- `validate-reviewer.mjs` enforces the checklist with `--criteria`: a selected ID with no
|
|
86
|
+
verdict, a verdict for an ID that was never selected, a `conformant` row with no file
|
|
87
|
+
evidence, and a `violated` row with no matching finding all fail. Without this the field
|
|
88
|
+
would be decoration - the validator is hand-written and does not apply
|
|
89
|
+
`additionalProperties`, so any array at all would have passed.
|
|
90
|
+
- `${CRITERIA}` lives in the shared cacheable prefix, identical for every reviewer.
|
|
91
|
+
Subsetting it per reviewer would invalidate the prefix for the whole panel and re-bill the
|
|
92
|
+
largest block in the phase.
|
|
93
|
+
- Phase 4 no longer transcribes SwiftUI interaction and accessibility rules inline; they
|
|
94
|
+
resolve from the registry that declares SwiftUI scope, so criteria and severities live in
|
|
95
|
+
one place. This removed the drift the release exists to close, and reclaimed budget.
|
|
96
|
+
- Phase 3's pre-flight no longer aborts unconditionally on a missing analysis document.
|
|
97
|
+
Steps that read that document are recorded `not-applicable (no Phase 1 in this mode)`,
|
|
98
|
+
which is what the `--dev` family always needed and never had.
|
|
99
|
+
- `migrate-prefs.mjs` derives its migratable-version set instead of enumerating one
|
|
100
|
+
`else if` per version. The old chain had to be extended by hand whenever
|
|
101
|
+
`TARGET_VERSION` moved, and forgetting made the migrator throw `unknown schemaVersion`
|
|
102
|
+
on the exact version it had just been released to migrate from.
|
|
103
|
+
- Token budget 51500 -> 52200, after 820 tokens were compressed first, per the note in
|
|
104
|
+
`token-budget.json`.
|
|
105
|
+
|
|
106
|
+
### Fixed
|
|
107
|
+
|
|
108
|
+
- **`ios-coding-standard` in the repo was a version behind the installed copy** (95 rules
|
|
109
|
+
v1.0.0 vs 99 rules v1.1.0). Because `installSkills()` copies the repo over the
|
|
110
|
+
destination, the next `/multi-agent:update` would have downgraded a user's registry -
|
|
111
|
+
harmless while nothing read it, a correctness regression the moment Phase 4 blocks on rule
|
|
112
|
+
IDs. Resynced to 99 rules.
|
|
113
|
+
- `modules/*.yml` overlays and `references/EXAMPLES.md` are deliberately NOT shipped: they
|
|
114
|
+
quote real module paths, symbol names and call-site counts from one codebase, so a shipped
|
|
115
|
+
overlay would bind another project's dialect slots to the wrong dialect. `modules/_TEMPLATE.yml`
|
|
116
|
+
carries the shape instead, and the skill states that an unbound slot disables its rules
|
|
117
|
+
rather than defaulting silently.
|
|
118
|
+
- `tracker-contract.md` contradicted itself on skipped phases: one section said to
|
|
119
|
+
`TaskCreate` every phase "even if it will be skipped in this mode", another said omitted
|
|
120
|
+
phases get no `TaskCreate` at all. The mode files and the generator follow the second, so
|
|
121
|
+
the example now matches it.
|
|
122
|
+
- Three docs disagreed on whether `--dev` runs Phase 5. They now agree with the generator.
|
|
123
|
+
- The dispatcher's modular-loading table had no row for `dev` or `dev-autopilot`.
|
|
124
|
+
|
|
125
|
+
## [13.6.0] - 2026-07-28
|
|
126
|
+
|
|
127
|
+
A review pass across the pipeline, the plugin marketplace and the companion MCP server.
|
|
128
|
+
Every gate was green before it started: each defect below was invisible to the suite,
|
|
129
|
+
and most were invisible because a gate asserted the wrong half of the contract. New
|
|
130
|
+
gates are named per item.
|
|
131
|
+
|
|
132
|
+
### Fixed
|
|
133
|
+
|
|
134
|
+
- **The Tier 2 Figma PAT could not be found on any migrated install.** `migrate-prefs.mjs`
|
|
135
|
+
consolidated `keychainMapping.figma_pat` into `.figma` and deleted the old key, but the
|
|
136
|
+
setup wizard kept *writing* `figma_pat` (so the next migration deleted the mapping it
|
|
137
|
+
had just created), both Tier 2 fetchers kept *reading* it, and the failure text told the
|
|
138
|
+
user to map the one key guaranteed not to survive. Tier 2 reported `missing-token` while
|
|
139
|
+
a valid PAT sat under the new name. Claude Code's MCP tier masked it; Copilot and Codex,
|
|
140
|
+
which serve no Figma MCP tools at all, fell straight to Tier 3. The lookup now lives once
|
|
141
|
+
in `lib/figma-token.sh` (canonical `figma`, legacy `figma_pat` as a fallback) rather than
|
|
142
|
+
duplicated per fetcher, which is what let the two copies drift from the migration.
|
|
143
|
+
Gate: `smoke-credential-key-alignment.sh`.
|
|
144
|
+
- **Claude Code installs registered no MCP server**, so every skill that reaches for a
|
|
145
|
+
`dev-toolkit` tool - `design-check`, the `ios_*` / `android_*` calls, the archive audits -
|
|
146
|
+
had nothing to call. Codex had registration from day one and Copilot gained it later;
|
|
147
|
+
Claude Code was last and easiest to miss, because a maintainer who registered it by hand
|
|
148
|
+
once sees a working tree forever. `--scope user` is not optional: `claude mcp add`
|
|
149
|
+
defaults to project scope, binding the server to whichever directory the installer ran
|
|
150
|
+
in. Uninstall now deregisters it too. The gate that existed to catch this iterated
|
|
151
|
+
`codex copilot`, so it structurally could not; the host list is now derived from the
|
|
152
|
+
installers on disk.
|
|
153
|
+
- **`@mmerterden/dev-toolkit-mcp` was not on the public npm registry**, while all three
|
|
154
|
+
installers register `npx -y @mmerterden/dev-toolkit-mcp`, which resolves from it. GitHub
|
|
155
|
+
Packages answers 401 to unauthenticated reads even for public packages, so pointing npx
|
|
156
|
+
there is not an alternative. Registration never failed - it only writes host config - so
|
|
157
|
+
the break surfaced later as an E404 on first tool use, and only for users whose `~/.npmrc`
|
|
158
|
+
lacked a scope redirect. Published; `publishConfig` now matches the pipeline's.
|
|
159
|
+
Gate: `smoke-mcp-package-resolvable.sh`.
|
|
160
|
+
- **157 instruction paths resolved only from a repo checkout.** `node pipeline/scripts/x.mjs`
|
|
161
|
+
works when cwd is this repo's root, which is never true during a run: the pipeline works
|
|
162
|
+
inside a worktree of the user's project. The class covered 85 script invocations (bare,
|
|
163
|
+
`node`-prefixed, env-prefixed, `&&`-chained), 16 skill references, 12 rules references,
|
|
164
|
+
and 4 files invoking `"$REPO_ROOT/pipeline/scripts/..."` where `$REPO_ROOT` was never
|
|
165
|
+
assigned anywhere in the pipeline. Gate: `smoke-install-relative-paths.sh`, which
|
|
166
|
+
enumerates the whole class rather than the instances.
|
|
167
|
+
- **`dynamicSkillLoading` had never worked on an installed tree.** Three breaks stacked: a
|
|
168
|
+
full install did not ship `.skills-index.json` (while the schema said it always did),
|
|
169
|
+
`match-skills.mjs` resolved its default index to `$HOME/pipeline/skills/...` from
|
|
170
|
+
`~/.claude/scripts/`, and the runtime contract in the orchestrator invoked a
|
|
171
|
+
repo-relative path. Its own smoke passed `--index` explicitly and ran from the repo, so
|
|
172
|
+
it never exercised either failure. The index now ships on every install for all three
|
|
173
|
+
hosts via one shared helper.
|
|
174
|
+
- **Two compliance skills were unreachable on Codex.** `apple-archive-compliance` and
|
|
175
|
+
`google-play-compliance` live in `skills/shared/core`, and Codex's installer copied only
|
|
176
|
+
`shared/external`. `smoke-compliance-skills.sh` asserted both were wired to four
|
|
177
|
+
consumers - reading the source tree, with no host awareness - so it passed while the host
|
|
178
|
+
had nothing to load. Gate: cross-host reachability parity in `smoke-install-layout.sh`.
|
|
179
|
+
- **A plugin skill whose name a pipeline skill also owns was dropped on the copy hosts.**
|
|
180
|
+
Claude Code reaches both because its loader namespaces plugin skills; Copilot and Codex
|
|
181
|
+
copy flat, so the iOS toolkit's `architecture` (that stack's structural rules) was
|
|
182
|
+
silently unreachable behind the pipeline's generic ADR skill, as was its `backlog` and
|
|
183
|
+
the common toolkit's `index`. Clashes are now plugin-prefixed, the flat analogue of what
|
|
184
|
+
the loader does, so the pipeline keeps the bare name and nothing is lost.
|
|
185
|
+
- **Plugin skills were frozen after their first install.** `skipNames` was a snapshot of
|
|
186
|
+
the destination, so a plugin skill matched its own previous copy and was skipped forever,
|
|
187
|
+
outliving every upstream fix in it. It is now the pipeline-owned set derived from the
|
|
188
|
+
source tree, and each plugin skill dir is wiped before refill so a file dropped upstream
|
|
189
|
+
does not linger.
|
|
190
|
+
- **Copilot skills referenced trees Copilot does not install.** Its skills were copied
|
|
191
|
+
byte-for-byte, so 15 references pointed at `~/.claude/scripts` and `~/.claude/lib` -
|
|
192
|
+
absent on a Copilot-only machine. Codex has had this rewrite since its installer was
|
|
193
|
+
written. Copilot now rewrites the trees it owns, and installs `rules/` as well, closing
|
|
194
|
+
the same silent-nothing fallback Codex's installer already documented.
|
|
195
|
+
- **`phase-tracker.sh` never reclaimed a stale lock on Linux.** It tried `stat -f %m`
|
|
196
|
+
before `stat -c %Y`, and on GNU coreutils `stat -f` is a *valid* flag (`--file-system`,
|
|
197
|
+
where `%m` is the mount point): it succeeds, returns something like `/`, the `||` never
|
|
198
|
+
fires, and the age arithmetic runs on a path. Every tracker call then spun the full ~5s
|
|
199
|
+
bound and fell open with a warning. Every other `stat` call site in the repo already had
|
|
200
|
+
the order right.
|
|
201
|
+
- **The portability gate accepted any file containing `||` anywhere in it**, which is every
|
|
202
|
+
shell script in the repo, so an unguarded BSD/GNU construct shipped green - the
|
|
203
|
+
`phase-tracker` bug above rode through that hole for releases. The guard is now checked
|
|
204
|
+
on the hit's own line, plus a dedicated `stat` ordering rule.
|
|
205
|
+
- **Model IDs were a generation behind.** 21 references dispatched `claude-opus-4-8` and
|
|
206
|
+
`claude-sonnet-4-6` on the opus/sonnet rungs. Both are still valid upstream, so nothing
|
|
207
|
+
errored - Phase 4's reviewer panel, the dev phase and the whole fallback ladder simply ran
|
|
208
|
+
the previous generation. Now `claude-opus-5` and `claude-sonnet-5`, with the rung/ID split
|
|
209
|
+
stated in the fallback contract. Gate: a generation guard in `smoke-model-fallback.sh`.
|
|
210
|
+
- **`rules/pipeline-output-formatting.md` did not exist.** Two shipped skills named it as
|
|
211
|
+
the PR-body contract to follow. The install-layout smoke had been *reporting* paths that
|
|
212
|
+
exist on no host as a note rather than failing on them, which is how it survived; that
|
|
213
|
+
claim is now strict.
|
|
214
|
+
- **A pre-migration preferences file failed validation before the migration could fix it.**
|
|
215
|
+
`firebase_sa`, `firebase_project` and `servicePatMap.figma_pat` are deleted by
|
|
216
|
+
`migrate-prefs.mjs` but were undeclared under `additionalProperties: false`. Declared as
|
|
217
|
+
deprecated tolerance slots, and the deprecated/deleted sets are now checked against each
|
|
218
|
+
other in both halves of the schema.
|
|
219
|
+
- `state.figmaAccess` was a BLOCKING pipeline-wide contract that the state schema never
|
|
220
|
+
declared. Declared, including the new `tier1Unavailable` cause.
|
|
221
|
+
- Four credential-store corrections: the resolver overwrote a pre-set `CRED_STORE` while
|
|
222
|
+
documenting it as an override; `doctor` advertised an env-var fallback that `get` does not
|
|
223
|
+
implement; `resolve_key` read `keychainMapping` with python3 only, so on a Windows box
|
|
224
|
+
without it the mapping was skipped in silence and a present token read as missing (node
|
|
225
|
+
is now the second reader, and node is guaranteed wherever this installs); and macOS
|
|
226
|
+
`list` read the item LABEL rather than the SERVICE attribute `get` looks up.
|
|
227
|
+
- `lint-mcp-refs.mjs` printed one checkmark for two different claims - "the server name is
|
|
228
|
+
recognised" and "its tools were verified to exist" - so Figma and XcodeBuildMCP read as
|
|
229
|
+
verified when nothing had probed them. Verified and declared are now distinguished.
|
|
230
|
+
- 15 orphan `<name>.md` files from a pre-directory install layout sat in a real
|
|
231
|
+
`~/.copilot/skills`, shadowing the proper directory form. Pruned, on both copy hosts.
|
|
232
|
+
|
|
233
|
+
### Changed
|
|
234
|
+
|
|
235
|
+
- Figma Tier 1 availability is now host-aware. Absent tools (the normal case on Copilot and
|
|
236
|
+
Codex, where the installer registers only `dev-toolkit`) record
|
|
237
|
+
`figmaAccess.tier1Unavailable = "host"` and fall straight to Tier 2 - no probe, no
|
|
238
|
+
re-auth retry, and no "recreate the MCP token" question the user cannot act on. Tools
|
|
239
|
+
present but failing auth is `"auth"`, where the retry does apply. On those two hosts a
|
|
240
|
+
mapped `figma` PAT is the primary path, not a fallback.
|
|
241
|
+
- Phase-doc token budgets recalibrated. The path correction above cost 196 tokens of pure
|
|
242
|
+
correctness; 149 were reclaimed first by compression (Phase 1's Figma tier table now
|
|
243
|
+
points at the Phase 0 probe that already resolved it; Phase 4's Codex constraints point
|
|
244
|
+
at the always-loaded AGENTS.md block). Five warn lines had been permanently amber, which
|
|
245
|
+
makes the amber tier useless as a signal - every warn reset to the documented
|
|
246
|
+
current+10%, the four maxes the new warn would have collided with to current+25%,
|
|
247
|
+
aggregate 51000 -> 51500.
|
|
248
|
+
- The install summary told users to register the Claude Code MCP server by hand. It no
|
|
249
|
+
longer does, because the installer does it.
|
|
250
|
+
|
|
251
|
+
## [13.5.1] - 2026-07-28
|
|
252
|
+
|
|
253
|
+
### Fixed
|
|
254
|
+
|
|
255
|
+
- `ios-coding-standard`'s local lint runner defaulted its scratch root to
|
|
256
|
+
`~/.claude/local/...`. On a Copilot- or Codex-only machine that made the script create
|
|
257
|
+
a stray `.claude` directory belonging to a host that is not installed, just to hold
|
|
258
|
+
lint output. It now picks whichever host tree exists and falls back to `$TMPDIR`.
|
|
259
|
+
Found by the Codex sync verification, which flags any `$HOME/.claude/...` reference
|
|
260
|
+
that reaches the Codex tree.
|
|
261
|
+
|
|
19
262
|
## [13.5.0] - 2026-07-28
|
|
20
263
|
|
|
21
264
|
### Fixed
|
package/README.md
CHANGED
|
@@ -60,9 +60,9 @@ The discipline behind all of this - bounded loops, evidence gates, token-budgete
|
|
|
60
60
|
|---|---|---|
|
|
61
61
|
| Full | `/multi-agent "task"` | All 8 phases, interactive |
|
|
62
62
|
| Autopilot | `/multi-agent:autopilot "task"` | All 8 phases, no confirmations |
|
|
63
|
-
| Dev | `/multi-agent:dev "task"` | Init → Dev → Commit → Report |
|
|
63
|
+
| Dev | `/multi-agent:dev "task"` | Init → Dev → Review → Test → Commit → Report |
|
|
64
64
|
| Local | `/multi-agent:local "task"` | Full pipeline, current branch (no worktree) |
|
|
65
|
-
| Finish | `/multi-agent:
|
|
65
|
+
| Finish | `/multi-agent:ship` | Run the review→test→commit→report tail over local work |
|
|
66
66
|
| Audit | `/multi-agent:design-check` | Mock-mode vs Figma conformance, local-only |
|
|
67
67
|
| Audit | `/multi-agent:testflight-validation` | Pre-submission gates for a TestFlight build: static archive audit → Apple's `altool --validate-app` → Review-Guidelines check. Validates only, never uploads |
|
|
68
68
|
|
|
@@ -131,7 +131,7 @@ Runs on **macOS**, **Linux**, and **Windows** (Git Bash / WSL). Shell and creden
|
|
|
131
131
|
| Repo | What it is |
|
|
132
132
|
|---|---|
|
|
133
133
|
| [`mmerterden/multi-agent-plugins`](https://github.com/mmerterden/multi-agent-plugins) | Marketplace of per-stack skill toolkits (iOS / Android / Frontend / Backend + common). `/multi-agent:stack` enables the matching plugin. |
|
|
134
|
-
| [`mmerterden/dev-toolkit-mcp`](https://github.com/mmerterden/dev-toolkit-mcp) | MCP server for UI testing / simulator capture / xcodebuild - powers the Phase 5 UI Bug Hunter.
|
|
134
|
+
| [`mmerterden/dev-toolkit-mcp`](https://github.com/mmerterden/dev-toolkit-mcp) | MCP server for UI testing / simulator capture / xcodebuild - powers the Phase 5 UI Bug Hunter. Published on the public npm registry as [`@mmerterden/dev-toolkit-mcp`](https://www.npmjs.com/package/@mmerterden/dev-toolkit-mcp); the installer registers it with each CLI for you, so `npx` resolves it with no extra configuration. |
|
|
135
135
|
|
|
136
136
|
## License
|
|
137
137
|
|
package/docs/features.md
CHANGED
|
@@ -26,7 +26,7 @@ Each phase reads its own spec file under `pipeline/multi-agent-refs/phases/phase
|
|
|
26
26
|
| Flag | Effect |
|
|
27
27
|
| ----------- | ----------------------------------------------------------------------------------- |
|
|
28
28
|
| `autopilot` | Skip all confirmation prompts; still fails safe on review blockers + build retries. |
|
|
29
|
-
| `--dev` | Strip to Init → Dev(Opus self-contained) → Commit → Report. No Phase 1/2
|
|
29
|
+
| `--dev` | Strip to Init → Dev(Opus self-contained) → Review → Test → Commit → Report. No Phase 1/2. |
|
|
30
30
|
| `--local` | No worktree - works directly in `$PROJECT_ROOT` on a local branch. |
|
|
31
31
|
|
|
32
32
|
Compose freely: `--dev --local autopilot` = shortest, least-friction path.
|
package/install/_common.mjs
CHANGED
|
@@ -321,6 +321,79 @@ export function copyFile(src, dest) {
|
|
|
321
321
|
cpSync(src, dest, { force: true });
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Remove `<name>.md` files left at the root of a skills tree by the
|
|
326
|
+
* pre-directory install layout.
|
|
327
|
+
*
|
|
328
|
+
* Skills have been `<name>/SKILL.md` for a long time, but an older layout wrote
|
|
329
|
+
* `<name>.md` flat. Nothing ever pruned those, so 15 of them sat in
|
|
330
|
+
* `~/.copilot/skills` indefinitely: dead duplicates of skills that also existed in
|
|
331
|
+
* their proper directory form, inflating the skill count and offering the host a
|
|
332
|
+
* second, stale copy of the same guidance.
|
|
333
|
+
*
|
|
334
|
+
* The prune condition is deliberately narrow: a file is removed ONLY when a
|
|
335
|
+
* directory of the same name sits beside it. That is unambiguous evidence of the old
|
|
336
|
+
* layout, and it cannot touch a note a user dropped in the tree - a loose `.md` is
|
|
337
|
+
* not a discoverable skill on any host, so there is no valid file this rule can hit.
|
|
338
|
+
*
|
|
339
|
+
* @param {string} skillsDir
|
|
340
|
+
* @returns {number} files removed
|
|
341
|
+
*/
|
|
342
|
+
export function pruneOrphanSkillFiles(skillsDir) {
|
|
343
|
+
if (!existsSync(skillsDir)) return 0;
|
|
344
|
+
let entries;
|
|
345
|
+
try {
|
|
346
|
+
entries = readdirSync(skillsDir, { withFileTypes: true });
|
|
347
|
+
} catch {
|
|
348
|
+
return 0;
|
|
349
|
+
}
|
|
350
|
+
const dirNames = new Set(entries.filter((e) => e.isDirectory()).map((e) => e.name));
|
|
351
|
+
let removed = 0;
|
|
352
|
+
for (const entry of entries) {
|
|
353
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
354
|
+
const base = entry.name.slice(0, -3);
|
|
355
|
+
if (!dirNames.has(base)) continue;
|
|
356
|
+
if (dryRun) {
|
|
357
|
+
console.log(` [dry-run] would prune stale flat skill file ${join(skillsDir, entry.name)}`);
|
|
358
|
+
removed++;
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
try {
|
|
362
|
+
rmSync(join(skillsDir, entry.name), { force: true });
|
|
363
|
+
removed++;
|
|
364
|
+
} catch {
|
|
365
|
+
/* non-fatal */
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return removed;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Ship the skills index into a host's skills tree.
|
|
373
|
+
*
|
|
374
|
+
* `.skills-index.json` is what `match-skills.mjs` reads when
|
|
375
|
+
* `prefs.global.dynamicSkillLoading` is on; `skills-index.md` is the human-readable
|
|
376
|
+
* twin. Both used to be copied ONLY under `--index-only`, while the preferences
|
|
377
|
+
* schema stated that a full install always ships the index. It did not, so turning
|
|
378
|
+
* the pref on after a normal install produced `cannot read index` and a silent
|
|
379
|
+
* fall back to eager loading. One helper, called from every target, so the two
|
|
380
|
+
* install modes cannot disagree about it again.
|
|
381
|
+
*
|
|
382
|
+
* @param {string} pipelineSrc - the repo's `pipeline/` directory
|
|
383
|
+
* @param {string} skillsDest - the host's skills directory
|
|
384
|
+
* @returns {number} files copied
|
|
385
|
+
*/
|
|
386
|
+
export function copySkillsIndex(pipelineSrc, skillsDest) {
|
|
387
|
+
let copied = 0;
|
|
388
|
+
for (const name of [".skills-index.json", "skills-index.md"]) {
|
|
389
|
+
const src = join(pipelineSrc, "skills", name);
|
|
390
|
+
if (!existsSync(src)) continue;
|
|
391
|
+
copyFile(src, join(skillsDest, name));
|
|
392
|
+
copied++;
|
|
393
|
+
}
|
|
394
|
+
return copied;
|
|
395
|
+
}
|
|
396
|
+
|
|
324
397
|
/**
|
|
325
398
|
* Write a file, honouring dry-run mode.
|
|
326
399
|
* @param {string} path
|
|
@@ -3,15 +3,20 @@
|
|
|
3
3
|
*
|
|
4
4
|
* WHY THIS IS SHARED
|
|
5
5
|
*
|
|
6
|
-
* Codex got MCP registration when its installer was written; Copilot never did
|
|
7
|
-
* result was a host that carried
|
|
8
|
-
* one of the 80 dev-toolkit tools those skills depend
|
|
9
|
-
* `ios_*` / `android_*` simulator call, the archive audits.
|
|
10
|
-
* time, and the skills only fail when a run actually reaches
|
|
6
|
+
* Codex got MCP registration when its installer was written; Copilot never did, and
|
|
7
|
+
* neither did Claude Code. The result was a host that carried the full pipeline skill
|
|
8
|
+
* set and could not call a single one of the 80 dev-toolkit tools those skills depend
|
|
9
|
+
* on - `design-check`, every `ios_*` / `android_*` simulator call, the archive audits.
|
|
10
|
+
* Nothing failed at install time, and the skills only fail when a run actually reaches
|
|
11
|
+
* for a tool.
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* Claude Code was the longest-lived instance of that bug and the easiest to miss,
|
|
14
|
+
* because a maintainer who registered the server by hand once sees a working tree
|
|
15
|
+
* forever while every fresh install ships without it.
|
|
16
|
+
*
|
|
17
|
+
* All three CLIs take the same shape (`<cli> mcp add [scope] <name> -- <command>
|
|
18
|
+
* [args...]`), so the difference was purely which installers called it. One
|
|
19
|
+
* implementation, three callers.
|
|
15
20
|
*
|
|
16
21
|
* @module install/_mcp-register
|
|
17
22
|
*/
|
|
@@ -27,17 +32,35 @@ export const MCP_SERVER_NAME = "dev-toolkit";
|
|
|
27
32
|
export const MCP_SERVER_PACKAGE = "@mmerterden/dev-toolkit-mcp";
|
|
28
33
|
|
|
29
34
|
/**
|
|
30
|
-
*
|
|
35
|
+
* Per-host CLI invocation details.
|
|
31
36
|
*
|
|
32
|
-
* A bare name is resolved through PATH by
|
|
33
|
-
* Copilot: `copilot` is frequently a
|
|
34
|
-
*
|
|
35
|
-
*
|
|
37
|
+
* `candidates`: where the CLI might live. A bare name is resolved through PATH by
|
|
38
|
+
* execFileSync. The explicit paths matter for Copilot: `copilot` is frequently a
|
|
39
|
+
* *shell alias* for `gh copilot`, and an alias is invisible to execFileSync - so a
|
|
40
|
+
* PATH-only lookup reports "not on PATH" on a machine where the user runs
|
|
41
|
+
* `copilot` every day. `gh` installs the real binary under
|
|
36
42
|
* ~/.local/share/gh/copilot/.
|
|
43
|
+
*
|
|
44
|
+
* `scopeArgs`: options inserted before the server name. Only Claude Code needs
|
|
45
|
+
* them, and it needs them badly: `claude mcp add` defaults to `--scope local`,
|
|
46
|
+
* which binds the server to whichever directory the installer happened to run
|
|
47
|
+
* in. Every other project would then see no dev-toolkit server at all. Codex
|
|
48
|
+
* writes `~/.codex/config.toml` and Copilot writes `~/.copilot/mcp-config.json`,
|
|
49
|
+
* both already user-global, so both take no scope flag - passing one errors.
|
|
37
50
|
*/
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
51
|
+
const HOSTS = Object.freeze({
|
|
52
|
+
claude: {
|
|
53
|
+
candidates: ["claude", join(homedir(), ".local", "bin", "claude")],
|
|
54
|
+
scopeArgs: ["--scope", "user"],
|
|
55
|
+
},
|
|
56
|
+
codex: {
|
|
57
|
+
candidates: ["codex"],
|
|
58
|
+
scopeArgs: [],
|
|
59
|
+
},
|
|
60
|
+
copilot: {
|
|
61
|
+
candidates: ["copilot", join(homedir(), ".local", "share", "gh", "copilot", "copilot")],
|
|
62
|
+
scopeArgs: [],
|
|
63
|
+
},
|
|
41
64
|
});
|
|
42
65
|
|
|
43
66
|
/**
|
|
@@ -45,28 +68,36 @@ const CLI_CANDIDATES = Object.freeze({
|
|
|
45
68
|
* try. Returning a bare name rather than null keeps the "not on PATH" diagnosis in one
|
|
46
69
|
* place: the execFileSync catch.
|
|
47
70
|
*
|
|
48
|
-
* @param {"codex"|"copilot"} host
|
|
71
|
+
* @param {"claude"|"codex"|"copilot"} host
|
|
49
72
|
* @returns {string}
|
|
50
73
|
*/
|
|
51
74
|
function resolveCli(host) {
|
|
52
|
-
const candidates =
|
|
75
|
+
const candidates = HOSTS[host]?.candidates || [host];
|
|
53
76
|
for (const c of candidates) {
|
|
54
77
|
if (c.includes("/") && existsSync(c)) return c;
|
|
55
78
|
}
|
|
56
79
|
return candidates[0];
|
|
57
80
|
}
|
|
58
81
|
|
|
82
|
+
/** Does this output say the server was already registered? */
|
|
83
|
+
function saysAlreadyExists(text) {
|
|
84
|
+
return /already exists/i.test(text);
|
|
85
|
+
}
|
|
86
|
+
|
|
59
87
|
/**
|
|
60
|
-
* Register the MCP server. Idempotent from the caller's point of view, though the
|
|
61
|
-
* CLIs get there
|
|
88
|
+
* Register the MCP server. Idempotent from the caller's point of view, though the
|
|
89
|
+
* three CLIs get there by three different routes:
|
|
62
90
|
*
|
|
63
91
|
* - `codex mcp add` replaces an existing entry and exits 0.
|
|
64
92
|
* - `copilot mcp add` refuses, printing `Server "<name>" already exists` and exiting 1.
|
|
93
|
+
* - `claude mcp add` refuses, printing `MCP server <name> already exists in <scope>
|
|
94
|
+
* config` and exiting **0**.
|
|
65
95
|
*
|
|
66
|
-
* So
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
96
|
+
* So neither the exit code nor the stream tells you on its own. A non-zero exit is not
|
|
97
|
+
* automatically a failure, and a zero exit is not automatically a fresh write - which
|
|
98
|
+
* is why the "already exists" test runs on both paths. Reporting an existing server as
|
|
99
|
+
* "skipped MCP registration (Command failed)" would send the user to fix something that
|
|
100
|
+
* is already correct, and that is exactly what the first version of this did.
|
|
70
101
|
*
|
|
71
102
|
* The existing entry is left alone rather than removed and re-added: a user may have
|
|
72
103
|
* added env vars or switched the command to a local checkout, and an installer has no
|
|
@@ -75,13 +106,14 @@ function resolveCli(host) {
|
|
|
75
106
|
* Never fatal: a missing CLI or a failed write must not fail the install, because the
|
|
76
107
|
* rest of the tree is still correct and useful. It prints the manual command instead.
|
|
77
108
|
*
|
|
78
|
-
* @param {"codex"|"copilot"} host
|
|
109
|
+
* @param {"claude"|"codex"|"copilot"} host
|
|
79
110
|
* @param {string} label - host label for log lines
|
|
80
111
|
* @returns {{registered: boolean, alreadyPresent?: boolean, reason?: string}}
|
|
81
112
|
*/
|
|
82
113
|
export function registerMcpServer(host, label) {
|
|
83
114
|
const cli = resolveCli(host);
|
|
84
|
-
const
|
|
115
|
+
const scopeArgs = HOSTS[host]?.scopeArgs || [];
|
|
116
|
+
const manual = `${host} mcp add ${scopeArgs.join(" ")}${scopeArgs.length ? " " : ""}${MCP_SERVER_NAME} -- npx -y ${MCP_SERVER_PACKAGE}`;
|
|
85
117
|
|
|
86
118
|
if (isDryRun()) {
|
|
87
119
|
console.log(` [dry-run] would run: ${manual}`);
|
|
@@ -100,17 +132,24 @@ export function registerMcpServer(host, label) {
|
|
|
100
132
|
try {
|
|
101
133
|
// Bounded: an installer must never hang on a child process. This is a local config
|
|
102
134
|
// write and returns in milliseconds.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
135
|
+
const stdout = execFileSync(
|
|
136
|
+
cli,
|
|
137
|
+
["mcp", "add", ...scopeArgs, MCP_SERVER_NAME, "--", "npx", "-y", MCP_SERVER_PACKAGE],
|
|
138
|
+
{ stdio: "pipe", timeout: 20_000 },
|
|
139
|
+
);
|
|
140
|
+
// Claude Code refuses a duplicate on the SUCCESS path (exit 0), so the check
|
|
141
|
+
// cannot live in the catch alone.
|
|
142
|
+
if (saysAlreadyExists(stdout?.toString() ?? "")) {
|
|
143
|
+
console.log(` -> ${label}: ${MCP_SERVER_NAME} MCP server already registered, left as-is`);
|
|
144
|
+
return { registered: true, alreadyPresent: true };
|
|
145
|
+
}
|
|
107
146
|
console.log(` -> ${label}: registered the ${MCP_SERVER_NAME} MCP server`);
|
|
108
147
|
return { registered: true };
|
|
109
148
|
} catch (e) {
|
|
110
149
|
// The CLI writes its diagnosis to stderr, which execFileSync parks on the error
|
|
111
150
|
// rather than in `message`. Read both, or "already exists" is invisible.
|
|
112
151
|
const out = `${e?.stderr?.toString() ?? ""}\n${e?.stdout?.toString() ?? ""}\n${e?.message ?? ""}`;
|
|
113
|
-
if (
|
|
152
|
+
if (saysAlreadyExists(out)) {
|
|
114
153
|
console.log(` -> ${label}: ${MCP_SERVER_NAME} MCP server already registered, left as-is`);
|
|
115
154
|
return { registered: true, alreadyPresent: true };
|
|
116
155
|
}
|