@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.
- package/README.md +2 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/SKILL.md +8 -7
- package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/mobile-quality-release.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/SKILL.md +16 -17
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/client-routing.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/staged-review-contract.md +195 -7
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/timeout-auth-and-capabilities.md +3 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/claude_review.sh +13 -5
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/codex_review.sh +9 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/kimi_review.sh +9 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/normalize_review_timeout.sh +22 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/opencode_review.sh +9 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.py +1540 -129
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_claude_review_probe.sh +8 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_client_compat.py +76 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh +1858 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_update_review_plan_intent.sh +789 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/update_review_plan_intent.py +513 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/SKILL.md +4 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/SKILL.md +2 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/SKILL.md +11 -10
- package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/SKILL.md +64 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/references/async-lifecycle-and-performance.md +72 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/references/runtime-and-project-contract.md +58 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/references/source-map.md +41 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/references/verification-diagnostics-and-security.md +63 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/SKILL.md +8 -10
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/design-routing-and-readiness.md +10 -14
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/verify-developer-experience.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/SKILL.md +135 -86
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/behavioral-aesthetic-logic.md +66 -80
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/delivery-contract.md +275 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-execution-checklist.md +88 -214
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-impl-naming-and-versioning.md +2 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-intake-and-acceptance.md +10 -8
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-system-source-of-truth.md +4 -5
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/external-ui-ux-quality-benchmarks.md +112 -95
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/frontend-code-evidence-map.md +30 -21
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/interaction-design-patterns.md +22 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/layout-recipes-and-screenshot-acceptance.md +20 -17
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/multi-project-token-consistency.md +7 -9
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/multi-stack-strategy.md +14 -10
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/operational-processing-workflows.md +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/platform-mobile-patterns.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/product-lifecycle-acceptance-and-iteration.md +9 -6
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/product-surface-patterns.md +3 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/source-map.md +37 -10
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/tokens-and-components.md +7 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/ui-ux-audit.md +8 -5
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/ui-ux-design-development.md +16 -5
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/visual-craft.md +4 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/SKILL.md +4 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/SKILL.md +4 -4
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/dual-track-review-gate.md +95 -5
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/extraction-quickstart.md +11 -9
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/r0-leakage-audit.md +102 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +54 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-to-skill-extraction.md +8 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/uiux-judgment-extraction.md +6 -6
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/validation-and-landing.md +4 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-ccl-skills.sh +69 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/extraction_review_gate.sh +22 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/impact-chain-gate.rb +49 -4
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/obligation-ledger.py +2748 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/register-firing-path-resolution.rb +20 -5
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/shared_git_surface_gate.py +1142 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_regressions.sh +17 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_skill_catalog.sh +41 -4
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_ci_checkout_ref_binding.sh +120 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_entrypoint_domain_scan_terms.sh +82 -8
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_extraction_review_gate.sh +336 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_self_adjudication.sh +82 -10
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_obligation_ledger.sh +1416 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_obligation_ledger_repo_audit.sh +57 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_register_firing_path_wiring.sh +141 -4
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_routing_pointer_integrity.sh +3 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_shared_git_surface_gate.sh +1696 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_uiux_delivery_contract.sh +2117 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_uiux_loading_budget.sh +316 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_validate_extraction_review_state.sh +1176 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_validate_skill_cross_refs.sh +31 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/validate-skill.sh +9 -4
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/validate_extraction_review_state.py +980 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/terminal-cli-dev/SKILL.md +8 -6
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/SKILL.md +8 -7
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/client-runtime-test-matrices.md +10 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/SKILL.md +6 -5
- package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/complex-workspace-patterns.md +1 -1
- package/dist/assets/release.json +175 -70
- 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
|
|
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
|
|
package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
**
|
|
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,
|
|
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
|
|
120
|
-
-
|
|
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
|
|
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
|
-
-
|
|
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
|
|
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
|
|
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
|
|
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.
|