Skip to content

Commit 0878b60

Browse files
committed
fix(c16): enforce TTFV and workspace-root scan ban
Prevent monorepo recursive inventory stalls with Hook neverApprove, SC16, GrokTurnChecklist scan-hygiene/ttfv, and aligned probes/tests.
1 parent 197b17c commit 0878b60

19 files changed

Lines changed: 460 additions & 25 deletions

File tree

‎changelogs/unreleased.md‎

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

8+
- **C16 TTFV + WorkspaceRootScanBan(PI-20260724-01)**:GrokTurnChecklist 增加 `scan-hygiene` / `ttfv-first-delivery`;Hook `neverApprove` 拦截 monorepo/workspace 根递归 inventory(绝对根路径、`dir /s`、cwd=根时的相对 `-Recurse`/`-Depth`);SC16 与 `17-compliance`/`01-common`/website 消费者对齐;探针与 Hook 一级子路径一致;`test:host-parity` 纳入回归矩阵。
89
- **意图驱动五宿主能力映射**:新增 active `host-capability-routing`、`CapabilityIntentDecisionV1`、`HostLeverCatalogV1`、`OriginalInstructionRefV1`、8-row local catalog 与纯校验器;薄 Rule 保留原指令权威、portable-first、CP/Auto/S01~S07 边界,5 个逻辑宿主/8 个 variant 在证据未知、过期、重复或 MCP absent 时 fail closed。当前 canonical native eligible=0,Phase 1 不新增 MCP primitive;README/website 明确收益、catalog/证据/消费者维护代价与 MCP Tool 升级阈值。
910
- **运行态产物分层存储与索引**:新增内容寻址 partition / immutable manifest / 原子 pointer 公共契约,以及 memory、runtime-state、report 三个 domain adapter;旧 Markdown/files 与 reader 保持,stale/corrupt 自动回退且 query 零写入。memory 10 Tools 不变,仅 additive `indexReceipt/coverage`;status/doctor 使用 compact projection;report 只扫描 allowlisted roots、默认 primary metadata,宽分页通过 `snapshotCursor` 固定 manifest,正文按 pointer/batch lazy hydrate,metadata-only 可用 compact projection。真实 3 进程 ×(5 warmup + 30 measurement)benchmark 的 W1/W3/W4 总读取量减少 91.45% / 90.76% / 98.98%,公共投影 mismatch=0;W2 延迟为观测项。W5 宽查询 smoke accepted:W5A/W5B 总读取下降 81.22% / 82.42%,W5C 全文 0 截断且总读取下降 48.88%,W5D compact 响应体下降 41.92%;token telemetry 不可见,结论仅声明 bytes/latency。
1011
- **Workflow completion Shadow**:新增唯一 completion reducer、九类 owner receipt adapter、受管且原子回读的 task input、bounded derived state、两阶段 report/memory commit sidecar、memory read-time digest 复验、stable task selector、risk ledger、`task verify/risk` 与 `status/doctor/trace --completion`;四类生产假绿和反向身份均 fail closed。

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,22 @@ Platform facts (Grok Build hooks docs): only `PreToolUse` is blocking; passive e
2424
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.
2525
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.
2626
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.
27-
9. Execute **GrokTurnChecklist** every non-trivial turn (PF-165): PC0~PC7 → Intent→Skill mandatory bundle → ContextReadPlan → work/gates → report+memory → honest platform ceiling. Never skip S05/S07/C17 because inject is missing.
27+
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.
2828

2929
## GrokTurnChecklist + Intent→Skill bundle (PF-165)
3030

3131
| Step | Must do |
3232
|------|---------|
3333
| entry-pc0-pc7 | Full PC0~PC7 first; re-emit after compact/resume |
3434
| intent-route | Final route before loading workflow Skills |
35-
| skill-bundle | Non-chat mandatory: `intent` + `compliance` + `user-visible-output-contract` + workflow Skill + `report` + `memory` |
35+
| skill-bundle | Non-chat mandatory: `intent` + `compliance` + `user-visible-output-contract` + workflow Skill + `report` + `memory` (minimal-sufficient; no full Skill encyclopedia preload) |
3636
| context-plan | Bounded plan/receipts only |
37+
| scan-hygiene | **WorkspaceRootScanBan**: no monorepo/workspace-root `Get-ChildItem -Recurse`; bind project path; exclude `node_modules`/`dist` |
38+
| ttfv-first-delivery | **TimeToFirstValueGate**: same user-visible turn delivers scope card OR first findings/conclusion OR hard block (non-chat) |
3739
| work-and-gates | CP/ECR as applicable |
3840
| report-memory | Non-chat write report + memory |
3941
| honest-ceiling | No inject / Stop hard-block / Grok===Codex claims |
4042

41-
Machine source: `scripts/lib/host-parity-scorecard.js` (`GROK_TURN_EXECUTION_CHECKLIST`, `GROK_INTENT_SKILL_BUNDLES`, `repairSteps`). Site doc: `website/docs/intro/host-parity-grok.md`.
43+
Machine source: `scripts/lib/host-parity-scorecard.js` (`GROK_TURN_EXECUTION_CHECKLIST`, `classifyWorkspaceRootScanSample`, `classifyTtfvOmissionSample`, `repairSteps`). Site doc: `website/docs/intro/host-parity-grok.md`.
4244

4345
The plugin is a discovery adapter, not a second rules source. Its passive Hook output must never be presented as kernel-injection evidence. Do not copy `AGENTS.md`, `.agents`, `.grok`, `.codex`, `.claude`, or `.gemini` into a child project.

‎hooks/_runtime/lifecycle-dangerous-command.cjs‎

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ function buildLifecycleDangerousCommandUtils({
44
path,
55
crypto,
66
CONTEXT_ROOT,
7+
WORKSPACE_ROOT,
78
APPROVAL_TTL_MS,
89
DANGEROUS_PATTERNS,
910
getToolName,
@@ -25,12 +26,137 @@ function buildLifecycleDangerousCommandUtils({
2526
.trim()
2627
}
2728

29+
/** Recursive inventory markers (R-02: dir /s must match; avoid \\b before /). */
30+
function commandHasRecursiveInventory(cmd) {
31+
if (/-Recurse/i.test(cmd)) return true
32+
if (/\bdir\s+\/s\b/i.test(cmd)) return true
33+
if (/\b(?:Get-ChildItem|gci)\b/i.test(cmd) && /-\s*Depth\s*[1-9]/i.test(cmd)) return true
34+
// find inventory (not findstr); C16 bans unbounded find at workspace root
35+
if (/\bfind\s+/i.test(cmd) && !/\bfindstr\b/i.test(cmd)) return true
36+
return false
37+
}
38+
39+
function resolveToolCwd(payload) {
40+
const input = (payload && (payload.tool_input || payload.toolInput)) || {}
41+
const raw = input.cwd || input.working_directory || input.workingDirectory || input.workingDir
42+
if (raw && String(raw).trim()) {
43+
try { return path.resolve(String(raw).trim()) } catch { /* fall through */ }
44+
}
45+
try { return path.resolve(CONTEXT_ROOT || '.') } catch { return '' }
46+
}
47+
48+
/**
49+
* True when command names an explicit path segment under workspace root
50+
* e.g. E:\Worker\queuebit or E:\Worker\queuebit\docs (R-04: no trailing slash required).
51+
*/
52+
function commandTargetsWorkspaceChild(cmdLower, rootLower) {
53+
const escaped = rootLower.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
54+
return new RegExp(escaped + '[\\\\/][a-z0-9._-]+', 'i').test(cmdLower)
55+
}
56+
57+
function isPathSwitchToken(tok) {
58+
const t = String(tok || '').replace(/^["']|["']$/g, '')
59+
if (!t) return true
60+
// PowerShell / cmd switches are not project paths (R-02: /s must not count as child)
61+
if (/^-\w/.test(t) || /^\/[a-z0-9?]+$/i.test(t)) return true
62+
if (t === '.' || t === '.\\' || t === './' || t === '..') return true
63+
// bare integers are switch values (e.g. -Depth 3), not paths
64+
if (/^\d+$/.test(t)) return true
65+
return false
66+
}
67+
68+
/** Switches that consume the following token as a value, not a path. */
69+
const VALUE_TAKING_SWITCHES = new Set([
70+
'-depth', '-filter', '-include', '-exclude', '-name', '-literalpath',
71+
'-path', '-file', '-attributes', '-newerthan', '-olderthan'
72+
])
73+
74+
/**
75+
* Relative/project child path token (e.g. queuebit, .\docs, -Path queuebit) — not bare "." / switches.
76+
*/
77+
function commandHasRelativeChildPath(cmd) {
78+
const s = String(cmd || '')
79+
const pathFlag = s.match(/-Path\s+(?:"([^"]+)"|'([^']+)'|(\S+))/i)
80+
if (pathFlag) {
81+
const tok = String(pathFlag[1] || pathFlag[2] || pathFlag[3] || '').replace(/^["']|["']$/g, '')
82+
if (tok && !isPathSwitchToken(tok) && !/^[A-Za-z]:[\\/]*$/.test(tok)) {
83+
return true
84+
}
85+
}
86+
// positional path: Get-ChildItem queuebit -Recurse (skip switches and their values)
87+
const after = s.match(/\b(?:Get-ChildItem|gci|dir)\b([\s\S]*)$/i)
88+
if (!after) return false
89+
const tokens = String(after[1] || '').match(/(?:"[^"]+"|'[^']+'|[^\s]+)/g) || []
90+
for (let i = 0; i < tokens.length; i++) {
91+
const tok = tokens[i].replace(/^["']|["']$/g, '')
92+
if (isPathSwitchToken(tok)) {
93+
if (VALUE_TAKING_SWITCHES.has(tok.toLowerCase()) && i + 1 < tokens.length) {
94+
i += 1 // skip switch value
95+
}
96+
continue
97+
}
98+
// first non-switch token is a path candidate
99+
return /[a-z0-9._-]/i.test(tok)
100+
}
101+
return false
102+
}
103+
104+
/**
105+
* C16 / PI-20260724-01: ban recursive inventory rooted at monorepo/workspace root.
106+
* Allows project-scoped recurse when path contains <workspaceRoot>/<child>...
107+
* R-03: also ban relative recurse when cwd === workspace root and no child path token.
108+
* @param {string} command
109+
* @param {string} workspaceRoot
110+
* @param {{ cwd?: string }} [options]
111+
*/
112+
function isWorkspaceRootRecursiveInventory(command, workspaceRoot, options = {}) {
113+
const cmd = stripApprovalMarker(command)
114+
if (!cmd || !commandHasRecursiveInventory(cmd)) return false
115+
const rootFs = path.resolve(String(workspaceRoot || CONTEXT_ROOT || '').trim() || '.')
116+
if (!rootFs || rootFs.length < 2) return false
117+
const rootLower = rootFs.replace(/[\\/]+$/, '').toLowerCase()
118+
const cmdLower = cmd.replace(/\//g, '\\').toLowerCase()
119+
const rootAlt = rootLower.replace(/\\/g, '/')
120+
const mentionsRoot =
121+
cmdLower.includes(rootLower) ||
122+
cmd.replace(/\\/g, '/').toLowerCase().includes(rootAlt)
123+
124+
if (mentionsRoot) {
125+
if (commandTargetsWorkspaceChild(cmdLower, rootLower)) return false
126+
return true
127+
}
128+
129+
// R-03: cwd is workspace root + recursive inventory without absolute root literal
130+
let cwdResolved = ''
131+
try {
132+
cwdResolved = path.resolve(String(options.cwd || CONTEXT_ROOT || '').trim() || '.').replace(/[\\/]+$/, '')
133+
} catch {
134+
cwdResolved = ''
135+
}
136+
const cwdIsRoot = cwdResolved && cwdResolved.toLowerCase() === rootLower
137+
if (!cwdIsRoot) return false
138+
if (commandHasRelativeChildPath(cmd)) return false
139+
return true
140+
}
141+
28142
function checkDangerousCommand(payload, platform) {
29143
if (!isCommandTool(payload, platform)) return null
30144
const cmd = getCommandText(payload)
31145
const readOnlySearch = /^\s*(?:rg|grep|Select-String)\b/i.test(cmd)
32146
if (readOnlySearch && !/[;&|`$()]/.test(cmd.replace(/["'][^"']*["']/g, ''))) return null
33-
const danger = DANGEROUS_PATTERNS.find(p => p.re.test(stripApprovalMarker(cmd)))
147+
const stripped = stripApprovalMarker(cmd)
148+
const workspaceRoot = WORKSPACE_ROOT || CONTEXT_ROOT
149+
const cwd = resolveToolCwd(payload)
150+
if (isWorkspaceRootRecursiveInventory(stripped, workspaceRoot, { cwd })) {
151+
return {
152+
re: null,
153+
reason: 'Blocked: workspace-root recursive inventory (C16/TTFV/PI-20260724-01); bind project path (Test-Path / list_dir one level)',
154+
neverApprove: true,
155+
command: cmd,
156+
code: 'workspace-root-scan-ban'
157+
}
158+
}
159+
const danger = DANGEROUS_PATTERNS.find(p => p.re.test(stripped))
34160
if (!danger) return null
35161
return { ...danger, command: cmd }
36162
}
@@ -126,6 +252,7 @@ function buildLifecycleDangerousCommandUtils({
126252
isCommandTool,
127253
checkDangerousCommand,
128254
stripApprovalMarker,
255+
isWorkspaceRootRecursiveInventory,
129256
extractApprovalId,
130257
hashDangerousCommand,
131258
pruneDangerousApprovals,

‎hooks/_runtime/lifecycle.cjs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,6 +1071,7 @@ const {
10711071
path,
10721072
crypto,
10731073
CONTEXT_ROOT,
1074+
WORKSPACE_ROOT,
10741075
APPROVAL_TTL_MS,
10751076
DANGEROUS_PATTERNS,
10761077
getToolName,

‎host-projections/AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
- C13:规范资产文件分拆:AI 新建 DevCodex 规范资产 `.md`(instructions / skills / prompts / templates / 规范源等)超 500 行必须拆分(已有文件豁免);业务项目需求、技术方案、报告和正式项目文档不因 C13 强制拆分,按项目自身规范、可读性和用户要求判断
3131
- C14:多任务检查点:≥2 个独立任务:每完成一个追加进度到记忆 + 输出进度快照
3232
- C15:架构质量视角:dev/fix 的需求/问题定义与代码设计须从架构师+平台工程师双视角评估:消费者范围、共享契约边界、模块职责、可扩展性、可维护性、易上手性;模块化只在真实复用者、演进边界或跨模块共享契约存在时成立
33-
- C16:规模判断与批量分批:分析、审查、扫描或批量操作前必须先识别唯一项目/root,并执行 `ProjectArtifactScaleRoutingGate` 的 bounded inventory,按文件数、可解析字节、最大文件、目录集中度、派生产物比例和消费者扩散面决定 `single-pass / batched / sampled+deep-read / blocked`;≥10 文件 mutation 或非 small corpus 必须分批并写 checkpoint,禁止先无界扫描超时后再补分批
33+
- C16:规模判断与批量分批 + 扫描卫生 + TTFV:分析、审查、扫描或批量操作前必须先识别唯一项目/root,并执行 `ProjectArtifactScaleRoutingGate` 的 bounded inventory,按文件数、可解析字节、最大文件、目录集中度、派生产物比例和消费者扩散面决定 `single-pass / batched / sampled+deep-read / blocked`;≥10 文件 mutation 或非 small corpus 必须分批并写 checkpoint,禁止先无界扫描超时后再补分批。**WorkspaceRootScanBan**:项目可知时禁止对 monorepo/workspace 根 `Get-ChildItem -Recurse` / `dir /s`;cwd=workspace 根时禁止无子路径相对递归;inventory 排除 `node_modules`/`dist`。**TimeToFirstValueGate**:非 chat 在 PC0~PC7 与最小 ContextReadPlan 后,同一用户可见回复须交付范围卡/首批结论/明确阻断之一
3434
- C17:过程改进记录:每条非空用户消息先登记中性治理候选,完成合理性评估和上下文归因后再按语义形成 `GovernanceIntakeDecision`;关键词不得作为权威触发/分类依据。用户建议的策略经确认更优,或揭示规范未定义/不完整且可泛化时,必须走 Improvement Intake:将策略写入 `data/process-improvements.md`(优化清单,PI);若同时暴露规范缺口,再联动 `data/pending-fixes.md`(PF)。复合意图逐项 all-of 验证;不得询问是否记录;所有模式命中后都必须显式回执已记录的 `PI-xxx / PF-xxx`
3535
- C18:全模式入口检查不可跳过:同 S07
3636
- C19:确认后前置复审:每次用户明确确认后、进入下一阶段前,必须执行 `PostConfirmationReviewScopeGate`:低风险单文件或纯文案可做轻量复审;高风险、多模块、公共 API/配置、安全能力、package/adapter、文档消费者、控制面或多真相源同步任务必须升级为冻结清单驱动的全面复审;命中控制面 / 多文件联动 / 真相源同步 / 模板-示例-校验链场景必须追加交叉验证,并显式输出结果;若发现阻断性问题,先修正并告知用户,再重新确认;无阻断问题方可推进

‎host-projections/copilot-instructions.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
- C13:规范资产文件分拆:AI 新建 DevCodex 规范资产 `.md`(instructions / skills / prompts / templates / 规范源等)超 500 行必须拆分(已有文件豁免);业务项目需求、技术方案、报告和正式项目文档不因 C13 强制拆分,按项目自身规范、可读性和用户要求判断
3131
- C14:多任务检查点:≥2 个独立任务:每完成一个追加进度到记忆 + 输出进度快照
3232
- C15:架构质量视角:dev/fix 的需求/问题定义与代码设计须从架构师+平台工程师双视角评估:消费者范围、共享契约边界、模块职责、可扩展性、可维护性、易上手性;模块化只在真实复用者、演进边界或跨模块共享契约存在时成立
33-
- C16:规模判断与批量分批:分析、审查、扫描或批量操作前必须先识别唯一项目/root,并执行 `ProjectArtifactScaleRoutingGate` 的 bounded inventory,按文件数、可解析字节、最大文件、目录集中度、派生产物比例和消费者扩散面决定 `single-pass / batched / sampled+deep-read / blocked`;≥10 文件 mutation 或非 small corpus 必须分批并写 checkpoint,禁止先无界扫描超时后再补分批
33+
- C16:规模判断与批量分批 + 扫描卫生 + TTFV:分析、审查、扫描或批量操作前必须先识别唯一项目/root,并执行 `ProjectArtifactScaleRoutingGate` 的 bounded inventory,按文件数、可解析字节、最大文件、目录集中度、派生产物比例和消费者扩散面决定 `single-pass / batched / sampled+deep-read / blocked`;≥10 文件 mutation 或非 small corpus 必须分批并写 checkpoint,禁止先无界扫描超时后再补分批。**WorkspaceRootScanBan**:项目可知时禁止对 monorepo/workspace 根 `Get-ChildItem -Recurse` / `dir /s`;cwd=workspace 根时禁止无子路径相对递归;inventory 排除 `node_modules`/`dist`。**TimeToFirstValueGate**:非 chat 在 PC0~PC7 与最小 ContextReadPlan 后,同一用户可见回复须交付范围卡/首批结论/明确阻断之一
3434
- C17:过程改进记录:每条非空用户消息先登记中性治理候选,完成合理性评估和上下文归因后再按语义形成 `GovernanceIntakeDecision`;关键词不得作为权威触发/分类依据。用户建议的策略经确认更优,或揭示规范未定义/不完整且可泛化时,必须走 Improvement Intake:将策略写入 `data/process-improvements.md`(优化清单,PI);若同时暴露规范缺口,再联动 `data/pending-fixes.md`(PF)。复合意图逐项 all-of 验证;不得询问是否记录;所有模式命中后都必须显式回执已记录的 `PI-xxx / PF-xxx`
3535
- C18:全模式入口检查不可跳过:同 S07
3636
- C19:确认后前置复审:每次用户明确确认后、进入下一阶段前,必须执行 `PostConfirmationReviewScopeGate`:低风险单文件或纯文案可做轻量复审;高风险、多模块、公共 API/配置、安全能力、package/adapter、文档消费者、控制面或多真相源同步任务必须升级为冻结清单驱动的全面复审;命中控制面 / 多文件联动 / 真相源同步 / 模板-示例-校验链场景必须追加交叉验证,并显式输出结果;若发现阻断性问题,先修正并告知用户,再重新确认;无阻断问题方可推进
@@ -78,7 +78,7 @@
7878
## Passive-hook 宿主(Grok)
7979

8080
- Grok 等 passive-hook 宿主:优先 `devcodex grok` Full 入口;禁止把 full-capable 解读为 UserPromptSubmit 已注入 PC0。
81-
- GrokTurnChecklist(可扫):PC0~PC7 → Intent→Skill bundle(非 chat:intent+compliance+user-visible-output-contract+工作流+report+memory)→ 实质任务 → report/memory/台账;不得因无 inject/省 token 省略 S05/S07/C17。
81+
- GrokTurnChecklist(可扫):PC0~PC7 → Intent→Skill bundle(非 chat:intent+compliance+user-visible-output-contract+工作流+report+memory)→ **scan-hygiene(C16 禁 workspace 根 Recurse)** → **TTFV 首轮交付** → 实质任务 → report/memory/台账;不得因无 inject/省 token 省略 S05/S07/C17。
8282

8383
## 按需扩展与故障回退
8484

‎instructions.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ SCV 结果必须写入报告;控制面任务的 ECR-7 必须引用 SCV 证据
412412
| 影响点 | `prod`(默认)| `dev` |
413413
|--------|:------------:|:-----:|
414414
| CP 门控 | 🔴 强制等待用户确认 | 🔴 强制等待用户确认 |
415-
| 合规检查 | 不执行 | 全量 FC1~FC7 + SC1~SC15 + RC1~RC4 + T1~T13 |
415+
| 合规检查 | 不执行 | 全量 FC1~FC7 + SC1~SC16 + RC1~RC4 + T1~T13 |
416416
| 入口检查输出 | 输出 PC0~PC7 基础状态,PC4 标注 N/A | 输出 PC0~PC7,PC4 执行完整规范雷达 |
417417
| 合规状态块 | 不输出 | 输出全量状态块(chat 豁免合规块,但仍须预检查)|
418418
| 安全底线 S01~S06 | 🔴 强制 | 🔴 强制 |

0 commit comments

Comments
 (0)