@ccoalm/ccl-skills 0.7.0 → 0.8.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 (91) hide show
  1. package/README.md +2 -2
  2. package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/SKILL.md +8 -7
  3. package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/mobile-quality-release.md +1 -1
  4. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/SKILL.md +16 -17
  5. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/client-routing.md +1 -1
  6. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/staged-review-contract.md +195 -7
  7. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/timeout-auth-and-capabilities.md +3 -3
  8. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/claude_review.sh +13 -5
  9. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/codex_review.sh +9 -3
  10. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/kimi_review.sh +9 -3
  11. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/normalize_review_timeout.sh +22 -0
  12. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/opencode_review.sh +9 -3
  13. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.py +1540 -129
  14. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_claude_review_probe.sh +8 -3
  15. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_client_compat.py +76 -1
  16. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh +1858 -3
  17. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_update_review_plan_intent.sh +789 -0
  18. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/update_review_plan_intent.py +513 -0
  19. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/SKILL.md +4 -1
  20. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/SKILL.md +2 -1
  21. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/SKILL.md +11 -10
  22. package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/SKILL.md +64 -0
  23. package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/agents/openai.yaml +4 -0
  24. package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/references/async-lifecycle-and-performance.md +72 -0
  25. package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/references/runtime-and-project-contract.md +58 -0
  26. package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/references/source-map.md +41 -0
  27. package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/references/verification-diagnostics-and-security.md +63 -0
  28. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/SKILL.md +8 -10
  29. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/design-routing-and-readiness.md +10 -14
  30. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/verify-developer-experience.md +1 -1
  31. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/SKILL.md +135 -86
  32. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/behavioral-aesthetic-logic.md +66 -80
  33. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/delivery-contract.md +275 -0
  34. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-execution-checklist.md +88 -214
  35. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-impl-naming-and-versioning.md +2 -2
  36. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-intake-and-acceptance.md +10 -8
  37. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-system-source-of-truth.md +4 -5
  38. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/external-ui-ux-quality-benchmarks.md +112 -95
  39. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/frontend-code-evidence-map.md +30 -21
  40. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/interaction-design-patterns.md +22 -3
  41. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/layout-recipes-and-screenshot-acceptance.md +20 -17
  42. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/multi-project-token-consistency.md +7 -9
  43. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/multi-stack-strategy.md +14 -10
  44. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/operational-processing-workflows.md +2 -0
  45. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/platform-mobile-patterns.md +1 -1
  46. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/product-lifecycle-acceptance-and-iteration.md +9 -6
  47. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/product-surface-patterns.md +3 -0
  48. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/source-map.md +37 -10
  49. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/tokens-and-components.md +7 -1
  50. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/ui-ux-audit.md +8 -5
  51. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/ui-ux-design-development.md +16 -5
  52. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/visual-craft.md +4 -2
  53. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/SKILL.md +4 -1
  54. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/SKILL.md +4 -4
  55. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/dual-track-review-gate.md +95 -5
  56. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/extraction-quickstart.md +11 -9
  57. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/r0-leakage-audit.md +102 -0
  58. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +54 -0
  59. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-to-skill-extraction.md +8 -0
  60. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/uiux-judgment-extraction.md +6 -6
  61. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/validation-and-landing.md +4 -3
  62. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-ccl-skills.sh +69 -2
  63. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/extraction_review_gate.sh +22 -0
  64. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/impact-chain-gate.rb +49 -4
  65. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/obligation-ledger.py +2748 -0
  66. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/register-firing-path-resolution.rb +20 -5
  67. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/shared_git_surface_gate.py +1142 -0
  68. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_regressions.sh +17 -0
  69. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_skill_catalog.sh +41 -4
  70. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_ci_checkout_ref_binding.sh +120 -0
  71. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_entrypoint_domain_scan_terms.sh +82 -8
  72. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_extraction_review_gate.sh +336 -0
  73. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_self_adjudication.sh +82 -10
  74. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_obligation_ledger.sh +1416 -0
  75. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_obligation_ledger_repo_audit.sh +57 -0
  76. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_register_firing_path_wiring.sh +141 -4
  77. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_routing_pointer_integrity.sh +3 -1
  78. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_shared_git_surface_gate.sh +1696 -0
  79. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_uiux_delivery_contract.sh +2117 -0
  80. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_uiux_loading_budget.sh +316 -0
  81. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_validate_extraction_review_state.sh +1176 -0
  82. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_validate_skill_cross_refs.sh +31 -1
  83. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/validate-skill.sh +9 -4
  84. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/validate_extraction_review_state.py +980 -0
  85. package/dist/assets/marketplace/plugins/ccl-skills/skills/terminal-cli-dev/SKILL.md +8 -6
  86. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/SKILL.md +8 -7
  87. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/client-runtime-test-matrices.md +10 -2
  88. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/SKILL.md +6 -5
  89. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/complex-workspace-patterns.md +1 -1
  90. package/dist/assets/release.json +175 -70
  91. package/package.json +1 -1
@@ -0,0 +1,513 @@
1
+ #!/usr/bin/env python3
2
+ """Append or compact bounded intent without lossy truncation.
3
+
4
+ The caller owns writer serialization and provides a trusted, stable parent
5
+ directory. ``--expected-sha256`` rejects input that was already stale when
6
+ opened; it is not a lock against concurrent writers.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import base64
13
+ import hashlib
14
+ import json
15
+ import os
16
+ import re
17
+ import stat
18
+ import sys
19
+ import tempfile
20
+ from pathlib import Path
21
+ from typing import NoReturn
22
+
23
+
24
+ MIN_INTENT_CHARS = 8
25
+ MAX_INTENT_CHARS = 4000
26
+ MAX_PLAN_BYTES = 32_000
27
+ PLAN_FIELDS = {"intent", "acceptance", "self_review", "evidence"}
28
+ SHA256_RE = re.compile(r"[0-9a-f]{64}")
29
+ STABLE_CORE_EVIDENCE_ID = "review-plan-intent-stable-core-v1"
30
+ STABLE_CORE_RESULT_RE = re.compile(r"chars=([1-9][0-9]{0,3});sha256=([0-9a-f]{64})")
31
+ HISTORY_EVIDENCE_PREFIX = "review-plan-intent-history-v1"
32
+ HISTORY_BASE64_CHUNK_CHARS = 1800
33
+ MAX_EVIDENCE_ROWS = 50
34
+
35
+
36
+ class UpdateError(Exception):
37
+ def __init__(self, reason: str, detail: str) -> None:
38
+ super().__init__(detail)
39
+ self.reason = reason
40
+ self.detail = detail
41
+
42
+
43
+ class DuplicateKeyError(ValueError):
44
+ pass
45
+
46
+
47
+ def reject_duplicate_keys(pairs: list[tuple[str, object]]) -> dict[str, object]:
48
+ result: dict[str, object] = {}
49
+ for key, value in pairs:
50
+ if key in result:
51
+ raise DuplicateKeyError
52
+ result[key] = value
53
+ return result
54
+
55
+
56
+ def fail(reason: str, detail: str) -> NoReturn:
57
+ raise UpdateError(reason, detail)
58
+
59
+
60
+ def read_regular(path: Path, *, label: str, max_bytes: int) -> tuple[bytes, int]:
61
+ if not hasattr(os, "O_NOFOLLOW") or not hasattr(os, "O_NONBLOCK"):
62
+ fail(
63
+ f"{label}_unsupported",
64
+ "this platform cannot safely open files without following links or blocking on special files",
65
+ )
66
+ flags = os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK | getattr(os, "O_CLOEXEC", 0)
67
+ fd = -1
68
+ try:
69
+ fd = os.open(path, flags)
70
+ except OSError as exc:
71
+ fail(f"{label}_unreadable", str(exc))
72
+ try:
73
+ info = os.fstat(fd)
74
+ if not stat.S_ISREG(info.st_mode):
75
+ fail(f"{label}_not_regular", "expected a regular, non-symlink file")
76
+ if info.st_nlink != 1:
77
+ fail(f"{label}_hardlinked", "refusing a multiply-linked file")
78
+ if label == "plan" and hasattr(os, "geteuid") and info.st_uid != os.geteuid():
79
+ fail("plan_not_owned", "the plan must be owned by the current user")
80
+ if info.st_size > max_bytes:
81
+ fail(f"{label}_too_large", f"maximum is {max_bytes} bytes")
82
+ with os.fdopen(fd, "rb") as handle:
83
+ fd = -1
84
+ chunks: list[bytes] = []
85
+ remaining = max_bytes + 1
86
+ while remaining:
87
+ chunk = handle.read(min(65_536, remaining))
88
+ if not chunk:
89
+ break
90
+ chunks.append(chunk)
91
+ remaining -= len(chunk)
92
+ data = b"".join(chunks)
93
+ except OSError as exc:
94
+ fail(f"{label}_unreadable", str(exc))
95
+ finally:
96
+ if fd >= 0:
97
+ os.close(fd)
98
+ if len(data) > max_bytes:
99
+ fail(f"{label}_too_large", f"maximum is {max_bytes} bytes")
100
+ return data, stat.S_IMODE(info.st_mode)
101
+
102
+
103
+ def decode_utf8(data: bytes, *, label: str) -> str:
104
+ try:
105
+ return data.decode("utf-8")
106
+ except UnicodeDecodeError as exc:
107
+ fail(f"{label}_invalid_utf8", f"invalid UTF-8 at byte {exc.start}")
108
+
109
+
110
+ def digest(data: bytes) -> str:
111
+ return hashlib.sha256(data).hexdigest()
112
+
113
+
114
+ def normalized_intent(
115
+ value: object, *, reason_prefix: str, minimum: int = MIN_INTENT_CHARS
116
+ ) -> str:
117
+ if not isinstance(value, str):
118
+ fail(f"{reason_prefix}_not_string", "intent must be a string")
119
+ stripped = value.strip()
120
+ if value != stripped:
121
+ fail(f"{reason_prefix}_not_normalized", "intent must not have outer whitespace")
122
+ if len(value) < minimum:
123
+ fail(f"{reason_prefix}_too_short", f"minimum is {minimum} characters")
124
+ return value
125
+
126
+
127
+ def remove_file_line_ending(value: str) -> str:
128
+ """Remove only the one line ending used as a text-file delimiter."""
129
+ if value.endswith("\r\n"):
130
+ return value[:-2]
131
+ if value.endswith(("\n", "\r")):
132
+ return value[:-1]
133
+ return value
134
+
135
+
136
+ def render_plan(plan: dict[str, object]) -> bytes:
137
+ try:
138
+ encoded = (json.dumps(plan, ensure_ascii=False, indent=2) + "\n").encode(
139
+ "utf-8"
140
+ )
141
+ except UnicodeEncodeError as exc:
142
+ fail("updated_plan_invalid_unicode", f"cannot encode UTF-8 at character {exc.start}")
143
+ if len(encoded) > MAX_PLAN_BYTES:
144
+ fail("updated_plan_too_large", f"maximum is {MAX_PLAN_BYTES} bytes")
145
+ return encoded
146
+
147
+
148
+ def stable_core_identity(plan: dict[str, object]) -> tuple[int, str]:
149
+ evidence = plan.get("evidence")
150
+ if not isinstance(evidence, list):
151
+ fail("intent_core_identity_missing", "plan evidence has no stable-core identity")
152
+ records = [
153
+ item
154
+ for item in evidence
155
+ if isinstance(item, dict) and item.get("id") == STABLE_CORE_EVIDENCE_ID
156
+ ]
157
+ if not records:
158
+ fail("intent_core_identity_missing", "plan evidence has no stable-core identity")
159
+ if len(records) != 1:
160
+ fail("intent_core_identity_invalid", "plan must contain exactly one stable-core identity")
161
+ record = records[0]
162
+ if set(record) != {"id", "result"} or not isinstance(record.get("result"), str):
163
+ fail("intent_core_identity_invalid", "stable-core identity has an invalid schema")
164
+ match = STABLE_CORE_RESULT_RE.fullmatch(record["result"])
165
+ if match is None:
166
+ fail("intent_core_identity_invalid", "stable-core identity has an invalid encoding")
167
+ core_chars = int(match.group(1))
168
+ if not MIN_INTENT_CHARS <= core_chars <= MAX_INTENT_CHARS:
169
+ fail(
170
+ "intent_core_identity_invalid",
171
+ f"stable-core character length must be between {MIN_INTENT_CHARS} and {MAX_INTENT_CHARS}",
172
+ )
173
+ return core_chars, match.group(2)
174
+
175
+
176
+ def text_digest(value: str, *, reason: str, label: str) -> str:
177
+ try:
178
+ return digest(value.encode("utf-8"))
179
+ except UnicodeEncodeError as exc:
180
+ fail(reason, f"{label} cannot encode as UTF-8 at character {exc.start}")
181
+
182
+
183
+ def archive_discarded_intent(
184
+ plan: dict[str, object], *, old_intent: str, core: str
185
+ ) -> None:
186
+ """Retain the exact old intent bytes before compacting its visible field."""
187
+ suffix = old_intent[len(core) :]
188
+ if not suffix:
189
+ return
190
+ evidence = plan.get("evidence")
191
+ if not isinstance(evidence, list):
192
+ fail("intent_history_invalid", "plan evidence must be an array")
193
+ used_ids = {
194
+ item.get("id")
195
+ for item in evidence
196
+ if isinstance(item, dict) and isinstance(item.get("id"), str)
197
+ }
198
+ group = None
199
+ for group_number in range(1, MAX_EVIDENCE_ROWS + 1):
200
+ candidate_group = f"{group_number:04d}"
201
+ candidate_prefix = f"{HISTORY_EVIDENCE_PREFIX}-{candidate_group}"
202
+ if not any(
203
+ evidence_id == f"{candidate_prefix}-manifest"
204
+ or evidence_id.startswith(f"{candidate_prefix}-part-")
205
+ for evidence_id in used_ids
206
+ ):
207
+ group = candidate_group
208
+ break
209
+ if group is None:
210
+ fail("intent_history_full", "no unique intent-history evidence group remains")
211
+ try:
212
+ old_bytes = old_intent.encode("utf-8")
213
+ suffix_bytes = suffix.encode("utf-8")
214
+ except UnicodeEncodeError as exc:
215
+ fail(
216
+ "intent_history_invalid_unicode",
217
+ f"old intent cannot encode as UTF-8 at character {exc.start}",
218
+ )
219
+ encoded_suffix = base64.b64encode(suffix_bytes).decode("ascii")
220
+ chunks = [
221
+ encoded_suffix[index : index + HISTORY_BASE64_CHUNK_CHARS]
222
+ for index in range(0, len(encoded_suffix), HISTORY_BASE64_CHUNK_CHARS)
223
+ ]
224
+ prefix = f"{HISTORY_EVIDENCE_PREFIX}-{group}"
225
+ rows: list[dict[str, str]] = [
226
+ {
227
+ "id": f"{prefix}-manifest",
228
+ "result": (
229
+ f"format={HISTORY_EVIDENCE_PREFIX};"
230
+ f"old_chars={len(old_intent)};"
231
+ f"old_sha256={hashlib.sha256(old_bytes).hexdigest()};"
232
+ f"core_chars={len(core)};"
233
+ f"suffix_bytes={len(suffix_bytes)};"
234
+ f"suffix_sha256={hashlib.sha256(suffix_bytes).hexdigest()};"
235
+ f"encoding=base64-utf8;parts={len(chunks)}"
236
+ ),
237
+ }
238
+ ]
239
+ rows.extend(
240
+ {
241
+ "id": f"{prefix}-part-{part_number:04d}",
242
+ "result": (
243
+ f"format={HISTORY_EVIDENCE_PREFIX};group={group};"
244
+ f"part={part_number}/{len(chunks)};data={chunk}"
245
+ ),
246
+ }
247
+ for part_number, chunk in enumerate(chunks, start=1)
248
+ )
249
+ if len(evidence) + len(rows) > MAX_EVIDENCE_ROWS:
250
+ fail(
251
+ "intent_history_evidence_overflow",
252
+ f"zero-loss compaction needs {len(rows)} history rows but the plan "
253
+ f"would exceed {MAX_EVIDENCE_ROWS} evidence rows",
254
+ )
255
+ plan["evidence"] = [*evidence, *rows]
256
+
257
+
258
+ def atomic_replace(path: Path, data: bytes, mode: int) -> None:
259
+ fd = -1
260
+ temp_path: Path | None = None
261
+ try:
262
+ try:
263
+ fd, raw_temp = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
264
+ temp_path = Path(raw_temp)
265
+ with os.fdopen(fd, "wb") as handle:
266
+ fd = -1
267
+ handle.write(data)
268
+ handle.flush()
269
+ os.fchmod(handle.fileno(), mode)
270
+ os.fsync(handle.fileno())
271
+ except OSError as exc:
272
+ fail("plan_write_failed", str(exc))
273
+ try:
274
+ os.replace(temp_path, path)
275
+ temp_path = None
276
+ except OSError as exc:
277
+ fail("plan_write_failed", str(exc))
278
+ try:
279
+ directory_flags = (
280
+ os.O_RDONLY
281
+ | getattr(os, "O_DIRECTORY", 0)
282
+ | getattr(os, "O_CLOEXEC", 0)
283
+ )
284
+ directory_fd = os.open(path.parent, directory_flags)
285
+ try:
286
+ os.fsync(directory_fd)
287
+ finally:
288
+ os.close(directory_fd)
289
+ except OSError as exc:
290
+ fail(
291
+ "plan_committed_durability_unknown",
292
+ f"the target was replaced with sha256={digest(data)} but directory sync failed ({exc}); re-read the plan and do not retry blindly",
293
+ )
294
+ finally:
295
+ cleanup_errors: list[str] = []
296
+ if fd >= 0:
297
+ try:
298
+ os.close(fd)
299
+ except OSError as exc:
300
+ cleanup_errors.append(f"close failed: {exc}")
301
+ if temp_path is not None:
302
+ try:
303
+ temp_path.unlink()
304
+ except FileNotFoundError:
305
+ pass
306
+ except OSError as exc:
307
+ cleanup_errors.append(f"temporary-file removal failed: {exc}")
308
+ if cleanup_errors:
309
+ active_error = sys.exc_info()[1]
310
+ detail = "; ".join(cleanup_errors)
311
+ if isinstance(active_error, UpdateError):
312
+ detail = (
313
+ f"{detail}; original failure was {active_error.reason}: "
314
+ f"{active_error.detail}"
315
+ )
316
+ raise UpdateError("plan_cleanup_failed", detail)
317
+
318
+
319
+ def parse_args(argv: list[str]) -> argparse.Namespace:
320
+ parser = argparse.ArgumentParser(
321
+ description=(
322
+ "Update a review plan intent without lossy truncation. Overflow is "
323
+ "an error and leaves the plan unchanged."
324
+ )
325
+ )
326
+ parser.add_argument("--plan", required=True, type=Path)
327
+ parser.add_argument("--append-intent-file", type=Path)
328
+ parser.add_argument("--compact-core-intent-file", type=Path)
329
+ parser.add_argument("--latest-intent-file", type=Path)
330
+ parser.add_argument(
331
+ "--expected-sha256",
332
+ help="optional stale-input guard for the current plan bytes",
333
+ )
334
+ return parser.parse_args(argv)
335
+
336
+
337
+ def run(argv: list[str]) -> int:
338
+ args = parse_args(argv)
339
+ append_mode = args.append_intent_file is not None
340
+ compact_mode = (
341
+ args.compact_core_intent_file is not None or args.latest_intent_file is not None
342
+ )
343
+ if append_mode == compact_mode or (
344
+ compact_mode
345
+ and (
346
+ args.compact_core_intent_file is None
347
+ or args.latest_intent_file is None
348
+ )
349
+ ):
350
+ fail(
351
+ "intent_update_mode_invalid",
352
+ "choose append, or supply both compact core and latest intent files",
353
+ )
354
+ plan_bytes, plan_mode = read_regular(
355
+ args.plan, label="plan", max_bytes=MAX_PLAN_BYTES
356
+ )
357
+ if plan_mode & 0o7000:
358
+ fail("plan_mode_unsupported", "setuid, setgid, and sticky plan modes are unsupported")
359
+ plan_mode &= 0o777
360
+ current_digest = digest(plan_bytes)
361
+ if args.expected_sha256 is not None:
362
+ expected = args.expected_sha256.lower()
363
+ if SHA256_RE.fullmatch(expected) is None:
364
+ fail("expected_sha256_invalid", "expected exactly 64 hexadecimal characters")
365
+ if expected != current_digest:
366
+ fail("plan_digest_mismatch", "the plan changed after the caller read it")
367
+
368
+ try:
369
+ plan = json.loads(
370
+ decode_utf8(plan_bytes, label="plan"),
371
+ object_pairs_hook=reject_duplicate_keys,
372
+ )
373
+ except DuplicateKeyError:
374
+ fail("plan_duplicate_key", "duplicate JSON object keys are unsupported")
375
+ except json.JSONDecodeError as exc:
376
+ fail("plan_invalid_json", f"JSON parse failed at line {exc.lineno} column {exc.colno}")
377
+ if not isinstance(plan, dict) or set(plan) != PLAN_FIELDS:
378
+ fail("plan_schema_invalid", "top-level fields must be intent, acceptance, self_review, evidence")
379
+ old_intent = normalized_intent(plan["intent"], reason_prefix="plan_intent")
380
+
381
+ if append_mode:
382
+ if len(old_intent) > MAX_INTENT_CHARS:
383
+ fail("plan_intent_overflow", f"maximum is {MAX_INTENT_CHARS} characters")
384
+ incoming_bytes, _ = read_regular(
385
+ args.append_intent_file, label="intent_input", max_bytes=MAX_PLAN_BYTES
386
+ )
387
+ incoming = remove_file_line_ending(
388
+ decode_utf8(incoming_bytes, label="intent_input")
389
+ )
390
+ if not incoming:
391
+ fail("intent_append_empty", "append input must not be empty")
392
+ candidate = old_intent + incoming
393
+ reason_prefix = "intent_append"
394
+ else:
395
+ core_bytes, _ = read_regular(
396
+ args.compact_core_intent_file,
397
+ label="intent_core",
398
+ max_bytes=MAX_PLAN_BYTES,
399
+ )
400
+ latest_bytes, _ = read_regular(
401
+ args.latest_intent_file,
402
+ label="intent_latest",
403
+ max_bytes=MAX_PLAN_BYTES,
404
+ )
405
+ core = normalized_intent(
406
+ remove_file_line_ending(decode_utf8(core_bytes, label="intent_core")),
407
+ reason_prefix="intent_core",
408
+ )
409
+ latest = normalized_intent(
410
+ remove_file_line_ending(decode_utf8(latest_bytes, label="intent_latest")),
411
+ reason_prefix="intent_latest",
412
+ minimum=1,
413
+ )
414
+ if not old_intent.startswith(core):
415
+ fail(
416
+ "intent_core_not_preserved",
417
+ "compact core must be an exact prefix of the current intent",
418
+ )
419
+ if latest in old_intent:
420
+ fail(
421
+ "intent_latest_not_new",
422
+ "latest transition is already present in the current intent",
423
+ )
424
+ core_chars, core_sha256 = stable_core_identity(plan)
425
+ if len(old_intent) < core_chars:
426
+ fail(
427
+ "plan_core_identity_mismatch",
428
+ "current intent is shorter than its persisted stable-core identity",
429
+ )
430
+ persisted_core = old_intent[:core_chars]
431
+ if text_digest(
432
+ persisted_core,
433
+ reason="plan_core_identity_mismatch",
434
+ label="persisted stable core",
435
+ ) != core_sha256:
436
+ fail(
437
+ "plan_core_identity_mismatch",
438
+ "persisted stable-core identity does not match the current intent",
439
+ )
440
+ if len(core) != core_chars or text_digest(
441
+ core,
442
+ reason="intent_core_identity_mismatch",
443
+ label="compact core",
444
+ ) != core_sha256:
445
+ fail(
446
+ "intent_core_identity_mismatch",
447
+ "compact core does not match the plan's persisted stable-core identity",
448
+ )
449
+ archive_discarded_intent(plan, old_intent=old_intent, core=core)
450
+ candidate = f"{core}\n\n{latest}"
451
+ reason_prefix = "intent_compact"
452
+
453
+ candidate = normalized_intent(candidate, reason_prefix=reason_prefix)
454
+ if len(candidate) > MAX_INTENT_CHARS:
455
+ fail(
456
+ f"{reason_prefix}_overflow",
457
+ f"candidate has {len(candidate)} characters; maximum is {MAX_INTENT_CHARS}; rebuild intent as core + latest and keep history in evidence/prior results",
458
+ )
459
+
460
+ plan["intent"] = candidate
461
+ updated = render_plan(plan)
462
+ atomic_replace(args.plan, updated, plan_mode)
463
+ # flush inside the guarded path: without it the receipt sits in the stdout
464
+ # buffer and a closed pipe only surfaces BrokenPipeError at interpreter
465
+ # shutdown, after main() returned rc 0 and past its handler.
466
+ print(
467
+ "review_plan_intent_updated "
468
+ f"mode={'append' if append_mode else 'compact'} "
469
+ f"chars={len(candidate)} old_sha256={current_digest} new_sha256={digest(updated)}",
470
+ flush=True,
471
+ )
472
+ return 0
473
+
474
+
475
+ def main() -> int:
476
+ try:
477
+ return run(sys.argv[1:])
478
+ except UpdateError as exc:
479
+ print(
480
+ f"review_plan_intent_error: reason={exc.reason} detail={exc.detail}",
481
+ file=sys.stderr,
482
+ )
483
+ return 2
484
+ except (BrokenPipeError, OSError):
485
+ # The success receipt is printed (flushing) only after atomic_replace
486
+ # committed, and every other file operation converts its OSError to
487
+ # UpdateError, so an OS-level error here means the receipt was lost on
488
+ # a closed/broken stdout — not that the update failed. rc 0 with no
489
+ # receipt would read as "no update happened"; report the committed-but-
490
+ # unreported state the same way a failed durability sync does.
491
+ try:
492
+ print(
493
+ "review_plan_intent_error: reason=plan_committed_receipt_lost "
494
+ "detail=stdout closed before the success receipt was delivered; "
495
+ "re-read the plan for the committed state and do not retry blindly",
496
+ file=sys.stderr,
497
+ )
498
+ sys.stderr.flush()
499
+ except (BrokenPipeError, OSError):
500
+ pass
501
+ # Point stdout at devnull so the interpreter's shutdown flush of the
502
+ # broken pipe cannot override this exit status with 120.
503
+ try:
504
+ devnull = os.open(os.devnull, os.O_WRONLY)
505
+ os.dup2(devnull, sys.stdout.fileno())
506
+ os.close(devnull)
507
+ except (OSError, ValueError):
508
+ pass
509
+ return 2
510
+
511
+
512
+ if __name__ == "__main__":
513
+ raise SystemExit(main())
@@ -18,7 +18,10 @@ Use this for implementation of new backend products and services. It should adap
18
18
  - Use codebase-specific skills only when the task is explicitly about an existing legacy/workspace repository.
19
19
  - Development references should turn already-chosen architecture into code, tests, and generated artifacts. If a change requires choosing ownership, security model, source of truth, or release governance, first apply `go-microservice-architecture`.
20
20
  - For money, billing, quota, permission, tenant/user data isolation, high-impact AI, repeated writes, async finality, or incident-explanation risk, apply `product-rd-workflow` high-risk resilience gates and route test-layer design through `testing-strategy`.
21
- - When a change edits strings, templates, or config values that are returned to, persisted for, emitted to, served to, synchronized with, or configured for client consumption (error copy, labels, notification text, localization payloads, content/CMS/seed rows, message or notification templates, flag-delivered content), classify the consumers with a recorded bounded check (client repo / contract / locale search) before closing on API/log evidence; if any client surface renders the value user-facing, or consumers are unknown, load `product-ui-ux-design` and record its implementation-owner checkpoint — including the consuming client stack owner(s) and `testing-strategy` per that checkpoint's field list — with client-side rendered-evidence routing. Backend-only closure without that recorded consumer check is invalid.
21
+ - When a change can alter what a client renders or which state, action, or decision path it offers—including strings/templates/config/flags and API/event/schema fields, enums, status/progress, permission/capability signals, defaults, or result shapes—load `../product-ui-ux-design/references/delivery-contract.md`, create the applicable full or lightweight record in that contract, and follow its canonical consumer-universe classification, design/test/client handoffs, and terminal-status rules.
22
+ - This Go owner returns only its `producer_record` delta: immutable binding, build/schema/config artifact identity, exact command/environment, and API/event/log/output observation.
23
+
24
+ - For a standalone Go CLI, this skill owns Go parser/library implementation mechanics. Any change to a user-facing command tree, subcommand, flag/default/action path, help/output/exit behavior, confirmation, progress, or recovery path also loads `terminal-cli-dev`, which owns the terminal contract and its UI/UX/testing handoff. Only internal parser refactors proven to preserve all user-visible semantics may skip that owner.
22
25
 
23
26
  ## Generalization Discipline
24
27
 
@@ -16,7 +16,8 @@ Use this for product backend work that calls, hosts, evaluates, or operates LLM
16
16
  - Use `product-rd-workflow` first when the request spans product goal, PRD, architecture, implementation plan, release, and learning loop.
17
17
  - Use `product-rd-workflow` first for AI/algorithm product launch SOPs, business acceptance baselines, build-vs-buy ROI, new-vs-iteration launch gates, or multi-algorithm product quality gates. This skill owns inference implementation/evaluation mechanics after the product gate is defined.
18
18
  - For high-impact answers or decisions where wrong output can mislead users, affect money/rights/access, or create support/compliance risk, use `product-rd-workflow` high-risk resilience gates before fallback, downgrade, or launch decisions.
19
- - When a change edits strings, templates, or config values that are returned to, persisted for, emitted to, served to, synchronized with, or configured for client consumption (error copy, labels, notification text, localization payloads, content/CMS/seed rows, message or notification templates, flag-delivered content), classify the consumers with a recorded bounded check (client repo / contract / locale search) before closing on API/log evidence; if any client surface renders the value user-facing, or consumers are unknown, load `product-ui-ux-design` and record its implementation-owner checkpoint — including the consuming client stack owner(s) and `testing-strategy` per that checkpoint's field list — with client-side rendered-evidence routing. Inference-side closure without that recorded consumer check is invalid.
19
+ - When a change can alter what a client renders or which state, action, or decision path it offers—including strings/templates/config/flags and API/event/schema fields, enums, status/progress, permission/capability signals, defaults, or result shapes—you must load `../product-ui-ux-design/references/delivery-contract.md`, create the applicable full or lightweight record in that contract, and follow its canonical consumer-universe classification, design/test/client handoffs, and terminal-status rules.
20
+ - This inference owner returns only its `producer_record` delta: immutable binding, prompt/model/config/artifact identity, exact command/environment, and API/event/log/output observation.
20
21
 
21
22
  ## Generalization Discipline
22
23
 
@@ -23,9 +23,9 @@ For evaluating whether Taro is the right choice for a given project (vs native,
23
23
 
24
24
  ## Maturity Baseline
25
25
 
26
- The current rule set is **vendor-spec + framework-canonical**, not `mature confirmed`. It is grounded in: host-platform official guidelines (WeChat/Alipay/Douyin/Baidu), Taro official documentation and examples, and canonical Taro-ecosystem UI component libraries (taroify, NutUI-Taro, tdesign React mapping). It is not yet confirmed against a production-quality miniapp portfolio observed end-to-end. Treat positive rules as defaults to apply, anti-patterns as guardrails, and upgrade rules to `confirmed` only after they pass a real feature delivery without correction.
26
+ The current baseline is **vendor-spec + framework-canonical**, not `mature confirmed`: host-platform guidelines, Taro documentation/examples, and canonical Taro UI libraries (taroify, NutUI-Taro, tdesign React mapping). No production-quality miniapp portfolio has been observed end-to-end. Apply positive rules as defaults and anti-patterns as guardrails, and mark them `confirmed` only after a correction-free real feature delivery.
27
27
 
28
- **On existing team mini-program codebases**: a team already running a Taro (or other) mini-program codebase does NOT automatically supply positive rules. Production use is evidence of distribution, not of quality. Until that codebase has been audited end-to-end against this skill's rules — or piloted through one real feature delivery cycle with retrospect captured via `skill-extraction-workflow` — it carries the `quality-unverified` label from `references/source-evidence-map.md`. Forward work uses this skill from the start; lessons from the existing code flow back through the extraction workflow, not by silently lifting patterns.
28
+ **Existing team codebases** do not automatically supply positive rules: production use proves distribution, not quality. Until audited end-to-end against this skill or piloted through one real feature with a `skill-extraction-workflow` retrospective, label them `quality-unverified` per `references/source-evidence-map.md`. Use this skill from the start and feed lessons back through extraction, not silently copy patterns.
29
29
 
30
30
  ## Runtime Compatibility
31
31
 
@@ -101,7 +101,7 @@ Cross-checking rule: when editing code shared with a React web project, also che
101
101
 
102
102
  ## Core Workflow
103
103
 
104
- Before editing Taro/native mini-program code, page config, host capability adapters, platform project files, styles, assets, or tests, complete enough analysis and planning for the change to be reviewable. Scale the plan to risk: a simple low-risk single-page change can use a short inline plan; multi-target, user-visible, API-visible, host-capability, platform-review/release, bug-fix, branch/MR, unclear-risk, or high-risk work needs explicit task split, design checkpoint, host/target verification matrix, acceptance checks, verification commands, rollback or stop conditions, and named handoffs to design, testing, web/app, backend, release, or diagnosis skills before edits.
104
+ Before editing Taro/native mini-program code, page config, host capability adapters, platform project files, styles, assets, or tests, complete enough analysis and planning for the change to be reviewable. Scale the plan to risk: a simple low-risk single-page change can use a short inline plan; multi-target, API-visible, host-capability, platform-review/release, bug-fix, branch/MR, unclear-risk, or high-risk work needs explicit task split, host/target verification matrix, acceptance checks, verification commands, rollback or stop conditions, and named handoffs to testing, web/app, backend, release, or diagnosis skills before edits. Runtime-visible work consumes the canonical Design brief and Test Phase 0 before implementation.
105
105
 
106
106
  1. Resolve the miniapp platform and delivery shape.
107
107
  - Host platform target(s): WeChat, Alipay, Douyin/TikTok, Baidu, or several at once. Multi-target = a separate verification matrix; one target compiling is not proof another target passes.
@@ -116,8 +116,8 @@ Before editing Taro/native mini-program code, page config, host capability adapt
116
116
  - For native miniapp: WeChat uses `app.json` + `project.config.json` + `sitemap.json` + `ext.json` (plugin/extension); Alipay uses `app.json` + `mini.project.json`; Douyin/Baidu use their own platform project files. `manifest.json` + `pages.json` is uni-app shape, not native; only include it when the repo is uni-app. Also inspect package config, build scripts, and CI jobs as applicable.
117
117
  - Identify platform-branching code paths: `process.env.TARO_ENV` checks in Taro, conditional compilation blocks, or platform-specific files (`*.weapp.tsx`, `*.alipay.tsx`). Confirm branching lives at the adapter/wrapper layer, not in render code.
118
118
  - Identify whether the change must be shared, forked, or guarded by capability detection.
119
- - For visible changes, map the design checkpoint to page states, navigation/back behavior, permission prompts, loading geometry, error recovery, and screenshot acceptance. Also record `product-ui-ux-design`'s implementation-owner checkpoint before the first edit — its field list (design/stack/test owners, entry-rule evidence, rendered/device evidence status) and copy-only path are authoritative there; load the named owner skills rather than only naming them, and treat a completion claim without `captured/verified` rendered evidence as incomplete — an explicitly accepted gap closes the slice only as `pre-runtime-test ready` / handoff, never as complete/done.
120
- - For UI/UX redesign slices meeting `product-ui-ux-design`'s page-slice trigger conditions — that gate's trigger list is authoritative and must be checked, not paraphrased, whenever a screen/surface change could be a redesign, restyle, new-style declaration, structural/visual-system change, continuation, or redesigned-surface reference — apply its cross-stack page-slice gate before Taro or host-platform mechanics: RED-first focused assertion, IA regrouping by user intent/consequence, behavior-contract preservation, state matrix, rendered evidence, and the design verdict (`accepted` / `rejected` / `pending`; missing = `pending`, and `design-rejected` blocks complete/MR-ready/normal/draft MR per the **Rejected-surface rule**). Translate Web/App examples into host developer-tool or real-device proof; a browser/H5-only preview does not satisfy mini-program UI/UX evidence.
119
+ - For every visible UI change, load `../product-ui-ux-design/references/delivery-contract.md` and consume either its full Design brief + Phase 0 or its valid low-risk copy-only record + lightweight Phase 0 before coding. The lightweight path checks semantics, accessible name, localization, rendered extent, and target-host render without inventing unrelated matrices; risk-bearing copy uses the full path. For full slices, map structure, state/adaptation matrices, behavior and criteria to pages/host adapters; record route/back/share entry, hosts, capabilities, recovery geometry, and preserved behavior. A mini-program `web-view` has a host member here and a separate web-content owner member; browser/H5-only preview satisfies neither the shipped-host bridge nor the complete owner set.
120
+ - Before the first implementation edit, add the canonical `client_entry` defined there: local rule identifier or short quote and implementation decision, target surface/runtime, planned run/capture command, and behavior that must remain unchanged.
121
121
 
122
122
  3. Define the miniapp contract before coding.
123
123
  - Pages, route params, tab ownership, back behavior, deep links, scene/query entry, and share/open-from-chat behavior. Treat every scene/share/QR param as untrusted input: schema-parse it, server-authorize the referenced target against the current identity, and require backend-issued, TTL-bounded, replay-protected share tokens for attribution or unlock flows. Client-side attribution is never the final source of truth.
@@ -155,15 +155,16 @@ Before editing Taro/native mini-program code, page config, host capability adapt
155
155
  4. 静态资源:图片/字体被 wxml/wxss 引用 → `grep -rn "<filename>" src/`;分包资源 → 查每个分包 `pages` 列表
156
156
  5. 平台条件编译(Taro 多端):`#ifdef WEAPP / ALIPAY` 内的 import 在另一端不存在;按目标平台跑 build 看 warning
157
157
  - For visible changes, inspect the rendered page in the relevant developer tool (WeChat DevTools, Alipay IDE, Douyin DevTools, Baidu DevTools), simulator, preview build, or real device and capture evidence where feasible.
158
- - For systemic UI/UX redesign slices, diff the declared host target list against repo-configured build targets; every configured target must be classified as shipped (needs rendered evidence), product-level permanently excluded (can complete) — valid only when the authoritative build/release target source already stopped shipping that target before this slice; removing or disabling a target within the slice is a separate product/release scope change that routes through its product/risk/release owners and cannot satisfy this gate's evidence for the same slice; explanatory docs or an MR comment alone are temporary-skip authority, never permanent exclusion — or temporary slice-skip for a shipped target (leaves that host `pre-runtime-test ready` / `blocked`), and any unclassified target blocks completion.
158
+ - For systemic UI/UX redesign slices, diff the declared host target list against repo-configured build targets; every configured target must be classified as shipped (needs rendered evidence), product-level permanently excluded (can complete) — valid only when the authoritative build/release target source already stopped shipping that target before this slice; removing or disabling a target within the slice is a separate product/release scope change that routes through its product/risk/release owners and cannot satisfy this gate's evidence for the same slice; explanatory docs or an MR comment alone are temporary-skip authority, never permanent exclusion — or temporary slice-skip for a shipped target (leaves that host `pre-runtime-test-ready` / `blocked`), and any unclassified target blocks completion.
159
159
  - For UI/UX redesign evidence, include declared host targets, host developer-tool or real-device channel, loading/empty/error/final states, long text or text-scale behavior where supported, permission/capability prompts, route/share/scene entry when relevant, and screenshot or equivalent host-rendered artifact. Mark each dimension covered or `N/A` with a one-line reason; `N/A` is valid only when the reason names a verifiable structural fact, explains why that fact makes the dimension unreachable or unchanged for this slice, and includes a checkable pointer such as a file path, config key, or commit that resolves at review time. Persist evidence artifacts where reviewers can access them using sanitized/test accounts and redacting tokens, PII, credentials, private paths, and raw personal data; remove temporary smoke files or generated preview helpers before commit unless the repo intentionally owns them.
160
- - For mini-program runtime changes, developer-tool or real-device smoke is a completion gate, not optional evidence. This includes changes to `Taro.*` or host APIs, `wx.*`/`my.*` calls, chunked/streaming transport, foreground/background recovery, route/share/scene behavior, storage/session restore, permissions/capabilities, and host-rendered loading/error/final states. If the tool or device is missing, first attempt discovery and normal setup; if still unavailable, stop at `pre-runtime-test ready` or `blocked` and name the owner, attempted commands, residual risk, and next unblock action. `pre-runtime-test ready` is a handoff-only label; it is not merge-ready, release-ready, or complete.
160
+ - Return the complete canonical client-record member defined in `../product-ui-ux-design/references/delivery-contract.md` for testing Phase 1 and the design verdict. The member includes its applied rule/decision, affected files/components, preserved behavior, exact command, immutable candidate binding, producer member/version actually exercised, artifacts, tested host/tool/device targets, states/dimensions/capabilities, criterion-mapped observations, coverage boundary, and gaps. A host render proves only the captured host/member states; it cannot close an unbound producer member. `testing-strategy` records aggregate sufficiency before the design owner records the candidate-bound verdict.
161
+ - For mini-program runtime changes, developer-tool or real-device smoke is a completion gate, not optional evidence. This includes changes to `Taro.*` or host APIs, `wx.*`/`my.*` calls, chunked/streaming transport, foreground/background recovery, route/share/scene behavior, storage/session restore, permissions/capabilities, and host-rendered loading/error/final states. If the tool or device is missing, first attempt discovery and normal setup; if still unavailable, stop at `pre-runtime-test-ready` or `blocked` and name the owner, attempted commands, residual risk, and next unblock action. `pre-runtime-test-ready` is a handoff-only label; it is not merge-ready, release-ready, or complete.
161
162
  - If an automation, remote-control, or screenshot channel reports a blank or stale mini-program surface while a human operator can see the real host page rendering, treat it as an observation-channel conflict before treating it as an app defect. Re-check focus/window/permission state, capture the human-visible state through another channel when possible, label which evidence came from the automation channel versus the human-visible host, and only mark "blank screen" as a product defect after at least one host-visible channel reproduces it.
162
163
  - When a human operator's already-authenticated host client or device is used as the runtime test surface, treat it as a human-assisted host test: record observer role/source class, sanitized account class, host client/device, entry path, actions performed, redacted artifacts, state changes such as login/logout or permission prompts, and restoration outcome in the project evidence. Label it as manual, scenario-scoped evidence; it does not replace required automated assertions or other host checks. If requested logout/account-switch/storage/permission restoration is not confirmed, mark the host test blocked or incomplete until restored or handed off to a named owner. Do not record personal phone numbers, personal operator names, chat/contact handles, tokens, private account names, or reviewer credentials in shared artifacts.
163
164
  - Treat appid, dev-tool login, plugin authorization, service-port availability, and host identity/configuration as part of the runtime verification surface, not as background noise. A generated preview QR or a backend login success does not prove the mini-program runtime path until the correct host/app identity and permissions are exercised in the tool or on device.
164
165
  - Verify route entry, share/deep-link scene params, auth state, storage restore, network error, permission denial, and primary recovery path for affected flows.
165
- - For detail pages and deep-link/share/QR entry points, test missing or stale route params, missing local storage/cache payloads, expired auth, and direct cold entry. These states must resolve to an explicit empty/error/recovery state or safe redirect; a permanent loading spinner or blank screen under cold entry or missing-param entry is a blocking defect that must be fixed or explicitly marked `blocked` with a real owner, resolution path, and target follow-up point before the flow can be called complete. For stale storage/cache, include at least one real-device or emulator state test with a prior-version or manually seeded cache payload. Developer-tool-only stale-cache evidence is fallback evidence and must be labeled as a `device-state gap`; a flow with an open `device-state gap` is `blocked` or `pre-runtime-test ready`, not complete.
166
- - For payment, subscription, login, phone, camera/media, or write-finality changes, verify sandbox/mock plus one platform-specific happy path. If platform evidence is unavailable after remediation, stop at `pre-runtime-test ready` or `blocked`; do not complete the work by only recording the gap.
166
+ - For detail pages and deep-link/share/QR entry points, test missing or stale route params, missing local storage/cache payloads, expired auth, and direct cold entry. These states must resolve to an explicit empty/error/recovery state or safe redirect; a permanent loading spinner or blank screen under cold entry or missing-param entry is a blocking defect that must be fixed or explicitly marked `blocked` with a real owner, resolution path, and target follow-up point before the flow can be called complete. For stale storage/cache, include at least one real-device or emulator state test with a prior-version or manually seeded cache payload. Developer-tool-only stale-cache evidence is fallback evidence and must be labeled as a `device-state gap`; a flow with an open `device-state gap` is `blocked` or `pre-runtime-test-ready`, not complete.
167
+ - For payment, subscription, login, phone, camera/media, or write-finality changes, verify sandbox/mock plus one platform-specific happy path. If platform evidence is unavailable after remediation, stop at `pre-runtime-test-ready` or `blocked`; do not complete the work by only recording the gap.
167
168
  - For release work, verify app id/env, version, build output, platform review checklist, gray release/rollback path, analytics version tag, and owner handoff. For every **shipped** host platform, compile + developer-tool/real-device evidence is blocking — "recorded as unverified" is only acceptable for targets the release is not actually shipping. Mini-program rollback through host-platform re-review is slow; risky flows must therefore have a **server-side feature flag with safe default + tested kill-switch runbook** in place before submission. Capture the current official platform-policy doc URL + date for every review-sensitive area touched (payment, privacy, AI/generated content, minors, financial/medical/legal copy) — policy text changes faster than skill rules.
168
169
 
169
170
  ## Non-Negotiable Rules
@@ -184,7 +185,7 @@ Before editing Taro/native mini-program code, page config, host capability adapt
184
185
  - Do not scatter backend enum/string literals through mini-program pages, scene/share/QR parsing, host bridge payload handling, storage, analytics, or tests. Centralize finite-value parsing, display labels, defaults, and unknown-value behavior at the API/client-domain boundary, and keep raw literals only in clearly named boundary conversion tests that cover every known external value plus unknown/default behavior. Migrate existing non-boundary test raw literals for that value in the same pull request or mark each remaining use with `finite-value-debt: <task-ref> <owner> <deadline> <reason>`, even when the current slice does not introduce a new mapper.
185
186
  - Do not ship auth, payment, phone, location, camera, share, subscription, or generated-content flows without explicit denial/error/retry states.
186
187
  - Do not ship a host platform without compile + developer-tool/real-device evidence for that platform — "recorded as unverified" is only acceptable for non-shipped targets.
187
- - Do not claim a mini-program runtime fix is complete when developer-tool or real-device smoke did not run. Build output, unit tests, source-regex checks, and independent code review can make the branch `pre-runtime-test ready`; they cannot make host-runtime behavior complete.
188
+ - Do not claim a mini-program runtime fix is complete when developer-tool or real-device smoke did not run. Build output, unit tests, source-regex checks, and independent code review can make the branch `pre-runtime-test-ready`; they cannot make host-runtime behavior complete.
188
189
  - Do not submit a risky flow for platform review without a server-side feature flag (safe default + kill-switch runbook); platform-review rollback is too slow to be the only lever. The flag does not stop client-only effects (permission prompts triggered at startup, SDK auto-collection on load, host-platform config already submitted) — the flow's **client-side code path itself must no-op when the flag is off**, the SDK must not load until the flag is on, and any host-config change submitted at review time must be reviewed for "what if we need to disable this without a new submission" before approval.
189
190
  - Do not ship review-sensitive surfaces (payment, privacy disclosure, AI/generated content, minors, financial/medical/legal copy, account deletion, SDK data collection) without naming the current platform-policy doc URL + date you read.
190
191
  - Do not claim platform review or real-device readiness without current evidence.