Skip to content

Commit b464c27

Browse files
committed
feat(auto): sticky execution mode, loose @rocky match, ExecutionModeV1 inject
Keep executionMode=auto across the same session after @rocky/@devcodex-auto or natural-language auto authorization, instead of resetting every prompt. Loosen alias token boundaries for CJK/punctuation adjacency, inject ExecutionModeV1 on UserPromptSubmit, and clear sticky only on explicit exit or session mismatch. Auto whitelist boundary is unchanged.
1 parent 74c5785 commit b464c27

7 files changed

Lines changed: 238 additions & 11 deletions

File tree

‎agents/devcodex-auto.agent.md‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
name: DevCodex Auto
3-
description: AI 开发规范助手(全自动模式 v1.1)— 显式 @devcodex-auto、全局默认 @rocky、Profile autoAliases 替换别名或明确自然语言 auto 授权 + 白名单路径下自动推进,安全底线仍强制执行。所有规则由 instructions/ 自动注入。
3+
description: AI 开发规范助手(全自动模式 v1.2 sticky)— 显式 @devcodex-auto、全局默认 @rocky、Profile autoAliases 替换别名或明确自然语言 auto 授权;会话 sticky 保持 auto;白名单路径下自动推进,安全底线仍强制执行。
44
tools:
55
- edit
66
- execute
@@ -12,10 +12,12 @@ disable-model-invocation: true
1212

1313
## 全自动模式
1414

15-
`DevCodex Auto` 不是“所有任务都自动推进”,而是 **Auto v1.1 最小闭环**:
15+
`DevCodex Auto` 不是“所有任务都自动推进”,而是 **Auto v1.2 最小闭环(含 Sticky Auto)**:
1616

1717
- **正式入口**:显式 `@devcodex-auto`、全局默认 `@rocky`、项目 Profile `config.json` 的 `extensions.devcodex.autoAliases` 替换别名,或明确自然语言 auto 授权;配置了 `autoAliases` 时该列表替换全局默认别名,空数组表示关闭默认别名;模糊提及、询问 auto 规则、未生效昵称或普通“继续”不算授权
18-
- **hook-enforced 宿主**:仅对白名单路径自动推进;非白名单路径默认回确认模式
18+
- **Sticky Auto**:入口命中后同 session 保持 `executionMode=auto`,后续无别名确认/继续不掉回 confirm;退出词:`退出 auto` / `关闭自动模式` / `exit auto mode` / `切回确认模式`
19+
- **别名匹配**:允许中文/标点贴靠(`请@rocky执行`);`UserPromptSubmit` 注入 `ExecutionModeV1`
20+
- **hook-enforced 宿主**:仅对白名单路径自动推进;非白名单路径默认回确认模式(白名单不因 sticky 扩大)
1921
- **instruction-fallback 宿主**:如 JetBrains / Cursor,只同步规则语义,不承诺 runtime 级硬放行;支持 Hook 的宿主按白名单执行 runtime 放行
2022
- **白名单范围**:DevCodex 治理文件、`.devcodex/` 产物、README 与 auto 专属回归脚本
2123
- **执行契约**:控制面、多批次、预计修改 ≥10 文件或发布前置任务必须先形成 ExecutionContract,并按 `allowedPaths`、`requiredArtifacts`、`validationRoute` 推进

‎hooks/_runtime/lifecycle-bootstrap-state.cjs‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,16 @@ function buildLifecycleBootstrapStateUtils(ctx) {
442442
updatedAt: '',
443443
updatedAtMs: 0
444444
},
445+
stickyAuto: {
446+
active: false,
447+
source: '',
448+
kind: '',
449+
sessionKey: '',
450+
updatedAt: '',
451+
updatedAtMs: 0,
452+
authorityRef: '',
453+
reason: ''
454+
},
445455
cp3Runtime: {},
446456
governanceIntake: emptyGovernanceIntakeState(),
447457
turnLiveness: createTurnLivenessState(),
@@ -504,6 +514,7 @@ function buildLifecycleBootstrapStateUtils(ctx) {
504514
bootstrap: { ...current.bootstrap, ...(saved.bootstrap || {}) },
505515
visible: { ...current.visible, ...(saved.visible || {}) },
506516
stickyProject: { ...current.stickyProject, ...(saved.stickyProject || {}), ...(metaState?.stickyProject || {}) },
517+
stickyAuto: { ...current.stickyAuto, ...(saved.stickyAuto || {}), ...(metaState?.stickyAuto || {}) },
507518
cp3Runtime: { ...current.cp3Runtime, ...(saved.cp3Runtime || {}) },
508519
governanceIntake: normalizeGovernanceIntakeState(saved.governanceIntake),
509520
turnLiveness: normalizeTurnLivenessState(saved.turnLiveness),
@@ -525,6 +536,7 @@ function buildLifecycleBootstrapStateUtils(ctx) {
525536
bootstrap: { ...(state.bootstrap || {}) },
526537
visible: { ...(state.visible || {}) },
527538
stickyProject: { ...(state.stickyProject || {}) },
539+
stickyAuto: { ...(state.stickyAuto || {}) },
528540
dangerousApprovals: { ...(state.dangerousApprovals || {}) }
529541
}
530542
fs.mkdirSync(META_STATE_PATHS.dir, { recursive: true })
@@ -540,6 +552,7 @@ function buildLifecycleBootstrapStateUtils(ctx) {
540552
state.activeProjectSource = previousState?.activeProjectSource || (CONTEXT_PROJECT ? 'context' : '')
541553
state.lastMultiProjectWarningKey = previousState?.lastMultiProjectWarningKey || ''
542554
state.stickyProject = { ...state.stickyProject, ...(previousState?.stickyProject || {}) }
555+
state.stickyAuto = { ...state.stickyAuto, ...(previousState?.stickyAuto || {}) }
543556
state.cp3Runtime = { ...(previousState?.cp3Runtime || {}) }
544557
state.governanceIntake = normalizeGovernanceIntakeState(previousState?.governanceIntake)
545558
state.turnLiveness = normalizeTurnLivenessState(previousState?.turnLiveness)

‎hooks/_runtime/lifecycle-project-target.cjs‎

Lines changed: 133 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -235,11 +235,26 @@ function buildLifecycleProjectTargetUtils({
235235

236236
function hasMentionToken(prompt, alias) {
237237
const escaped = escapeRegExp(alias)
238-
return new RegExp(`(^|\\s)${escaped}(?=$|[\\s,.;:!?,。;:!?])`, 'i').test(prompt)
238+
// Loose token boundary: allow CJK/punctuation adjacency (请@rocky执行 / (@rocky)),
239+
// but reject alias glued to other identifier chars (ok@rocky / @rockyish).
240+
return new RegExp(`(?:^|[^A-Za-z0-9_@])${escaped}(?=$|[^A-Za-z0-9_-])`, 'i').test(String(prompt || ''))
239241
}
240242

241243
const DEFAULT_AUTO_ALIASES = ['@rocky']
242244

245+
function emptyStickyAuto(reason) {
246+
return {
247+
active: false,
248+
source: '',
249+
kind: '',
250+
sessionKey: '',
251+
updatedAt: '',
252+
updatedAtMs: 0,
253+
authorityRef: '',
254+
reason: reason || ''
255+
}
256+
}
257+
243258
function getConfiguredAutoAliases(state, target) {
244259
if (typeof readProjectProfileConfig !== 'function') return DEFAULT_AUTO_ALIASES.slice()
245260
const activeProject = target?.activeProject || state?.activeProject || ''
@@ -260,23 +275,128 @@ function buildLifecycleProjectTargetUtils({
260275
return validAliases
261276
}
262277

263-
function hasAutoAuthorizationPrompt(prompt, state, target) {
264-
if (hasMentionToken(String(prompt || ''), '@devcodex-auto')) return true
278+
function resolveAutoAuthorization(prompt, state, target) {
279+
const text = String(prompt || '')
280+
if (hasMentionToken(text, '@devcodex-auto')) {
281+
return { authorized: true, source: '@devcodex-auto', kind: 'explicit' }
282+
}
265283
for (const alias of getConfiguredAutoAliases(state, target)) {
266-
if (hasMentionToken(String(prompt || ''), alias)) return true
284+
if (hasMentionToken(text, alias)) {
285+
return { authorized: true, source: alias, kind: 'alias' }
286+
}
267287
}
268-
const normalized = String(prompt || '').replace(/\s+/g, ' ').trim()
288+
const normalized = text.replace(/\s+/g, ' ').trim()
269289
const naturalLanguageAutoPatterns = [
270290
/(?:进入|启用|开启|使用|切换到)\s*(?:auto|自动|全自动)\s*(?:模式|执行|推进|处理)?/i,
271291
/(?:auto|自动|全自动)\s*(?:模式)?\s*(?:开始|继续|执行|推进|处理|修复|实施)/i,
272292
/(?:run|continue|proceed)\s+(?:in\s+)?auto\s+mode/i
273293
]
274-
return naturalLanguageAutoPatterns.some(pattern => pattern.test(normalized))
294+
if (naturalLanguageAutoPatterns.some(pattern => pattern.test(normalized))) {
295+
return { authorized: true, source: 'natural-language', kind: 'nl' }
296+
}
297+
return { authorized: false, source: '', kind: '' }
298+
}
299+
300+
function hasAutoAuthorizationPrompt(prompt, state, target) {
301+
return resolveAutoAuthorization(prompt, state, target).authorized === true
302+
}
303+
304+
function hasAutoExitPrompt(prompt) {
305+
const normalized = String(prompt || '').replace(/\s+/g, ' ').trim()
306+
if (!normalized) return false
307+
const exitPatterns = [
308+
/(?:退出|关闭|停用|结束)\s*(?:auto|自动|全自动)\s*(?:模式)?/i,
309+
/(?:exit|leave|disable|turn\s+off)\s+(?:auto\s+mode|auto)\b/i,
310+
/(?:切回|切换到)\s*确认模式/i
311+
]
312+
return exitPatterns.some(pattern => pattern.test(normalized))
313+
}
314+
315+
function getValidStickyAuto(state, payload) {
316+
const sticky = state?.stickyAuto || {}
317+
if (!sticky.active) return null
318+
const updatedAtMs = Number(sticky.updatedAtMs || 0)
319+
if (!updatedAtMs || Date.now() - updatedAtMs > STICKY_PROJECT_TTL_MS) return null
320+
const currentSessionKey = getPayloadSessionKey(payload)
321+
const stickySessionKey = String(sticky.sessionKey || '').trim()
322+
// Fail only on explicit session mismatch. Allow:
323+
// - both empty (file-scoped sticky for hosts without session ids)
324+
// - sticky has session but current prompt omits it (common host payload gaps)
325+
// Reject when both present and differ.
326+
if (currentSessionKey && stickySessionKey && currentSessionKey !== stickySessionKey) {
327+
return null
328+
}
329+
return sticky
330+
}
331+
332+
function setStickyAuto(state, source, kind, payload) {
333+
if (!state || typeof state !== 'object') return
334+
const sessionKey = getPayloadSessionKey(payload)
335+
const now = Date.now()
336+
const authorityRef = `auto:${source || 'unknown'}:${sessionKey || 'no-session'}:${now}`
337+
state.stickyAuto = {
338+
active: true,
339+
source: source || 'unknown',
340+
kind: kind || 'unknown',
341+
sessionKey,
342+
updatedAt: new Date().toISOString(),
343+
updatedAtMs: now,
344+
authorityRef,
345+
reason: ''
346+
}
347+
}
348+
349+
function clearStickyAuto(state, reason) {
350+
if (!state || typeof state !== 'object') return
351+
state.stickyAuto = emptyStickyAuto(reason || '')
275352
}
276353

277354
function detectExecutionMode(payload, state, target) {
278355
const prompt = extractUserPrompt(payload)
279-
return hasAutoAuthorizationPrompt(prompt, state, target) ? EXECUTION_MODE.AUTO : EXECUTION_MODE.CONFIRM
356+
if (hasAutoExitPrompt(prompt)) {
357+
clearStickyAuto(state, 'user-exit')
358+
return EXECUTION_MODE.CONFIRM
359+
}
360+
const auth = resolveAutoAuthorization(prompt, state, target)
361+
if (auth.authorized) {
362+
setStickyAuto(state, auth.source, auth.kind, payload)
363+
return EXECUTION_MODE.AUTO
364+
}
365+
const sticky = getValidStickyAuto(state, payload)
366+
if (sticky) {
367+
// Refresh TTL while the same session keeps working under auto.
368+
state.stickyAuto = {
369+
...sticky,
370+
updatedAt: new Date().toISOString(),
371+
updatedAtMs: Date.now(),
372+
reason: ''
373+
}
374+
return EXECUTION_MODE.AUTO
375+
}
376+
if (state?.stickyAuto?.active) clearStickyAuto(state, 'sticky-expired')
377+
return EXECUTION_MODE.CONFIRM
378+
}
379+
380+
function buildExecutionModeContextMessage(state) {
381+
const mode = state?.executionMode === EXECUTION_MODE.AUTO ? EXECUTION_MODE.AUTO : EXECUTION_MODE.CONFIRM
382+
const sticky = state?.stickyAuto || {}
383+
const stickyActive = mode === EXECUTION_MODE.AUTO && sticky.active === true
384+
const source = sticky.source || (mode === EXECUTION_MODE.AUTO ? 'prompt' : 'none')
385+
const authority = sticky.authorityRef ? ` authorityRef=${sticky.authorityRef}` : ''
386+
if (mode === EXECUTION_MODE.AUTO) {
387+
return [
388+
`ExecutionModeV1: auto`,
389+
`sticky=${stickyActive ? 'true' : 'false'}`,
390+
`source=${source}${authority}`,
391+
'CP1/CP2/CP3 auto-pass; do not wait for per-gate user confirmation; S01/S03-S07/C01/C10/C18 not waived; auto whitelist boundary unchanged; exit with 退出auto / exit auto mode'
392+
].join(' | ')
393+
}
394+
return [
395+
'ExecutionModeV1: confirm',
396+
'sticky=false',
397+
'source=none',
398+
'confirm mode: wait for explicit CP confirmation'
399+
].join(' | ')
280400
}
281401

282402
function buildMultiProjectBlockMessage() {
@@ -314,7 +434,13 @@ function buildLifecycleProjectTargetUtils({
314434
buildMultiProjectWarningKey,
315435
shouldSuppressMultiProjectWarning,
316436
hasAutoAuthorizationPrompt,
437+
hasAutoExitPrompt,
438+
resolveAutoAuthorization,
439+
getValidStickyAuto,
440+
setStickyAuto,
441+
clearStickyAuto,
317442
detectExecutionMode,
443+
buildExecutionModeContextMessage,
318444
buildMultiProjectBlockMessage
319445
}
320446
}

‎hooks/_runtime/lifecycle.cjs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ const {
350350
setStickyProject,
351351
shouldSuppressMultiProjectWarning,
352352
detectExecutionMode,
353+
buildExecutionModeContextMessage,
353354
buildMultiProjectBlockMessage
354355
} = buildLifecycleProjectTargetUtils({
355356
fs,
@@ -1322,6 +1323,7 @@ async function main() {
13221323
'UserPromptSubmit',
13231324
[
13241325
buildBootstrapMessage(state),
1326+
buildExecutionModeContextMessage(state),
13251327
continuationResolution
13261328
? `TaskResolutionV1 resolved-active: ${continuationResolution.candidate.project}/${continuationResolution.candidate.kind}/${continuationResolution.candidate.displayName}. The name only locates the task; rehydrate identity, sessions, and current bound artifacts before continuing.`
13271329
: '',

‎instructions/01-common.instructions.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ version: 1.15.3
135135
当用户选择 `@devcodex-auto`、全局默认 `@rocky`、Profile `config.json` 的 `extensions.devcodex.autoAliases` 替换别名,或在文本宿主中明确自然语言授权 auto(如“进入 auto 模式执行”“全自动继续”“run in auto mode”)时:
136136

137137
- Auto v1.1 正式入口包括显式 `@devcodex-auto`、全局默认 `@rocky`、项目 Profile 配置的 `extensions.devcodex.autoAliases` 替换别名与明确自然语言 auto 授权;配置了 `autoAliases` 时该列表替换全局默认别名,空数组表示关闭默认别名;模糊提及、追问 auto 规则、普通“继续”或未生效昵称不等价于 auto 授权
138+
- **Sticky Auto**:有效入口命中后会话级保持 `executionMode=auto`(与 sticky 项目同量级 TTL);后续无别名的确认/继续/补充不掉回 confirm;显式 `退出 auto` / `关闭自动模式` / `exit auto mode` / `切回确认模式` 或 sticky 过期/换会话后回到 confirm
139+
- **别名匹配**:允许中文/标点贴靠(`请@rocky执行`);`UserPromptSubmit` 注入 `ExecutionModeV1` 供模型消费;**白名单不因 sticky 扩大**
138140
- 仅在 `hook-enforced` 宿主中,对治理文件 / `.devcodex/` 产物 / README / auto 专属回归脚本等**白名单路径**启用自动推进
139141
- 非白名单路径默认切回确认模式,不承诺“所有源码任务自动执行”
140142
- `instruction-fallback` 宿主(如 JetBrains / Cursor)只保留 auto 规则语义,不承诺 runtime 级行为;支持 Hook 的宿主默认采用 `safety-only`:白名单边界输出提醒,`strict` 模式下才形成 runtime 硬拦截
@@ -232,7 +234,7 @@ version: 1.15.3
232234
| 安全底线 S01~S06 | 🔴 强制(不受 ENV_MODE 影响)| 🔴 强制(不受 ENV_MODE 影响)|
233235
| S07(入口检查强制)| 🔴 致命自修正(`instruction-fallback` 模式自检触发,自动补输出 PC0~PC7 基础状态)| 🔴 致命自修正(`instruction-fallback` 模式自检触发,自动补输出 PC0~PC7 + dev 扩展诊断)|
234236

235-
> **CP 跳过路径**:显式 `@devcodex-auto`、全局默认 `@rocky`、Profile `extensions.devcodex.autoAliases` 替换别名或明确自然语言 auto 授权(如“进入 auto 模式执行”);这是 Agent 级行为,与 ENV_MODE 无关。
237+
> **CP 跳过路径**:显式 `@devcodex-auto`、全局默认 `@rocky`、Profile `extensions.devcodex.autoAliases` 替换别名、明确自然语言 auto 授权,或会话 **Sticky Auto** 仍有效;这是 Agent 级行为,与 ENV_MODE 无关。
236238
237239
## NODE_META 读取规则
238240

‎scripts/test-hooks-runtime.js‎

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,85 @@ function main() {
242242
runBootstrapReads()
243243
const defaultAliasState = JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'))
244244
assert.strictEqual(defaultAliasState.executionMode, 'auto')
245+
assert.strictEqual(defaultAliasState.stickyAuto?.active, true)
246+
assert.strictEqual(defaultAliasState.stickyAuto?.source, '@rocky')
247+
248+
// Sticky Auto: next turn without @rocky stays auto (same session)
249+
cleanState({ mode: 'dev', agent: TEST_AGENT })
250+
const stickyAutoOut1 = run({
251+
hookEventName: 'UserPromptSubmit',
252+
session_id: 'sticky-auto-session',
253+
prompt: '@rocky 开始需求'
254+
})
255+
assert.ok(
256+
/ExecutionModeV1:\s*auto/i.test(String(stickyAutoOut1.systemMessage || '')),
257+
'UserPromptSubmit should inject ExecutionModeV1: auto'
258+
)
259+
let stickyAutoState = JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'))
260+
assert.strictEqual(stickyAutoState.executionMode, 'auto')
261+
run({
262+
hookEventName: 'UserPromptSubmit',
263+
session_id: 'sticky-auto-session',
264+
prompt: '确认'
265+
})
266+
stickyAutoState = JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'))
267+
assert.strictEqual(stickyAutoState.executionMode, 'auto', 'sticky auto must survive follow-up without @rocky')
268+
assert.strictEqual(stickyAutoState.stickyAuto?.active, true)
269+
270+
// Loose CJK adjacency: 请@rocky执行
271+
cleanState({ mode: 'dev', agent: TEST_AGENT })
272+
run({
273+
hookEventName: 'UserPromptSubmit',
274+
session_id: 'cjk-auto-session',
275+
prompt: '请@rocky执行当前需求'
276+
})
277+
const cjkAutoState = JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'))
278+
assert.strictEqual(cjkAutoState.executionMode, 'auto', 'CJK-adjacent @rocky must enter auto')
279+
280+
// Explicit exit auto clears sticky
281+
cleanState({ mode: 'dev', agent: TEST_AGENT })
282+
run({
283+
hookEventName: 'UserPromptSubmit',
284+
session_id: 'exit-auto-session',
285+
prompt: '@rocky 进入任务'
286+
})
287+
run({
288+
hookEventName: 'UserPromptSubmit',
289+
session_id: 'exit-auto-session',
290+
prompt: '退出 auto 模式'
291+
})
292+
const exitAutoState = JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'))
293+
assert.strictEqual(exitAutoState.executionMode, 'confirm')
294+
assert.strictEqual(exitAutoState.stickyAuto?.active, false)
295+
296+
// Sticky survives host payloads that omit session_id after first auto turn
297+
cleanState({ mode: 'dev', agent: TEST_AGENT })
298+
run({
299+
hookEventName: 'UserPromptSubmit',
300+
session_id: 'omit-session-auto',
301+
prompt: '@rocky 启动'
302+
})
303+
run({
304+
hookEventName: 'UserPromptSubmit',
305+
prompt: '继续推进'
306+
})
307+
const omitSessionSticky = JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'))
308+
assert.strictEqual(omitSessionSticky.executionMode, 'auto', 'sticky auto must tolerate missing session_id on follow-up')
309+
310+
// Explicit different session_id drops sticky
311+
cleanState({ mode: 'dev', agent: TEST_AGENT })
312+
run({
313+
hookEventName: 'UserPromptSubmit',
314+
session_id: 'session-a-auto',
315+
prompt: '@rocky 启动'
316+
})
317+
run({
318+
hookEventName: 'UserPromptSubmit',
319+
session_id: 'session-b-auto',
320+
prompt: '继续推进'
321+
})
322+
const crossSessionSticky = JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'))
323+
assert.strictEqual(crossSessionSticky.executionMode, 'confirm', 'different session_id must not inherit sticky auto')
245324

246325
cleanState({
247326
mode: 'dev',

‎skills/cp-gate/SKILL.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ CP 门控**不受 ENV_MODE 影响**。dev/prod 均强制保持 CP1→CP2 顺序
2121
> 当用户选择 `@devcodex-auto`、全局默认 `@rocky`、Profile 配置的 auto 替换别名,或在文本宿主中明确自然语言授权 auto(如“进入 auto 模式执行”“全自动继续”“run in auto mode”)时:
2222
2323
- Auto v1.1 正式入口包括显式 `@devcodex-auto`、全局默认 `@rocky`、项目 Profile `extensions.devcodex.autoAliases` 替换别名与明确自然语言 auto 授权;配置了 `autoAliases` 时该列表替换全局默认别名,空数组表示关闭默认别名;模糊提及、询问 auto 规则、普通“继续”或未生效昵称不等价于 auto 授权
24+
- **Sticky Auto(v1.2)**:有效入口命中后写入会话级 `stickyAuto`(TTL 与 sticky 项目同量级);后续同 session 无别名的“确认/继续/补充”**保持** `executionMode=auto`,直到显式退出(`退出 auto` / `关闭自动模式` / `exit auto mode` / `切回确认模式`)或 sticky 过期/换会话
25+
- **别名匹配**:允许中文/标点贴靠(如 `请@rocky执行`、`(@rocky)`);拒绝与标识符粘连(如 `ok@rocky`)
26+
- **模型可见回执**:`UserPromptSubmit` 注入 `ExecutionModeV1: auto|confirm`(含 sticky/source/authorityRef 与 CP auto-pass 提示);白名单边界**不**因 sticky 扩大
2427
- `hook-enforced` 宿主下,CP 自动通过对白名单路径形成无提醒通过;非白名单路径在默认 `safety-only` 下提醒放行,在 `strict` 模式下回确认模式并硬拦截
2528
- `instruction-fallback` 宿主(如 JetBrains / Cursor)只同步 auto 规则说明,不承诺 runtime 级 CP 行为;支持 Hook 的宿主由 `DEVCODEX_HOOK_ENFORCEMENT` 决定提醒或硬拦截
2629
- `auto:` / `/auto` / profile `executionMode` 不属于本轮正式入口

0 commit comments

Comments
 (0)