@mmerterden/multi-agent-pipeline 20.8.0 → 20.8.2
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 +48 -0
- package/docs/facts.json +3 -3
- package/index.js +1 -0
- package/install/_codex-agents.mjs +1 -1
- package/install/_common.mjs +486 -53
- package/install/_mcp-register.mjs +173 -117
- package/install/claude.mjs +281 -220
- package/install/codex.mjs +7 -7
- package/install/copilot.mjs +13 -11
- package/install/index.mjs +92 -27
- package/install/templates/claude-hooks.json +9 -9
- package/manifest.json +112 -114
- package/package.json +5 -2
- package/pipeline/commands/multi-agent/update/SKILL.md +28 -17
- package/pipeline/lib/confusables.json +79 -33
- package/pipeline/lib/extract-conventions.sh +3 -3
- package/pipeline/lib/json-file-lock.mjs +27 -7
- package/pipeline/lib/normalize-text.mjs +86 -17
- package/pipeline/lib/outbound-gate.mjs +13 -4
- package/pipeline/lib/redact.mjs +87 -13
- package/pipeline/multi-agent-refs/analysis/evidence.md +1 -1
- package/pipeline/multi-agent-refs/analysis/synthesis.md +1 -1
- package/pipeline/multi-agent-refs/component-dispatch.md +1 -1
- package/pipeline/multi-agent-refs/conventions-defaults.md +1 -1
- package/pipeline/multi-agent-refs/features/unattended-security.md +2 -2
- package/pipeline/scripts/agent-guard.py +150 -25
- package/pipeline/scripts/audit-log.sh +3 -4
- package/pipeline/scripts/autopilot-runner.mjs +14 -5
- package/pipeline/scripts/doctor.mjs +8 -2
- package/pipeline/scripts/gen-skills-index.mjs +13 -1
- package/pipeline/scripts/log-metric.sh +9 -3
- package/pipeline/scripts/migrate-prefs.mjs +18 -4
- package/pipeline/scripts/pre-commit-check.sh +119 -27
- package/pipeline/scripts/scan-agent-config.sh +9 -9
- package/pipeline/scripts/unattended_policy.py +12 -3
- package/pipeline/scripts/uninstall.mjs +88 -1
- package/pipeline/scripts/usage-identity.mjs +1 -1
- package/pipeline/scripts/usage-register.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +30 -30
- package/pipeline/skills/shared/README.md +64 -64
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +2 -2
- package/pipeline/skills/shared/external/alarmkit/evals/evals.json +2 -2
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +6 -0
- package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +3 -0
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +3 -0
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +3 -4
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +5 -3
- package/pipeline/skills/shared/external/authentication/SKILL.md +29 -17
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +3 -1
- package/pipeline/skills/shared/external/background-processing/SKILL.md +10 -8
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +7 -7
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +6 -3
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +43 -0
- package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +4 -2
- package/pipeline/skills/shared/external/core-data/SKILL.md +12 -2
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +31 -0
- package/pipeline/skills/shared/external/coreml/SKILL.md +1 -1
- package/pipeline/skills/shared/external/cryptokit/SKILL.md +1 -1
- package/pipeline/skills/shared/external/device-integrity/SKILL.md +11 -5
- package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +2 -2
- package/pipeline/skills/shared/external/ios-module-structure/modules/_TEMPLATE.yml +1 -1
- package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +6 -6
- package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +3 -2
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +20 -18
- package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +4 -4
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +12 -9
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/_shared.py +201 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +64 -30
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +14 -16
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +20 -12
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +7 -7
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +26 -12
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +4 -3
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +51 -24
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +19 -9
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +26 -19
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +39 -46
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +37 -63
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +4 -2
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +5 -4
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +3 -2
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +2 -2
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +4 -4
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +15 -6
- package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +2 -1
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +8 -4
- package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +1 -1
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +25 -6
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/skill-creator/template.md +7 -1
- package/pipeline/skills/shared/external/storekit/references/core-patterns.md +6 -1
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +3 -2
- package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +1 -1
- package/pipeline/skills/shared/external/swift-security/SKILL.md +10 -8
- package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +8 -5
- package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +13 -9
- package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +7 -6
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +8 -5
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +3 -2
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +5 -3
- package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +17 -9
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +4 -2
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +13 -6
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +3 -1
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +8 -5
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +15 -7
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +8 -6
- package/pipeline/scripts/gen-ref-toc.mjs +0 -279
- package/pipeline/scripts/make-manifest.mjs +0 -199
- package/pipeline/scripts/scorecard-snapshot.mjs +0 -178
|
@@ -11,47 +11,16 @@ import os
|
|
|
11
11
|
import re
|
|
12
12
|
import sys
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
LSK = re.compile(rf"LocalizationStringKey((?:\.[A-Za-z_][{WORD}]*)+)")
|
|
19
|
-
LITERAL_LOCALIZED = re.compile(rf"\"([{WORD}.]+)\"\.localized\b")
|
|
20
|
-
WEB_T = re.compile(r"(?<![\w$])t\(\s*(['\"])([A-Za-z0-9_.\-]+)\1")
|
|
21
|
-
WEB_DOLLAR_T = re.compile(r"\$t\(\s*(['\"])([A-Za-z0-9_.\-]+)\1")
|
|
22
|
-
TRAILING_MARKER = re.compile(r"\s*\([^()]*\)\s*$")
|
|
23
|
-
COMPONENT_MARKER = re.compile(r"\([^)]*(owned by|bile\u015fen|component)[^)]*\)", re.IGNORECASE)
|
|
24
|
-
PLACEHOLDER = re.compile(r"<[^<>]+>")
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def strip_accessor(chain):
|
|
28
|
-
segments = chain.strip(".").split(".")
|
|
29
|
-
while segments and segments[-1].startswith("localized"):
|
|
30
|
-
segments.pop()
|
|
31
|
-
return ".".join(segments)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def normalize_key(key):
|
|
35
|
-
return ".".join(seg[:1].upper() + seg[1:] for seg in key.split("."))
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def source_files(root):
|
|
39
|
-
if os.path.isfile(root):
|
|
40
|
-
yield root
|
|
41
|
-
return
|
|
42
|
-
for current, dirs, files in os.walk(root):
|
|
43
|
-
probe = current.replace(os.sep, "/") + "/"
|
|
44
|
-
if any(part in probe for part in SKIP_PARTS):
|
|
45
|
-
dirs[:] = []
|
|
46
|
-
continue
|
|
47
|
-
for name in sorted(files):
|
|
48
|
-
if os.path.splitext(name)[1] in EXTENSIONS:
|
|
49
|
-
yield os.path.join(current, name)
|
|
14
|
+
from _shared import (
|
|
15
|
+
ACCESSOR_EXTENSIONS, DEFAULT_KEY_TYPES, LITERAL_KEY, WEB_DOLLAR_T, WEB_T, clean_key, is_component, is_dynamic,
|
|
16
|
+
key_type_warning, key_types, lsk_keys, normalize_key, raw_key, source_files,
|
|
17
|
+
)
|
|
50
18
|
|
|
51
19
|
|
|
52
|
-
def scan_code(root):
|
|
20
|
+
def scan_code(root, types=DEFAULT_KEY_TYPES):
|
|
53
21
|
keys = set()
|
|
54
22
|
corpus = []
|
|
23
|
+
accessor_texts = []
|
|
55
24
|
scanned = 0
|
|
56
25
|
for path in source_files(root):
|
|
57
26
|
try:
|
|
@@ -61,28 +30,16 @@ def scan_code(root):
|
|
|
61
30
|
continue
|
|
62
31
|
scanned += 1
|
|
63
32
|
corpus.append(text.lower())
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
33
|
+
if os.path.splitext(path)[1] in ACCESSOR_EXTENSIONS:
|
|
34
|
+
accessor_texts.append(text)
|
|
35
|
+
keys.update(key.lower() for key in lsk_keys(text, types))
|
|
36
|
+
for pattern, group in ((LITERAL_KEY, 1), (WEB_T, 2), (WEB_DOLLAR_T, 2)):
|
|
67
37
|
keys.update(hit.group(group).lower() for hit in pattern.finditer(text))
|
|
68
|
-
return keys, "\n".join(corpus), scanned
|
|
69
|
-
|
|
38
|
+
return keys, "\n".join(corpus), scanned, key_type_warning(accessor_texts, types, mapping_hint=True)
|
|
70
39
|
|
|
71
|
-
def is_component(row):
|
|
72
|
-
text = " ".join(str(row.get(f) or "") for f in ("newKey", "element", "note"))
|
|
73
|
-
return bool(COMPONENT_MARKER.search(text))
|
|
74
40
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
element = str(row.get("element") or "").lower()
|
|
78
|
-
note = str(row.get("note") or "").lower()
|
|
79
|
-
return (
|
|
80
|
-
key.startswith("(dynamic")
|
|
81
|
-
or bool(PLACEHOLDER.search(key))
|
|
82
|
-
or "dinamik" in element
|
|
83
|
-
or "dynamic" in element
|
|
84
|
-
or "enumerate at runtime" in note
|
|
85
|
-
)
|
|
41
|
+
def filled(value):
|
|
42
|
+
return bool(str(value or "").strip())
|
|
86
43
|
|
|
87
44
|
|
|
88
45
|
def suggested_exists(root, key):
|
|
@@ -100,12 +57,26 @@ def main():
|
|
|
100
57
|
parser.add_argument("--screen-path", required=True, help="folder or single file of the built screen")
|
|
101
58
|
parser.add_argument("--resources-root", help="resources checkout or snapshot; turns on the Suggested-file check")
|
|
102
59
|
parser.add_argument("--out", help="JSON report destination; stdout when omitted")
|
|
60
|
+
parser.add_argument(
|
|
61
|
+
"--key-type", action="append", metavar="NAME",
|
|
62
|
+
help="type whose member chains are keys; repeatable or comma list. Falls back to the mapping's "
|
|
63
|
+
f"keyType, then {', '.join(DEFAULT_KEY_TYPES)}",
|
|
64
|
+
)
|
|
103
65
|
args = parser.parse_args()
|
|
104
66
|
|
|
105
67
|
with open(args.mapping, encoding="utf-8") as stream:
|
|
106
68
|
mapping = json.load(stream)
|
|
107
69
|
rows = mapping.get("rows") or []
|
|
108
|
-
|
|
70
|
+
from_mapping = mapping.get("keyType") or []
|
|
71
|
+
if isinstance(from_mapping, str):
|
|
72
|
+
from_mapping = [from_mapping]
|
|
73
|
+
elif not (isinstance(from_mapping, list) and all(isinstance(name, str) for name in from_mapping)):
|
|
74
|
+
parser.error(f"keyType in the mapping must be a string or a list of strings, got {from_mapping!r}")
|
|
75
|
+
try:
|
|
76
|
+
types = key_types(args.key_type or from_mapping)
|
|
77
|
+
except ValueError as exc:
|
|
78
|
+
parser.error(str(exc))
|
|
79
|
+
code_keys, corpus, scanned, type_warning = scan_code(args.screen_path, types)
|
|
109
80
|
|
|
110
81
|
verified = 0
|
|
111
82
|
exempt = 0
|
|
@@ -114,8 +85,7 @@ def main():
|
|
|
114
85
|
map_keys = set()
|
|
115
86
|
|
|
116
87
|
for row in rows:
|
|
117
|
-
|
|
118
|
-
key = raw_key if raw_key.startswith("(dynamic") else TRAILING_MARKER.sub("", raw_key)
|
|
88
|
+
key = clean_key(raw_key(row))
|
|
119
89
|
component = is_component(row)
|
|
120
90
|
dynamic = is_dynamic(row, key)
|
|
121
91
|
if key:
|
|
@@ -136,11 +106,11 @@ def main():
|
|
|
136
106
|
verdict = row.get("verdict")
|
|
137
107
|
new = row.get("new") or {}
|
|
138
108
|
legacy = row.get("legacy") or {}
|
|
139
|
-
if verdict != "new" and not new.get("tr") and not dynamic:
|
|
109
|
+
if verdict != "new" and not filled(new.get("tr")) and not dynamic:
|
|
140
110
|
gaps.append({"key": key, "issue": "new.tr empty"})
|
|
141
|
-
if verdict in ("reuse", "review") and not (legacy.get("en") or legacy.get("tr")):
|
|
111
|
+
if verdict in ("reuse", "review") and not (filled(legacy.get("en")) or filled(legacy.get("tr"))):
|
|
142
112
|
gaps.append({"key": key, "issue": f"verdict={verdict} but no legacy value"})
|
|
143
|
-
if new.get("tr") and new.get("tr") == new.get("en"):
|
|
113
|
+
if filled(new.get("tr")) and new.get("tr") == new.get("en"):
|
|
144
114
|
untranslated.append(key)
|
|
145
115
|
if args.resources_root and not component and not dynamic and key and not suggested_exists(args.resources_root, key):
|
|
146
116
|
gaps.append({"key": key, "issue": f"no Suggested/{normalize_key(key)}.json"})
|
|
@@ -160,6 +130,8 @@ def main():
|
|
|
160
130
|
"valueGaps": gaps,
|
|
161
131
|
"untranslated": untranslated,
|
|
162
132
|
}
|
|
133
|
+
if type_warning:
|
|
134
|
+
report["keyTypeWarning"] = type_warning
|
|
163
135
|
text = json.dumps(report, indent=2, ensure_ascii=False) + "\n"
|
|
164
136
|
if args.out:
|
|
165
137
|
with open(args.out, "w", encoding="utf-8") as sink:
|
|
@@ -175,6 +147,8 @@ def main():
|
|
|
175
147
|
f"value gaps {len(gaps)} ({len(no_legacy)} hard), untranslated {len(untranslated)}",
|
|
176
148
|
file=sys.stderr,
|
|
177
149
|
)
|
|
150
|
+
if type_warning:
|
|
151
|
+
print(f"# {type_warning}", file=sys.stderr)
|
|
178
152
|
for item in unverified[:12]:
|
|
179
153
|
print(f"# UNVERIFIED {item['key']} ({item['element']})", file=sys.stderr)
|
|
180
154
|
for key in not_in_map[:12]:
|
|
@@ -182,7 +156,7 @@ def main():
|
|
|
182
156
|
if hard:
|
|
183
157
|
print(f"# FAIL: {hard} hard issue(s); fix them or record a conscious waiver before publishing", file=sys.stderr)
|
|
184
158
|
sys.exit(2)
|
|
185
|
-
print("# PASS", file=sys.stderr)
|
|
159
|
+
print("# PASS, but see the key type WARNING above" if type_warning else "# PASS", file=sys.stderr)
|
|
186
160
|
|
|
187
161
|
|
|
188
162
|
if __name__ == "__main__":
|
|
@@ -309,9 +309,11 @@ func lookUp(_ text: String) async throws -> MKMapItem? {
|
|
|
309
309
|
Reverse lookup is `MKReverseGeocodingRequest(location:)`. Formatting and
|
|
310
310
|
batch lookups are in [Geocoding and Places on iOS 26](references/mapkit-patterns.md#geocoding-and-places-on-ios-26).
|
|
311
311
|
|
|
312
|
-
### CLGeocoder (
|
|
312
|
+
### CLGeocoder (available since iOS 5, deprecated in iOS 26)
|
|
313
313
|
|
|
314
|
-
Use it only while the deployment target is below iOS 26.
|
|
314
|
+
Use it only while the deployment target is below iOS 26. The SDK's deprecation
|
|
315
|
+
messages point reverse lookups to `MKReverseGeocodingRequest` and forward
|
|
316
|
+
lookups to `MKGeocodingRequest`. Every `CLPlacemark`
|
|
315
317
|
field is optional. `CLGeocoder` is not `Sendable`, so Swift 6 rejects one
|
|
316
318
|
shared global instance; make a geocoder for each lookup instead.
|
|
317
319
|
|
|
@@ -14,10 +14,11 @@ charges.
|
|
|
14
14
|
Examples assume Swift 6.3 and iOS 26.
|
|
15
15
|
|
|
16
16
|
One rule shapes advanced checkouts: a single `PKPaymentRequest` may carry at
|
|
17
|
-
most one
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
most one of `multiTokenContexts`, `recurringPaymentRequest`,
|
|
18
|
+
`automaticReloadPaymentRequest` and `deferredPaymentRequest`. When a flow
|
|
19
|
+
needs two of them, build two requests. `applePayLaterAvailability` (iOS 17+)
|
|
20
|
+
is not part of that rule; it is a separate opt-out of Apple Pay Later that
|
|
21
|
+
defaults to enabled.
|
|
21
22
|
|
|
22
23
|
## Setup
|
|
23
24
|
|
|
@@ -10,10 +10,11 @@ Each `PKPaymentRequest` may set only one of these:
|
|
|
10
10
|
- `recurringPaymentRequest`
|
|
11
11
|
- `automaticReloadPaymentRequest`
|
|
12
12
|
- `deferredPaymentRequest`
|
|
13
|
-
- the Apple Pay Later availability setting
|
|
14
13
|
- `multiTokenContexts`
|
|
15
14
|
|
|
16
|
-
If a checkout needs two, split it into two requests.
|
|
15
|
+
If a checkout needs two, split it into two requests. `applePayLaterAvailability`
|
|
16
|
+
(iOS 17+) sits outside this rule: it opts the request out of Apple Pay Later,
|
|
17
|
+
and defaults to enabled; the exclusion covers only the four above.
|
|
17
18
|
|
|
18
19
|
## Recurring payments
|
|
19
20
|
|
|
@@ -133,8 +133,8 @@ drawing throws there. `PKContentVersion` names each step:
|
|
|
133
133
|
|---|---|
|
|
134
134
|
| `.version1` | Original inks: pen, pencil, marker (iPadOS 14 era) |
|
|
135
135
|
| `.version2` | Inks added in iPadOS 17: `.monoline`, `.fountainPen`, `.watercolor`, `.crayon` |
|
|
136
|
-
| `.version3` |
|
|
137
|
-
| `.version4` |
|
|
136
|
+
| `.version3` | The new fountain pen ink (iOS 17.5, macOS 14.5) |
|
|
137
|
+
| `.version4` | The new reed pen ink (iOS 26, macOS 26, visionOS 26) |
|
|
138
138
|
|
|
139
139
|
When reviewing compatibility, lay out this whole table before recommending a
|
|
140
140
|
cap, and if the fix is a curated picker, say that `PKToolPicker(toolItems:)`
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"id": 1,
|
|
20
20
|
"name": "older-ipad-sync",
|
|
21
21
|
"prompt": "Teachers annotate worksheets on new iPads and students open them on iPads still running iPadOS 16. Students report that some worksheets fail to open. Our plan is to set maximumSupportedContentVersion to .version2 on the canvas. Review the plan.",
|
|
22
|
-
"expected_output": "Explains that inks newer than the recipient supports make loading fail, gives the full content version map (version1 pen/pencil/marker, version2 the iPadOS 17 inks, version3
|
|
22
|
+
"expected_output": "Explains that inks newer than the recipient supports make loading fail, gives the full content version map (version1 pen/pencil/marker, version2 the iPadOS 17 inks, version3 the new fountain pen from iOS 17.5, version4 the new reed pen), and says .version2 does not help iPadOS 16, which needs .version1 or a verified fallback drawing. The cap must be set on both canvas and tool picker. Notes .reed needs 26 and curated picker items need iOS 18 or visionOS 2.",
|
|
23
23
|
"files": [],
|
|
24
24
|
"expectations": [
|
|
25
25
|
"States that unsupported inks make older systems fail to load the drawing",
|
|
@@ -168,7 +168,7 @@ func describe(_ sketch: PKDrawing) -> String {
|
|
|
168
168
|
switch sketch.requiredContentVersion {
|
|
169
169
|
case .version1: "pen, pencil, marker"
|
|
170
170
|
case .version2: "adds monoline, fountain pen, watercolor, crayon"
|
|
171
|
-
case .version3: "adds
|
|
171
|
+
case .version3: "adds the new fountain pen (iOS 17.5)"
|
|
172
172
|
case .version4: "adds reed pen"
|
|
173
173
|
@unknown default: "unknown future content"
|
|
174
174
|
}
|
|
@@ -215,9 +215,9 @@ func downgradedCopy(of sketch: PKDrawing) -> PKDrawing {
|
|
|
215
215
|
}
|
|
216
216
|
```
|
|
217
217
|
|
|
218
|
-
Swapping the ink is not always enough. The
|
|
219
|
-
|
|
220
|
-
before upload.
|
|
218
|
+
Swapping the ink is not always enough. The copies keep every other stroke
|
|
219
|
+
property, and only `requiredContentVersion` says what the result needs, which
|
|
220
|
+
is why the result is checked again before upload.
|
|
221
221
|
|
|
222
222
|
## A configurable SwiftUI wrapper
|
|
223
223
|
|
|
@@ -32,10 +32,14 @@ against the Xcode 26 SDK you ship with before relying on it.
|
|
|
32
32
|
Add `@preconcurrency import PermissionKit`. The SwiftUI and UIKit/AppKit
|
|
33
33
|
pieces (`PermissionButton`, `AskCenter.ask(_:in:)`) come in through
|
|
34
34
|
cross-import overlays, so importing SwiftUI or UIKit next to PermissionKit is
|
|
35
|
-
enough.
|
|
36
|
-
a
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
enough. Most of the framework's types are not `Sendable`: `PermissionQuestion`
|
|
36
|
+
(a class), `PermissionResponse`, `PermissionChoice`, `CommunicationTopic`,
|
|
37
|
+
`AskCenter` and `CommunicationLimits` declare no conformance. The exceptions
|
|
38
|
+
are `CommunicationHandle`, which is `Sendable`, and the SwiftUI views
|
|
39
|
+
`PermissionButton` and `CommunicationLimitsButton`. Because the question is not
|
|
40
|
+
`Sendable`, under Swift 6 a plain import turns every `ask(_:in:)` call from the
|
|
41
|
+
main actor into a "sending 'question' risks causing data races" error;
|
|
42
|
+
`@preconcurrency` is what lets the examples below compile.
|
|
39
43
|
|
|
40
44
|
Do not add made-up entitlement keys for PermissionKit. If a signing
|
|
41
45
|
requirement exists, it will be in Apple's documentation and in the Xcode
|
|
@@ -177,7 +181,11 @@ those.
|
|
|
177
181
|
in which the child sends the question; it does not return the parent's
|
|
178
182
|
answer. Decisions arrive later on `responses(for:)`. If the child backs out of
|
|
179
183
|
the send sheet, no `PermissionResponse` ever arrives, so a pending request
|
|
180
|
-
needs its own expiry or retry.
|
|
184
|
+
needs its own expiry or retry. `PermissionQuestion` has a settable
|
|
185
|
+
`expirationDate: Date?` (nil by default); once that date passes, the parent
|
|
186
|
+
can no longer respond. Set it before asking to bound the window, then move the
|
|
187
|
+
local entry to expired at the same date, since the SDK delivers nothing when a
|
|
188
|
+
question lapses.
|
|
181
189
|
|
|
182
190
|
```swift
|
|
183
191
|
@available(iOS 26.2, *)
|
|
@@ -233,7 +241,8 @@ examples are in the
|
|
|
233
241
|
|
|
234
242
|
Track each question by `question.id` (a `UUID`). Since a cancelled send
|
|
235
243
|
produces nothing, give every pending entry a way out: a retry, an expiry, or
|
|
236
|
-
both. When
|
|
244
|
+
both. When the question carries an `expirationDate`, use that date for the
|
|
245
|
+
local expiry so the app and the parent's window close together. When a flow combines known-handle checks with responses, keep the
|
|
237
246
|
bundle-identifier guard from the section above.
|
|
238
247
|
|
|
239
248
|
```swift
|
|
@@ -186,7 +186,8 @@ print(q.expirationDate as Any) // nil unless set
|
|
|
186
186
|
```
|
|
187
187
|
|
|
188
188
|
A question exposes `id`, `choices` (each with a `title`), `defaultChoice` and
|
|
189
|
-
an optional `expirationDate
|
|
189
|
+
an optional, settable `expirationDate`; after that date the parent can no
|
|
190
|
+
longer respond.
|
|
190
191
|
|
|
191
192
|
## Limits-check view model
|
|
192
193
|
|
|
@@ -425,9 +425,13 @@ func handleChatAction(_ action: String, threadID: String?, typed: String?) async
|
|
|
425
425
|
## Grouping
|
|
426
426
|
|
|
427
427
|
Notifications with the same `threadIdentifier` (local) or `thread-id` (APNs)
|
|
428
|
-
stack into one group; each distinct value is its own group.
|
|
429
|
-
and `summaryArgumentCount` on
|
|
430
|
-
|
|
428
|
+
stack into one group; each distinct value is its own group. Grouping comes
|
|
429
|
+
only from that identifier. `summaryArgument` and `summaryArgumentCount` on
|
|
430
|
+
`UNMutableNotificationContent` are deprecated as ignored on iOS 15 and later
|
|
431
|
+
(watchOS 8, tvOS 15); do not set them. A category can still supply a summary
|
|
432
|
+
format through `categorySummaryFormat` (iOS 12+, not deprecated). The argument
|
|
433
|
+
list in that format is optional, and with the per-notification argument ignored
|
|
434
|
+
a count-only format is the one to rely on:
|
|
431
435
|
|
|
432
436
|
```swift
|
|
433
437
|
let chat = UNNotificationCategory(
|
|
@@ -435,7 +439,7 @@ let chat = UNNotificationCategory(
|
|
|
435
439
|
actions: [],
|
|
436
440
|
intentIdentifiers: [],
|
|
437
441
|
hiddenPreviewsBodyPlaceholder: nil,
|
|
438
|
-
categorySummaryFormat: "%u new messages
|
|
442
|
+
categorySummaryFormat: "%u new messages",
|
|
439
443
|
options: []
|
|
440
444
|
)
|
|
441
445
|
```
|
package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md
CHANGED
|
@@ -385,7 +385,7 @@ enum NotificationCategories {
|
|
|
385
385
|
actions: [reply, markRead],
|
|
386
386
|
intentIdentifiers: [],
|
|
387
387
|
hiddenPreviewsBodyPlaceholder: nil,
|
|
388
|
-
categorySummaryFormat: "%u more
|
|
388
|
+
categorySummaryFormat: "%u more messages",
|
|
389
389
|
options: [.customDismissAction]
|
|
390
390
|
)
|
|
391
391
|
|
|
@@ -147,16 +147,35 @@ apart.
|
|
|
147
147
|
entities that carry `CollisionComponent` shapes. Use it to pick entities and
|
|
148
148
|
target gestures, never to find planes.
|
|
149
149
|
- For simple placement on a detected plane, use `AnchorEntity(.plane(...))`.
|
|
150
|
-
|
|
151
|
-
|
|
150
|
+
This is the placement path inside `RealityView`: `RealityViewCameraContent`
|
|
151
|
+
exposes no `ARSession`, so there is nothing to raycast against there.
|
|
152
|
+
- To intersect a real surface at a tapped point, host an `ARView` (through
|
|
153
|
+
`UIViewRepresentable`), call its `raycast(from:allowing:alignment:)`, and
|
|
154
|
+
anchor with `AnchorEntity(raycastResult:)`. The same view offers
|
|
155
|
+
`makeRaycastQuery(from:allowing:alignment:)` when an `ARRaycastQuery` is
|
|
156
|
+
needed for `arView.session.raycast(_:)`.
|
|
152
157
|
|
|
153
158
|
```swift
|
|
159
|
+
import RealityKit
|
|
160
|
+
import SwiftUI
|
|
161
|
+
|
|
162
|
+
struct PlaneDropView: View {
|
|
163
|
+
var body: some View {
|
|
164
|
+
RealityView { content in
|
|
165
|
+
content.camera = .spatialTracking
|
|
166
|
+
let floor = AnchorEntity(.plane(.horizontal, classification: .any, minimumBounds: [0.3, 0.3]))
|
|
167
|
+
floor.addChild(ModelEntity(mesh: .generateBox(size: 0.1)))
|
|
168
|
+
content.add(floor)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
154
173
|
@MainActor
|
|
155
|
-
func placeOnSurface(_ item: Entity,
|
|
156
|
-
guard let hit =
|
|
174
|
+
func placeOnSurface(_ item: Entity, at point: CGPoint, in arView: ARView) {
|
|
175
|
+
guard let hit = arView.raycast(from: point, allowing: .estimatedPlane, alignment: .any).first else { return }
|
|
157
176
|
let anchor = AnchorEntity(raycastResult: hit)
|
|
158
177
|
anchor.addChild(item)
|
|
159
|
-
|
|
178
|
+
arView.scene.addAnchor(anchor)
|
|
160
179
|
}
|
|
161
180
|
```
|
|
162
181
|
|
|
@@ -283,7 +302,7 @@ store it when you plan to cancel it later, or drop it with `_ =` as here.
|
|
|
283
302
|
- [ ] Interaction notes explain that `CollisionComponent` provides the hittable shape, paired with `InputTargetComponent`
|
|
284
303
|
- [ ] A Handoffs line mentions `AccessibilityComponent` and sends detailed SwiftUI and accessibility policy to the sibling skills
|
|
285
304
|
- [ ] Entity hit tests, `ray(...)` and ARKit surface raycasts are kept distinct
|
|
286
|
-
- [ ] Surface placement uses `
|
|
305
|
+
- [ ] Surface placement inside `RealityView` uses `AnchorEntity(.plane(...))`; a tap raycast uses `ARView.raycast(from:allowing:alignment:)` with `AnchorEntity(raycastResult:)`
|
|
287
306
|
- [ ] Per-frame logic uses `SceneEvents.Update`, not SwiftUI timers
|
|
288
307
|
- [ ] Large scenes load with async `ModelEntity(named:)`, not `Entity.load(named:)`
|
|
289
308
|
- [ ] Anchor targets match the kind of surface the feature needs
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"States that hitTest finds collision shapes on virtual entities, not physical surfaces",
|
|
24
24
|
"States that ray(through:in:to:) is not evidence of a real surface",
|
|
25
25
|
"Suggests AnchorEntity(.plane(...)) for simple plane placement",
|
|
26
|
-
"
|
|
26
|
+
"Notes that RealityViewCameraContent exposes no ARSession and uses ARView.raycast(from:allowing:alignment:) with AnchorEntity(raycastResult:) for one-shot tap placement",
|
|
27
27
|
"Keeps collision shapes for entity interaction only"
|
|
28
28
|
]
|
|
29
29
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Skill templates
|
|
2
2
|
|
|
3
|
-
Two skeletons. Copy the one that matches the layer, fill it, then delete every line that fails the cut test. The toolkits use these frontmatter keys: `name`, `description`, `allowed-tools`, `user-invocable`, plus an optional `argument-hint
|
|
3
|
+
Two skeletons. Copy the one that matches the layer, fill it, then delete every line that fails the cut test. The toolkits use these frontmatter keys: `name`, `description`, `allowed-tools`, `user-invocable`, plus an optional `argument-hint` and a `metadata` block with `version` (semver of the skill text) and `source` (`multi-agent-pipeline` for a first-party skill, otherwise `<upstream> (<license>)` for the upstream it derives from, which a `NOTICE-*.md` beside it must list). For a shared knowledge skill in the pipeline source the block with `source` is required: the pipeline's skill lint fails one that has none. Elsewhere the block is optional; drop it rather than leave a placeholder in it.
|
|
4
4
|
|
|
5
5
|
## Reference skill
|
|
6
6
|
|
|
@@ -10,6 +10,9 @@ name: {aspect-noun}
|
|
|
10
10
|
description: "{What it covers} for {platform}. {When to reach for it}. Triggers: '{phrase}', '{phrase}', '{phrase}'."
|
|
11
11
|
user-invocable: true
|
|
12
12
|
allowed-tools: Bash, Read, Edit, Write, Glob, Grep
|
|
13
|
+
metadata:
|
|
14
|
+
version: {x.y.z}
|
|
15
|
+
source: {multi-agent-pipeline | upstream}
|
|
13
16
|
---
|
|
14
17
|
|
|
15
18
|
# {name} - {short title}
|
|
@@ -44,6 +47,9 @@ description: "{What it does} for {platform}, with {guardrails}. Use when {situat
|
|
|
44
47
|
user-invocable: true
|
|
45
48
|
argument-hint: "{args}"
|
|
46
49
|
allowed-tools: Bash, Read
|
|
50
|
+
metadata:
|
|
51
|
+
version: {x.y.z}
|
|
52
|
+
source: {multi-agent-pipeline | upstream}
|
|
47
53
|
---
|
|
48
54
|
|
|
49
55
|
# {name} - {short title}
|
|
@@ -99,7 +99,7 @@ struct RecipesApp: App {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
private static func watchTransactions() -> Task<Void, Never> {
|
|
102
|
-
Task
|
|
102
|
+
Task(priority: .background) {
|
|
103
103
|
for await incoming in StoreKit.Transaction.updates {
|
|
104
104
|
if case .verified(let receipt) = incoming {
|
|
105
105
|
await EntitlementStore.shared.refresh()
|
|
@@ -114,6 +114,11 @@ struct RecipesApp: App {
|
|
|
114
114
|
Unverified updates fall through untouched. For each verified one,
|
|
115
115
|
entitlements are recomputed first and the transaction finished afterwards.
|
|
116
116
|
|
|
117
|
+
A plain `Task` is enough here. It inherits the main actor from the `App`, but
|
|
118
|
+
the loop spends its time suspended in `for await` and the entitlement refresh
|
|
119
|
+
runs on the main-actor `EntitlementStore` anyway; `Task.detached` would only add
|
|
120
|
+
a hop and drop the task-local values.
|
|
121
|
+
|
|
117
122
|
## Computing Entitlements
|
|
118
123
|
|
|
119
124
|
```swift
|
|
@@ -7,10 +7,11 @@ metadata:
|
|
|
7
7
|
|
|
8
8
|
# Swift Concurrency
|
|
9
9
|
|
|
10
|
-
Baseline: Swift 6.
|
|
10
|
+
Baseline: Swift 6.2 compilers and later. The aim of every change made with this
|
|
11
11
|
skill is data-race safety through isolation and `Sendable`, reached with the
|
|
12
12
|
smallest possible change in behavior. Features newer than Swift 6.0 name the
|
|
13
|
-
release or proposal that introduced them.
|
|
13
|
+
release or proposal that introduced them; the few that need Swift 6.3, such as
|
|
14
|
+
`weak let` (SE-0481), are marked as 6.3 only.
|
|
14
15
|
|
|
15
16
|
Not for the smallest fix to a single diagnostic (use `swift-concurrency-expert`)
|
|
16
17
|
or a file-by-file review pass (use `swift-concurrency-pro`).
|
package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md
CHANGED
|
@@ -157,7 +157,7 @@ nonisolated struct OffMainBackupBatch {
|
|
|
157
157
|
|
|
158
158
|
`weak let` declares a weak reference that is never reassigned. Because the
|
|
159
159
|
binding cannot change, a type storing only `weak let` references to `Sendable`
|
|
160
|
-
objects can itself be `Sendable`.
|
|
160
|
+
objects can itself be `Sendable`. Swift 6.3 only.
|
|
161
161
|
|
|
162
162
|
```swift
|
|
163
163
|
final class SessionObserver: Sendable {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: swift-security
|
|
3
|
-
description: "Apple client security: Keychain (SecItem, kSecClass, OSStatus, iOS
|
|
3
|
+
description: "Apple client security: Keychain (SecItem, kSecClass, OSStatus, iOS, macOS), Face ID/Touch ID keychain items, CryptoKit (AES-GCM, ChaChaPoly, ECDSA, ECDH, HPKE, ML-KEM), Secure Enclave, OAuth token and API key storage, SecTrust/SPKI pinning, keychain sharing, moving secrets out of UserDefaults or plists, OWASP MASVS/MASTG. Use when writing, reviewing or migrating code that stores secrets, holds keys or evaluates trust. Not for passkeys, App Attest or App Review."
|
|
4
4
|
metadata:
|
|
5
5
|
source: multi-agent-pipeline
|
|
6
6
|
---
|
|
@@ -81,9 +81,9 @@ and should be worded that way.
|
|
|
81
81
|
`errSecDuplicateItem`, call `SecItemUpdate`. Deleting and re-adding is
|
|
82
82
|
reserved for changing an item's access control, which `SecItemUpdate`
|
|
83
83
|
cannot do.
|
|
84
|
-
6. `SecItem*` calls must not run on the main actor. Isolate them in an actor
|
|
85
|
-
iOS
|
|
86
|
-
|
|
84
|
+
6. `SecItem*` calls must not run on the main actor. Isolate them in an actor
|
|
85
|
+
(available from iOS 13) or, in completion-handler code, a private serial
|
|
86
|
+
queue.
|
|
87
87
|
7. On macOS, pass `kSecUseDataProtectionKeychain: true` unless the code is
|
|
88
88
|
deliberately working with the legacy file-based keychain.
|
|
89
89
|
8. Never encrypt twice with the same AES-GCM nonce under one key.
|
|
@@ -111,9 +111,10 @@ These answers apply across every reference file in this skill.
|
|
|
111
111
|
`.privateKeyUsage` in its flags, for example
|
|
112
112
|
`[.privateKeyUsage, .biometryCurrentSet]`.
|
|
113
113
|
- Any code path that shows Face ID needs `NSFaceIDUsageDescription` in
|
|
114
|
-
`Info.plist`.
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
`Info.plist`. The `LAContext` header asks apps to supply it, shows its string
|
|
115
|
+
the first time Face ID is used, and fails evaluations with
|
|
116
|
+
`LAError.biometryNotAvailable` once the user denies Face ID for the app. That
|
|
117
|
+
applies to keychain reads that trigger Face ID as much as to `evaluatePolicy`.
|
|
117
118
|
- `arc4random` on Apple platforms is backed by a cryptographically secure
|
|
118
119
|
generator, not RC4. Its limits are a 32-bit result and the bias of `% n`. For
|
|
119
120
|
keys, nonces, salts and tokens, use `SecRandomCopyBytes`,
|
|
@@ -130,7 +131,8 @@ in hardware, and checks of server trust. Adjacent topics appear here only to
|
|
|
130
131
|
point at the right skill:
|
|
131
132
|
|
|
132
133
|
- `authentication`: passkeys, Sign in with Apple, OAuth sign-in flows,
|
|
133
|
-
`ASAuthorizationController`, credential state, sign-in UX
|
|
134
|
+
`ASAuthorizationController`, credential state, sign-in UX, and `LAContext`
|
|
135
|
+
prompts and policy that do not guard a keychain item.
|
|
134
136
|
- `cryptokit`: CryptoKit questions with no storage, keychain, Secure Enclave,
|
|
135
137
|
trust or compliance angle.
|
|
136
138
|
- `device-integrity`: DeviceCheck and App Attest attestations and assertions.
|
|
@@ -4,7 +4,8 @@ What is here: the Security types for certificates, trust, identities and policie
|
|
|
4
4
|
pinning, whether by leaf, by issuing CA, by public-key digest or declaratively through
|
|
5
5
|
`NSPinnedDomains`; building your own trust policy; mutual TLS; the points where App
|
|
6
6
|
Transport Security and trust evaluation overlap; and running a pin set in production.
|
|
7
|
-
It applies from iOS 12
|
|
7
|
+
It applies from iOS 12 and macOS 10.14 through the current releases (checked
|
|
8
|
+
against the iOS 26.4 SDK).
|
|
8
9
|
|
|
9
10
|
What is not here: transport concerns other than checking TLS certificates, managing
|
|
10
11
|
certificates on your servers, and ATS as a configuration topic (the `ios-networking`
|
|
@@ -72,7 +73,7 @@ func checkTrustOffMain(_ trust: SecTrust, then report: @escaping @Sendable (Trus
|
|
|
72
73
|
`nonisolated(unsafe)`: the caller promises not to touch the trust while it is
|
|
73
74
|
being evaluated. `report` is `@Sendable` because it runs on that queue.
|
|
74
75
|
|
|
75
|
-
|
|
76
|
+
The iOS 26.4 SDK still has no async/await version of these calls. A continuation
|
|
76
77
|
bridges the gap:
|
|
77
78
|
|
|
78
79
|
```swift
|
|
@@ -477,7 +478,8 @@ Storing certificates and identities in the Keychain is covered in
|
|
|
477
478
|
- ATS requires, for every `URLSession` connection: TLS 1.2 or later, RSA keys of at
|
|
478
479
|
least 2048 bits or ECC of at least 256 bits, SHA-256 or stronger certificate
|
|
479
480
|
signatures, AES-128 or AES-256, and forward secrecy.
|
|
480
|
-
-
|
|
481
|
+
- For connections to IP-address literals, check Apple's `NSAppTransportSecurity`
|
|
482
|
+
documentation for how ATS applies on the deployment target.
|
|
481
483
|
- Keys that draw App Review scrutiny: `NSAllowsArbitraryLoads`,
|
|
482
484
|
`NSAllowsArbitraryLoadsForMedia`, `NSAllowsArbitraryLoadsInWebContent`,
|
|
483
485
|
`NSExceptionAllowsInsecureHTTPLoads`, `NSExceptionMinimumTLSVersion`.
|
|
@@ -491,8 +493,9 @@ Storing certificates and identities in the Keychain is covered in
|
|
|
491
493
|
| iOS 13 / macOS 10.15 | `SecTrustEvaluateAsyncWithError`; `SecTrustEvaluate` deprecated |
|
|
492
494
|
| iOS 14 / macOS 11 | `NSPinnedDomains`; `SecTrustCopyKey` replaces `SecTrustCopyPublicKey` |
|
|
493
495
|
| iOS 15 / macOS 12 | `SecTrustCopyCertificateChain` replaces `SecTrustGetCertificateAtIndex` and `SecTrustGetCertificateCount` |
|
|
494
|
-
| iOS
|
|
495
|
-
| iOS 18 / macOS 15 |
|
|
496
|
+
| iOS 18 / macOS 15 | `SecCertificateCopyNotValidBeforeDate` and `SecCertificateCopyNotValidAfterDate` |
|
|
497
|
+
| iOS 18.4 / macOS 15.4 | `kSecTrustQWACValidation`, `kSecTrustQCStatements` and the `kSecPolicyApple*` server and client policy constants |
|
|
498
|
+
| iOS 26 / macOS 26 | No new trust-evaluation API; still no async/await form of `SecTrustEvaluateAsyncWithError` |
|
|
496
499
|
|
|
497
500
|
## Threads and performance
|
|
498
501
|
|