claude-dev-env 1.58.0 → 1.60.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/CLAUDE.md +2 -2
- package/_shared/pr-loop/scripts/code_rules_gate.py +36 -3
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +6 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviews_disabled_constants.py +1 -0
- package/_shared/pr-loop/scripts/reviews_disabled.py +12 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +265 -0
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +29 -0
- package/audit-rubrics/category_rubrics/category-b-selector-engine-compat.md +1 -1
- package/audit-rubrics/category_rubrics/category-e-dead-code.md +1 -0
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -1
- package/audit-rubrics/prompts/category-b-selector-engine-compat.md +2 -2
- package/bin/install.mjs +100 -27
- package/bin/install.test.mjs +133 -1
- package/docs/CODE_RULES.md +3 -3
- package/hooks/blocking/code_rules_annotations_length.py +153 -0
- package/hooks/blocking/code_rules_dead_dataclass_field.py +319 -0
- package/hooks/blocking/code_rules_dead_module_constant.py +321 -0
- package/hooks/blocking/code_rules_duplicate_body.py +439 -0
- package/hooks/blocking/code_rules_enforcer.py +190 -21
- package/hooks/blocking/code_rules_magic_values.py +98 -0
- package/hooks/blocking/code_rules_shared.py +41 -0
- package/hooks/blocking/code_rules_typeddict_stub.py +172 -0
- package/hooks/blocking/config/__init__.py +5 -0
- package/hooks/blocking/config/verified_commit_constants.py +106 -0
- package/hooks/blocking/destructive_command_blocker.py +1027 -12
- package/hooks/blocking/hook_prose_detector_consistency.py +150 -0
- package/hooks/blocking/subprocess_budget_completeness.py +380 -0
- package/hooks/blocking/test_code_rules_enforcer_annotations.py +225 -0
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +1 -0
- package/hooks/blocking/test_code_rules_enforcer_cross_skill_duplicate.py +146 -0
- package/hooks/blocking/test_code_rules_enforcer_dead_dataclass_field.py +467 -0
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +188 -0
- package/hooks/blocking/test_code_rules_enforcer_duplicate_body.py +330 -0
- package/hooks/blocking/test_code_rules_enforcer_duplicate_body_hook_routing.py +179 -0
- package/hooks/blocking/test_code_rules_enforcer_magic_slice_bounds.py +133 -0
- package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias.py +415 -0
- package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias_hook_routing.py +156 -0
- package/hooks/blocking/test_destructive_command_blocker.py +622 -3
- package/hooks/blocking/test_hook_prose_detector_consistency.py +265 -0
- package/hooks/blocking/test_subprocess_budget_completeness.py +588 -0
- package/hooks/blocking/test_verdict_directory_write_blocker.py +720 -0
- package/hooks/blocking/test_verification_verdict_store.py +278 -0
- package/hooks/blocking/test_verified_commit_gate.py +368 -0
- package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +131 -0
- package/hooks/blocking/test_verifier_verdict_minter.py +214 -0
- package/hooks/blocking/test_workflow_substitution_slot_blocker.py +242 -0
- package/hooks/blocking/verdict_directory_write_blocker.py +667 -0
- package/hooks/blocking/verification_verdict_store.py +446 -0
- package/hooks/blocking/verified_commit_gate.py +523 -0
- package/hooks/blocking/verified_commit_message_accuracy_blocker.py +152 -0
- package/hooks/blocking/verifier_verdict_minter.py +299 -0
- package/hooks/blocking/workflow_substitution_slot_blocker.py +159 -0
- package/hooks/diagnostic/test_hook_log_extractor.py +3 -3
- package/hooks/hooks.json +58 -1
- package/hooks/hooks_constants/blocking_check_limits.py +1 -0
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +16 -0
- package/hooks/hooks_constants/dead_dataclass_field_constants.py +25 -0
- package/hooks/hooks_constants/dead_module_constant_constants.py +20 -0
- package/hooks/hooks_constants/destructive_command_segment_constants.py +178 -0
- package/hooks/hooks_constants/duplicate_function_body_constants.py +34 -0
- package/hooks/hooks_constants/hook_prose_detector_consistency_constants.py +30 -0
- package/hooks/hooks_constants/precommit_code_rules_gate_constants.py +1 -1
- package/hooks/hooks_constants/subprocess_budget_completeness_constants.py +5 -0
- package/hooks/hooks_constants/workflow_substitution_slot_blocker_constants.py +22 -0
- package/package.json +1 -1
- package/rules/docstring-prose-matches-implementation.md +43 -0
- package/rules/file-global-constants.md +7 -1
- package/rules/hook-prose-matches-detector.md +26 -0
- package/rules/no-cross-skill-duplicate-helpers.md +29 -0
- package/rules/no-inline-destructive-literals.md +11 -0
- package/rules/workflow-substitution-slots.md +7 -0
- package/skills/_shared/pr-loop/scripts/preflight_worktree.py +392 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/preflight_constants.py +70 -0
- package/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +263 -0
- package/skills/autoconverge/SKILL.md +67 -19
- package/skills/autoconverge/reference/closing-report.md +59 -17
- package/skills/autoconverge/reference/convergence.md +7 -3
- package/skills/autoconverge/reference/stop-conditions.md +7 -2
- package/skills/autoconverge/workflow/aggregate_runs.py +371 -0
- package/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +193 -76
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +76 -0
- package/skills/autoconverge/workflow/converge.contract.test.mjs +206 -206
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +265 -0
- package/skills/autoconverge/workflow/converge.mjs +234 -42
- package/skills/autoconverge/workflow/convergence_summary.py +110 -0
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ab1c2d3e4f5a6b7c8.jsonl +2 -0
- package/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +7 -0
- package/skills/autoconverge/workflow/render_report.py +488 -397
- package/skills/autoconverge/workflow/test_aggregate_runs.py +134 -0
- package/skills/autoconverge/workflow/test_convergence_summary.py +132 -0
- package/skills/autoconverge/workflow/test_render_report.py +488 -259
- package/skills/pr-converge/reference/per-tick.md +28 -8
- package/skills/pr-converge/scripts/check_convergence.py +195 -64
- package/skills/pr-converge/scripts/test_check_convergence.py +173 -2
- package/skills/rebase/SKILL.md +2 -4
- package/skills/update/SKILL.md +37 -5
- package/system-prompts/software-engineer.xml +2 -6
- package/hooks/blocking/content_search_to_zoekt_redirector.py +0 -59
- package/hooks/blocking/content_search_zoekt_bash_block_reason.py +0 -25
- package/hooks/blocking/content_search_zoekt_block_payload.py +0 -21
- package/hooks/blocking/content_search_zoekt_indexed_paths.py +0 -24
- package/hooks/blocking/content_search_zoekt_indexed_roots_config.py +0 -131
- package/hooks/blocking/content_search_zoekt_redirect_guidance.py +0 -52
- package/hooks/blocking/test_content_search_to_zoekt_redirector_integration.py +0 -61
- package/hooks/blocking/test_content_search_to_zoekt_redirector_unit.py +0 -92
- package/hooks/blocking/test_content_search_zoekt_indexed_roots_config.py +0 -102
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import { strict as assert } from 'node:assert';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
const workflowDirectory = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const convergeSource = readFileSync(join(workflowDirectory, 'converge.mjs'), 'utf8');
|
|
9
|
+
|
|
10
|
+
function functionBody(functionName) {
|
|
11
|
+
const functionStart = convergeSource.indexOf(`function ${functionName}(`);
|
|
12
|
+
assert.notEqual(functionStart, -1, `expected ${functionName} to exist`);
|
|
13
|
+
const nextFunctionStart = convergeSource.indexOf('\nfunction ', functionStart + 1);
|
|
14
|
+
const functionEnd = nextFunctionStart === -1 ? convergeSource.length : nextFunctionStart;
|
|
15
|
+
return convergeSource.slice(functionStart, functionEnd);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const productionModule = new Function(
|
|
19
|
+
`${functionBody('classifyCopilotOutcome')}\n` +
|
|
20
|
+
`${functionBody('resolveCopilotDown')}\n` +
|
|
21
|
+
'return { classifyCopilotOutcome, resolveCopilotDown };',
|
|
22
|
+
)();
|
|
23
|
+
const { classifyCopilotOutcome, resolveCopilotDown } = productionModule;
|
|
24
|
+
|
|
25
|
+
function copilotResult(overrides) {
|
|
26
|
+
return {
|
|
27
|
+
sha: 'abcdef0',
|
|
28
|
+
clean: false,
|
|
29
|
+
down: false,
|
|
30
|
+
findings: [],
|
|
31
|
+
...overrides,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
test('an out-of-usage Copilot result (down) routes to the down kind', () => {
|
|
36
|
+
const outcome = classifyCopilotOutcome(copilotResult({ clean: true, down: true }));
|
|
37
|
+
assert.equal(outcome.kind, 'down');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('a down Copilot result routes to down even when clean is false', () => {
|
|
41
|
+
const outcome = classifyCopilotOutcome(copilotResult({ clean: false, down: true }));
|
|
42
|
+
assert.equal(outcome.kind, 'down');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('a dead Copilot gate agent retries rather than passing', () => {
|
|
46
|
+
assert.equal(classifyCopilotOutcome(null).kind, 'retry');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('a reachable Copilot gate with no findings and no clean verdict retries', () => {
|
|
50
|
+
const outcome = classifyCopilotOutcome(copilotResult({ clean: false, down: false }));
|
|
51
|
+
assert.equal(outcome.kind, 'retry');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('Copilot findings route to a fix when Copilot is reachable and not down', () => {
|
|
55
|
+
const outcome = classifyCopilotOutcome(
|
|
56
|
+
copilotResult({
|
|
57
|
+
findings: [
|
|
58
|
+
{
|
|
59
|
+
file: 'a.py',
|
|
60
|
+
line: 1,
|
|
61
|
+
severity: 'P1',
|
|
62
|
+
category: 'bug',
|
|
63
|
+
title: 't',
|
|
64
|
+
detail: 'd',
|
|
65
|
+
replyToCommentId: null,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
70
|
+
assert.equal(outcome.kind, 'fix');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('COPILOT_SCHEMA carries a required down field', () => {
|
|
74
|
+
const schemaStart = convergeSource.indexOf('const COPILOT_SCHEMA =');
|
|
75
|
+
const schemaEnd = convergeSource.indexOf('const HEAD_SCHEMA =');
|
|
76
|
+
assert.notEqual(schemaStart, -1, 'expected COPILOT_SCHEMA to exist');
|
|
77
|
+
const schemaSource = convergeSource.slice(schemaStart, schemaEnd);
|
|
78
|
+
assert.match(schemaSource, /down:\s*\{\s*type:\s*'boolean'/);
|
|
79
|
+
assert.match(schemaSource, /required:\s*\[[^\]]*'down'[^\]]*\]/);
|
|
80
|
+
assert.doesNotMatch(
|
|
81
|
+
schemaSource,
|
|
82
|
+
/blocker:/,
|
|
83
|
+
'the Copilot gate no longer surfaces a blocker; a down result carries the outage',
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('the Copilot gate prompt detects an out-of-usage notice and returns a down result', () => {
|
|
88
|
+
const copilotPrompt = functionBody('runCopilotGate');
|
|
89
|
+
assert.match(
|
|
90
|
+
copilotPrompt,
|
|
91
|
+
/quota|out of usage|out-of-usage/i,
|
|
92
|
+
'expected the gate to name the out-of-usage / quota signal',
|
|
93
|
+
);
|
|
94
|
+
assert.match(
|
|
95
|
+
copilotPrompt,
|
|
96
|
+
/down:\s*true/,
|
|
97
|
+
'expected the gate to return down:true on an out-of-usage notice',
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('the step-1 out-of-usage down-detection requires the notice commit_id to start with HEAD', () => {
|
|
102
|
+
const copilotPrompt = functionBody('runCopilotGate');
|
|
103
|
+
const stepOneStart = copilotPrompt.indexOf('`1.');
|
|
104
|
+
assert.notEqual(stepOneStart, -1, 'expected a step-1 instruction in the gate prompt');
|
|
105
|
+
const stepTwoStart = copilotPrompt.indexOf('`2.', stepOneStart);
|
|
106
|
+
assert.notEqual(stepTwoStart, -1, 'expected a step-2 instruction in the gate prompt');
|
|
107
|
+
const stepOneText = copilotPrompt.slice(stepOneStart, stepTwoStart);
|
|
108
|
+
assert.match(
|
|
109
|
+
stepOneText,
|
|
110
|
+
/commit_id starts with \$\{head\}/,
|
|
111
|
+
'expected step 1 to scope the out-of-usage notice to reviews whose commit_id starts with HEAD, matching step 2 and the convergence gate',
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('a Copilot no-show after the poll cap returns a down result rather than a blocker', () => {
|
|
116
|
+
const copilotPrompt = functionBody('runCopilotGate');
|
|
117
|
+
const noReviewStart = copilotPrompt.indexOf('No review after');
|
|
118
|
+
assert.notEqual(noReviewStart, -1, 'expected a no-show branch in the gate prompt');
|
|
119
|
+
const noReviewBranch = copilotPrompt.slice(noReviewStart, noReviewStart + 200);
|
|
120
|
+
assert.match(
|
|
121
|
+
noReviewBranch,
|
|
122
|
+
/down:\s*true/,
|
|
123
|
+
'expected a Copilot no-show after the poll cap to return down:true',
|
|
124
|
+
);
|
|
125
|
+
assert.doesNotMatch(
|
|
126
|
+
noReviewBranch,
|
|
127
|
+
/blocker:/,
|
|
128
|
+
'expected the no-show branch to carry a down result, not a blocker',
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('checkConvergence wires the --copilot-down flag from a copilotDown argument', () => {
|
|
133
|
+
const checkConvergenceBody = functionBody('checkConvergence');
|
|
134
|
+
assert.match(
|
|
135
|
+
checkConvergenceBody,
|
|
136
|
+
/copilotDown \? ' --copilot-down' : ''/,
|
|
137
|
+
'expected checkConvergence to append --copilot-down when copilotDown is set',
|
|
138
|
+
);
|
|
139
|
+
assert.match(
|
|
140
|
+
checkConvergenceBody,
|
|
141
|
+
/\$\{copilotDownFlag\}/,
|
|
142
|
+
'expected the --copilot-down flag to be interpolated into the script invocation',
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('the COPILOT phase routes a down outcome to FINALIZE with the gate bypassed', () => {
|
|
147
|
+
const copilotPhaseStart = convergeSource.indexOf("if (phase === 'COPILOT') {");
|
|
148
|
+
assert.notEqual(copilotPhaseStart, -1, 'expected a COPILOT phase block');
|
|
149
|
+
const downBranchStart = convergeSource.indexOf("copilotOutcome.kind === 'down'", copilotPhaseStart);
|
|
150
|
+
assert.notEqual(downBranchStart, -1, 'expected the COPILOT phase to handle a down outcome');
|
|
151
|
+
const downBranch = convergeSource.slice(downBranchStart, downBranchStart + 400);
|
|
152
|
+
assert.match(downBranch, /copilotDown = true/);
|
|
153
|
+
assert.match(downBranch, /copilotNote =/);
|
|
154
|
+
assert.match(downBranch, /phase = 'FINALIZE'/);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('resolveCopilotDown reports down only for a down outcome', () => {
|
|
158
|
+
assert.equal(resolveCopilotDown({ kind: 'down' }), true);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('resolveCopilotDown clears the bypass for an approved outcome', () => {
|
|
162
|
+
assert.equal(resolveCopilotDown({ kind: 'approved' }), false);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('resolveCopilotDown clears the bypass for a fix outcome carrying findings', () => {
|
|
166
|
+
assert.equal(
|
|
167
|
+
resolveCopilotDown({
|
|
168
|
+
kind: 'fix',
|
|
169
|
+
findings: [
|
|
170
|
+
{
|
|
171
|
+
file: 'a.py',
|
|
172
|
+
line: 1,
|
|
173
|
+
severity: 'P1',
|
|
174
|
+
category: 'bug',
|
|
175
|
+
title: 't',
|
|
176
|
+
detail: 'd',
|
|
177
|
+
replyToCommentId: null,
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
}),
|
|
181
|
+
false,
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('resolveCopilotDown clears the bypass for a retry outcome', () => {
|
|
186
|
+
assert.equal(resolveCopilotDown({ kind: 'retry' }), false);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test('the standards-only Copilot sub-path resets copilotDown before FINALIZE', () => {
|
|
190
|
+
const standardsBranchStart = convergeSource.indexOf(
|
|
191
|
+
'isStandardsOnlyRound(copilotOutcome.findings)',
|
|
192
|
+
);
|
|
193
|
+
assert.notEqual(
|
|
194
|
+
standardsBranchStart,
|
|
195
|
+
-1,
|
|
196
|
+
'expected the COPILOT phase to handle a standards-only Copilot fix outcome',
|
|
197
|
+
);
|
|
198
|
+
const standardsBranch = convergeSource.slice(standardsBranchStart, standardsBranchStart + 600);
|
|
199
|
+
const resetIndex = standardsBranch.indexOf('copilotDown = false');
|
|
200
|
+
const finalizeIndex = standardsBranch.indexOf("phase = 'FINALIZE'");
|
|
201
|
+
assert.notEqual(
|
|
202
|
+
resetIndex,
|
|
203
|
+
-1,
|
|
204
|
+
'expected the standards-only sub-path to reset copilotDown so a recovered Copilot is not bypassed',
|
|
205
|
+
);
|
|
206
|
+
assert.notEqual(finalizeIndex, -1, 'expected the standards-only sub-path to reach FINALIZE');
|
|
207
|
+
assert.ok(
|
|
208
|
+
resetIndex < finalizeIndex,
|
|
209
|
+
'expected copilotDown to be cleared before the transition to FINALIZE',
|
|
210
|
+
);
|
|
211
|
+
assert.match(
|
|
212
|
+
standardsBranch.slice(0, finalizeIndex),
|
|
213
|
+
/copilotNote = null/,
|
|
214
|
+
'expected the standards-only sub-path to clear the stale copilotNote alongside copilotDown',
|
|
215
|
+
);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test('the COPILOT phase recomputes copilotDown from each gate outcome via resolveCopilotDown', () => {
|
|
219
|
+
const copilotPhaseStart = convergeSource.indexOf("if (phase === 'COPILOT') {");
|
|
220
|
+
assert.notEqual(copilotPhaseStart, -1, 'expected a COPILOT phase block');
|
|
221
|
+
const finalizePhaseStart = convergeSource.indexOf(
|
|
222
|
+
"if (phase === 'FINALIZE') {",
|
|
223
|
+
copilotPhaseStart,
|
|
224
|
+
);
|
|
225
|
+
assert.notEqual(finalizePhaseStart, -1, 'expected a FINALIZE phase block after COPILOT');
|
|
226
|
+
const copilotPhase = convergeSource.slice(copilotPhaseStart, finalizePhaseStart);
|
|
227
|
+
assert.match(
|
|
228
|
+
copilotPhase,
|
|
229
|
+
/copilotDown = resolveCopilotDown\(copilotOutcome\)/,
|
|
230
|
+
'expected the COPILOT phase to recompute copilotDown from the current outcome so a recovered Copilot is never bypassed',
|
|
231
|
+
);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test('markReady receives copilotDown so it can opt the unflagged hook out of the Copilot gate', () => {
|
|
235
|
+
const finalizeStart = convergeSource.indexOf("if (phase === 'FINALIZE') {");
|
|
236
|
+
assert.notEqual(finalizeStart, -1, 'expected a FINALIZE phase block');
|
|
237
|
+
const markReadyCall = convergeSource.indexOf('await markReady(', finalizeStart);
|
|
238
|
+
assert.notEqual(markReadyCall, -1, 'expected the FINALIZE phase to call markReady');
|
|
239
|
+
const callSlice = convergeSource.slice(markReadyCall, markReadyCall + 40);
|
|
240
|
+
assert.match(
|
|
241
|
+
callSlice,
|
|
242
|
+
/markReady\(head,\s*copilotDown\)/,
|
|
243
|
+
'expected markReady to receive copilotDown so the mark-ready agent can opt the unflagged hook out of the Copilot gate',
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test('the markReady prompt opts the unflagged convergence hook out of Copilot when copilotDown', () => {
|
|
248
|
+
const markReadyBody = functionBody('markReady');
|
|
249
|
+
assert.match(
|
|
250
|
+
markReadyBody,
|
|
251
|
+
/copilotDown/,
|
|
252
|
+
'expected markReady to branch on copilotDown',
|
|
253
|
+
);
|
|
254
|
+
assert.match(
|
|
255
|
+
markReadyBody,
|
|
256
|
+
/CLAUDE_REVIEWS_DISABLED/,
|
|
257
|
+
'expected the markReady prompt to set CLAUDE_REVIEWS_DISABLED so the unflagged hook re-derives the Copilot bypass',
|
|
258
|
+
);
|
|
259
|
+
assert.match(
|
|
260
|
+
markReadyBody,
|
|
261
|
+
/copilot/,
|
|
262
|
+
'expected the markReady opt-out to name the copilot token',
|
|
263
|
+
);
|
|
264
|
+
});
|
|
265
|
+
|