Skip to content

Commit 321ca57

Browse files
committed
feat(hooks): Grok Stop hard-continue + EnforcementHonesty (R9-R12)
Wire evaluateStopCompletionGate on Stop (Grok/strict hard block), preserve decision:block in adaptGrokOutput, extend control-plane paths for R10, and document Honesty/R12 order. Update host-parity cannotClaim floor and unit tests.
1 parent 352d1bd commit 321ca57

18 files changed

Lines changed: 706 additions & 37 deletions

File tree

‎changelogs/unreleased.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
66
## 当前未发布实现候选
77

8+
- **Grok Stop 完成硬续 + EnforcementHonesty(20260726)**:`lifecycle-stop-gate.cjs` + lifecycle Stop 接线;`adaptGrokOutput`/`blockOutput` 保留 Stop `decision:block`;R9 `pr1-skipped`;R10 扩展 `CONTROL_PLANE_SOURCE_RE`(host-projections/host-parity)并复用 `checkCpGate`(safety-only 披露 `cp2-unconfirmed-write`);compliance/report/dev-default/cp-gate/dev-plan-review Honesty+R12 顺序索引;cannotClaim 改为「无正文/softCap fail-open」;探针 `test-lifecycle-stop-gate.js` + host-adapters/parity 更新。需求:`20260726-grok-stop-enforcement-honesty` B1–B4+B2b。
89
- **工作区 Skill 双层 resolve(S2 / W>G)**:新增 `hooks/_runtime/skill-resolution.cjs`(唯一 Owner):`workspace-namespace` 下 `.devcodex/workspace/skills` 优先于 `~/.agents/skills`,**reserved** skill id 禁止 W 覆盖;同次 digest/content 契约;`profile_skill_plan` / `BundleDecisionV2` 附加 `resolutionPlan` 并改写 selected source identity;Hook 对 workspace-skill 做 managed/control-plane carve-out;CLI `devcodex skill resolve`;`npm run test:skill-resolve`(含 hook 路径矩阵;并入 `test:control-plane`);`validation-manifest` 节点 `skill-resolve` + `skill-resolution-hook-paths` 进入 fast/full/profile-deploy/package-release;apply 目标禁入 W 树。需求:`双层Skill用户优先与意图加载` CP2 v1.2 S2 / CP3。
10+
- **WorkspaceSkillAutoMatch(P0.5 用户可见闭环 / PF-213)**:新增 `hooks/_runtime/workspace-skill-auto-match.cjs`:UserPromptSubmit 按 W skill `name`/`description` 引号触发语/id 匹配 → `resolveSkillRead` → `additionalContext` 注入正文(Claude/Codex 等 inject 宿主);Grok UPS 仍诚实不宣称 inject,但 **Stop `decision:block`** 在回复未遵守 skill 时强制再答一轮(官方 Stop Decision Control);CLI `devcodex skill match <prompt>`;探针 `test-workspace-skill-auto-match.js` / manifest 节点 `skill-auto-match`。禁止仅 CLI resolve 绿冒充「发 test 即生效」。
911
- **SourceExistenceVerificationGate(规范吸纳防复发)**:`spec-absorption` 强制在输出「可吸纳」前对 source-root 做同名/近邻存在性验证;台账 `open/pending absorption` 不得单独成 absorb。`AbsorptionCandidateMatrixV1` 增加 `sourceExistence`;planner 阻断 `unverified` 与 `present/equivalent-covered` 的 absorb;并列「可关账清单」。负向:ledger-status-only。验证:`node scripts/plan-absorption-candidates.js --self-test`、`npm run test:residual-absorption-controls`、V81 needles。
1012
- **ExecutableAbsorption + ProbeNecessity(假吸纳禁止)**:吸纳必须可执行——`enforcementLevel` 不得为 checklist-only/none;每条 absorb 必填 `probeNecessity`(required/conditional/not-required/forbidden、alwaysOnImpact、复杂度与误报)。只写 Skill 正文或「以后再补探针」不得 ready/absorbed。分类器 `classifyExecutableAbsorptionSample`。验证:planner self-test + residual + V81。
1113
- **可执行吸纳主包 A 实施(R1~R4 + T2)**:`ProgressReportFastPath`(skill-gap/compliance/SC16);`TaskPhaseProjectionGate`(cp-gate/user-visible-output);`ClosedArtifactNoReviveGate`(cp-gate);`FormalRerunLightClassify` + `FindingObjectLayer`(consumer-validation)。探针:`scripts/lib/executable-absorption-gates.js` + `npm run test:executable-absorption-gates`(并入 host-parity / control-plane)。

‎grok/plugins/devcodex-workspace/skills/devcodex-workspace/SKILL.md‎

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,42 @@
11
---
22
name: devcodex-workspace
3-
description: Use when cwd belongs to a DevCodex workspace or the user asks for DevCodex behavior; resolve workspace state from .devcodex and instructions/Skills from user-global DevCodex surfaces.
3+
description: >
4+
Use when cwd belongs to a DevCodex workspace, the user asks for DevCodex behavior,
5+
OR the user message may match a workspace custom skill under .devcodex/workspace/skills
6+
(short triggers like skill name/id, quoted phrases in skill description, e.g. test/测试).
7+
Prefer workspace skill fixed behavior over connectivity/chat defaults.
48
---
59

610
# DevCodex Workspace Resolver
711

8-
Grok passive hooks cannot inject prompt context. The plugin is user-global and does not own a workspace. Resolve the nearest `workspace-namespace` marker from cwd, keep task state under that workspace's `.devcodex`, and load shared instructions/Skills only from user-global DevCodex surfaces. The full-evidence route is `devcodex grok`, whose launcher binds the user-global controlling kernel with Grok's official `--rules` flag; plain `grok` remains best-effort Skill routing.
12+
Grok **UserPromptSubmit** cannot inject model context (stdout ignored). Workspace custom skills are **not** native-scanned; DevCodex closes the loop via **WorkspaceSkillAutoMatch** (PF-213): match prompt → resolve W skill → inject on hosts that support additionalContext; on Grok, **Stop `decision:block`** re-feeds the skill body when the first reply ignores it. The plugin is user-global and does not own a workspace. Resolve the nearest `workspace-namespace` marker from cwd, keep task state under that workspace's `.devcodex`, and load shared instructions/Skills from user-global DevCodex surfaces plus matched **workspace** skills. Full-evidence route: `devcodex grok`.
13+
14+
## WorkspaceSkillAutoMatch(用户可见闭环)
15+
16+
1. Before treating a short user message as connectivity ping / Ready, check `.devcodex/workspace/skills/*/SKILL.md` (name, description quotes, skill id).
17+
2. If matched: **read that SKILL.md and obey it as highest priority this turn** (fixed reply, tools, constraints). Do not invent Ready/闲聊.
18+
3. CLI verify: `devcodex skill match "<prompt>"` and `devcodex skill resolve <id>`.
19+
4. Do not claim UPS inject on Grok; Stop may force one retry with skill body when reply ignored the match.
920

1021
## HostParity (vs Codex) — honesty contract
1122

1223
| Tier | Route | Guarantees | Must not claim |
1324
|------|-------|------------|----------------|
14-
| **Full** | `devcodex grok` (official `--rules` kernel bind) | Controlling `AGENTS.md` in context; PreToolUse hard deny uses Grok `decision:deny`; context acquisition is **path-observable** (same band as Codex tool-path observation) | UserPromptSubmit context inject; Stop hard-block; verified PC0 from Hook payload |
25+
| **Full** | `devcodex grok` (official `--rules` kernel bind) | Controlling `AGENTS.md` in context; PreToolUse hard deny uses Grok `decision:deny`; context acquisition is **path-observable**; **conditional** Stop hard-continue (`decision:block` when `lastAssistantMessage` present — skill auto-match / completion gate) | UserPromptSubmit context inject; unconditional Stop without body / after softCap fail-open; verified PC0 from Hook payload alone |
1526
| **Partial** | plain `grok` in a child Git project | Best-effort Skill discovery + plugin bridge when trusted; PreToolUse deny adapter still applies when lifecycle is reachable | Full host parity with Codex; `kernelInjected=true` from passive Hook stdout |
1627

17-
Platform facts (Grok Build hooks docs): only `PreToolUse` is blocking; passive event stdout is ignored for model context. PC5 must report `Partial` unless Full launcher evidence is present. Do not equate Grok with Codex `hook-enforced` bootstrap.
28+
Platform facts (Grok Build hooks docs): `PreToolUse` and `Stop`/`SubagentStop` can hard-control; UserPromptSubmit is passive (no inject). PC5 must report `Partial` unless Full launcher evidence is present. Do not equate Grok with Codex UPS inject bootstrap.
1829

1930
1. Preserve the semantic intent seed before reading project context.
2031
2. Bind the nearest valid `workspace-namespace` root from cwd. Do not require or create workspace `AGENTS.md`, `.agents`, `.grok`, `.codex`, `.claude`, `.gemini`, or `.github`.
2132
3. Resolve the project namespace relative to that workspace and bind runtime state under `.devcodex/<project>/`.
22-
4. Follow the kernel's intent-first route and read only selected files from `~/.agents/skills/` plus the resulting workspace Profile/context plan.
33+
4. Follow the kernel's intent-first route and read only selected files from `~/.agents/skills/` plus the resulting workspace Profile/context plan, **and any WorkspaceSkillAutoMatch hit under `.devcodex/workspace/skills/`**.
2334
5. Use `~/.agents/devcodex/instructions.full.md` only for an explicit fallback; workspace `.devcodex` remains the sole workspace-owned DevCodex surface.
2435
6. Before substantive output, satisfy the parent kernel's visible entry-check (PC0~PC7). Runtime cannot inject that block on Grok; models still own S07 user-visible output.
2536
7. Optional assist: call MCP `profile_compose_entry_check` to obtain a portable PC0~PC7 block, or rely on PreToolUse deny reasons that embed the same template when context acquisition is incomplete.
2637
8. Run `devcodex doctor` / `devcodex status` and read `hostParity` (`HostParityScorecardV1`): `full-capable` means hard path is ready; still use `devcodex grok` for Full session kernel evidence. `partial` lists **failedChecks** and **executable repairSteps** (commands); re-run doctor after each fix.
2738
9. Execute **GrokTurnChecklist** every non-trivial turn (PF-165 + C16/PI-20260724-01): PC0~PC7 → Intent→Skill mandatory bundle → ContextReadPlan → **scan-hygiene** → **TTFV first delivery** → work/gates → report+memory → honest platform ceiling. Never skip S05/S07/C17 because inject is missing.
39+
10. **Workspace skill trigger**: if user message matches a W skill, obey that skill before checklist bulk for chat-like turns.
2840

2941
## GrokTurnChecklist + Intent→Skill bundle (PF-165)
3042

@@ -38,7 +50,7 @@ Platform facts (Grok Build hooks docs): only `PreToolUse` is blocking; passive e
3850
| ttfv-first-delivery | **TimeToFirstValueGate**: same user-visible turn delivers scope card OR first findings/conclusion OR hard block (non-chat) |
3951
| work-and-gates | CP/ECR as applicable |
4052
| report-memory | Non-chat write report + memory |
41-
| honest-ceiling | No inject / Stop hard-block / Grok===Codex claims |
53+
| honest-ceiling | No inject / unconditional Stop hard-block / Grok===Codex claims; Stop is conditional (body + softCap) |
4254

4355
Machine source: `scripts/lib/host-parity-scorecard.js` (`GROK_TURN_EXECUTION_CHECKLIST`, `classifyWorkspaceRootScanSample`, `classifyTtfvOmissionSample`, `repairSteps`). Site doc: `website/docs/intro/host-parity-grok.md`.
4456

‎hooks/_runtime/lifecycle-hook-output.cjs‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,16 @@ function buildLifecycleHookOutput({ env, enforcementMode }) {
9494
if (['stop', 'agentstop', 'subagentstop'].includes(event)) return { decision: 'block', reason: message }
9595
return { continue: true, systemMessage: message }
9696
}
97+
if (platform === 'grok') {
98+
if (event === 'pretooluse' || event === 'permissionrequest') {
99+
return { decision: 'deny', reason: message }
100+
}
101+
// Official Grok Stop Decision Control: decision:block + reason fed back to the model.
102+
if (['stop', 'subagentstop', 'agentstop'].includes(event)) {
103+
return { decision: 'block', reason: message }
104+
}
105+
return { continue: true, systemMessage: message }
106+
}
97107
return toolBlockOutput(eventName, reason, detail)
98108
}
99109

@@ -131,9 +141,11 @@ function buildLifecycleHookOutput({ env, enforcementMode }) {
131141
if (platform === 'copilot') {
132142
return ['pretooluse', 'stop', 'agentstop', 'subagentstop'].includes(event)
133143
}
134-
// Grok Build: only PreToolUse is blocking; all other lifecycle events are passive
135-
// (stdout ignored). Never advertise UserPromptSubmit/Stop hard-block parity with Codex.
136-
if (platform === 'grok') return event === 'pretooluse' || event === 'permissionrequest'
144+
// Grok Build: PreToolUse deny + Stop/SubagentStop block (official Stop Decision Control).
145+
// UserPromptSubmit remains non-hard for inject; UPS inject still not claimed.
146+
if (platform === 'grok') {
147+
return ['pretooluse', 'permissionrequest', 'stop', 'subagentstop', 'agentstop'].includes(event)
148+
}
137149
return ['pretooluse', 'permissionrequest'].includes(event)
138150
}
139151

‎hooks/_runtime/lifecycle-host-adapters.cjs‎

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -152,11 +152,10 @@ function isGrokImportedClaudePayload(host, payload, originalEvent) {
152152
}
153153

154154
/**
155-
* Grok Build official PreToolUse contract:
156-
* allow → { "decision": "allow" }
157-
* deny → { "decision": "deny", "reason": "..." }
158-
* PassivePassive events ignore stdout; never emit blocking decisions there.
159-
* @see ~/.grok/docs/user-guide/10-hooks.md
155+
* Grok Build official contracts (see ~/.grok/docs/user-guide/10-hooks.md):
156+
* PreToolUse allow/deny → { "decision": "allow"|"deny", "reason"? }
157+
* Stop/SubagentStop block → { "decision": "block", "reason": "..." } (fed back to model)
158+
* UserPromptSubmit / other non-tool events: do not claim context inject; strip hard deny there.
160159
*/
161160
function adaptGrokOutput(originalEvent, output) {
162161
const value = output && typeof output === 'object' && !Array.isArray(output) ? { ...output } : { continue: true }
@@ -166,6 +165,7 @@ function adaptGrokOutput(originalEvent, output) {
166165

167166
const event = normalizeEventToken(originalEvent)
168167
const isPreTool = event === 'pretooluse'
168+
const isStop = event === 'stop' || event === 'subagentstop' || event === 'agentstop'
169169
const permission = value.hookSpecificOutput?.permissionDecision
170170
const reason = value.reason
171171
|| value.hookSpecificOutput?.permissionDecisionReason
@@ -188,15 +188,34 @@ function adaptGrokOutput(originalEvent, output) {
188188
return value
189189
}
190190

191-
// PassivePassive events (UserPromptSubmit, Stop, PreCompact, …): stdout is ignored for model
192-
// context. Strip hard-block decisions so we never pretend inject/block worked.
191+
// Stop Decision Control: preserve block so incomplete closure can force another turn.
192+
if (isStop) {
193+
if (value.decision === 'block' || value.decision === 'deny') {
194+
return Object.freeze({
195+
decision: 'block',
196+
reason: String(reason || 'DevCodex requires another agent turn.'),
197+
devcodexGrokEvidenceMode: 'stop-decision-block'
198+
})
199+
}
200+
if (value.decision === 'allow') {
201+
return Object.freeze({ decision: 'allow', devcodexGrokEvidenceMode: 'stop-decision-allow' })
202+
}
203+
// Soft reminder only
204+
const soft = { continue: true, devcodexGrokEvidenceMode: 'stop-soft' }
205+
if (value.systemMessage) soft.systemMessage = value.systemMessage
206+
if (value.hookSpecificOutput && typeof value.hookSpecificOutput === 'object') {
207+
soft.hookSpecificOutput = { ...value.hookSpecificOutput }
208+
}
209+
return Object.freeze(soft)
210+
}
211+
212+
// Other non-tool events: do not claim UPS inject / hard-block parity.
193213
const next = { ...value, continue: true }
194214
delete next.decision
195215
if (next.hookSpecificOutput && typeof next.hookSpecificOutput === 'object') {
196216
const hso = { ...next.hookSpecificOutput }
197217
delete hso.permissionDecision
198218
delete hso.permissionDecisionReason
199-
// Keep systemMessage/additionalContext only as possible UI annotations; do not claim injection.
200219
next.hookSpecificOutput = hso
201220
next.devcodexGrokEvidenceMode = 'passive-hook-no-context-injection'
202221
} else {

0 commit comments

Comments
 (0)