@ccoalm/ccl-skills 0.9.0 → 0.11.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/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/SKILL.md +4 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/staged-review-contract.md +23 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.py +178 -4
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh +127 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/defect-diagnosis/SKILL.md +2 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/SKILL.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/SKILL.md +1 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/SKILL.md +11 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/references/async-lifecycle-and-performance.md +16 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/references/source-map.md +1 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/SKILL.md +8 -8
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/delivery-lifecycle.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/dispatch-owner-skills.md +9 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/problem-resolution-and-learning.md +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/SKILL.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/tag-and-prod-pipeline-gate.md +9 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/SKILL.md +17 -20
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/attention-budget-ratchet.md +37 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/description-authoring.md +9 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/dual-track-review-gate.md +26 -44
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/eval-routing.md +24 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/external-practice-controls.md +16 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/extraction-quickstart.md +5 -5
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/rule-consolidation.md +3 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +59 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-to-skill-extraction.md +2 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/validation-and-landing.md +3 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-ccl-skills.sh +35 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-contract-anchors.sh +126 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-size-budget.sh +197 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/contract-anchors.tsv +16 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-routing-bank.rb +210 -36
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/extraction_review_gate.sh +3 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/gate_receipt.py +576 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/impact-chain-gate.rb +35 -6
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/review_ledger_binding.py +476 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_antipattern_grep_panel.sh +80 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_body_compliance_grading.sh +99 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_impact_chain_refscripts.sh +81 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_regressions.sh +28 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_size_budget.sh +251 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_contract_anchors.sh +196 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_routing_bank_grader_diagnostics.sh +222 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_extraction_review_gate.sh +16 -10
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_frozen_case_sanctity.sh +178 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_frozen_case_sanctity_selfproof.sh +108 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_gate_receipt.sh +431 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_pinned_phrase_mutation_walk.sh +151 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_review_ledger_binding.sh +336 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_routing_bank_integrity.sh +86 -5
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_routing_pointer_integrity.sh +41 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_validate_extraction_review_state.sh +141 -28
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/validate_extraction_review_state.py +139 -38
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/SKILL.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/SKILL.md +9 -8
- package/dist/assets/release.json +110 -45
- package/package.json +1 -1
|
@@ -13,7 +13,7 @@ Choose the smallest useful mode:
|
|
|
13
13
|
|
|
14
14
|
- **Review mode**: normal pre-merge or plan review. Find blocking or materially misleading issues.
|
|
15
15
|
- **Challenge mode**: adversarial pass modeled after `codex challenge`. Try to break the diff or decision by finding production failure paths.
|
|
16
|
-
- **Complete mode**: local exact-candidate deep-self-review checkpoint after a passed final
|
|
16
|
+
- **Complete mode**: local exact-candidate deep-self-review checkpoint after a passed final round. It invokes no reviewer and grants no human or merge authority.
|
|
17
17
|
- **Consult mode**: ask Claude a bounded question when no diff or plan review is needed.
|
|
18
18
|
|
|
19
19
|
Use challenge mode when the user asks for "challenge", "poke holes", "try to break it", "adversarial", or when the change touches money, permissions, privacy, compliance, tenant/user data, production rollout, high-impact AI, architecture, economics, or IA.
|
|
@@ -68,8 +68,9 @@ staged-contract and client-routing references below for details.
|
|
|
68
68
|
|
|
69
69
|
Current contract: review/challenge may use a stamped
|
|
70
70
|
`review_plan_source=derived-default`; `complete` requires a plan and output uses
|
|
71
|
-
schema 3. Automation retains one chain
|
|
72
|
-
|
|
71
|
+
schema 3. Automation retains one chain (one review, at most four challenges) plus one
|
|
72
|
+
succession.
|
|
73
|
+
Positive challenge capacity opens it at index 1; budget zero is untracked.
|
|
73
74
|
The sole release/high-risk budget-zero exception is a controller-proved
|
|
74
75
|
`markdown-punctuation-only` review: it requires `wording_only_boundary`, permits
|
|
75
76
|
no `complete`, and rejects an author assertion alone (recipe:
|
|
@@ -289,6 +289,24 @@ refreshes, mode changes, and renamed invocations do not reset Agent authority.
|
|
|
289
289
|
An initial `review` with positive challenge capacity must start this chain at
|
|
290
290
|
index 1; an untracked initial review is single-round and therefore uses budget 0.
|
|
291
291
|
|
|
292
|
+
**Chain succession.** A fix that edits a selected owner's package moves
|
|
293
|
+
`selected_skills_sha256` and ends its chain by construction, so the post-fix
|
|
294
|
+
candidate can never be challenged inside it. One succeeding chain may open at
|
|
295
|
+
index 1 in `challenge` mode by supplying `--predecessor-chain-result-file` — the
|
|
296
|
+
ended chain's terminal receipt — instead of an in-chain prior result. The
|
|
297
|
+
controller accepts it only when that receipt is a tracked challenge at its own
|
|
298
|
+
chain's terminal index, carries this chain's `review_scope_sha256` and matching
|
|
299
|
+
stage/depth/risk-tags/budget, preserves the controller digest, owner-selection
|
|
300
|
+
source, and selected owner names, and binds a candidate that DIFFERS from this
|
|
301
|
+
packet: the owner-package digest is the one binding allowed to move, because its
|
|
302
|
+
move is why the chain ended, and an unmoved candidate is a repeat round wearing a
|
|
303
|
+
new chain id. Earlier challenge focuses carry forward, so a succession focus must
|
|
304
|
+
differ from every focus the ended chain spent. The result records
|
|
305
|
+
`predecessor_chain_id`, `predecessor_result_sha256`, and
|
|
306
|
+
`predecessor_candidate_sha256`, and counts `material_candidate_change` as a
|
|
307
|
+
satisfied self-review trigger. Succession carries history rather than resetting
|
|
308
|
+
it: consumers still sum rounds across both chains.
|
|
309
|
+
|
|
292
310
|
The chain binds task scope, candidate identity per round, result hashes, mode,
|
|
293
311
|
status, challenge focus, controller, and selected owners. The opaque
|
|
294
312
|
`review_scope_sha256` always hashes normalized intent, acceptance, stage/depth,
|
|
@@ -314,6 +332,11 @@ self-review plus explicit task reframing; it does not silently create a new
|
|
|
314
332
|
Agent budget. An untracked challenge is one-off advisory evidence; it cannot
|
|
315
333
|
enter a later Agent round or satisfy the local completion checkpoint.
|
|
316
334
|
|
|
335
|
+
Two consequences follow from those stable bindings and must be planned for before round 1:
|
|
336
|
+
|
|
337
|
+
- The selected-owner digest hashes each selected owner package's current working tree, and owners derive from the candidate's own paths — so a candidate edit inside any selected owner package invalidates every prior receipt and the next tracked round fails `review_chain_invalid`. For a self-hosted candidate (a skill-repo diff editing the package that owns it) that is nearly every applied fix — one confined to files outside every selected owner drifts only the candidate hash and may continue in-chain: "do not reset Agent authority" promises no continuation, and the in-chain tolerance for older candidate hashes is reachable only while the fix stays outside its selected owners.
|
|
338
|
+
- A chain restarted after such a break re-enters the same cumulative Agent budget and must never be counted as fresh authority; the bounded restart recipe for the extraction lane (batched dispositions, cross-chain round accounting, full-context first packet, terminal disposition at the cap) is owned by the extraction workflow's dual-track gate reference.
|
|
339
|
+
|
|
317
340
|
The controller is stateless and prevents accidental/cooperative resets only. A
|
|
318
341
|
trusted host or platform must retain the ledger when hostile local callers are in
|
|
319
342
|
scope; a repository-local counter cannot authenticate human authority.
|
package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.py
CHANGED
|
@@ -429,6 +429,10 @@ CONTROLLER_OWNED_FIELDS = {
|
|
|
429
429
|
"owner_selection_source",
|
|
430
430
|
"owner_gaps",
|
|
431
431
|
"observed_skill_usage",
|
|
432
|
+
"predecessor_candidate_sha256",
|
|
433
|
+
"predecessor_chain_id",
|
|
434
|
+
"predecessor_challenge_focuses",
|
|
435
|
+
"predecessor_result_sha256",
|
|
432
436
|
"prior_challenge_focuses",
|
|
433
437
|
"prior_review_result_sha256",
|
|
434
438
|
"residual_risks",
|
|
@@ -1598,6 +1602,111 @@ def _stable_binding_matches(
|
|
|
1598
1602
|
)
|
|
1599
1603
|
|
|
1600
1604
|
|
|
1605
|
+
def _validate_chain_succession(
|
|
1606
|
+
path_value: str,
|
|
1607
|
+
*,
|
|
1608
|
+
review_chain_id: str,
|
|
1609
|
+
challenge_budget: int,
|
|
1610
|
+
review_scope_sha256: str,
|
|
1611
|
+
stage: str,
|
|
1612
|
+
review_depth: str,
|
|
1613
|
+
risk_tags: list[str],
|
|
1614
|
+
packet_hash: str,
|
|
1615
|
+
review_controller_sha256: str,
|
|
1616
|
+
owner_selection_source: str,
|
|
1617
|
+
selected_skill_names: list[str],
|
|
1618
|
+
) -> dict[str, Any]:
|
|
1619
|
+
"""Validate the terminal receipt of the chain this one succeeds.
|
|
1620
|
+
|
|
1621
|
+
A fix that edits the reviewed owner package moves ``selected_skills_sha256``
|
|
1622
|
+
and ends its chain by construction, so the post-fix candidate can never be
|
|
1623
|
+
challenged inside that chain. Succession carries the ended chain forward:
|
|
1624
|
+
every binding must still hold EXCEPT the owner-package digest whose move is
|
|
1625
|
+
the reason the chain ended, and the candidate MUST have moved — a succession
|
|
1626
|
+
whose candidate is unchanged is a repeat round wearing a new chain id.
|
|
1627
|
+
"""
|
|
1628
|
+
|
|
1629
|
+
def reject(reason: str) -> None:
|
|
1630
|
+
raise GateError(f"chain succession {reason}", "review_chain_invalid")
|
|
1631
|
+
|
|
1632
|
+
prior, result_hash = _load_prior_review_result(path_value, 1)
|
|
1633
|
+
if prior.get("predecessor_chain_id") is not None:
|
|
1634
|
+
reject("predecessor is itself a succession round; succession does not compose")
|
|
1635
|
+
prior_budget = prior.get("challenge_budget")
|
|
1636
|
+
if (
|
|
1637
|
+
prior.get("schema_version") != 3
|
|
1638
|
+
or prior.get("mode") != "challenge"
|
|
1639
|
+
or prior.get("status") not in ("passed", "findings")
|
|
1640
|
+
or prior.get("review_chain_tracked") is not True
|
|
1641
|
+
or not isinstance(prior_budget, int)
|
|
1642
|
+
or isinstance(prior_budget, bool)
|
|
1643
|
+
or prior_budget < 1
|
|
1644
|
+
):
|
|
1645
|
+
reject("predecessor is not a tracked challenge receipt")
|
|
1646
|
+
if (
|
|
1647
|
+
prior.get("autonomous_review_index") != prior_budget + 1
|
|
1648
|
+
or prior.get("challenge_index") != prior_budget
|
|
1649
|
+
or prior.get("autonomous_reviews_remaining") != 0
|
|
1650
|
+
or prior.get("autonomous_review_allowed") is not False
|
|
1651
|
+
):
|
|
1652
|
+
# Terminality is the receipt's own arithmetic, not just its index: a
|
|
1653
|
+
# forged receipt can carry a terminal index while every other field still
|
|
1654
|
+
# says the chain has rounds left.
|
|
1655
|
+
reject("predecessor is not its chain's terminal round")
|
|
1656
|
+
predecessor_chain_id = prior.get("review_chain_id")
|
|
1657
|
+
if not isinstance(predecessor_chain_id, str) or not predecessor_chain_id.strip():
|
|
1658
|
+
reject("predecessor carries no chain id")
|
|
1659
|
+
# A succession opens a second chain, so the chain it opens cannot be the chain
|
|
1660
|
+
# it succeeds: reusing the id would let one chain keep succeeding itself, and
|
|
1661
|
+
# every succession is a round the per-chain budget did not authorize. The
|
|
1662
|
+
# closeout validator refuses this too, but only for a lane it reads whole --
|
|
1663
|
+
# the controller mints one receipt at a time, and a caller that never closes a
|
|
1664
|
+
# ledger never reaches that check. Refuse where the receipt is made.
|
|
1665
|
+
if predecessor_chain_id == review_chain_id:
|
|
1666
|
+
reject("may not succeed its own chain")
|
|
1667
|
+
if _review_scope_digest(prior.get("review_scope")) != prior.get(
|
|
1668
|
+
"review_scope_sha256"
|
|
1669
|
+
):
|
|
1670
|
+
reject("predecessor carries a scope digest its own recorded scope does not produce")
|
|
1671
|
+
if prior.get("review_scope_sha256") != review_scope_sha256:
|
|
1672
|
+
reject("predecessor was reviewed under a different scope")
|
|
1673
|
+
if (
|
|
1674
|
+
prior.get("stage") != stage
|
|
1675
|
+
or prior.get("review_depth") != review_depth
|
|
1676
|
+
or prior.get("risk_tags") != risk_tags
|
|
1677
|
+
or prior_budget != challenge_budget
|
|
1678
|
+
):
|
|
1679
|
+
reject("predecessor carries the current scope digest with contradicting scope fields")
|
|
1680
|
+
if (
|
|
1681
|
+
prior.get("review_controller_sha256") != review_controller_sha256
|
|
1682
|
+
or prior.get("owner_selection_source") != owner_selection_source
|
|
1683
|
+
or prior.get("selected_skills") != selected_skill_names
|
|
1684
|
+
):
|
|
1685
|
+
reject("predecessor does not preserve the controller and owner selection")
|
|
1686
|
+
candidate_hash = prior.get("candidate_sha256")
|
|
1687
|
+
if (
|
|
1688
|
+
not isinstance(candidate_hash, str)
|
|
1689
|
+
or len(candidate_hash) != 64
|
|
1690
|
+
or prior.get("packet_sha256") != candidate_hash
|
|
1691
|
+
):
|
|
1692
|
+
reject("predecessor does not bind one frozen candidate")
|
|
1693
|
+
if candidate_hash == packet_hash:
|
|
1694
|
+
reject("candidate has not moved, so this is a repeat round rather than a succession")
|
|
1695
|
+
focuses: list[str] = []
|
|
1696
|
+
for value in [
|
|
1697
|
+
*(prior.get("prior_challenge_focuses") or []),
|
|
1698
|
+
prior.get("challenge_focus"),
|
|
1699
|
+
]:
|
|
1700
|
+
if isinstance(value, str) and value.strip():
|
|
1701
|
+
focuses.append(value)
|
|
1702
|
+
return {
|
|
1703
|
+
"chain_id": predecessor_chain_id,
|
|
1704
|
+
"result_sha256": result_hash,
|
|
1705
|
+
"candidate_sha256": candidate_hash,
|
|
1706
|
+
"focuses": focuses,
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
|
|
1601
1710
|
def _wording_only_error(reason: str) -> None:
|
|
1602
1711
|
raise GateError(reason, "wording_only_proof_invalid")
|
|
1603
1712
|
|
|
@@ -2540,6 +2649,7 @@ def freeze_review_profile(
|
|
|
2540
2649
|
or args.review_chain_id
|
|
2541
2650
|
or args.autonomous_review_index is not None
|
|
2542
2651
|
or args.prior_review_result_file
|
|
2652
|
+
or args.predecessor_chain_result_file
|
|
2543
2653
|
):
|
|
2544
2654
|
raise GateError(
|
|
2545
2655
|
"complete mode accepts only a completion review result, candidate, and self-review plan",
|
|
@@ -2703,6 +2813,7 @@ def freeze_review_profile(
|
|
|
2703
2813
|
or args.review_chain_id is not None
|
|
2704
2814
|
or args.autonomous_review_index is not None
|
|
2705
2815
|
or args.prior_review_result_file
|
|
2816
|
+
or args.predecessor_chain_result_file
|
|
2706
2817
|
):
|
|
2707
2818
|
_wording_only_error(
|
|
2708
2819
|
"--wording-only-proof-file is valid only for one untracked review with challenge budget 0"
|
|
@@ -2863,7 +2974,11 @@ def freeze_review_profile(
|
|
|
2863
2974
|
"review_chain_invalid",
|
|
2864
2975
|
)
|
|
2865
2976
|
else:
|
|
2866
|
-
if
|
|
2977
|
+
if (
|
|
2978
|
+
args.autonomous_review_index is not None
|
|
2979
|
+
or args.prior_review_result_file
|
|
2980
|
+
or args.predecessor_chain_result_file
|
|
2981
|
+
):
|
|
2867
2982
|
raise GateError(
|
|
2868
2983
|
"Agent review-chain inputs require --review-chain-id",
|
|
2869
2984
|
"review_chain_invalid",
|
|
@@ -2934,13 +3049,54 @@ def freeze_review_profile(
|
|
|
2934
3049
|
previous_challenge_focuses: list[str] = []
|
|
2935
3050
|
prior_review_result_hashes: list[str] = []
|
|
2936
3051
|
prior_review_candidate_hashes: list[str] = []
|
|
3052
|
+
succession: dict[str, Any] | None = None
|
|
3053
|
+
inherited_challenge_focuses: list[str] = []
|
|
2937
3054
|
if review_chain_tracked:
|
|
3055
|
+
if args.predecessor_chain_result_file:
|
|
3056
|
+
if args.mode != "challenge":
|
|
3057
|
+
raise GateError(
|
|
3058
|
+
"chain succession applies only to a challenge round",
|
|
3059
|
+
"review_chain_invalid",
|
|
3060
|
+
)
|
|
3061
|
+
if not challenge_focus:
|
|
3062
|
+
raise GateError("challenge mode requires a non-empty --focus")
|
|
3063
|
+
if challenge_budget == 0:
|
|
3064
|
+
raise GateError("challenge mode requires a positive challenge budget")
|
|
3065
|
+
if autonomous_review_index != 1 or args.challenge_index != 1:
|
|
3066
|
+
raise GateError(
|
|
3067
|
+
"a succession chain opens at Agent round 1 with challenge index 1",
|
|
3068
|
+
"review_chain_invalid",
|
|
3069
|
+
)
|
|
3070
|
+
if args.prior_review_result_file:
|
|
3071
|
+
raise GateError(
|
|
3072
|
+
"a succession chain opens with no in-chain prior review result",
|
|
3073
|
+
"review_chain_invalid",
|
|
3074
|
+
)
|
|
3075
|
+
succession = _validate_chain_succession(
|
|
3076
|
+
args.predecessor_chain_result_file,
|
|
3077
|
+
review_chain_id=review_chain_id,
|
|
3078
|
+
challenge_budget=challenge_budget,
|
|
3079
|
+
review_scope_sha256=review_scope_sha256,
|
|
3080
|
+
stage=args.stage,
|
|
3081
|
+
review_depth=review_depth,
|
|
3082
|
+
risk_tags=risk_tags,
|
|
3083
|
+
packet_hash=packet_hash,
|
|
3084
|
+
review_controller_sha256=review_controller_sha256,
|
|
3085
|
+
owner_selection_source=owner_selection_source,
|
|
3086
|
+
selected_skill_names=[item["name"] for item in selected_skills],
|
|
3087
|
+
)
|
|
3088
|
+
# Inherited focuses get their own field. prior_challenge_focuses carries
|
|
3089
|
+
# in-chain arithmetic that consumers derive from the round index, and a
|
|
3090
|
+
# succession round would otherwise arrive at index 1 carrying focuses the
|
|
3091
|
+
# index cannot explain -- which is exactly how the completion checkpoint
|
|
3092
|
+
# rejected it.
|
|
3093
|
+
inherited_challenge_focuses = list(succession["focuses"])
|
|
2938
3094
|
if args.mode == "review" and autonomous_review_index != 1:
|
|
2939
3095
|
raise GateError(
|
|
2940
3096
|
"tracked review mode is Agent round 1; later Agent rounds use challenge mode",
|
|
2941
3097
|
"review_chain_invalid",
|
|
2942
3098
|
)
|
|
2943
|
-
if args.mode == "challenge":
|
|
3099
|
+
if args.mode == "challenge" and succession is None:
|
|
2944
3100
|
if not challenge_focus:
|
|
2945
3101
|
raise GateError("challenge mode requires a non-empty --focus")
|
|
2946
3102
|
if challenge_budget == 0:
|
|
@@ -2961,6 +3117,11 @@ def freeze_review_profile(
|
|
|
2961
3117
|
args.prior_review_result_file, start=1
|
|
2962
3118
|
):
|
|
2963
3119
|
prior, result_hash = _load_prior_review_result(path_value, expected_index)
|
|
3120
|
+
if prior.get("predecessor_chain_id") is not None:
|
|
3121
|
+
raise GateError(
|
|
3122
|
+
f"prior review result {expected_index} is a succession round, which is one-shot",
|
|
3123
|
+
"review_chain_invalid",
|
|
3124
|
+
)
|
|
2964
3125
|
expected_mode = "review" if expected_index == 1 else "challenge"
|
|
2965
3126
|
focus = prior.get("challenge_focus")
|
|
2966
3127
|
candidate_hash = prior.get("candidate_sha256")
|
|
@@ -3038,7 +3199,9 @@ def freeze_review_profile(
|
|
|
3038
3199
|
previous_challenge_focuses.append(focus)
|
|
3039
3200
|
prior_review_result_hashes.append(result_hash)
|
|
3040
3201
|
prior_review_candidate_hashes.append(candidate_hash)
|
|
3041
|
-
if challenge_focus and challenge_focus in
|
|
3202
|
+
if challenge_focus and challenge_focus in (
|
|
3203
|
+
previous_challenge_focuses + inherited_challenge_focuses
|
|
3204
|
+
):
|
|
3042
3205
|
raise GateError(
|
|
3043
3206
|
"challenge focus must differ from earlier challenges",
|
|
3044
3207
|
"review_chain_invalid",
|
|
@@ -3062,7 +3225,7 @@ def freeze_review_profile(
|
|
|
3062
3225
|
if (
|
|
3063
3226
|
prior_review_candidate_hashes
|
|
3064
3227
|
and prior_review_candidate_hashes[-1] != packet_hash
|
|
3065
|
-
):
|
|
3228
|
+
) or succession is not None:
|
|
3066
3229
|
self_review_satisfied_triggers.append("material_candidate_change")
|
|
3067
3230
|
if high_risk:
|
|
3068
3231
|
self_review_satisfied_triggers.append("risk_or_scope_escalation")
|
|
@@ -3120,6 +3283,12 @@ def freeze_review_profile(
|
|
|
3120
3283
|
"skill_delivery": "native-installed",
|
|
3121
3284
|
"prior_challenge_focuses": previous_challenge_focuses,
|
|
3122
3285
|
"prior_review_result_sha256": prior_review_result_hashes,
|
|
3286
|
+
"predecessor_challenge_focuses": inherited_challenge_focuses,
|
|
3287
|
+
"predecessor_chain_id": succession["chain_id"] if succession else None,
|
|
3288
|
+
"predecessor_result_sha256": succession["result_sha256"] if succession else None,
|
|
3289
|
+
"predecessor_candidate_sha256": (
|
|
3290
|
+
succession["candidate_sha256"] if succession else None
|
|
3291
|
+
),
|
|
3123
3292
|
"self_review_satisfied_triggers": self_review_satisfied_triggers,
|
|
3124
3293
|
"required_concerns": [
|
|
3125
3294
|
{"id": concern_id, "description": description}
|
|
@@ -3394,6 +3563,10 @@ def composite_base(
|
|
|
3394
3563
|
"review_scope": _canonical_review_scope(profile),
|
|
3395
3564
|
"review_scope_sha256": profile["review_scope_sha256"],
|
|
3396
3565
|
"prior_review_result_sha256": profile["prior_review_result_sha256"],
|
|
3566
|
+
"predecessor_challenge_focuses": profile["predecessor_challenge_focuses"],
|
|
3567
|
+
"predecessor_chain_id": profile["predecessor_chain_id"],
|
|
3568
|
+
"predecessor_result_sha256": profile["predecessor_result_sha256"],
|
|
3569
|
+
"predecessor_candidate_sha256": profile["predecessor_candidate_sha256"],
|
|
3397
3570
|
"challenge_rounds_remaining": challenge_rounds_remaining,
|
|
3398
3571
|
"autonomous_review_budget": autonomous_review_budget,
|
|
3399
3572
|
"autonomous_review_index": autonomous_review_index,
|
|
@@ -3691,6 +3864,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3691
3864
|
parser.add_argument("--review-chain-id")
|
|
3692
3865
|
parser.add_argument("--autonomous-review-index", type=int)
|
|
3693
3866
|
parser.add_argument("--prior-review-result-file", action="append", default=[])
|
|
3867
|
+
parser.add_argument("--predecessor-chain-result-file", default=None)
|
|
3694
3868
|
parser.add_argument("--completion-review-result-file")
|
|
3695
3869
|
parser.add_argument("--allow-fallback-egress", action="store_true")
|
|
3696
3870
|
parser.add_argument("--host-remediation-attempted", action="store_true")
|
package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh
CHANGED
|
@@ -3199,6 +3199,133 @@ printf '%s\n' "$passed_round_one" >"$WORK/passed-round-one.json"
|
|
|
3199
3199
|
check "a passed first tracked round still owes its challenge before completion" \
|
|
3200
3200
|
'[ "$passed_round_one_rc" = 0 ] && json_fields "$passed_round_one" status=passed autonomous_review_index=1 autonomous_reviews_remaining=2 autonomous_review_allowed=true next_action=run_challenge completion_gated=true'
|
|
3201
3201
|
|
|
3202
|
+
# Chain succession. A fix that touches the owner package moves selected_skills_sha256
|
|
3203
|
+
# and ends the chain by design, so the post-fix candidate can never be challenged
|
|
3204
|
+
# inside it. Succession opens ONE new chain whose first Agent round is a challenge,
|
|
3205
|
+
# inheriting the ended chain's terminal receipt. The owner-binding move is the
|
|
3206
|
+
# expected difference; every other binding must still hold, and a succession whose
|
|
3207
|
+
# candidate did not actually move is a repeat round, not a succession.
|
|
3208
|
+
succession_saved_diff="$(cat "$WORK/diff.patch")"
|
|
3209
|
+
printf 'diff --git a/x b/x\n--- a/x\n+++ b/x\n@@ -1 +1 @@\n-pre\n+fix-pending\n' >"$WORK/diff.patch"
|
|
3210
|
+
reset_case findings unavailable unavailable
|
|
3211
|
+
succ_one="$(run_gate --challenge-budget 1 --review-chain-id succ-phase-one --autonomous-review-index 1)"; succ_one_rc=$?
|
|
3212
|
+
printf '%s\n' "$succ_one" >"$WORK/succ-round-one.json"
|
|
3213
|
+
reset_case findings unavailable unavailable
|
|
3214
|
+
succ_two="$(run_challenge_gate --focus phase-one-surface --review-chain-id succ-phase-one --autonomous-review-index 2 --prior-review-result-file "$WORK/succ-round-one.json")"; succ_two_rc=$?
|
|
3215
|
+
printf '%s\n' "$succ_two" >"$WORK/succ-round-two.json"
|
|
3216
|
+
check "the succession fixture builds a terminal budget-one phase-one chain" \
|
|
3217
|
+
'[ "$succ_one_rc" = 0 ] && [ "$succ_two_rc" = 0 ] && json_fields "$succ_two" autonomous_review_index=2 autonomous_reviews_remaining=0'
|
|
3218
|
+
|
|
3219
|
+
# The fix batch lands: the candidate moves and the owner package hash moves with it.
|
|
3220
|
+
printf 'diff --git a/x b/x\n--- a/x\n+++ b/x\n@@ -1 +1 @@\n-pre\n+fix-applied\n' >"$WORK/diff.patch"
|
|
3221
|
+
python3 - "$WORK/succ-round-two.json" \
|
|
3222
|
+
"$WORK/succ-predecessor-owner-moved.json" \
|
|
3223
|
+
"$WORK/succ-predecessor-forged-controller.json" \
|
|
3224
|
+
"$WORK/succ-predecessor-foreign-scope.json" \
|
|
3225
|
+
"$WORK/succ-predecessor-forged-terminal.json" <<'PY'
|
|
3226
|
+
import json
|
|
3227
|
+
from pathlib import Path
|
|
3228
|
+
import sys
|
|
3229
|
+
|
|
3230
|
+
source = json.loads(Path(sys.argv[1]).read_text())
|
|
3231
|
+
# The owner package legitimately moved: the fix edited the reviewed skill itself.
|
|
3232
|
+
owner_moved = json.loads(json.dumps(source))
|
|
3233
|
+
owner_moved["selected_skills_sha256"] = "a" * 64
|
|
3234
|
+
Path(sys.argv[2]).write_text(json.dumps(owner_moved, separators=(",", ":")))
|
|
3235
|
+
# The controller itself must not move under a succession.
|
|
3236
|
+
forged = json.loads(json.dumps(source))
|
|
3237
|
+
forged["review_controller_sha256"] = "0" * 64
|
|
3238
|
+
Path(sys.argv[3]).write_text(json.dumps(forged, separators=(",", ":")))
|
|
3239
|
+
# A predecessor from a differently-scoped chain is not this candidate's history.
|
|
3240
|
+
foreign = json.loads(json.dumps(source))
|
|
3241
|
+
foreign["review_scope"] = dict(foreign["review_scope"], stage="explore")
|
|
3242
|
+
foreign["stage"] = "explore"
|
|
3243
|
+
Path(sys.argv[4]).write_text(json.dumps(foreign, separators=(",", ":")))
|
|
3244
|
+
# A terminal index with non-terminal arithmetic: the index alone cannot establish
|
|
3245
|
+
# that the succeeded chain actually ended.
|
|
3246
|
+
forged_terminal = json.loads(json.dumps(source))
|
|
3247
|
+
forged_terminal["challenge_index"] = 0
|
|
3248
|
+
forged_terminal["autonomous_reviews_remaining"] = 1
|
|
3249
|
+
forged_terminal["autonomous_review_allowed"] = True
|
|
3250
|
+
Path(sys.argv[5]).write_text(json.dumps(forged_terminal, separators=(",", ":")))
|
|
3251
|
+
PY
|
|
3252
|
+
|
|
3253
|
+
reset_case passed unavailable unavailable
|
|
3254
|
+
succ_phase_two="$(run_challenge_gate --focus post-fix-batch --review-chain-id succ-phase-two --autonomous-review-index 1 --predecessor-chain-result-file "$WORK/succ-round-two.json")"; succ_phase_two_rc=$?
|
|
3255
|
+
check "a succession chain opens its first Agent round as a challenge on the moved candidate" \
|
|
3256
|
+
'[ "$succ_phase_two_rc" = 0 ] && json_fields "$succ_phase_two" mode=challenge review_chain_tracked=true review_chain_id=succ-phase-two autonomous_review_index=1 predecessor_chain_id=succ-phase-one predecessor_result_sha256='"$(shasum -a 256 "$WORK/succ-round-two.json" | awk '{print $1}')"''
|
|
3257
|
+
|
|
3258
|
+
reset_case passed unavailable unavailable
|
|
3259
|
+
out="$(run_challenge_gate --focus owner-moved --review-chain-id succ-owner-moved --autonomous-review-index 1 --predecessor-chain-result-file "$WORK/succ-predecessor-owner-moved.json")"; rc=$?
|
|
3260
|
+
check "a succession accepts the owner-package hash move that ended the prior chain" \
|
|
3261
|
+
'[ "$rc" = 0 ] && json_fields "$out" mode=challenge predecessor_chain_id=succ-phase-one'
|
|
3262
|
+
|
|
3263
|
+
reset_case passed unavailable unavailable
|
|
3264
|
+
out="$(run_challenge_gate --focus no-predecessor --review-chain-id succ-orphan --autonomous-review-index 1)"; rc=$?
|
|
3265
|
+
check "a tracked challenge cannot open a chain without a predecessor receipt" \
|
|
3266
|
+
'[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=review_chain_invalid && case "$out" in *"chain succession"*) false;; *) true;; esac'
|
|
3267
|
+
|
|
3268
|
+
reset_case passed unavailable unavailable
|
|
3269
|
+
out="$(run_challenge_gate --focus review-predecessor --review-chain-id succ-review-predecessor --autonomous-review-index 1 --predecessor-chain-result-file "$WORK/succ-round-one.json")"; rc=$?
|
|
3270
|
+
check "a succession rejects a predecessor that is not a challenge receipt" \
|
|
3271
|
+
'[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=review_chain_invalid && case "$out" in *"chain succession predecessor is not a tracked challenge receipt"*) true;; *) false;; esac'
|
|
3272
|
+
|
|
3273
|
+
# A mid-chain challenge is a live chain, not an ended one: succeeding it would
|
|
3274
|
+
# silently retire rounds the wrapper still owes. chain-round-two above is round 2
|
|
3275
|
+
# of a three-round budget, so it is a challenge that is NOT its chain's terminal.
|
|
3276
|
+
reset_case passed unavailable unavailable
|
|
3277
|
+
out="$(run_challenge_gate --challenge-budget 2 --focus non-terminal --review-chain-id succ-non-terminal --autonomous-review-index 1 --predecessor-chain-result-file "$WORK/chain-round-two.json")"; rc=$?
|
|
3278
|
+
check "a succession rejects a predecessor that is not its chain's terminal round" \
|
|
3279
|
+
'[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=review_chain_invalid && case "$out" in *"chain succession predecessor is not its chain"*) true;; *) false;; esac'
|
|
3280
|
+
|
|
3281
|
+
reset_case passed unavailable unavailable
|
|
3282
|
+
printf '%s\n' "$(cat "$WORK/succ-round-two.json")" >/dev/null
|
|
3283
|
+
printf 'diff --git a/x b/x\n--- a/x\n+++ b/x\n@@ -1 +1 @@\n-pre\n+fix-pending\n' >"$WORK/diff.patch"
|
|
3284
|
+
out="$(run_challenge_gate --focus unmoved-candidate --review-chain-id succ-unmoved --autonomous-review-index 1 --predecessor-chain-result-file "$WORK/succ-round-two.json")"; rc=$?
|
|
3285
|
+
check "a succession whose candidate did not move is a repeat round, not a succession" \
|
|
3286
|
+
'[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=review_chain_invalid && case "$out" in *"chain succession candidate has not moved"*) true;; *) false;; esac'
|
|
3287
|
+
printf 'diff --git a/x b/x\n--- a/x\n+++ b/x\n@@ -1 +1 @@\n-pre\n+fix-applied\n' >"$WORK/diff.patch"
|
|
3288
|
+
|
|
3289
|
+
# A succession opens a second chain, so the chain it opens cannot be the chain it
|
|
3290
|
+
# succeeds. The closeout validator already refuses this shape, but only for a lane
|
|
3291
|
+
# it reads whole: the controller mints receipts one at a time, and a caller that
|
|
3292
|
+
# never closes a ledger never reaches that check. Refusing at mint keeps the rule
|
|
3293
|
+
# where the receipt is made rather than where it is later audited.
|
|
3294
|
+
reset_case passed unavailable unavailable
|
|
3295
|
+
out="$(run_challenge_gate --focus self-succession --review-chain-id succ-phase-one --autonomous-review-index 1 --predecessor-chain-result-file "$WORK/succ-round-two.json")"; rc=$?
|
|
3296
|
+
check "a succession may not carry the chain id of the chain it succeeds" \
|
|
3297
|
+
'[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=review_chain_invalid && case "$out" in *"chain succession may not succeed its own chain"*) true;; *) false;; esac'
|
|
3298
|
+
|
|
3299
|
+
for succession_case in forged-controller foreign-scope forged-terminal; do
|
|
3300
|
+
reset_case passed unavailable unavailable
|
|
3301
|
+
out="$(run_challenge_gate --focus "succ-${succession_case}" --review-chain-id "succ-${succession_case}" --autonomous-review-index 1 --predecessor-chain-result-file "$WORK/succ-predecessor-${succession_case}.json")"; rc=$?
|
|
3302
|
+
check "a succession rejects a ${succession_case} predecessor" \
|
|
3303
|
+
'[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=review_chain_invalid && case "$out" in *"chain succession"*) true;; *) false;; esac'
|
|
3304
|
+
done
|
|
3305
|
+
# Succession is one-shot. If a succession receipt could be continued in-chain, or
|
|
3306
|
+
# could itself be the next succession's predecessor, the relaxation would daisy-chain
|
|
3307
|
+
# into unbounded autonomous rounds -- the exact bypass it must not become.
|
|
3308
|
+
printf '%s\n' "$succ_phase_two" >"$WORK/succ-phase-two.json"
|
|
3309
|
+
reset_case passed unavailable unavailable
|
|
3310
|
+
out="$(run_challenge_gate --focus chained-succession --review-chain-id succ-phase-three --autonomous-review-index 1 --predecessor-chain-result-file "$WORK/succ-phase-two.json")"; rc=$?
|
|
3311
|
+
check "a succession round cannot be the predecessor of another succession" \
|
|
3312
|
+
'[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=review_chain_invalid && case "$out" in *"succession does not compose"*) true;; *) false;; esac'
|
|
3313
|
+
|
|
3314
|
+
reset_case passed unavailable unavailable
|
|
3315
|
+
out="$(run_challenge_gate --focus continued-succession --review-chain-id succ-phase-two --autonomous-review-index 2 --prior-review-result-file "$WORK/succ-phase-two.json")"; rc=$?
|
|
3316
|
+
check "a succession round cannot be continued in its own chain" \
|
|
3317
|
+
'[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=review_chain_invalid && case "$out" in *"succession round, which is one-shot"*) true;; *) false;; esac'
|
|
3318
|
+
|
|
3319
|
+
# The completion checkpoint has to accept the succession round it will actually be
|
|
3320
|
+
# handed: a chain-index-1 challenge. Without this the ledger's terminal step is
|
|
3321
|
+
# unproven for exactly the chain shape the third round produces.
|
|
3322
|
+
reset_case passed unavailable unavailable
|
|
3323
|
+
out="$(run_completion_gate --challenge-budget 1 --completion-review-result-file "$WORK/succ-phase-two.json")"; rc=$?
|
|
3324
|
+
check "the completion checkpoint closes on a succession round" \
|
|
3325
|
+
'[ "$rc" = 0 ] && json_fields "$out" mode=complete status=passed review_chain_tracked=true review_chain_id=succ-phase-two autonomous_review_index=1 autonomous_review_allowed=false completion_gated=false'
|
|
3326
|
+
|
|
3327
|
+
printf '%s\n' "$succession_saved_diff" >"$WORK/diff.patch"
|
|
3328
|
+
|
|
3202
3329
|
python3 - "$WORK/passed-round-one.json" "$WORK/chain-round-two.json" \
|
|
3203
3330
|
"$WORK/round-one-forged-final-shape.json" \
|
|
3204
3331
|
"$WORK/round-two-forged-final-shape.json" <<'PY'
|
|
@@ -45,7 +45,7 @@ Use this skill for the full defect discipline: diagnose the immediate failure, f
|
|
|
45
45
|
|
|
46
46
|
3. Hypothesize.
|
|
47
47
|
- Write concrete causes that can be proven or rejected.
|
|
48
|
-
- For each hypothesis, define the expected observation if it is true.
|
|
48
|
+
- For each hypothesis, define the expected observation if it is true **and the observation that would falsify it** — then go get the falsifying one first. A search hit, a log line, or a plausible implementation detail proves the text EXISTS, not that it RAN on the path that failed: run it on the failing path for an observation only THAT cause predicts — reachability rules out non-execution and nothing else, so watching the suspected code execute clears no cause — or build a paired control differing in exactly ONE variable (every precondition of the predicate under test enumerated, both arms equal on all the others). Until an operation that could have falsified the cause has been run and did not, it stays a `hypothesis` — it must not become the basis of a fix, and it must not enter a commit/MR body, a durable note, or a report as the cause. Withdrawing a landed wrong cause costs far more than testing it. The probe itself stays inside the same boundaries the extraction workflow's falsification rule names — existing sandbox and permission limits, non-destructive, synthetic targets, no production or live credentials, no permission-boundary bypass; where no safe probe exists the cause simply stays a `hypothesis` and must never be upgraded by running an unsafe one.
|
|
49
49
|
|
|
50
50
|
4. Instrument.
|
|
51
51
|
- Add targeted logs, assertions, traces, metrics, local probes, or debugger breakpoints.
|
|
@@ -137,6 +137,7 @@ The **widen + multi-layer (Swiss Cheese) lens fires on complexity above, not onl
|
|
|
137
137
|
- Go backend implementation, testing, codegen, DB, Redis, MQ, or protobuf issue -> `go-microservice-dev` references.
|
|
138
138
|
- Python backend, AI-service host, worker, SDK/package, or batch-job architecture issue -> `python-service-architecture`.
|
|
139
139
|
- Python implementation, pytest, packaging, schema, ORM/migration, Redis, queue, async, or service-wiring issue -> `python-service-dev` references.
|
|
140
|
+
- Node.js implementation, runtime/module/type path, package-manager or lockfile, async lifecycle, stream, worker, outbound-client, or `node:test`/runner issue -> `nodejs-service-dev` references. A Node.js architecture or service-boundary issue has no architecture sibling by decision and must not be filed under a sibling stack's architecture skill -> `product-rd-workflow`'s architecture gate plus the relevant `platform-*` owner.
|
|
140
141
|
- LLM, inference, RAG, prompt, model-routing, streaming, fallback, evaluation, replay, shadow, token-cost, or agent-runtime/tool-call issue -> `llm-inference-integration`.
|
|
141
142
|
- Mobile app issue involving Flutter, Android, iOS, navigation, state, platform bridge, device capability, build/release, crash, accessibility, or performance -> `app-cross-platform-dev`.
|
|
142
143
|
- Mini-program issue involving WeChat/Alipay/Douyin/Baidu page routing, host-platform APIs, developer tools, review submission, release, real-device preview, or platform capability -> `miniapp-product-dev`.
|
|
@@ -30,7 +30,7 @@ Use this for implementation of new backend products and services. It should adap
|
|
|
30
30
|
- If an observed pattern only works for one product domain, discard it instead of turning it into a rule.
|
|
31
31
|
- Resolve conflicts by choosing the safer generic default: explicit contracts over hidden conventions, DB constraints over cache-only correctness, typed config over ad hoc strings, idempotent consumers over retry-only consumers, focused unit tests over live-infra tests by default, and fail-closed for auth/permission/data-integrity paths.
|
|
32
32
|
- Fuse patterns only when both are product-agnostic and reduce implementation ambiguity; otherwise keep the simpler rule.
|
|
33
|
-
- When adding or revising durable Go implementation guidance, check whether the lesson is generic backend service practice that should also update `python-service-dev`, or belongs in a shared workflow skill instead. If the rule depends on Go tooling, protobuf/Kitex/Hertz, Go concurrency, or Go package layout, keep it here and do not force a Python mirror.
|
|
33
|
+
- When adding or revising durable Go implementation guidance, check whether the lesson is generic backend service practice that should also update the sibling stack owners `python-service-dev` and `nodejs-service-dev`, or belongs in a shared workflow skill instead. Record each sibling as `update`, `unchanged`, or `route-to-shared` rather than leaving it unexamined. If the rule depends on Go tooling, protobuf/Kitex/Hertz, Go concurrency, or Go package layout, keep it here and do not force a Python or Node mirror.
|
|
34
34
|
- Example: if an existing project uses inline Redis keys but another wraps keys in typed builders, implement typed builders and discard inline string formatting as a reusable pattern.
|
|
35
35
|
|
|
36
36
|
## Development Workflow
|
package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/SKILL.md
CHANGED
|
@@ -12,6 +12,7 @@ Use this for product backend work that calls, hosts, evaluates, or operates LLM
|
|
|
12
12
|
- Use this skill for LLM gateway/client design, model registry, prompt versioning, agent/tool orchestration, streaming APIs, fallback, token/cost accounting, evals, replay, shadow comparison, batch inference, and inference observability.
|
|
13
13
|
- Use `go-microservice-architecture` or `go-microservice-dev` when the work is mainly a Go service with ordinary storage/RPC/MQ concerns and only minor LLM integration.
|
|
14
14
|
- Use `python-service-architecture` or `python-service-dev` when the work is mainly a Python service, AI-service host, worker, SDK/package, or batch job with ordinary API/storage/Redis/queue/pytest/packaging concerns and only minor inference integration.
|
|
15
|
+
- Use `nodejs-service-dev` on the same terms when that host is mainly a Node.js service, worker, or CLI/tooling with ordinary API/storage/queue/runner/packaging concerns and only minor inference integration; its architecture decisions must go to `product-rd-workflow`, never to a Node architecture sibling, which does not exist by decision.
|
|
15
16
|
- Use `defect-diagnosis` first when a model output, flaky eval, timeout, regression, fallback failure, or prompt/version issue must be reproduced and root-caused.
|
|
16
17
|
- Use `product-rd-workflow` first when the request spans product goal, PRD, architecture, implementation plan, release, and learning loop.
|
|
17
18
|
- 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.
|
|
@@ -12,6 +12,14 @@ description: Use when implementing, modifying, scaffolding, or testing Node.js b
|
|
|
12
12
|
- `testing-strategy` chooses test layers, coverage policy, contract/E2E scope, and CI gates. This skill owns Node runner, mock, fixture, and command mechanics after that choice.
|
|
13
13
|
- Standalone CLI/tooling stays here; language-stack CLI implementation must not be routed back to `terminal-cli-dev`, owner of the interface contract; logs/metrics/traces to `platform-observability`; cross-service timeout/retry/mTLS to `platform-service-connectivity`; rollout/rollback to `platform-release-engineering`.
|
|
14
14
|
- Browser UI goes to `web-react-dev`; LLM/RAG/agent-runtime behavior to `llm-inference-integration`. Keep language-neutral rules in their existing owner.
|
|
15
|
+
- Architecture, service-boundary, and data-ownership decisions have no Node architecture sibling skill by decision: they run through `product-rd-workflow`'s architecture gate plus the relevant `platform-*` owners, and this skill owns the Node-side implementation contract. Do not borrow the Go or Python architecture skill for a Node.js service.
|
|
16
|
+
- The runtime contract here is Node.js. Bun and Deno share much of this service shape, but their runtime, package, permission, and built-in-API semantics differ and are not verified against this skill's sources: reuse the boundary and lifecycle rules, confirm every runtime-specific claim against that runtime's own documentation, and treat a deliberate migration as a `product-rd-workflow` decision rather than a drop-in swap.
|
|
17
|
+
|
|
18
|
+
## Generalization Discipline
|
|
19
|
+
|
|
20
|
+
- Implement the repository in front of you; do not lift product nouns, service names, package paths, or organization-specific habits from a prior codebase into a reusable rule.
|
|
21
|
+
- When adding or revising durable Node.js guidance, place it deliberately. A rule that depends on Node runtime or toolchain mechanics — module resolution, TypeScript execution, event loop, streams, `worker_threads`, package-manager state — stays here. Generic backend service practice belongs in the shared workflow, testing, or architecture owner; a lesson that also holds for a sibling stack updates `go-microservice-dev` or `python-service-dev` in the same landing instead of living only here.
|
|
22
|
+
- The duty runs inward too: a generic backend rule already landed in a sibling stack skill applies here unless Node runtime semantics make it wrong. Record the per-sibling decision — `update`, `unchanged`, or `route-to-shared` — rather than leaving the sibling unexamined.
|
|
15
23
|
|
|
16
24
|
## Workflow
|
|
17
25
|
|
|
@@ -34,8 +42,10 @@ State input, output, error, cancellation, timeout, idempotency, and ownership be
|
|
|
34
42
|
- Propagate cancellation/deadlines to underlying work. A wrapper timeout that leaves work running is not cancellation.
|
|
35
43
|
- Use streams with backpressure for large/unbounded data. Use a bounded `worker_threads` pool only for measured CPU-intensive JavaScript, not ordinary async I/O.
|
|
36
44
|
- Preserve error causes and map once at the boundary. Do not swallow rejections or resume normal operation after an unknown fatal process error.
|
|
45
|
+
- Carry request context (correlation, tenant, deadline) in `AsyncLocalStorage` established at the owning boundary, not through every signature or a process-wide mutable. Pick the no-context behavior by what the value authorizes: a diagnostic value may declare a default, but a security-bearing one must fail the operation closed.
|
|
46
|
+
- Treat the outbound HTTP client as a bounded resource: its pool is unbounded by default, its timeouts are layered rather than an overall deadline, and an unconsumed response body holds its connection.
|
|
37
47
|
|
|
38
|
-
Read [async-lifecycle-and-performance.md](references/async-lifecycle-and-performance.md) when touching concurrency, streams, CPU work, shutdown, or performance.
|
|
48
|
+
Read [async-lifecycle-and-performance.md](references/async-lifecycle-and-performance.md) when touching concurrency, request context, outbound clients, streams, CPU work, shutdown, or performance.
|
|
39
49
|
|
|
40
50
|
### 3. Make lifecycle and exposure explicit
|
|
41
51
|
|
|
@@ -22,6 +22,13 @@ Node.js uses a small number of threads to serve many clients. A long callback re
|
|
|
22
22
|
- Remove listeners and timers during cleanup. Use `unref()` only when it matches lifecycle ownership; it is not a substitute for cancelling work.
|
|
23
23
|
- Retries must fit inside one overall deadline, use the connectivity owner's policy, and remain bounded. Never retry non-idempotent effects without an idempotency contract.
|
|
24
24
|
|
|
25
|
+
## Request context propagation
|
|
26
|
+
|
|
27
|
+
- Carry per-request context — correlation/trace ids, tenant, deadline, auth subject — in `AsyncLocalStorage` rather than threading it through every signature or parking it on a process-wide mutable. Prefer the built-in store over hand-rolled `async_hooks` context machinery: the custom form has to re-implement propagation across every async boundary and is where context silently vanishes.
|
|
28
|
+
- Establish the store once at the owning boundary (request/job/consumer entry) and read it inward. Outside an established context the read returns `undefined` — or the instance's configured default value, where the deployed runtime supports declaring one, which is a version-gated option to verify rather than assume. Either way, code that must also run without a request (startup, a shutdown drain, a background loop) needs an explicit branch or fallback, never a bare dereference of an absent context. Choose which by what the value authorizes: a correlation id may safely default, but a tenant, subject, or permission scope must make the operation fail closed when the context is missing. Declaring a default for those turns an absent context into work silently executed under the wrong identity — the store is a propagation mechanism, never the authorization decision.
|
|
29
|
+
- Propagation is not automatic across every boundary. The store stays coherent through asynchronous operations started inside the context, which is not the same as crossing an isolate or process edge: before relying on context inside a `worker_threads` worker or a child process, verify propagation there rather than assuming it, and pass the needed values explicitly across any edge you have not verified.
|
|
30
|
+
- What goes in the store is an implementation mechanic; which fields must exist and propagate is owned by `platform-observability`. Do not invent a field set here.
|
|
31
|
+
|
|
25
32
|
## Bounded concurrency
|
|
26
33
|
|
|
27
34
|
- Replace unbounded `Promise.all(items.map(...))` on variable-size input with a repository-standard limiter, queue, or batch window.
|
|
@@ -29,6 +36,15 @@ Node.js uses a small number of threads to serve many clients. A long callback re
|
|
|
29
36
|
- Track in-flight ownership so shutdown can await or abort it. A detached promise must have an explicit supervisor and error sink.
|
|
30
37
|
- Avoid per-request child processes or workers. If CPU offload is justified, measure task duration and transfer cost, then reuse a bounded pool.
|
|
31
38
|
|
|
39
|
+
## Outbound HTTP clients
|
|
40
|
+
|
|
41
|
+
- The process-wide dispatcher is a real contract, not a default to ignore. The built-in `fetch` routes through the globally configured dispatcher, so pooling, keep-alive, and timeout behavior for every outbound call are decided by that one object; set it deliberately at startup and treat replacing it as a service-wide change rather than a local one.
|
|
42
|
+
- **The defaults are unbounded where it matters.** The per-origin pool defaults to unlimited connections, and the number of distinct origins is unbounded unless capped, so an outbound burst is bounded only by whatever bounds the calling code carries. This is the bounded-concurrency rule above applied at the socket layer: bound connections per origin, and bound origins too when destinations are influenced by input.
|
|
43
|
+
- **Timeouts are layered, and none of them is an overall deadline.** Connect, response-headers, and response-body timeouts are separate settings; a request can stay within every one of them and still blow the caller's budget. The overall deadline comes from the caller's `AbortSignal` — per the cancellation rule above, a deadline not attached to the request is not a deadline.
|
|
44
|
+
- **An unconsumed response body holds its connection.** Consume or explicitly cancel the body even when only the status or headers were wanted; leaving it unread stalls the request and leaks the pooled connection. A happy-path test that never reads a body cannot see this, so exercise the discard path directly.
|
|
45
|
+
- Defaults and option names drift across runtime and client versions. Read the deployed version's own documentation and the repository's actual dispatcher wiring instead of assuming a number, and verify the pool/timeout settings a change depends on rather than restating them from memory.
|
|
46
|
+
- Retry, backoff, circuit breaking, mTLS, and cross-service timeout *policy* stay with `platform-service-connectivity`. This section owns only the Node-side client shape that implements whatever policy that owner sets.
|
|
47
|
+
|
|
32
48
|
## Streams and backpressure
|
|
33
49
|
|
|
34
50
|
- Prefer `node:stream/promises` `pipeline()` or an established equivalent so errors and teardown propagate across the chain.
|
|
@@ -17,6 +17,7 @@ Inspected 2026-08-30. This file records provenance and extraction limits; it is
|
|
|
17
17
|
| diagnostics | [Heap snapshots](https://nodejs.org/en/learn/diagnostics/memory/using-heap-snapshot), [flame graphs](https://nodejs.org/en/learn/diagnostics/flame-graphs) | performance claims need profiles/measurements; heap snapshots can stop the main thread and exhaust memory |
|
|
18
18
|
| security | [Node.js security best practices](https://nodejs.org/en/learn/getting-started/security-best-practices) | bound input work, harden dependencies, and use runtime permissions only as defense in depth |
|
|
19
19
|
| reproducible install / provenance | [npm ci](https://docs.npmjs.com/cli/v11/commands/npm-ci/), [npm provenance](https://docs.npmjs.com/generating-provenance-statements) | frozen lockfile install is the verification contract; provenance proves origin/build linkage, not code safety |
|
|
20
|
+
| outbound HTTP clients | [undici getting started](https://github.com/nodejs/undici/blob/main/docs/docs/getting-started.md), [undici Agent API](https://github.com/nodejs/undici/blob/main/docs/docs/api/Agent.md), [undici README](https://github.com/nodejs/undici/blob/main/README.md) | the global dispatcher backs the built-in `fetch`; per-origin pools default to unlimited connections and origins are unbounded unless capped; connect/headers/body timeouts are layered and none is an overall deadline; an unconsumed response body stalls the request and holds its pooled connection |
|
|
20
21
|
|
|
21
22
|
## Independent industry controls
|
|
22
23
|
|