@ccoalm/ccl-skills 0.18.5 → 0.18.7
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/agent-context/session-policy.md +5 -4
- package/dist/assets/marketplace/plugins/ccl-skills/agent-context/session-start.md +11 -9
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/host-input.py +135 -11
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/remind-review-covers-head.sh +34 -9
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/skill-extraction-gate-stop.sh +14 -3
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/skill-loading.py +39 -2
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_host_input.py +11 -0
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_proposed_next.py +180 -3
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_remind_review_covers_head.sh +19 -3
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_skill_loading.py +77 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/development-completion.md +4 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/AGENTS.md +11 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/kimi_review.sh +65 -11
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.py +4 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_cli_review_wrappers.sh +158 -6
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_client_compat.py +39 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh +48 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/SKILL.md +5 -5
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/design-review-gate-mechanics.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/pre-final-continuation-gate.md +4 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/review-reception.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/SKILL.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/SKILL.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/eval-routing.md +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/firing-point-placement.md +1 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +36 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/validation-and-landing.md +2 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-ccl-skills.sh +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-size-budget.sh +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-sync-pointers.sh +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-routing-bank.rb +11 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_body_compliance_grading.sh +79 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_impact_chain_refscripts.sh +4 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_register_pending_exclusion.sh +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_regressions.sh +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_route_drift.sh +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_size_budget.sh +4 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_source_register_lifecycle.sh +9 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_sync_pointers.sh +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_routing_bank_grader_diagnostics.sh +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_routing_bank_resolution.sh +32 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_routing_bank_surface_binding.sh +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_routing_prose_target.sh +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_gate_dateless_host.sh +3 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_gate_verdict_differential.sh +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_round_attribution.sh +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_self_adjudication.sh +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_source_refuted.sh +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_locale_independent_gates.sh +264 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_register_firing_path_resolution.sh +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_register_firing_path_wiring.sh +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_uiux_delivery_contract.sh +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_uiux_loading_budget.sh +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/validate-skill.sh +2 -0
- package/dist/assets/release.json +61 -56
- package/package.json +1 -1
|
@@ -141,13 +141,190 @@ class ProposedNextTests(unittest.TestCase):
|
|
|
141
141
|
self.payload['last_assistant_message'] = text
|
|
142
142
|
self.assert_block(self.run_hook())
|
|
143
143
|
for text in ('proposed-next: none — status only', '**proposed-next:** none — status only',
|
|
144
|
-
'proposed-next:
|
|
145
|
-
'proposed-next: none — awaiting approval',
|
|
146
|
-
'proposed-next: none - waiting for a resource'):
|
|
144
|
+
'proposed-next: none — all requested work is done and verified'):
|
|
147
145
|
with self.subTest(text=text):
|
|
148
146
|
self.payload['last_assistant_message'] = text
|
|
149
147
|
self.assertEqual(self.run_hook(), {})
|
|
150
148
|
|
|
149
|
+
def assert_decision_recheck(self, payload):
|
|
150
|
+
result = self.run_hook(payload)
|
|
151
|
+
self.assert_block(result)
|
|
152
|
+
self.assertIn('Decision recheck', result['reason'])
|
|
153
|
+
self.assertIn('security', result['reason'])
|
|
154
|
+
self.assertIn('supplies no new goal or authorization', result['reason'])
|
|
155
|
+
self.assertEqual(self.run_hook(dict(payload, stop_hook_active=True)), {})
|
|
156
|
+
|
|
157
|
+
def test_user_dependent_stop_rechecks_the_blocker_once(self):
|
|
158
|
+
for events in ([], self.claude_load()):
|
|
159
|
+
self.events(events)
|
|
160
|
+
for text in ('proposed-next: blocked: need an explicit decision',
|
|
161
|
+
'proposed-next: none — awaiting approval',
|
|
162
|
+
'proposed-next: none - waiting for a resource',
|
|
163
|
+
'proposed-next: none — 等待确认安全负责人'):
|
|
164
|
+
with self.subTest(text=text):
|
|
165
|
+
self.assert_decision_recheck(dict(self.payload, last_assistant_message=text))
|
|
166
|
+
|
|
167
|
+
def test_permission_question_after_edits_rechecks_instead_of_formatting(self):
|
|
168
|
+
for events in (self.claude_load(), self.edit_events()):
|
|
169
|
+
self.events(events)
|
|
170
|
+
for text in ('Patch is ready. Should I proceed with the remaining tests?',
|
|
171
|
+
'Done with step 1. Do you want me to continue?',
|
|
172
|
+
'第一步已完成,是否继续?', '安全风险需要你决定,要不要我修改?',
|
|
173
|
+
'Patch ready. **Should I proceed?**', '**是否继续?**',
|
|
174
|
+
'Patch ready. __Should I proceed?__', '_是否继续?_',
|
|
175
|
+
'***Should I proceed?***', '*是否继续?*',
|
|
176
|
+
'**是否继续?**\nproposed-next: none — status only'):
|
|
177
|
+
with self.subTest(text=text):
|
|
178
|
+
self.assert_decision_recheck(dict(self.payload, last_assistant_message=text))
|
|
179
|
+
self.events([])
|
|
180
|
+
for text in ('Should I proceed?', '是否继续?', '**Should I proceed?**', '_是否继续?_'):
|
|
181
|
+
self.payload['last_assistant_message'] = text
|
|
182
|
+
self.assertEqual(self.run_hook(), {})
|
|
183
|
+
self.events(self.edit_events())
|
|
184
|
+
self.payload['last_assistant_message'] = 'Done; checks passed.'
|
|
185
|
+
self.assertEqual(self.run_hook(), {})
|
|
186
|
+
|
|
187
|
+
def test_long_permission_lines_finish_within_hook_timeout(self):
|
|
188
|
+
self.events(self.edit_events())
|
|
189
|
+
prefix = 'Should I do this; ' * 60000
|
|
190
|
+
for suffix, expected in (('', None), ('continue?', 'block')):
|
|
191
|
+
with self.subTest(terminal_question=bool(suffix)):
|
|
192
|
+
payload = dict(self.payload, last_assistant_message=prefix + suffix)
|
|
193
|
+
result = subprocess.run(
|
|
194
|
+
['python3', str(self.hooks / 'host-input.py'), 'proposed-next'],
|
|
195
|
+
input=json.dumps(payload), text=True, capture_output=True,
|
|
196
|
+
cwd=self.root, timeout=5)
|
|
197
|
+
self.assertEqual(result.returncode, 0, result.stderr)
|
|
198
|
+
self.assertEqual(result.stderr, '')
|
|
199
|
+
value = json.loads(result.stdout) if result.stdout else {}
|
|
200
|
+
self.assertEqual(value.get('decision'), expected)
|
|
201
|
+
|
|
202
|
+
def test_finished_none_states_are_not_waits(self):
|
|
203
|
+
self.events(self.claude_load())
|
|
204
|
+
for text in ('proposed-next: none — PR approved and merged',
|
|
205
|
+
'proposed-next: none — tests confirm the fix',
|
|
206
|
+
'proposed-next: none — permission tests added',
|
|
207
|
+
'proposed-next: none — authorization module refactored',
|
|
208
|
+
'proposed-next: none — status only; awaiting nothing',
|
|
209
|
+
'proposed-next: none — 已确认完成',
|
|
210
|
+
'proposed-next: none — 已批准并合并',
|
|
211
|
+
'proposed-next: none — merged after your approval',
|
|
212
|
+
'proposed-next: none — all done; let me know if you need more',
|
|
213
|
+
'proposed-next: none — fixed the await bug in fetch()',
|
|
214
|
+
'proposed-next: none — 修复了等待超时',
|
|
215
|
+
'proposed-next: none — 审批流程已实现',
|
|
216
|
+
'proposed-next: none — PR opened; awaiting review',
|
|
217
|
+
"Done.\nLet me know if you'd like me to make any other changes.\nproposed-next: none — complete",
|
|
218
|
+
'proposed-next: none — 完成,期待你的反馈', 'proposed-next: none — 已按你定义的接口实现',
|
|
219
|
+
'proposed-next: none — merged based on your approval'):
|
|
220
|
+
with self.subTest(text=text):
|
|
221
|
+
self.assertEqual(self.run_hook(dict(self.payload, last_assistant_message=text)), {})
|
|
222
|
+
|
|
223
|
+
def test_more_user_waits_and_labelled_questions_are_rechecked(self):
|
|
224
|
+
self.events(self.claude_load())
|
|
225
|
+
for text in ('proposed-next: none — your call', 'proposed-next: none — needs sign-off',
|
|
226
|
+
'proposed-next: none — 等你拍板', 'proposed-next: none — 需要你审批',
|
|
227
|
+
'Patch ready. Should I push it?\nproposed-next: none — work is ready',
|
|
228
|
+
'Ready to merge. OK to merge?', 'Should I proceed? Or stop?',
|
|
229
|
+
'Should I push the branch?\nproposed-next: none — status only',
|
|
230
|
+
'proposed-next: none — 待确认', 'proposed-next: none — pending approval',
|
|
231
|
+
'proposed-next: none — decision pending', 'proposed-next: none — 负责人未定',
|
|
232
|
+
'proposed-next: none — need the owner to sign off', 'proposed-next: none — 请选择方案 A 或 B',
|
|
233
|
+
'已改完。继续?\nproposed-next: none — 改动已完成', '要不要我继续\nproposed-next: none — 改动已完成',
|
|
234
|
+
'proposed-next: none — 由你决定', 'proposed-next: none — up to you',
|
|
235
|
+
'proposed-next: none — need access to prod', 'Done — should I push?\nproposed-next: none — ready',
|
|
236
|
+
'我可以继续吗?\nproposed-next: none — 改动已完成'):
|
|
237
|
+
with self.subTest(text=text):
|
|
238
|
+
self.assert_decision_recheck(dict(self.payload, last_assistant_message=text))
|
|
239
|
+
|
|
240
|
+
def test_announced_next_steps_after_work_recheck_instead_of_stopping(self):
|
|
241
|
+
plan = ('已完成两项检查。下一步:\n1. 补回归用例\n2. 重跑本地套件\n3. 跑一次付费对照生成\n'
|
|
242
|
+
'先推进前两步;付费对照还需要确定费用上限。')
|
|
243
|
+
for events in (self.edit_events(), self.claude_load(), self.codex_read()):
|
|
244
|
+
self.events(events)
|
|
245
|
+
for text in (plan, "Patch applied. Next, I'll rerun the suite and update the docs.",
|
|
246
|
+
'接下来我会补测试并重跑。', 'Now let me run the remaining checks.',
|
|
247
|
+
'The config change is in. I will now update the migration.',
|
|
248
|
+
'Step 1 is done; the paid comparison run still needs to settle a budget cap.',
|
|
249
|
+
'**接下来我先修复失败用例。**', '第一步完成,我马上跑回归。',
|
|
250
|
+
'下一步我先跑回归。\nproposed-next: none — status only'):
|
|
251
|
+
with self.subTest(text=text):
|
|
252
|
+
payload = dict(self.payload, last_assistant_message=text)
|
|
253
|
+
self.assert_decision_recheck(payload)
|
|
254
|
+
self.assertIn('not a stopping point', self.run_hook(payload)['reason'])
|
|
255
|
+
|
|
256
|
+
def test_optional_clause_does_not_hide_an_unconditional_next_step(self):
|
|
257
|
+
self.events(self.edit_events())
|
|
258
|
+
for text in ("Patch applied. Next, I'll rerun the tests; let me know if you need anything else.",
|
|
259
|
+
"Next, I'll rerun the tests. If you want, I can also add a flag.",
|
|
260
|
+
"Let me know if you need a flag; next, I'll rerun the tests.",
|
|
261
|
+
'改动已完成,接下来我会跑回归;如需其他调整请告诉我。',
|
|
262
|
+
'如需其他调整请告诉我。接下来我会跑回归。'):
|
|
263
|
+
with self.subTest(text=text):
|
|
264
|
+
self.assert_decision_recheck(dict(self.payload, last_assistant_message=text))
|
|
265
|
+
|
|
266
|
+
def test_condition_applies_to_its_own_announced_step(self):
|
|
267
|
+
self.events(self.edit_events())
|
|
268
|
+
for text in ("If you want, next I'll add an optional flag.",
|
|
269
|
+
"Next, I'll add the flag if you want it.",
|
|
270
|
+
'如果你需要,接下来我会补文档。',
|
|
271
|
+
'接下来我会补文档,如果你需要的话。'):
|
|
272
|
+
with self.subTest(text=text):
|
|
273
|
+
self.assertEqual(self.run_hook(dict(self.payload, last_assistant_message=text)), {})
|
|
274
|
+
|
|
275
|
+
def test_routine_test_permission_recheck_does_not_invent_a_cost_blocker(self):
|
|
276
|
+
self.events(self.edit_events())
|
|
277
|
+
for text in ('proposed-next: blocked: 小额测试等待你批准费用上限',
|
|
278
|
+
'测试环境的回归已准备好,是否开始?',
|
|
279
|
+
'开发环境验证准备完成,请确认授权?',
|
|
280
|
+
'Should I start the small model smoke test?'):
|
|
281
|
+
with self.subTest(text=text):
|
|
282
|
+
payload = dict(self.payload, last_assistant_message=text)
|
|
283
|
+
self.assert_decision_recheck(payload)
|
|
284
|
+
reason = self.run_hook(payload)['reason']
|
|
285
|
+
self.assertIn('Small tests and routine development/test-environment operations', reason)
|
|
286
|
+
self.assertIn('explicit user cost or run-count limits', reason)
|
|
287
|
+
self.assertNotIn('such as a cost cap or a paid run', reason)
|
|
288
|
+
|
|
289
|
+
def test_finished_reports_offers_and_quoted_plans_are_not_announcements(self):
|
|
290
|
+
self.events(self.edit_events())
|
|
291
|
+
for text in ('Done; all checks passed.', "If you want, I'll also add a CLI flag.",
|
|
292
|
+
'如需,我可以接着补文档。', '修复完成。我已经补了测试并重跑。',
|
|
293
|
+
'Earlier I said I would rerun the suite; it now passes.',
|
|
294
|
+
"Let me know if you need anything else and I'll start on it.",
|
|
295
|
+
'> 接下来我会补测试', "```text\nNext, I'll run the suite\n```",
|
|
296
|
+
"Plan was: next, I'll fix A.\nFixed A.\nFixed B.\nAll checks pass."):
|
|
297
|
+
with self.subTest(text=text):
|
|
298
|
+
self.assertEqual(self.run_hook(dict(self.payload, last_assistant_message=text)), {})
|
|
299
|
+
self.events([])
|
|
300
|
+
for text in ('接下来我会解释这个错误的含义。', "Next, I'll explain the error."):
|
|
301
|
+
with self.subTest(text=text, transcript='no work evidence'):
|
|
302
|
+
self.assertEqual(self.run_hook(dict(self.payload, last_assistant_message=text)), {})
|
|
303
|
+
self.assertEqual(self.run_hook(dict(
|
|
304
|
+
self.payload, last_assistant_message=text + '\nproposed-next: none — status only')), {})
|
|
305
|
+
|
|
306
|
+
def test_pleasantries_and_quoted_questions_after_edits_are_not_permission_asks(self):
|
|
307
|
+
self.events(self.edit_events())
|
|
308
|
+
for text in ('Done. Can I help with anything else?',
|
|
309
|
+
'Fixed. Q: why does continue fail?',
|
|
310
|
+
'Done.\n> Should I proceed?',
|
|
311
|
+
'这个问题是否已修复?', 'How should I interpret this error?',
|
|
312
|
+
'What is the default if I go ahead without a flag?',
|
|
313
|
+
'Does this look ok to you?', '不管要不要我做都行。',
|
|
314
|
+
'> **Should I proceed?**', '```text\n**是否继续?**\n```',
|
|
315
|
+
'`**Should I proceed?**`', '**Done; checks passed.**',
|
|
316
|
+
'__Done; checks passed.__'):
|
|
317
|
+
with self.subTest(text=text):
|
|
318
|
+
self.assertEqual(self.run_hook(dict(self.payload, last_assistant_message=text)), {})
|
|
319
|
+
|
|
320
|
+
def edit_events(self):
|
|
321
|
+
target = str(self.root / 'src.txt')
|
|
322
|
+
return [
|
|
323
|
+
{'type': 'assistant', 'message': {'content': [{'type': 'tool_use', 'id': 'edit',
|
|
324
|
+
'name': 'Edit', 'input': {'file_path': target, 'old_string': 'a', 'new_string': 'b'}}]}},
|
|
325
|
+
{'type': 'user', 'message': {'content': [{'type': 'tool_result',
|
|
326
|
+
'tool_use_id': 'edit', 'is_error': False, 'content': 'updated'}]}}]
|
|
327
|
+
|
|
151
328
|
def test_actionable_handoff_rechecks_continuation_instead_of_silently_stopping(self):
|
|
152
329
|
for events in ([], self.claude_load(), self.codex_read()):
|
|
153
330
|
self.events(events)
|
|
@@ -24,10 +24,11 @@ git -C "$repo" config user.name 'Test User'
|
|
|
24
24
|
commit() { printf '%s\n' "$2" >"$repo/$1"; git -C "$repo" add -A; git -C "$repo" commit -q -m "$3"; }
|
|
25
25
|
commit code.txt one "first change"
|
|
26
26
|
|
|
27
|
-
write_receipt() { # <head> <worktree_clean true|false>
|
|
27
|
+
write_receipt() { # <head> <worktree_clean true|false> [status | none to omit; default passed]
|
|
28
28
|
mkdir -p "$repo/.git/ccl-code-review"
|
|
29
|
-
jq -nc --arg h "$1" --argjson c "$2" \
|
|
30
|
-
'{schema_version:1,head:$h,worktree_clean:$c,mode:"review",status
|
|
29
|
+
jq -nc --arg h "$1" --argjson c "$2" --arg s "${3:-passed}" \
|
|
30
|
+
'{schema_version:1,head:$h,worktree_clean:$c,mode:"review",status:$s,recorded_at:"2026-01-01T00:00:00Z"}
|
|
31
|
+
| if .status == "none" then del(.status) else . end' \
|
|
31
32
|
>"$repo/.git/ccl-code-review/last-review.json"
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -45,6 +46,7 @@ probe() {
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
probe "no receipt" remind 'glab mr create --title x' "$repo" '没有记录到任何结论性的 code-review 结果'
|
|
49
|
+
probe "no receipt names the path it checked" remind 'glab mr create --title x' "$repo" 'ccl-code-review/last-review.json'
|
|
48
50
|
head1=$(git -C "$repo" rev-parse HEAD)
|
|
49
51
|
write_receipt "$head1" true
|
|
50
52
|
probe "covered head" quiet 'glab mr create --title x'
|
|
@@ -53,6 +55,18 @@ probe "commit in the same command" remind 'git add -A && git commit -m fix && gh
|
|
|
53
55
|
probe "HEAD moves only after the PR opens" quiet 'gh pr create --fill && git checkout main'
|
|
54
56
|
probe "draft, commit, then ready" remind 'gh pr create --draft --fill && git add -A && git commit -m fix && git push && gh pr ready' "$repo" '会先改动 HEAD'
|
|
55
57
|
|
|
58
|
+
# Coverage is not disposition: findings on the covered HEAD still remind.
|
|
59
|
+
write_receipt "$head1" true findings
|
|
60
|
+
probe "findings on the covered head" remind 'glab mr merge 8 --yes' "$repo" '结论是 findings,不是 passed'
|
|
61
|
+
probe "findings reminder names the receipt it read" remind 'gh pr ready 12' "$repo" 'ccl-code-review/last-review.json'
|
|
62
|
+
write_receipt "$head1" true none
|
|
63
|
+
[ "$(jq -r 'has("status")' "$repo/.git/ccl-code-review/last-review.json")" = false ] \
|
|
64
|
+
|| { echo "FAIL: fixture still carries a status" >&2; exit 1; }
|
|
65
|
+
probe "a receipt without a status is not a pass" remind 'glab mr create --title x' "$repo" '不是 passed'
|
|
66
|
+
probe "a missing status is not reported as findings" remind 'glab mr create --title x' "$repo" '没有记录可识别的结论'
|
|
67
|
+
write_receipt "$head1" true
|
|
68
|
+
probe "passed on the covered head" quiet 'glab mr merge 8 --yes'
|
|
69
|
+
|
|
56
70
|
commit test.txt added "add regression test after review"
|
|
57
71
|
probe "commit after review" remind 'glab mr create --title x' "$repo" 'add regression test after review'
|
|
58
72
|
probe "gh pr ready" remind 'gh pr ready 12'
|
|
@@ -82,6 +96,8 @@ head2=$(git -C "$repo" rev-parse HEAD)
|
|
|
82
96
|
write_receipt "$head2" true
|
|
83
97
|
git -C "$repo" commit -q --amend -m "reworded message only"
|
|
84
98
|
probe "message-only amend keeps the reviewed tree" quiet 'glab mr create --title x'
|
|
99
|
+
write_receipt "$head2" true findings
|
|
100
|
+
probe "message-only amend keeps the findings too" remind 'glab mr create --title x' "$repo" '结论是 findings,不是 passed'
|
|
85
101
|
|
|
86
102
|
# Rewritten history with different content: the reviewed commit is gone.
|
|
87
103
|
git -C "$repo" reset -q --hard "$head1"
|
|
@@ -77,6 +77,83 @@ class SkillLoadingTests(unittest.TestCase):
|
|
|
77
77
|
self.assertNotIn('permissionDecision":"ask', json.dumps(result))
|
|
78
78
|
self.assertEqual(self.run_hook(self.payload()), {})
|
|
79
79
|
|
|
80
|
+
def ccl_checkout(self):
|
|
81
|
+
root = self.root / 'ccl'
|
|
82
|
+
(root / 'skills' / 'skill-extraction-workflow').mkdir(parents=True)
|
|
83
|
+
(root / 'skills' / 'skill-extraction-workflow' / 'SKILL.md').write_text('marker')
|
|
84
|
+
(root / 'skills' / 'sample-owner' / 'references').mkdir(parents=True)
|
|
85
|
+
(root / 'hooks').mkdir()
|
|
86
|
+
return root
|
|
87
|
+
|
|
88
|
+
def extraction_loaded(self, ident='ext'):
|
|
89
|
+
self.append({'type': 'assistant', 'message': {'content': [
|
|
90
|
+
{'type': 'tool_use', 'id': ident, 'name': 'Skill',
|
|
91
|
+
'input': {'skill': 'ccl-skills:skill-extraction-workflow'}}]}},
|
|
92
|
+
{'type': 'user', 'message': {'content': [
|
|
93
|
+
{'type': 'tool_result', 'tool_use_id': ident, 'content': 'Loaded owner'}]}})
|
|
94
|
+
|
|
95
|
+
def test_shared_skill_edit_names_the_extraction_owner_at_the_first_edit(self):
|
|
96
|
+
root = self.ccl_checkout()
|
|
97
|
+
target = root / 'skills' / 'sample-owner' / 'scripts' / 'gate.sh'
|
|
98
|
+
target.parent.mkdir(parents=True)
|
|
99
|
+
result = self.run_hook(self.payload(tool_input={'file_path': str(target)}))
|
|
100
|
+
self.assertEqual(self.decision(result), 'deny')
|
|
101
|
+
reason = result['hookSpecificOutput']['permissionDecisionReason']
|
|
102
|
+
self.assertIn('skill-extraction-workflow', reason)
|
|
103
|
+
self.assertIn('charter', reason.lower())
|
|
104
|
+
|
|
105
|
+
def test_shared_skill_markdown_edit_reaches_the_checkpoint(self):
|
|
106
|
+
root = self.ccl_checkout()
|
|
107
|
+
target = root / 'skills' / 'sample-owner' / 'references' / 'rule.md'
|
|
108
|
+
result = self.run_hook(self.payload(tool_input={'file_path': str(target)}))
|
|
109
|
+
self.assertEqual(self.decision(result), 'deny')
|
|
110
|
+
self.assertIn('skill-extraction-workflow',
|
|
111
|
+
result['hookSpecificOutput']['permissionDecisionReason'])
|
|
112
|
+
|
|
113
|
+
def test_markdown_outside_a_ccl_checkout_still_skips_the_checkpoint(self):
|
|
114
|
+
outside = self.root / 'product' / 'skills' / 'thing' / 'notes.md'
|
|
115
|
+
outside.parent.mkdir(parents=True)
|
|
116
|
+
self.assertEqual(self.run_hook(self.payload(tool_input={'file_path': str(outside)})), {})
|
|
117
|
+
cached = self.ccl_checkout() / 'skills' / 'sample-owner' / 'SKILL.md'
|
|
118
|
+
# An installed plugin copy carries the same marker; only the cache exclusion skips it.
|
|
119
|
+
cache_root = self.root / 'plugins' / 'cache' / 'ccl'
|
|
120
|
+
(cache_root / 'skills' / 'skill-extraction-workflow').mkdir(parents=True)
|
|
121
|
+
(cache_root / 'skills' / 'skill-extraction-workflow' / 'SKILL.md').write_text('marker')
|
|
122
|
+
plugin_cache = cache_root / 'skills' / 'x' / 'SKILL.md'
|
|
123
|
+
plugin_cache.parent.mkdir(parents=True)
|
|
124
|
+
self.assertEqual(self.run_hook(self.payload(tool_input={'file_path': str(plugin_cache)})), {})
|
|
125
|
+
# The checkpoint is still unspent for a real shared-skill edit.
|
|
126
|
+
result = self.run_hook(self.payload(tool_input={'file_path': str(cached)}))
|
|
127
|
+
self.assertEqual(self.decision(result), 'deny')
|
|
128
|
+
|
|
129
|
+
def test_checkout_under_an_ancestor_named_like_a_surface_is_recognised(self):
|
|
130
|
+
root = self.root / 'skills' / 'ccl'
|
|
131
|
+
(root / 'skills' / 'skill-extraction-workflow').mkdir(parents=True)
|
|
132
|
+
(root / 'skills' / 'skill-extraction-workflow' / 'SKILL.md').write_text('marker')
|
|
133
|
+
target = root / 'skills' / 'sample-owner' / 'SKILL.md'
|
|
134
|
+
target.parent.mkdir(parents=True)
|
|
135
|
+
result = self.run_hook(self.payload(tool_input={'file_path': str(target)}))
|
|
136
|
+
self.assertEqual(self.decision(result), 'deny')
|
|
137
|
+
self.assertIn('skill-extraction-workflow',
|
|
138
|
+
result['hookSpecificOutput']['permissionDecisionReason'])
|
|
139
|
+
|
|
140
|
+
def test_codex_install_copy_is_exempt_like_the_plugin_cache(self):
|
|
141
|
+
install = self.root / '.codex' / 'ccl'
|
|
142
|
+
(install / 'skills' / 'skill-extraction-workflow').mkdir(parents=True)
|
|
143
|
+
(install / 'skills' / 'skill-extraction-workflow' / 'SKILL.md').write_text('marker')
|
|
144
|
+
target = install / 'skills' / 'x' / 'SKILL.md'
|
|
145
|
+
target.parent.mkdir(parents=True)
|
|
146
|
+
self.assertEqual(self.run_hook(self.payload(tool_input={'file_path': str(target)})), {})
|
|
147
|
+
self.assertEqual(self.decision(self.run_hook(self.payload())), 'deny')
|
|
148
|
+
|
|
149
|
+
def test_shared_skill_edit_with_the_owner_loaded_keeps_the_generic_reason(self):
|
|
150
|
+
root = self.ccl_checkout()
|
|
151
|
+
self.extraction_loaded()
|
|
152
|
+
target = root / 'hooks' / 'gate.sh'
|
|
153
|
+
result = self.run_hook(self.payload(tool_input={'file_path': str(target)}))
|
|
154
|
+
self.assertEqual(self.decision(result), 'deny')
|
|
155
|
+
self.assertNotIn('charter', result['hookSpecificOutput']['permissionDecisionReason'].lower())
|
|
156
|
+
|
|
80
157
|
def test_native_patch_and_all_targets(self):
|
|
81
158
|
patch = '*** Begin Patch\n*** Add File: README.md\n+doc\n*** Add File: src/new.ts\n+code\n*** End Patch'
|
|
82
159
|
value = self.run_hook(self.payload('apply_patch', tool_input={'command': patch}))
|
|
@@ -5,9 +5,10 @@ This transition applies across implementation owners, including narrow fixes and
|
|
|
5
5
|
## Before invoking review
|
|
6
6
|
|
|
7
7
|
1. Recover the current task, actual diff, scope and authorization. Changing the implementation or scope reopens this check; an earlier plan review cannot discharge review of the resulting code.
|
|
8
|
-
2. Finish
|
|
8
|
+
2. Finish a deep self-review in the main implementing agent, not a subagent — it holds the intent and decisions, and independence comes from the external review — covering every changed branch and failure path, plus privacy, authority and data-loss risk, then affected verification. A failed quality check calls for available in-scope diagnosis and cleanup under [refactoring discipline](../../product-rd-workflow/references/refactoring-discipline.md#responding-to-quality-gates); preserve behavior and readability, rerun the check, and escalate only a remaining real blocker. Use `testing-strategy` to select tests: changed named test properties require the killing-mutation walk on disposable or restore-guarded resources; when the same contract has two implementations or paths, use differential/equivalence checks with bounded, asserted known differences. Record a concrete applicability or unavailable-evidence reason when a test family does not run. Do not force a full mutation framework or differential suite onto an unrelated change.
|
|
9
9
|
3. Reuse a terminal independent review only when it covers the current candidate and satisfies the applicable owner gate, reviewer independence and required depth. Record the receipt location and its candidate identifier (commit or diff/packet digest), then compare with the current candidate using the owning gate's binding rules; HEAD alone cannot cover uncommitted edits. A different or missing candidate identifier cannot discharge review. A loaded skill, self-review, planned command, unfinished handle or unverified prose claim is not that evidence. A native subagent result counts only when the owning gate accepts its independence and evidence; it never silently substitutes for a required CLI receipt.
|
|
10
|
-
4.
|
|
10
|
+
4. Self-review and the external review below are yours to run; neither waits on a person. An ordinary change needs no human reviewer, sign-off or risk owner; human sign-off applies only where the product-rd design gate or the safety rules require it for a merge, launch or destructive action.
|
|
11
|
+
5. An explicit user instruction to skip review controls this task: record `skipped`, not `passed`, and preserve any separate landing restrictions. Record its original wording and current scope; a superseded or unrelated instruction is not a skip for this task. Do not ask for confirmation of ordinary review already within the authorized development task. User client restrictions and existing confidentiality boundaries still control reviewer selection; capability matters, not a numeric CLI or skill version.
|
|
11
12
|
|
|
12
13
|
## Invoke and finish
|
|
13
14
|
|
|
@@ -23,7 +24,7 @@ A review quotes the repository's own tracked contract files for the changed path
|
|
|
23
24
|
|
|
24
25
|
Walk these before you open a pull or merge request, push to one that is already open, ask for a merge, or report the work done or ready — every time, not only after the first review:
|
|
25
26
|
|
|
26
|
-
1. Name the commit or packet the last conclusive review covered and compare it with the current candidate: HEAD plus staged, unstaged and untracked implementation files. For a review whose candidate `--base` derived from the whole worktree, the controller records that commit in the worktree's git directory (`ccl-code-review/last-review.json`; a bare `--diff-file` or `--paths` review records nothing), and the plugin's pull-request hook repeats this comparison when you open, ready or merge one; its reminder is this step firing, not a new question.
|
|
27
|
+
1. Name the commit or packet the last conclusive review covered and compare it with the current candidate: HEAD plus staged, unstaged and untracked implementation files. For a review whose candidate `--base` derived from the whole worktree, the controller records that commit in the worktree's git directory (`ccl-code-review/last-review.json`; a bare `--diff-file` or `--paths` review records nothing), and the plugin's pull-request hook repeats this comparison when you open, ready or merge one; its reminder is this step firing, not a new question. A successful completion checkpoint on such a candidate records it as well, so a chain whose findings were disposed reads as passed; coverage alone is not disposition, and a covered receipt with findings or no status must still be dispositioned before a ready report.
|
|
27
28
|
2. Any difference makes the current candidate unreviewed: a fix for a finding of any severity, an added or changed test, a changelog or doc line, a rebase that changed a file the candidate touches. Only the review's own record files (result JSON, disposition notes) are exempt.
|
|
28
29
|
3. An unreviewed candidate must get the owning gate's renewed review now, run by you: a fresh full run per [manual invocation](manual-invocation-and-prompts.md); the skill-extraction lane runs its own delta pass instead. Pushing it for a human to review does not discharge it, and "awaiting human review" never stands in for the run.
|
|
29
30
|
4. Apply the [review continuation checkpoint](#review-continuation-checkpoint) after five renewed runs or earlier when findings recur without progress. Necessary review continues within the task's authority; unresolved P0/P1 or unreviewed changes still prevent a ready report.
|
|
@@ -10,7 +10,8 @@ Rules:
|
|
|
10
10
|
their arguments, returned bytes and completed lifecycle. This does not permit
|
|
11
11
|
arbitrary commands or workspace access. A malformed, timeout, or inconclusive
|
|
12
12
|
wrapper result is not a pass.
|
|
13
|
-
- **Never pin
|
|
13
|
+
- **Never pin a lane to a CLI version's vocabulary — reading it or writing it.**
|
|
14
|
+
`parse_probe_result.py`
|
|
14
15
|
gates on *shape*, not on field/value names: the isolation proof is the exact
|
|
15
16
|
`tools` allowlist plus the tool_use scan, which no init field can bypass.
|
|
16
17
|
Field-name and value whitelists were tried twice (`fast_mode_state`, then
|
|
@@ -19,6 +20,15 @@ Rules:
|
|
|
19
20
|
or empty-container, and `agents`/`capabilities` are list-of-strings checks
|
|
20
21
|
with no value vocabulary. Only `permissionMode` stays value-pinned (it widens
|
|
21
22
|
what the runtime may do with no tool added).
|
|
23
|
+
The write side is the same class and was missed once: config a wrapper
|
|
24
|
+
GENERATES and then submits to the runtime's own validator as an admission
|
|
25
|
+
precondition carries a key that release may not know, and rejecting it turns
|
|
26
|
+
the whole lane off. So a generated setting that a per-invocation environment
|
|
27
|
+
variable already carries is belt, not the gate — on rejection regenerate the
|
|
28
|
+
strict subset without it and revalidate, unconditionally rather than by
|
|
29
|
+
matching the runtime's error wording, which is the same pin relocated. Only a
|
|
30
|
+
setting with no second, independent path to the same property may fail the
|
|
31
|
+
lane closed, and admission must never widen on the retry.
|
|
22
32
|
- **Skill, command and plugin lists are vocabulary, not a boundary.**
|
|
23
33
|
`slash_commands`, `terminal_slash_commands`, `skills` and `plugins` may hold
|
|
24
34
|
any value and are never judged by name, shape, or origin; only `mcp_servers`
|
package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/kimi_review.sh
CHANGED
|
@@ -721,7 +721,7 @@ fi
|
|
|
721
721
|
# prevention. The cooperative probe below attempts only private-workspace
|
|
722
722
|
# Read/Glob/Grep canaries and rejects any observed tool exposure.
|
|
723
723
|
install_packet_only_config() {
|
|
724
|
-
"$PYTHON_BIN_PATH" - "$RUNTIME_HOME/config.toml" "$PACKET_DELIVERY" <<'PY'
|
|
724
|
+
"$PYTHON_BIN_PATH" - "$RUNTIME_HOME/config.toml" "$PACKET_DELIVERY" "${1:-guard}" <<'PY'
|
|
725
725
|
import json
|
|
726
726
|
import os
|
|
727
727
|
from pathlib import Path
|
|
@@ -730,6 +730,15 @@ import tomllib
|
|
|
730
730
|
|
|
731
731
|
config_path = Path(sys.argv[1])
|
|
732
732
|
delivery = sys.argv[2]
|
|
733
|
+
# "guard" writes the watcher table; "omit" leaves it out for a runtime whose
|
|
734
|
+
# own validator does not recognise it. Both are equally isolated: every
|
|
735
|
+
# invocation sets KIMI_CODE_WATCH, which the runtime reads ahead of the config.
|
|
736
|
+
watch_mode = sys.argv[3]
|
|
737
|
+
# A retry regenerates from the previous generation, so the marker is dropped on
|
|
738
|
+
# the way in and re-added below; the output is the same bytes either way.
|
|
739
|
+
generated_marker = (
|
|
740
|
+
"# Generated by code-review; source-home hooks, tools, and permissions are not inherited."
|
|
741
|
+
)
|
|
733
742
|
source = config_path.read_text(encoding="utf-8") if config_path.exists() else ""
|
|
734
743
|
lines = source.splitlines(keepends=True)
|
|
735
744
|
kept = []
|
|
@@ -793,6 +802,8 @@ def decoded_assignment_root(line):
|
|
|
793
802
|
|
|
794
803
|
for line in lines:
|
|
795
804
|
stripped = line.strip()
|
|
805
|
+
if stripped == generated_marker:
|
|
806
|
+
continue
|
|
796
807
|
table_root = decoded_table_root(stripped)
|
|
797
808
|
if table_root is not None:
|
|
798
809
|
table_allowed = table_root in safe_table_roots
|
|
@@ -828,10 +839,19 @@ guard = (
|
|
|
828
839
|
"[tools]\n"
|
|
829
840
|
f"enabled = {json.dumps(enabled_tools, separators=(',', ':'))}\n"
|
|
830
841
|
)
|
|
842
|
+
if watch_mode == "guard":
|
|
843
|
+
guard += "[watch]\nenabled = false\n"
|
|
831
844
|
rendered = "".join(kept).rstrip() + guard
|
|
832
845
|
parsed = tomllib.loads(rendered)
|
|
833
846
|
allowed_roots = safe_root_keys | safe_table_roots | {"tools"}
|
|
834
|
-
|
|
847
|
+
expected_watch = {"enabled": False} if watch_mode == "guard" else None
|
|
848
|
+
if watch_mode == "guard":
|
|
849
|
+
allowed_roots = allowed_roots | {"watch"}
|
|
850
|
+
if (
|
|
851
|
+
set(parsed) - allowed_roots
|
|
852
|
+
or parsed.get("tools") != {"enabled": enabled_tools}
|
|
853
|
+
or parsed.get("watch") != expected_watch
|
|
854
|
+
):
|
|
835
855
|
raise SystemExit("generated packet-only policy failed semantic verification")
|
|
836
856
|
replacement = config_path.with_name(f".{config_path.name}.code-review.tmp")
|
|
837
857
|
replacement.write_text(rendered, encoding="utf-8")
|
|
@@ -839,14 +859,30 @@ os.chmod(replacement, 0o600)
|
|
|
839
859
|
os.replace(replacement, config_path)
|
|
840
860
|
PY
|
|
841
861
|
}
|
|
842
|
-
install_packet_only_config \
|
|
843
|
-
|| die_inconclusive kimi_packet_only_config_failed capability_missing true
|
|
844
862
|
DOCTOR_STDOUT="$RUN_ROOT/doctor.stdout"
|
|
845
863
|
DOCTOR_STDERR="$RUN_ROOT/doctor.stderr"
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
864
|
+
validate_packet_only_config() {
|
|
865
|
+
doctor_rc=0
|
|
866
|
+
KIMI_CODE_HOME="$RUNTIME_HOME" KIMI_CODE_WATCH=0 KIMI_DISABLE_TELEMETRY=1 \
|
|
867
|
+
timeout --kill-after=1s 15s "$KIMI_BIN_PATH" doctor config "$RUNTIME_HOME/config.toml" \
|
|
868
|
+
>"$DOCTOR_STDOUT" 2>"$DOCTOR_STDERR" \
|
|
869
|
+
|| doctor_rc=$?
|
|
870
|
+
[ "$doctor_rc" -eq 0 ]
|
|
871
|
+
}
|
|
872
|
+
install_packet_only_config guard \
|
|
873
|
+
|| die_inconclusive kimi_packet_only_config_failed capability_missing true
|
|
874
|
+
# The watcher table is belt on top of KIMI_CODE_WATCH, which every invocation
|
|
875
|
+
# below sets and which the runtime reads ahead of the config. A runtime whose
|
|
876
|
+
# validator does not know the table must not cost the whole lane, so retry once
|
|
877
|
+
# with a strict subset of the same policy — admission cannot widen, and the
|
|
878
|
+
# retry is unconditional rather than matched against the runtime's error
|
|
879
|
+
# wording, which would be the same release-vocabulary pin in another place.
|
|
880
|
+
if ! validate_packet_only_config; then
|
|
881
|
+
install_packet_only_config omit \
|
|
882
|
+
|| die_inconclusive kimi_packet_only_config_failed capability_missing true
|
|
883
|
+
validate_packet_only_config \
|
|
884
|
+
|| die_inconclusive kimi_packet_only_config_unrecognized capability_missing true "$doctor_rc"
|
|
885
|
+
fi
|
|
850
886
|
|
|
851
887
|
# Exercise the forbidden built-in tool surface without depending on a version
|
|
852
888
|
# string. In MCP mode the generated config still exposes the one packet reader,
|
|
@@ -866,7 +902,7 @@ PROBE_TIMEOUT="$TIMEOUT"
|
|
|
866
902
|
[ "$PROBE_TIMEOUT" -le 60 ] || PROBE_TIMEOUT=60
|
|
867
903
|
(
|
|
868
904
|
cd "$RUN_WORKSPACE" || exit 2
|
|
869
|
-
KIMI_CODE_HOME="$RUNTIME_HOME" KIMI_DISABLE_TELEMETRY=1 \
|
|
905
|
+
KIMI_CODE_HOME="$RUNTIME_HOME" KIMI_CODE_WATCH=0 KIMI_DISABLE_TELEMETRY=1 \
|
|
870
906
|
timeout --kill-after=1s "${PROBE_TIMEOUT}s" "$KIMI_BIN_PATH" \
|
|
871
907
|
--skills-dir "$ACTIVE_SKILLS_DIR" --prompt "$PROBE_PROMPT" \
|
|
872
908
|
--output-format stream-json
|
|
@@ -876,6 +912,24 @@ if [ "$probe_rc" -ne 0 ]; then
|
|
|
876
912
|
if grep -qiE 'EMFILE|too many open files' "$PROBE_STDERR"; then
|
|
877
913
|
die_inconclusive kimi_host_resource_exhausted client_unavailable true "$probe_rc"
|
|
878
914
|
fi
|
|
915
|
+
# NO SUB-CLASSIFICATION OF PROVIDER PROSE. Splitting this failure into quota
|
|
916
|
+
# and auth reasons was tried and withdrawn: four independent review rounds
|
|
917
|
+
# each broke the predicate on a new message. A digit boundary excluded 4290
|
|
918
|
+
# but not an offset of exactly 429; requiring a status word before the number
|
|
919
|
+
# matched `code` inside `decode`; matching what the message said instead
|
|
920
|
+
# matched `hit` inside `whitelisted` and still missed `credits are exhausted`.
|
|
921
|
+
# That is the shape this directory's contract already names — a predicate over
|
|
922
|
+
# a vocabulary the control does not own — and same-class recurrence is the cue
|
|
923
|
+
# to remove the capability rather than guard it again.
|
|
924
|
+
#
|
|
925
|
+
# Nothing about the gate changes: every one of those reasons was
|
|
926
|
+
# die_inconclusive and cascade-eligible, so the split only ever decided an
|
|
927
|
+
# operator hint, and a wrong hint is worse than none. The contract also
|
|
928
|
+
# forbids putting raw provider text in the payload, so the honest replacement
|
|
929
|
+
# is not a looser regex but a classifier over the structured error the probe
|
|
930
|
+
# already streams, the way the Claude lane's envelope classifier works. That
|
|
931
|
+
# is its own change, against a real sample.
|
|
932
|
+
:
|
|
879
933
|
die_inconclusive kimi_tool_capability_unverified capability_missing true "$probe_rc"
|
|
880
934
|
fi
|
|
881
935
|
# MCP mode enables the packet reader for the later formal transport, but this
|
|
@@ -962,14 +1016,14 @@ run_started=$SECONDS
|
|
|
962
1016
|
if [ "$PACKET_DELIVERY" = inline ]; then
|
|
963
1017
|
(
|
|
964
1018
|
cd "$RUN_WORKSPACE" || exit 2
|
|
965
|
-
KIMI_CODE_HOME="$RUNTIME_HOME" KIMI_DISABLE_TELEMETRY=1 \
|
|
1019
|
+
KIMI_CODE_HOME="$RUNTIME_HOME" KIMI_CODE_WATCH=0 KIMI_DISABLE_TELEMETRY=1 \
|
|
966
1020
|
timeout --kill-after=1s "${FORMAL_TIMEOUT}s" "$KIMI_BIN_PATH" --skills-dir "$ACTIVE_SKILLS_DIR" \
|
|
967
1021
|
--prompt "$PROMPT" --output-format stream-json
|
|
968
1022
|
) >"$EVENTS" 2>"$STDERR_FILE"
|
|
969
1023
|
else
|
|
970
1024
|
(
|
|
971
1025
|
cd "$RUN_WORKSPACE" || exit 2
|
|
972
|
-
KIMI_CODE_HOME="$RUNTIME_HOME" KIMI_DISABLE_TELEMETRY=1 \
|
|
1026
|
+
KIMI_CODE_HOME="$RUNTIME_HOME" KIMI_CODE_WATCH=0 KIMI_DISABLE_TELEMETRY=1 \
|
|
973
1027
|
timeout --kill-after=1s "${FORMAL_TIMEOUT}s" "$KIMI_BIN_PATH" --skills-dir "$ACTIVE_SKILLS_DIR" \
|
|
974
1028
|
--agent-file "$AGENT_FILE" --prompt "$PROMPT" --output-format stream-json
|
|
975
1029
|
) >"$EVENTS" 2>"$STDERR_FILE"
|
package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.py
CHANGED
|
@@ -4661,8 +4661,10 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
4661
4661
|
)
|
|
4662
4662
|
# Only a candidate derived from the whole worktree says what HEAD holds:
|
|
4663
4663
|
# a bare --diff-file packet or a --paths slice may cover less than HEAD.
|
|
4664
|
+
# A completion checkpoint records too: it is what turns a findings
|
|
4665
|
+
# receipt into a passed one once every finding is disposed.
|
|
4664
4666
|
anchors_wanted = (
|
|
4665
|
-
args.mode in {"review", "challenge"}
|
|
4667
|
+
args.mode in {"review", "challenge", "complete"}
|
|
4666
4668
|
and bool(args.base)
|
|
4667
4669
|
and not args.paths
|
|
4668
4670
|
)
|
|
@@ -4831,6 +4833,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
4831
4833
|
),
|
|
4832
4834
|
)
|
|
4833
4835
|
result.update(completion_metadata)
|
|
4836
|
+
record_local_review(review_anchor, result)
|
|
4834
4837
|
return emit(result, 0)
|
|
4835
4838
|
last_reason_code = "no_independent_reviewer_available"
|
|
4836
4839
|
for client in order:
|