claude-dev-env 1.75.0 → 1.76.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/_shared/pr-loop/scripts/code_rules_gate.py +60 -5
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/inline_duplicate_body_span_constants.py +22 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +147 -3
- package/docs/CODE_RULES.md +1 -1
- package/hooks/blocking/CLAUDE.md +2 -2
- package/hooks/blocking/claude_md_orphan_file_blocker.py +170 -20
- package/hooks/blocking/code_rules_duplicate_body.py +378 -26
- package/hooks/blocking/code_rules_enforcer.py +36 -5
- package/hooks/blocking/code_rules_imports_logging.py +679 -1
- package/hooks/blocking/code_rules_shared.py +8 -5
- package/hooks/blocking/code_rules_test_assertions.py +6 -7
- package/hooks/blocking/test_claude_md_orphan_file_blocker.py +484 -0
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +1 -0
- package/hooks/blocking/test_code_rules_enforcer_import_block_sort.py +157 -0
- package/hooks/blocking/test_code_rules_enforcer_same_file_inline_duplicate.py +466 -0
- package/hooks/blocking/test_code_rules_enforcer_split_test_assertions.py +11 -9
- package/hooks/blocking/test_code_rules_js_resume_task_enumeration.py +758 -0
- package/hooks/blocking/test_code_rules_logging_printf_tokens.py +134 -0
- package/hooks/blocking/test_verification_verdict_store.py +66 -1
- package/hooks/blocking/test_verifier_verdict_minter.py +64 -5
- package/hooks/blocking/verification_verdict_store.py +19 -5
- package/hooks/blocking/verifier_verdict_minter.py +18 -15
- package/hooks/hooks_constants/blocking_check_limits.py +30 -1
- package/hooks/hooks_constants/claude_md_orphan_file_blocker_constants.py +52 -24
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +41 -1
- package/hooks/hooks_constants/duplicate_function_body_constants.py +21 -5
- package/package.json +1 -1
- package/rules/claude-md-orphan-file.md +7 -8
- package/rules/docstring-prose-matches-implementation.md +1 -0
- package/rules/package-inventory-stale-entry.md +8 -0
- package/skills/anthropic-plan/CLAUDE.md +1 -1
- package/skills/anthropic-plan/SKILL.md +15 -2
- package/skills/autoconverge/workflow/converge.contract.test.mjs +12 -19
- package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +71 -0
- package/skills/autoconverge/workflow/converge.mjs +86 -110
- package/skills/bugteam/scripts/bugteam_code_rules_gate.py +58 -4
- package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_code_rules_gate_constants.py +9 -0
- package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +42 -0
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
"""Tests for check_js_resume_task_enumeration_coverage.
|
|
2
|
+
|
|
3
|
+
The check catches the JS/.mjs analog of the Category O6/O8
|
|
4
|
+
docstring-prose-vs-implementation drift: a spawn function's JSDoc enumerates
|
|
5
|
+
the resume tasks of its sibling resume function in a parenthetical
|
|
6
|
+
``(repair-verify, hardening-verify)`` list while the resume function dispatches
|
|
7
|
+
on a ``task === '<name>'`` branch the enumeration omits. The drift this
|
|
8
|
+
reproduces is PR #754: spawnVerifierAgent's JSDoc lists
|
|
9
|
+
``(repair-verify, hardening-verify)`` after resumeVerifierAgent gained a
|
|
10
|
+
``fix-verify`` branch.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import importlib.util
|
|
16
|
+
import pathlib
|
|
17
|
+
import sys
|
|
18
|
+
|
|
19
|
+
_PACKAGE_ROOT = pathlib.Path(__file__).resolve().parents[2]
|
|
20
|
+
_SHIPPED_CONVERGE_MJS = (
|
|
21
|
+
_PACKAGE_ROOT / "skills" / "autoconverge" / "workflow" / "converge.mjs"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
_HOOK_DIRECTORY = pathlib.Path(__file__).parent
|
|
25
|
+
if str(_HOOK_DIRECTORY) not in sys.path:
|
|
26
|
+
sys.path.insert(0, str(_HOOK_DIRECTORY))
|
|
27
|
+
|
|
28
|
+
_module_spec = importlib.util.spec_from_file_location(
|
|
29
|
+
"code_rules_imports_logging",
|
|
30
|
+
_HOOK_DIRECTORY / "code_rules_imports_logging.py",
|
|
31
|
+
)
|
|
32
|
+
assert _module_spec is not None
|
|
33
|
+
assert _module_spec.loader is not None
|
|
34
|
+
_imports_logging_module = importlib.util.module_from_spec(_module_spec)
|
|
35
|
+
_module_spec.loader.exec_module(_imports_logging_module)
|
|
36
|
+
|
|
37
|
+
check_js_resume_task_enumeration_coverage = (
|
|
38
|
+
_imports_logging_module.check_js_resume_task_enumeration_coverage
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
_MJS_PATH = "skills/autoconverge/workflow/converge.mjs"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _drifted_verifier_source() -> str:
|
|
45
|
+
return (
|
|
46
|
+
"/**\n"
|
|
47
|
+
" * Spawn the verifier code-verifier agent once per converge round,\n"
|
|
48
|
+
" * establishing its role so each later resume (repair-verify,\n"
|
|
49
|
+
" * hardening-verify) continues the same session.\n"
|
|
50
|
+
" * @returns {Promise<string|undefined>} the runtime agent id\n"
|
|
51
|
+
" */\n"
|
|
52
|
+
"async function spawnVerifierAgent() {\n"
|
|
53
|
+
" return undefined\n"
|
|
54
|
+
"}\n"
|
|
55
|
+
"\n"
|
|
56
|
+
"function resumeVerifierAgent(agentId, task, context) {\n"
|
|
57
|
+
" if (task === 'fix-verify') {\n"
|
|
58
|
+
" return doFix(agentId, context)\n"
|
|
59
|
+
" }\n"
|
|
60
|
+
" if (task === 'repair-verify') {\n"
|
|
61
|
+
" return doRepair(agentId, context)\n"
|
|
62
|
+
" }\n"
|
|
63
|
+
" return doHardening(agentId, context)\n"
|
|
64
|
+
"}\n"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _aligned_verifier_source() -> str:
|
|
69
|
+
return _drifted_verifier_source().replace(
|
|
70
|
+
"each later resume (repair-verify,\n * hardening-verify)",
|
|
71
|
+
"each later resume (fix-verify, repair-verify,\n * hardening-verify)",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def should_flag_resume_branch_absent_from_spawn_enumeration() -> None:
|
|
76
|
+
issues = check_js_resume_task_enumeration_coverage(_drifted_verifier_source(), _MJS_PATH)
|
|
77
|
+
assert len(issues) == 1
|
|
78
|
+
assert "fix-verify" in issues[0]
|
|
79
|
+
assert "spawnVerifierAgent" in issues[0]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def should_pass_when_enumeration_names_every_resume_branch() -> None:
|
|
83
|
+
issues = check_js_resume_task_enumeration_coverage(_aligned_verifier_source(), _MJS_PATH)
|
|
84
|
+
assert issues == []
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def should_ignore_python_files() -> None:
|
|
88
|
+
issues = check_js_resume_task_enumeration_coverage(
|
|
89
|
+
_drifted_verifier_source(), "skills/autoconverge/workflow/converge.py"
|
|
90
|
+
)
|
|
91
|
+
assert issues == []
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def should_ignore_spawn_jsdoc_without_resume_enumeration() -> None:
|
|
95
|
+
source = (
|
|
96
|
+
"/**\n"
|
|
97
|
+
" * Spawn an agent with no resume enumeration in its prose.\n"
|
|
98
|
+
" */\n"
|
|
99
|
+
"async function spawnVerifierAgent() {\n"
|
|
100
|
+
" return undefined\n"
|
|
101
|
+
"}\n"
|
|
102
|
+
"\n"
|
|
103
|
+
"function resumeVerifierAgent(agentId, task, context) {\n"
|
|
104
|
+
" if (task === 'fix-verify') {\n"
|
|
105
|
+
" return doFix(agentId, context)\n"
|
|
106
|
+
" }\n"
|
|
107
|
+
" return doHardening(agentId, context)\n"
|
|
108
|
+
"}\n"
|
|
109
|
+
)
|
|
110
|
+
assert check_js_resume_task_enumeration_coverage(source, _MJS_PATH) == []
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def should_match_spawn_to_resume_by_role_only() -> None:
|
|
114
|
+
source = (
|
|
115
|
+
"/**\n"
|
|
116
|
+
" * Spawn the editor so each later resume (fix-edit) continues the\n"
|
|
117
|
+
" * same session.\n"
|
|
118
|
+
" */\n"
|
|
119
|
+
"async function spawnCodeEditorAgent() {\n"
|
|
120
|
+
" return undefined\n"
|
|
121
|
+
"}\n"
|
|
122
|
+
"\n"
|
|
123
|
+
"function resumeCodeEditorAgent(agentId, task, context) {\n"
|
|
124
|
+
" if (task === 'fix-edit') {\n"
|
|
125
|
+
" return doFix(agentId, context)\n"
|
|
126
|
+
" }\n"
|
|
127
|
+
" return doFallback(agentId, context)\n"
|
|
128
|
+
"}\n"
|
|
129
|
+
"\n"
|
|
130
|
+
"function resumeVerifierAgent(agentId, task, context) {\n"
|
|
131
|
+
" if (task === 'unrelated-verify') {\n"
|
|
132
|
+
" return doFix(agentId, context)\n"
|
|
133
|
+
" }\n"
|
|
134
|
+
" return doHardening(agentId, context)\n"
|
|
135
|
+
"}\n"
|
|
136
|
+
)
|
|
137
|
+
assert check_js_resume_task_enumeration_coverage(source, _MJS_PATH) == []
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def should_skip_test_files() -> None:
|
|
141
|
+
issues = check_js_resume_task_enumeration_coverage(
|
|
142
|
+
_drifted_verifier_source(), "skills/autoconverge/workflow/converge.test.mjs"
|
|
143
|
+
)
|
|
144
|
+
assert issues == []
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def should_not_flag_single_word_task_named_in_enumeration() -> None:
|
|
148
|
+
source = (
|
|
149
|
+
"/**\n"
|
|
150
|
+
" * Spawn the fixer so each later resume (verify-commit, commit, and\n"
|
|
151
|
+
" * recovery) continues the same session.\n"
|
|
152
|
+
" */\n"
|
|
153
|
+
"async function spawnFixerAgent() {\n"
|
|
154
|
+
" return undefined\n"
|
|
155
|
+
"}\n"
|
|
156
|
+
"\n"
|
|
157
|
+
"function resumeFixerAgent(agentId, task, context) {\n"
|
|
158
|
+
" if (task === 'verify-commit') {\n"
|
|
159
|
+
" return doVerify(agentId, context)\n"
|
|
160
|
+
" }\n"
|
|
161
|
+
" if (task === 'commit') {\n"
|
|
162
|
+
" return doCommit(agentId, context)\n"
|
|
163
|
+
" }\n"
|
|
164
|
+
" return doRecovery(agentId, context)\n"
|
|
165
|
+
"}\n"
|
|
166
|
+
)
|
|
167
|
+
assert check_js_resume_task_enumeration_coverage(source, _MJS_PATH) == []
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def should_flag_drift_under_oxford_comma_enumeration() -> None:
|
|
171
|
+
source = (
|
|
172
|
+
"/**\n"
|
|
173
|
+
" * Spawn the fixer so each later resume (verify-commit, commit, and\n"
|
|
174
|
+
" * recovery) continues the same session.\n"
|
|
175
|
+
" */\n"
|
|
176
|
+
"async function spawnFixerAgent() {\n"
|
|
177
|
+
" return undefined\n"
|
|
178
|
+
"}\n"
|
|
179
|
+
"\n"
|
|
180
|
+
"function resumeFixerAgent(agentId, task, context) {\n"
|
|
181
|
+
" if (task === 'verify-commit') {\n"
|
|
182
|
+
" return doVerify(agentId, context)\n"
|
|
183
|
+
" }\n"
|
|
184
|
+
" if (task === 'commit') {\n"
|
|
185
|
+
" return doCommit(agentId, context)\n"
|
|
186
|
+
" }\n"
|
|
187
|
+
" if (task === 'recovery') {\n"
|
|
188
|
+
" return doRecovery(agentId, context)\n"
|
|
189
|
+
" }\n"
|
|
190
|
+
" return doBrandNewUndocumented(agentId, context)\n"
|
|
191
|
+
"}\n"
|
|
192
|
+
).replace(
|
|
193
|
+
" return doBrandNewUndocumented(agentId, context)\n",
|
|
194
|
+
" if (task === 'brand-new-undocumented') {\n"
|
|
195
|
+
" return doBrandNewUndocumented(agentId, context)\n"
|
|
196
|
+
" }\n"
|
|
197
|
+
" return doRecovery(agentId, context)\n",
|
|
198
|
+
)
|
|
199
|
+
issues = check_js_resume_task_enumeration_coverage(source, _MJS_PATH)
|
|
200
|
+
assert len(issues) == 1
|
|
201
|
+
assert "brand-new-undocumented" in issues[0]
|
|
202
|
+
assert "spawnFixerAgent" in issues[0]
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def should_not_overrun_into_sibling_on_unbalanced_prose_brace() -> None:
|
|
206
|
+
source = (
|
|
207
|
+
"/**\n"
|
|
208
|
+
" * Spawn the verifier so each later resume (fix-verify) continues the\n"
|
|
209
|
+
" * same session.\n"
|
|
210
|
+
" */\n"
|
|
211
|
+
"async function spawnVerifierAgent() {\n"
|
|
212
|
+
" return undefined\n"
|
|
213
|
+
"}\n"
|
|
214
|
+
"\n"
|
|
215
|
+
"function resumeVerifierAgent(agentId, task, context) {\n"
|
|
216
|
+
" if (task === 'fix-verify') {\n"
|
|
217
|
+
" return run(agentId, 'return JSON shaped like { newSha ...')\n"
|
|
218
|
+
" }\n"
|
|
219
|
+
" return doHardening(agentId, context)\n"
|
|
220
|
+
"}\n"
|
|
221
|
+
"\n"
|
|
222
|
+
"/**\n"
|
|
223
|
+
" * Spawn other so each later resume (stray-leak) continues the session.\n"
|
|
224
|
+
" */\n"
|
|
225
|
+
"async function spawnOtherAgent() {\n"
|
|
226
|
+
" return undefined\n"
|
|
227
|
+
"}\n"
|
|
228
|
+
"\n"
|
|
229
|
+
"function resumeOtherAgent(agentId, task, context) {\n"
|
|
230
|
+
" if (task === 'stray-leak') {\n"
|
|
231
|
+
" return doStray(agentId, context)\n"
|
|
232
|
+
" }\n"
|
|
233
|
+
" return doDefault(agentId, context)\n"
|
|
234
|
+
"}\n"
|
|
235
|
+
)
|
|
236
|
+
issues = check_js_resume_task_enumeration_coverage(source, _MJS_PATH)
|
|
237
|
+
assert issues == []
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def should_not_count_task_dispatch_inside_prompt_string() -> None:
|
|
241
|
+
source = (
|
|
242
|
+
"/**\n"
|
|
243
|
+
" * Spawn the verifier so each later resume (fix-verify) continues the\n"
|
|
244
|
+
" * same session.\n"
|
|
245
|
+
" */\n"
|
|
246
|
+
"async function spawnVerifierAgent() {\n"
|
|
247
|
+
" return undefined\n"
|
|
248
|
+
"}\n"
|
|
249
|
+
"\n"
|
|
250
|
+
"function resumeVerifierAgent(agentId, task, context) {\n"
|
|
251
|
+
" const prompt = `when task === 'gamma-task' is set, do the thing`\n"
|
|
252
|
+
" if (task === 'fix-verify') {\n"
|
|
253
|
+
" return doFix(agentId, prompt)\n"
|
|
254
|
+
" }\n"
|
|
255
|
+
" return doHardening(agentId, context)\n"
|
|
256
|
+
"}\n"
|
|
257
|
+
)
|
|
258
|
+
assert check_js_resume_task_enumeration_coverage(source, _MJS_PATH) == []
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def should_flag_drift_when_task_list_follows_descriptive_parenthetical() -> None:
|
|
262
|
+
source = (
|
|
263
|
+
"/**\n"
|
|
264
|
+
" * Spawn the verifier on resume (re-establishing the session). Each\n"
|
|
265
|
+
" * later resume (alpha-task, beta-task) continues the same session.\n"
|
|
266
|
+
" */\n"
|
|
267
|
+
"async function spawnVerifierAgent() {\n"
|
|
268
|
+
" return undefined\n"
|
|
269
|
+
"}\n"
|
|
270
|
+
"\n"
|
|
271
|
+
"function resumeVerifierAgent(agentId, task, context) {\n"
|
|
272
|
+
" if (task === 'alpha-task') {\n"
|
|
273
|
+
" return doA(agentId, context)\n"
|
|
274
|
+
" }\n"
|
|
275
|
+
" if (task === 'beta-task') {\n"
|
|
276
|
+
" return doB(agentId, context)\n"
|
|
277
|
+
" }\n"
|
|
278
|
+
" if (task === 'gamma-task') {\n"
|
|
279
|
+
" return doG(agentId, context)\n"
|
|
280
|
+
" }\n"
|
|
281
|
+
" return doDefault(agentId, context)\n"
|
|
282
|
+
"}\n"
|
|
283
|
+
)
|
|
284
|
+
issues = check_js_resume_task_enumeration_coverage(source, _MJS_PATH)
|
|
285
|
+
assert len(issues) == 1
|
|
286
|
+
assert "gamma-task" in issues[0]
|
|
287
|
+
assert "spawnVerifierAgent" in issues[0]
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def should_flag_single_word_task_absent_from_mixed_enumeration() -> None:
|
|
291
|
+
source = (
|
|
292
|
+
"/**\n"
|
|
293
|
+
" * Spawn the fixer so each later resume (verify-commit and recovery)\n"
|
|
294
|
+
" * continues the same session.\n"
|
|
295
|
+
" */\n"
|
|
296
|
+
"async function spawnFixerAgent() {\n"
|
|
297
|
+
" return undefined\n"
|
|
298
|
+
"}\n"
|
|
299
|
+
"\n"
|
|
300
|
+
"function resumeFixerAgent(agentId, task, context) {\n"
|
|
301
|
+
" if (task === 'verify-commit') {\n"
|
|
302
|
+
" return doVerify(agentId, context)\n"
|
|
303
|
+
" }\n"
|
|
304
|
+
" if (task === 'commit') {\n"
|
|
305
|
+
" return doCommit(agentId, context)\n"
|
|
306
|
+
" }\n"
|
|
307
|
+
" return doRecovery(agentId, context)\n"
|
|
308
|
+
"}\n"
|
|
309
|
+
)
|
|
310
|
+
issues = check_js_resume_task_enumeration_coverage(source, _MJS_PATH)
|
|
311
|
+
assert len(issues) == 1
|
|
312
|
+
assert "'commit'" in issues[0]
|
|
313
|
+
assert "spawnFixerAgent" in issues[0]
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def should_anchor_enumeration_to_adjacent_jsdoc_only() -> None:
|
|
317
|
+
source = (
|
|
318
|
+
"/**\n"
|
|
319
|
+
" * Earlier helper. The resume (re-entry) path is taken on retry.\n"
|
|
320
|
+
" */\n"
|
|
321
|
+
"function earlierHelper() {\n"
|
|
322
|
+
" return 1\n"
|
|
323
|
+
"}\n"
|
|
324
|
+
"\n"
|
|
325
|
+
"/**\n"
|
|
326
|
+
" * Spawn the verifier so each later resume (fix-verify, repair-verify,\n"
|
|
327
|
+
" * hardening-verify) continues the same session.\n"
|
|
328
|
+
" */\n"
|
|
329
|
+
"async function spawnVerifierAgent() {\n"
|
|
330
|
+
" return undefined\n"
|
|
331
|
+
"}\n"
|
|
332
|
+
"\n"
|
|
333
|
+
"function resumeVerifierAgent(agentId, task, context) {\n"
|
|
334
|
+
" if (task === 'fix-verify') {\n"
|
|
335
|
+
" return doFix(agentId, context)\n"
|
|
336
|
+
" }\n"
|
|
337
|
+
" if (task === 'repair-verify') {\n"
|
|
338
|
+
" return doRepair(agentId, context)\n"
|
|
339
|
+
" }\n"
|
|
340
|
+
" return doHardening(agentId, context)\n"
|
|
341
|
+
"}\n"
|
|
342
|
+
)
|
|
343
|
+
assert check_js_resume_task_enumeration_coverage(source, _MJS_PATH) == []
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def should_not_flag_descriptive_resume_parenthetical() -> None:
|
|
347
|
+
source = (
|
|
348
|
+
"/**\n"
|
|
349
|
+
" * Spawn the repairer agent once per round, establishing its role so\n"
|
|
350
|
+
" * each later resume (re-establishing the session) continues the same\n"
|
|
351
|
+
" * conversation.\n"
|
|
352
|
+
" */\n"
|
|
353
|
+
"async function spawnRepairerAgent() {\n"
|
|
354
|
+
" return undefined\n"
|
|
355
|
+
"}\n"
|
|
356
|
+
"\n"
|
|
357
|
+
"function resumeRepairerAgent(agentId, task, context) {\n"
|
|
358
|
+
" if (task === 'repair-verify') {\n"
|
|
359
|
+
" return doRepair(agentId, context)\n"
|
|
360
|
+
" }\n"
|
|
361
|
+
" return doFallback(agentId, context)\n"
|
|
362
|
+
"}\n"
|
|
363
|
+
)
|
|
364
|
+
assert check_js_resume_task_enumeration_coverage(source, _MJS_PATH) == []
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def should_not_count_task_dispatch_inside_double_quoted_string() -> None:
|
|
368
|
+
source = (
|
|
369
|
+
"/**\n"
|
|
370
|
+
" * Spawn the verifier so each later resume (fix-verify) continues the\n"
|
|
371
|
+
" * same session.\n"
|
|
372
|
+
" */\n"
|
|
373
|
+
"async function spawnVerifierAgent() {\n"
|
|
374
|
+
" return undefined\n"
|
|
375
|
+
"}\n"
|
|
376
|
+
"\n"
|
|
377
|
+
"function resumeVerifierAgent(agentId, task, context) {\n"
|
|
378
|
+
" const note = \"when task === 'leaked-from-dq' the agent acts\"\n"
|
|
379
|
+
" if (task === 'fix-verify') {\n"
|
|
380
|
+
" return doFix(agentId, note)\n"
|
|
381
|
+
" }\n"
|
|
382
|
+
" return doHardening(agentId, context)\n"
|
|
383
|
+
"}\n"
|
|
384
|
+
)
|
|
385
|
+
assert check_js_resume_task_enumeration_coverage(source, _MJS_PATH) == []
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def should_not_count_task_dispatch_inside_single_quoted_string() -> None:
|
|
389
|
+
source = (
|
|
390
|
+
"/**\n"
|
|
391
|
+
" * Spawn the verifier so each later resume (fix-verify) continues the\n"
|
|
392
|
+
" * same session.\n"
|
|
393
|
+
" */\n"
|
|
394
|
+
"async function spawnVerifierAgent() {\n"
|
|
395
|
+
" return undefined\n"
|
|
396
|
+
"}\n"
|
|
397
|
+
"\n"
|
|
398
|
+
"function resumeVerifierAgent(agentId, task, context) {\n"
|
|
399
|
+
" const note = 'handle the task === \"phantom\" edge'\n"
|
|
400
|
+
" if (task === 'fix-verify') {\n"
|
|
401
|
+
" return doFix(agentId, note)\n"
|
|
402
|
+
" }\n"
|
|
403
|
+
" return doHardening(agentId, context)\n"
|
|
404
|
+
"}\n"
|
|
405
|
+
)
|
|
406
|
+
assert check_js_resume_task_enumeration_coverage(source, _MJS_PATH) == []
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def should_still_flag_real_dispatch_after_lone_backtick_in_quoted_string() -> None:
|
|
410
|
+
source = (
|
|
411
|
+
"/**\n"
|
|
412
|
+
" * Spawn the verifier so each later resume (fix-verify) continues the\n"
|
|
413
|
+
" * same session.\n"
|
|
414
|
+
" */\n"
|
|
415
|
+
"async function spawnVerifierAgent() {\n"
|
|
416
|
+
" return undefined\n"
|
|
417
|
+
"}\n"
|
|
418
|
+
"\n"
|
|
419
|
+
"function resumeVerifierAgent(agentId, task, context) {\n"
|
|
420
|
+
" const note = \"use the ` char in markdown\"\n"
|
|
421
|
+
" if (task === 'fix-verify') {\n"
|
|
422
|
+
" return doFix(agentId, note)\n"
|
|
423
|
+
" }\n"
|
|
424
|
+
" if (task === 'undocumented-leak') {\n"
|
|
425
|
+
" return doLeak(agentId, context)\n"
|
|
426
|
+
" }\n"
|
|
427
|
+
" return doHardening(agentId, context)\n"
|
|
428
|
+
"}\n"
|
|
429
|
+
)
|
|
430
|
+
issues = check_js_resume_task_enumeration_coverage(source, _MJS_PATH)
|
|
431
|
+
assert len(issues) == 1
|
|
432
|
+
assert "undocumented-leak" in issues[0]
|
|
433
|
+
assert "spawnVerifierAgent" in issues[0]
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def should_still_flag_real_dispatch_after_template_literal_ending_in_escaped_backslash() -> None:
|
|
437
|
+
source = (
|
|
438
|
+
"/**\n"
|
|
439
|
+
" * Spawn the verifier so each later resume (fix-verify) continues the\n"
|
|
440
|
+
" * same session.\n"
|
|
441
|
+
" */\n"
|
|
442
|
+
"async function spawnVerifierAgent() {\n"
|
|
443
|
+
" return undefined\n"
|
|
444
|
+
"}\n"
|
|
445
|
+
"\n"
|
|
446
|
+
"function resumeVerifierAgent(agentId, task, context) {\n"
|
|
447
|
+
" const note = `a path ending in a backslash C:\\\\`\n"
|
|
448
|
+
" if (task === 'fix-verify') {\n"
|
|
449
|
+
" return doFix(agentId, note)\n"
|
|
450
|
+
" }\n"
|
|
451
|
+
" if (task === 'undocumented-leak') {\n"
|
|
452
|
+
" return doLeak(agentId, context)\n"
|
|
453
|
+
" }\n"
|
|
454
|
+
" return doHardening(agentId, context)\n"
|
|
455
|
+
"}\n"
|
|
456
|
+
)
|
|
457
|
+
issues = check_js_resume_task_enumeration_coverage(source, _MJS_PATH)
|
|
458
|
+
assert len(issues) == 1
|
|
459
|
+
assert "undocumented-leak" in issues[0]
|
|
460
|
+
assert "spawnVerifierAgent" in issues[0]
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def should_flag_dispatch_after_brace_inside_line_comment() -> None:
|
|
464
|
+
source = (
|
|
465
|
+
"/**\n"
|
|
466
|
+
" * Spawn the verifier so each later resume (fix-verify) continues the\n"
|
|
467
|
+
" * same session.\n"
|
|
468
|
+
" */\n"
|
|
469
|
+
"async function spawnVerifierAgent() {\n"
|
|
470
|
+
" return undefined\n"
|
|
471
|
+
"}\n"
|
|
472
|
+
"\n"
|
|
473
|
+
"function resumeVerifierAgent(agentId, task, context) {\n"
|
|
474
|
+
" if (task === 'fix-verify') {\n"
|
|
475
|
+
" return doFix(agentId, context)\n"
|
|
476
|
+
" } // close brace } here\n"
|
|
477
|
+
" if (task === 'phantom-comment') {\n"
|
|
478
|
+
" return doPhantom(agentId, context)\n"
|
|
479
|
+
" }\n"
|
|
480
|
+
" return doHardening(agentId, context)\n"
|
|
481
|
+
"}\n"
|
|
482
|
+
)
|
|
483
|
+
issues = check_js_resume_task_enumeration_coverage(source, _MJS_PATH)
|
|
484
|
+
assert len(issues) == 1
|
|
485
|
+
assert "phantom-comment" in issues[0]
|
|
486
|
+
assert "spawnVerifierAgent" in issues[0]
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def should_flag_dispatch_after_brace_inside_regex_literal() -> None:
|
|
490
|
+
source = (
|
|
491
|
+
"/**\n"
|
|
492
|
+
" * Spawn the verifier so each later resume (fix-verify) continues the\n"
|
|
493
|
+
" * same session.\n"
|
|
494
|
+
" */\n"
|
|
495
|
+
"async function spawnVerifierAgent() {\n"
|
|
496
|
+
" return undefined\n"
|
|
497
|
+
"}\n"
|
|
498
|
+
"\n"
|
|
499
|
+
"function resumeVerifierAgent(agentId, task, context) {\n"
|
|
500
|
+
" const pattern = /a}b/\n"
|
|
501
|
+
" if (task === 'fix-verify') {\n"
|
|
502
|
+
" return doFix(agentId, pattern)\n"
|
|
503
|
+
" }\n"
|
|
504
|
+
" if (task === 'phantom-regex') {\n"
|
|
505
|
+
" return doPhantom(agentId, context)\n"
|
|
506
|
+
" }\n"
|
|
507
|
+
" return doHardening(agentId, context)\n"
|
|
508
|
+
"}\n"
|
|
509
|
+
)
|
|
510
|
+
issues = check_js_resume_task_enumeration_coverage(source, _MJS_PATH)
|
|
511
|
+
assert len(issues) == 1
|
|
512
|
+
assert "phantom-regex" in issues[0]
|
|
513
|
+
assert "spawnVerifierAgent" in issues[0]
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def should_flag_dispatch_after_brace_inside_block_comment() -> None:
|
|
517
|
+
source = (
|
|
518
|
+
"/**\n"
|
|
519
|
+
" * Spawn the verifier so each later resume (fix-verify) continues the\n"
|
|
520
|
+
" * same session.\n"
|
|
521
|
+
" */\n"
|
|
522
|
+
"async function spawnVerifierAgent() {\n"
|
|
523
|
+
" return undefined\n"
|
|
524
|
+
"}\n"
|
|
525
|
+
"\n"
|
|
526
|
+
"function resumeVerifierAgent(agentId, task, context) {\n"
|
|
527
|
+
" if (task === 'fix-verify') {\n"
|
|
528
|
+
" return doFix(agentId, context)\n"
|
|
529
|
+
" }\n"
|
|
530
|
+
" /* a stray brace } in a block comment */\n"
|
|
531
|
+
" if (task === 'phantom-block') {\n"
|
|
532
|
+
" return doPhantom(agentId, context)\n"
|
|
533
|
+
" }\n"
|
|
534
|
+
" return doHardening(agentId, context)\n"
|
|
535
|
+
"}\n"
|
|
536
|
+
)
|
|
537
|
+
issues = check_js_resume_task_enumeration_coverage(source, _MJS_PATH)
|
|
538
|
+
assert len(issues) == 1
|
|
539
|
+
assert "phantom-block" in issues[0]
|
|
540
|
+
assert "spawnVerifierAgent" in issues[0]
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def should_flag_dispatch_after_keyword_position_regex_with_stray_brace() -> None:
|
|
544
|
+
source = (
|
|
545
|
+
"/**\n"
|
|
546
|
+
" * Spawn the verifier so each later resume (fix-verify) continues the\n"
|
|
547
|
+
" * same session.\n"
|
|
548
|
+
" */\n"
|
|
549
|
+
"async function spawnVerifierAgent() {\n"
|
|
550
|
+
" return undefined\n"
|
|
551
|
+
"}\n"
|
|
552
|
+
"\n"
|
|
553
|
+
"function resumeVerifierAgent(agentId, task, context) {\n"
|
|
554
|
+
" if (task === 'fix-verify') {\n"
|
|
555
|
+
" return /pat}tern/.test(context)\n"
|
|
556
|
+
" }\n"
|
|
557
|
+
" if (task === 'undocumented-leak') {\n"
|
|
558
|
+
" return doLeak(agentId, context)\n"
|
|
559
|
+
" }\n"
|
|
560
|
+
" return doHardening(agentId, context)\n"
|
|
561
|
+
"}\n"
|
|
562
|
+
)
|
|
563
|
+
issues = check_js_resume_task_enumeration_coverage(source, _MJS_PATH)
|
|
564
|
+
assert len(issues) == 1
|
|
565
|
+
assert "undocumented-leak" in issues[0]
|
|
566
|
+
assert "spawnVerifierAgent" in issues[0]
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def should_flag_dispatch_after_column_zero_function_inside_template_literal() -> None:
|
|
570
|
+
source = (
|
|
571
|
+
"/**\n"
|
|
572
|
+
" * Spawn the verifier so each later resume (fix-verify) continues the\n"
|
|
573
|
+
" * same session.\n"
|
|
574
|
+
" */\n"
|
|
575
|
+
"async function spawnVerifierAgent() {\n"
|
|
576
|
+
" return undefined\n"
|
|
577
|
+
"}\n"
|
|
578
|
+
"\n"
|
|
579
|
+
"function resumeVerifierAgent(agentId, task, context) {\n"
|
|
580
|
+
" const prompt = `do this:\n"
|
|
581
|
+
"function notReal() { return 1 }\n"
|
|
582
|
+
"then finish`\n"
|
|
583
|
+
" if (task === 'fix-verify') {\n"
|
|
584
|
+
" return doFix(agentId, prompt)\n"
|
|
585
|
+
" }\n"
|
|
586
|
+
" if (task === 'undocumented-leak') {\n"
|
|
587
|
+
" return doLeak(agentId, context)\n"
|
|
588
|
+
" }\n"
|
|
589
|
+
" return doHardening(agentId, context)\n"
|
|
590
|
+
"}\n"
|
|
591
|
+
)
|
|
592
|
+
issues = check_js_resume_task_enumeration_coverage(source, _MJS_PATH)
|
|
593
|
+
assert len(issues) == 1
|
|
594
|
+
assert "undocumented-leak" in issues[0]
|
|
595
|
+
assert "spawnVerifierAgent" in issues[0]
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def should_ignore_presume_parenthetical_before_real_resume_enumeration() -> None:
|
|
599
|
+
source = (
|
|
600
|
+
"/**\n"
|
|
601
|
+
" * We presume (alpha-beta) is fine; each later resume (fix-verify)\n"
|
|
602
|
+
" * continues the same session.\n"
|
|
603
|
+
" */\n"
|
|
604
|
+
"async function spawnVerifierAgent() {\n"
|
|
605
|
+
" return undefined\n"
|
|
606
|
+
"}\n"
|
|
607
|
+
"\n"
|
|
608
|
+
"function resumeVerifierAgent(agentId, task, context) {\n"
|
|
609
|
+
" if (task === 'fix-verify') {\n"
|
|
610
|
+
" return doFix(agentId, context)\n"
|
|
611
|
+
" }\n"
|
|
612
|
+
" if (task === 'undocumented') {\n"
|
|
613
|
+
" return doUndoc(agentId, context)\n"
|
|
614
|
+
" }\n"
|
|
615
|
+
" return doHardening(agentId, context)\n"
|
|
616
|
+
"}\n"
|
|
617
|
+
)
|
|
618
|
+
issues = check_js_resume_task_enumeration_coverage(source, _MJS_PATH)
|
|
619
|
+
assert len(issues) == 1
|
|
620
|
+
assert "'undocumented'" in issues[0]
|
|
621
|
+
assert "fix-verify" not in issues[0]
|
|
622
|
+
assert "spawnVerifierAgent" in issues[0]
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
def should_not_flag_single_hyphenated_word_descriptive_parenthetical() -> None:
|
|
626
|
+
for each_descriptive_word in ("re-entry", "fast-forward", "pre-flight", "no-op", "auto-resume"):
|
|
627
|
+
source = (
|
|
628
|
+
"/**\n"
|
|
629
|
+
f" * Spawn the verifier so each later resume ({each_descriptive_word})\n"
|
|
630
|
+
" * continues the same session.\n"
|
|
631
|
+
" */\n"
|
|
632
|
+
"async function spawnVerifierAgent() {\n"
|
|
633
|
+
" return undefined\n"
|
|
634
|
+
"}\n"
|
|
635
|
+
"\n"
|
|
636
|
+
"function resumeVerifierAgent(agentId, task, context) {\n"
|
|
637
|
+
" if (task === 'repair-verify') {\n"
|
|
638
|
+
" return doRepair(agentId, context)\n"
|
|
639
|
+
" }\n"
|
|
640
|
+
" return doHardening(agentId, context)\n"
|
|
641
|
+
"}\n"
|
|
642
|
+
)
|
|
643
|
+
assert check_js_resume_task_enumeration_coverage(source, _MJS_PATH) == []
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
def should_flag_real_dispatch_when_false_resume_header_precedes_it_in_template_literal() -> None:
|
|
647
|
+
source = (
|
|
648
|
+
"/**\n"
|
|
649
|
+
" * Spawn the verifier so each later resume (fix-verify) continues the\n"
|
|
650
|
+
" * same session.\n"
|
|
651
|
+
" */\n"
|
|
652
|
+
"async function spawnVerifierAgent() {\n"
|
|
653
|
+
" const doc = `see function resumeVerifierAgent( ... ) below`\n"
|
|
654
|
+
" return undefined\n"
|
|
655
|
+
"}\n"
|
|
656
|
+
"\n"
|
|
657
|
+
"function resumeVerifierAgent(agentId, task, context) {\n"
|
|
658
|
+
" if (task === 'fix-verify') {\n"
|
|
659
|
+
" return doFix(agentId, context)\n"
|
|
660
|
+
" }\n"
|
|
661
|
+
" if (task === 'real-leak') {\n"
|
|
662
|
+
" return doLeak(agentId, context)\n"
|
|
663
|
+
" }\n"
|
|
664
|
+
" return doHardening(agentId, context)\n"
|
|
665
|
+
"}\n"
|
|
666
|
+
)
|
|
667
|
+
issues = check_js_resume_task_enumeration_coverage(source, _MJS_PATH)
|
|
668
|
+
assert len(issues) == 1
|
|
669
|
+
assert "real-leak" in issues[0]
|
|
670
|
+
assert "spawnVerifierAgent" in issues[0]
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
def should_flag_real_dispatch_when_false_resume_header_precedes_it_in_comment() -> None:
|
|
674
|
+
source = (
|
|
675
|
+
"/**\n"
|
|
676
|
+
" * Spawn the verifier so each later resume (fix-verify) continues the\n"
|
|
677
|
+
" * same session.\n"
|
|
678
|
+
" */\n"
|
|
679
|
+
"async function spawnVerifierAgent() {\n"
|
|
680
|
+
" // function resumeVerifierAgent( is documented below\n"
|
|
681
|
+
" return undefined\n"
|
|
682
|
+
"}\n"
|
|
683
|
+
"\n"
|
|
684
|
+
"function resumeVerifierAgent(agentId, task, context) {\n"
|
|
685
|
+
" if (task === 'fix-verify') {\n"
|
|
686
|
+
" return doFix(agentId, context)\n"
|
|
687
|
+
" }\n"
|
|
688
|
+
" if (task === 'real-leak') {\n"
|
|
689
|
+
" return doLeak(agentId, context)\n"
|
|
690
|
+
" }\n"
|
|
691
|
+
" return doHardening(agentId, context)\n"
|
|
692
|
+
"}\n"
|
|
693
|
+
)
|
|
694
|
+
issues = check_js_resume_task_enumeration_coverage(source, _MJS_PATH)
|
|
695
|
+
assert len(issues) == 1
|
|
696
|
+
assert "real-leak" in issues[0]
|
|
697
|
+
assert "spawnVerifierAgent" in issues[0]
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
def should_not_count_task_substring_of_longer_identifier() -> None:
|
|
701
|
+
source = (
|
|
702
|
+
"/**\n"
|
|
703
|
+
" * Spawn the verifier so each later resume (fix-verify) continues the\n"
|
|
704
|
+
" * same session.\n"
|
|
705
|
+
" */\n"
|
|
706
|
+
"async function spawnVerifierAgent() {\n"
|
|
707
|
+
" return undefined\n"
|
|
708
|
+
"}\n"
|
|
709
|
+
"\n"
|
|
710
|
+
"function resumeVerifierAgent(agentId, task, context) {\n"
|
|
711
|
+
" if (subtask === 'phantom-sub') {\n"
|
|
712
|
+
" return doSubtask(agentId, context)\n"
|
|
713
|
+
" }\n"
|
|
714
|
+
" if (task === 'fix-verify') {\n"
|
|
715
|
+
" return doFix(agentId, context)\n"
|
|
716
|
+
" }\n"
|
|
717
|
+
" return doHardening(agentId, context)\n"
|
|
718
|
+
"}\n"
|
|
719
|
+
)
|
|
720
|
+
assert check_js_resume_task_enumeration_coverage(source, _MJS_PATH) == []
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
def should_still_flag_real_dispatch_alongside_task_substring_identifier() -> None:
|
|
724
|
+
source = (
|
|
725
|
+
"/**\n"
|
|
726
|
+
" * Spawn the verifier so each later resume (fix-verify) continues the\n"
|
|
727
|
+
" * same session.\n"
|
|
728
|
+
" */\n"
|
|
729
|
+
"async function spawnVerifierAgent() {\n"
|
|
730
|
+
" return undefined\n"
|
|
731
|
+
"}\n"
|
|
732
|
+
"\n"
|
|
733
|
+
"function resumeVerifierAgent(agentId, task, context) {\n"
|
|
734
|
+
" if (lastTask === 'phantom-sub') {\n"
|
|
735
|
+
" return doSubtask(agentId, context)\n"
|
|
736
|
+
" }\n"
|
|
737
|
+
" if (task === 'fix-verify') {\n"
|
|
738
|
+
" return doFix(agentId, context)\n"
|
|
739
|
+
" }\n"
|
|
740
|
+
" if (task === 'real-leak') {\n"
|
|
741
|
+
" return doLeak(agentId, context)\n"
|
|
742
|
+
" }\n"
|
|
743
|
+
" return doHardening(agentId, context)\n"
|
|
744
|
+
"}\n"
|
|
745
|
+
)
|
|
746
|
+
issues = check_js_resume_task_enumeration_coverage(source, _MJS_PATH)
|
|
747
|
+
assert len(issues) == 1
|
|
748
|
+
assert "real-leak" in issues[0]
|
|
749
|
+
assert "phantom-sub" not in issues[0]
|
|
750
|
+
assert "spawnVerifierAgent" in issues[0]
|
|
751
|
+
|
|
752
|
+
|
|
753
|
+
def should_not_flag_shipped_converge_workflow() -> None:
|
|
754
|
+
converge_source = _SHIPPED_CONVERGE_MJS.read_text(encoding="utf-8")
|
|
755
|
+
issues = check_js_resume_task_enumeration_coverage(
|
|
756
|
+
converge_source, "skills/autoconverge/workflow/converge.mjs"
|
|
757
|
+
)
|
|
758
|
+
assert issues == []
|