Skip to content

Commit 30cde4b

Browse files
committed
fix(cli): close global-adapters R1a review gaps
Wire HostParity repair steps to adapter-refresh guidance (source uses global-adapters apply), surface hosts-committed/Grok-failed partial state, and drop the CLI test file from npm pack files.
1 parent 06eb5d9 commit 30cde4b

6 files changed

Lines changed: 181 additions & 53 deletions

File tree

‎changelogs/unreleased.md‎

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

88
- **GlobalOnly 全局 adapter 本地刷新 CLI(R1a)**:新增 `devcodex global-adapters apply [--dry-run] [--json]` 与 `npm run global-adapters:apply*`,从当前包根复用 `applyGlobalHostConfig` 刷新用户级五宿主 adapter(不 pack/不 publish)。nextStep/PC5/audit/V8/doctor/help/website intro 按源码 vs 已发布分流;次选 R1b `npm install -g .`、R2 pack+tarball、R3 `npm update -g`;bare `devcodex update` 仅 workspace。`package.json` files 已纳入 `global-adapters-cli` / guidance 模块,避免 pack 后 require 断裂。**Profile 01/02/04/05/06/07 已同步 R1a~R4**(PI-173)。任务:`bugs/GlobalOnly刷全局副本命令口径漂移修复`。
9+
- **R1a 复审收口**:`host-parity-scorecard` repairSteps / recommendedEntry / userVisibleSummary 接入 `describeGlobalAdapterRefresh*`(源码 → `global-adapters apply`,已发布 → `npm -g`);`global-adapters apply` 在主机事务已提交但 Grok 集成失败时返回 `GLOBAL_ADAPTERS_HOSTS_COMMITTED_GROK_FAILED` + `partialState`;npm pack `files` 不再包含 `test-global-adapters-cli.js`。
910
- **文档站依赖审计加固**:Rspress 提升到 2.0.18,`brace-expansion` 提升到 5.0.8;`test:audit` 改为 fail-closed 结构化审计。当前唯一例外为仅影响 unstable RSC 的 `GHSA-qwww-vcr4-c8h2`,本站静态运行面禁止 RSC 标记,例外于 2026-08-15 到期;任何新 advisory、依赖链变化、RSC 使用或到期都会重新阻断。
1011
- **Grok 兼容导入去重**:Grok 原生会话兼容加载 `~/.claude/settings.json` 时,DevCodex Claude adapter 识别 Grok 的 camelCase payload 与 snake_case 事件并安全跳过重复 lifecycle;Grok 专用 plugin 保持唯一生命周期 owner,消除 `user_prompt_submit` / `stop` hook 报错和双重状态写入。
1112
- **五宿主全局 adapter 运行态修复**:`status`/`doctor` 从 receipt/files 假就绪升级为 `configured/adapterReady/contractStatus/nativeStatus/operationalState/ready/issues` 分层;五个可执行 adapter 均增加有界契约探针,只有原生 CLI 深探针通过才可标记 operational ready。doctor 增加原生版本与 Grok list/inspect、installed Hook contract、MCP initialize。Grok plugin source 改为用户级 runtime + cwd workspace 发现,同名受管 identity 经官方 CLI drain-all 后收敛到唯一 canonical;未知来源 mutation 前阻断,首次安装与多 identity 失败均保留/恢复原注册状态,受管恢复来源可继续收敛。Copilot CLI 按官方用户级目录部署 instructions、Hooks、MCP 与 Skills;Copilot IDE 仍保持独立 instruction-fallback ceiling。

‎package.json‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,6 @@
247247
"scripts/lib/cli-maintenance-commands.js",
248248
"scripts/lib/global-adapter-refresh-guidance.js",
249249
"scripts/lib/global-adapters-cli.js",
250-
"scripts/test-global-adapters-cli.js",
251250
"scripts/lib/host-parity-scorecard.js",
252251
"scripts/lib/parity-unaligned-ledger.js",
253252
"scripts/lib/parity-upgrade-decision.js",

‎scripts/lib/global-adapters-cli.js‎

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -212,9 +212,10 @@ function buildHandler(deps = {}) {
212212
}
213213

214214
const transactionStatus = result?.transaction?.status || (parsed.dryRun ? 'planned' : 'unknown')
215+
const hostsCommitted = transactionStatus === 'committed'
215216
const committed = parsed.dryRun
216217
? transactionStatus === 'planned'
217-
: transactionStatus === 'committed' && !grokIntegrationError
218+
: hostsCommitted && !grokIntegrationError
218219

219220
const payload = {
220221
schemaVersion: 'GlobalAdaptersApplyV1',
@@ -227,6 +228,7 @@ function buildHandler(deps = {}) {
227228
dryRun: parsed.dryRun === true,
228229
planDigest: result?.planDigest || null,
229230
transactionStatus,
231+
hostsCommitted,
230232
hosts: (result?.targets || []).map(target => target.host),
231233
hostResults: (result?.transaction?.hosts || []).map(item => ({
232234
host: item.host,
@@ -239,25 +241,46 @@ function buildHandler(deps = {}) {
239241
primary: guidance.primary,
240242
secondary: guidance.secondary
241243
},
242-
integrations: { grok: grokIntegration }
244+
integrations: { grok: grokIntegration },
245+
partialState: grokIntegrationError && hostsCommitted
246+
? {
247+
schemaVersion: 'GlobalAdaptersPartialStateV1',
248+
hostsTransaction: 'committed',
249+
grokIntegration: 'failed',
250+
note: 'User-global host adapters were written; Grok plugin integration failed afterward.',
251+
nextStep: 'Re-run `devcodex global-adapters apply` after fixing Grok home/plugin registration, or repair Grok registration only; do not assume a full rollback of host files.'
252+
}
253+
: null
243254
}
244255

245256
if (!committed) {
257+
const nextStep = grokIntegrationError && hostsCommitted
258+
? payload.partialState.nextStep
259+
: guidance.nextStepRefresh
260+
const message = grokIntegrationError && hostsCommitted
261+
? `Host adapters committed, but Grok integration failed: ${grokIntegration.error || grokIntegrationError.message}`
262+
: (grokIntegrationError
263+
? (grokIntegration.error || grokIntegrationError.message)
264+
: `Global host configuration ended with ${transactionStatus} status`)
246265
const envelope = createCliFailure(
247266
COMMAND,
248267
grokIntegrationError
249-
? (grokIntegration.errorCode || 'GROK_PLUGIN_INSTALL_FAILED')
268+
? (hostsCommitted
269+
? 'GLOBAL_ADAPTERS_HOSTS_COMMITTED_GROK_FAILED'
270+
: (grokIntegration.errorCode || 'GROK_PLUGIN_INSTALL_FAILED'))
250271
: (transactionStatus === 'partial' ? 'GLOBAL_HOST_CONFIG_PARTIAL' : 'GLOBAL_ADAPTERS_APPLY_FAILED'),
251-
grokIntegrationError
252-
? grokIntegration.error
253-
: `Global host configuration ended with ${transactionStatus} status`,
254-
guidance.nextStepRefresh,
272+
message,
273+
nextStep,
255274
cliMetadata,
256275
payload
257276
)
258277
if (parsed.json) printCliJson(consoleImpl, envelope)
259278
else {
260279
printHuman(payload, parsed.dryRun)
280+
if (payload.partialState) {
281+
consoleImpl.log(c.yellow(` ⚠ Host transaction committed; Grok integration failed.`))
282+
consoleImpl.log(c.dim(` ${payload.partialState.nextStep}`))
283+
}
261284
consoleImpl.log(c.red(` ${envelope.errorCode}: ${envelope.message}`))
262285
}
263286
processImpl.exitCode = 1

‎scripts/lib/host-parity-scorecard.js‎

Lines changed: 83 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,65 @@ const crypto = require('crypto')
1010
const fs = require('fs')
1111
const path = require('path')
1212
const { resolveGlobalHostTarget } = require('./global-host-target.js')
13+
const {
14+
describeGlobalAdapterRefreshForPackageRoot
15+
} = require('./global-adapter-refresh-guidance.js')
16+
17+
const PACKAGE_ROOT = path.join(__dirname, '..', '..')
18+
19+
/**
20+
* Repair command for missing/stale user-global adapters.
21+
* Source checkout → R1a `devcodex global-adapters apply`; published package → npm -g.
22+
*/
23+
function resolveAdapterRefreshGuidance(options = {}) {
24+
if (options.guidance && typeof options.guidance === 'object') return options.guidance
25+
const packageRoot = options.packageRoot
26+
? path.resolve(options.packageRoot)
27+
: PACKAGE_ROOT
28+
return describeGlobalAdapterRefreshForPackageRoot(packageRoot, {
29+
packageVersion: options.packageVersion || null,
30+
fs: options.fs || fs,
31+
path: options.path || path
32+
})
33+
}
34+
35+
function buildCheckRepairCatalog(guidance) {
36+
const command = guidance && guidance.primary
37+
? String(guidance.primary)
38+
: 'devcodex global-adapters apply'
39+
return Object.freeze({
40+
globalKernelAgentsMd: {
41+
check: 'globalKernelAgentsMd',
42+
command,
43+
detail: 'Install/refresh the user-global controlling kernel and stable runtime copy.'
44+
},
45+
globalCodexLifecycleReachable: {
46+
check: 'globalCodexLifecycleReachable',
47+
command,
48+
detail: 'Install/refresh the Codex user-global lifecycle runtime; workspace host directories are not used.'
49+
},
50+
denyAdapterContract: {
51+
check: 'denyAdapterContract',
52+
command,
53+
detail: 'lifecycle-host-adapters must export adaptGrokOutput and decision:deny mapping.'
54+
},
55+
pathObservableCapability: {
56+
check: 'pathObservableCapability',
57+
command,
58+
detail: 'lifecycle-bootstrap-state must treat Grok as path-observable (same band as Codex tool-path).'
59+
},
60+
globalGrokPluginInstalled: {
61+
check: 'globalGrokPluginInstalled',
62+
command,
63+
detail: 'Install/refresh the Grok plugin under the user-global Grok configuration root.'
64+
},
65+
globalGrokPluginConfigured: {
66+
check: 'globalGrokPluginConfigured',
67+
command,
68+
detail: 'Ensure the user-global Grok config registers the managed global plugin.'
69+
}
70+
})
71+
}
1372

1473
/** Scannable always-on checklist for passive-hook hosts (Grok). */
1574
const GROK_TURN_EXECUTION_CHECKLIST = Object.freeze([
@@ -64,39 +123,6 @@ function assertCannotClaimFloor(cannotClaim) {
64123
return { ok: true }
65124
}
66125

67-
const CHECK_REPAIR_CATALOG = Object.freeze({
68-
globalKernelAgentsMd: {
69-
check: 'globalKernelAgentsMd',
70-
command: 'npm install -g devcodex',
71-
detail: 'Install the user-global controlling kernel and stable runtime copy.'
72-
},
73-
globalCodexLifecycleReachable: {
74-
check: 'globalCodexLifecycleReachable',
75-
command: 'npm install -g devcodex',
76-
detail: 'Install the Codex user-global lifecycle runtime; workspace host directories are not used.'
77-
},
78-
denyAdapterContract: {
79-
check: 'denyAdapterContract',
80-
command: 'npm install -g devcodex',
81-
detail: 'lifecycle-host-adapters must export adaptGrokOutput and decision:deny mapping.'
82-
},
83-
pathObservableCapability: {
84-
check: 'pathObservableCapability',
85-
command: 'npm install -g devcodex',
86-
detail: 'lifecycle-bootstrap-state must treat Grok as path-observable (same band as Codex tool-path).'
87-
},
88-
globalGrokPluginInstalled: {
89-
check: 'globalGrokPluginInstalled',
90-
command: 'npm install -g devcodex',
91-
detail: 'Install the Grok plugin under the user-global Grok configuration root.'
92-
},
93-
globalGrokPluginConfigured: {
94-
check: 'globalGrokPluginConfigured',
95-
command: 'npm install -g devcodex',
96-
detail: 'Ensure the user-global Grok config registers the managed global plugin.'
97-
}
98-
})
99-
100126
function resolveGrokIntentSkillBundle(intent) {
101127
const key = String(intent || '').trim().toLowerCase()
102128
if (Object.prototype.hasOwnProperty.call(GROK_INTENT_SKILL_BUNDLES, key)) {
@@ -114,17 +140,20 @@ function resolveGrokIntentSkillBundle(intent) {
114140
}
115141
}
116142

117-
function buildGrokRepairSteps(checks = {}) {
143+
function buildGrokRepairSteps(checks = {}, options = {}) {
144+
const guidance = resolveAdapterRefreshGuidance(options)
145+
const catalog = buildCheckRepairCatalog(guidance)
146+
const fallbackCommand = guidance.primary || 'devcodex global-adapters apply'
118147
const steps = []
119148
for (const [key, ok] of Object.entries(checks || {})) {
120149
if (ok) continue
121-
const catalog = CHECK_REPAIR_CATALOG[key]
122-
if (catalog) {
123-
steps.push({ ...catalog, status: 'failed' })
150+
const entry = catalog[key]
151+
if (entry) {
152+
steps.push({ ...entry, status: 'failed' })
124153
} else {
125154
steps.push({
126155
check: key,
127-
command: 'npm install -g devcodex',
156+
command: fallbackCommand,
128157
detail: `Missing HostParity check: ${key}`,
129158
status: 'failed'
130159
})
@@ -369,10 +398,17 @@ function evaluateGrokHostParity(input = {}) {
369398

370399
// Full requires hardReady + recommendation to use launcher; plain child never auto-Full
371400
const tier = hardReady ? 'full-capable' : 'partial'
372-
const repairSteps = buildGrokRepairSteps(checks)
401+
const refreshGuidance = resolveAdapterRefreshGuidance({
402+
packageRoot: input.packageRoot || PACKAGE_ROOT,
403+
packageVersion: input.packageVersion,
404+
guidance: input.adapterRefreshGuidance,
405+
fs: input.fs,
406+
path: input.path
407+
})
408+
const repairSteps = buildGrokRepairSteps(checks, { guidance: refreshGuidance })
373409
const recommendedEntry = hardReady
374410
? 'devcodex grok # Full evidence: --rules binds the user-global controlling kernel'
375-
: (repairSteps[0] && repairSteps[0].command) || 'npm install -g devcodex'
411+
: (repairSteps[0] && repairSteps[0].command) || refreshGuidance.primary || 'devcodex global-adapters apply'
376412

377413
const cannotClaim = [...MIN_CANNOT_CLAIM]
378414
assertCannotClaimFloor(cannotClaim)
@@ -383,6 +419,7 @@ function evaluateGrokHostParity(input = {}) {
383419
.slice(0, 3)
384420
.map((s) => s.command)
385421
.join(' ; ')
422+
const fallbackFix = refreshGuidance.nextStepShort || refreshGuidance.primary || 'devcodex global-adapters apply'
386423

387424
const scorecard = {
388425
schemaVersion: 'HostParityScorecardV1',
@@ -404,13 +441,15 @@ function evaluateGrokHostParity(input = {}) {
404441
pluginRoot: hasGlobalGrokPlugin ? pluginRoot : null,
405442
scope: 'user-global',
406443
deny,
407-
bootstrap
444+
bootstrap,
445+
adapterRefreshPrimary: refreshGuidance.primary || null,
446+
adapterRefreshSourceCheckout: refreshGuidance.sourceCheckout === true
408447
},
409448
recommendedEntry,
410449
cannotClaim,
411450
userVisibleSummary: hardReady
412451
? 'Grok HostParity: full-capable (PreTool deny + path-observable + kernel). Use `devcodex grok` for Full session evidence. Inject/Stop still Partial. Follow GrokTurnChecklist + Intent→Skill bundle.'
413-
: `Grok HostParity: partial — failed: ${failedChecks.join(', ') || 'unknown'}. Fix: ${repairPreview || 'npm install -g devcodex'}. Then doctor --json hostParity.repairSteps.`
452+
: `Grok HostParity: partial — failed: ${failedChecks.join(', ') || 'unknown'}. Fix: ${repairPreview || fallbackFix}. Then doctor --json hostParity.repairSteps.`
414453
}
415454

416455
scorecard.digest = crypto
@@ -467,7 +506,6 @@ function entryCheckAssistSuffix(options = {}) {
467506
module.exports = {
468507
GROK_TURN_EXECUTION_CHECKLIST,
469508
GROK_INTENT_SKILL_BUNDLES,
470-
CHECK_REPAIR_CATALOG,
471509
MIN_CANNOT_CLAIM,
472510
assertCannotClaimFloor,
473511
evaluateGrokHostParity,
@@ -476,6 +514,8 @@ module.exports = {
476514
composePc4Line,
477515
resolveGrokIntentSkillBundle,
478516
buildGrokRepairSteps,
517+
buildCheckRepairCatalog,
518+
resolveAdapterRefreshGuidance,
479519
formatGrokTurnChecklistMarkdown,
480520
classifyGrokTurnOmissionSample,
481521
classifyWorkspaceRootScanSample,

‎scripts/test-global-adapters-cli.js‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,45 @@ try {
6767
assert.strictEqual(invalid.ok, false)
6868
assert.strictEqual(invalid.errorCode, 'GLOBAL_ADAPTERS_PACKAGE_ROOT_INVALID')
6969
assert.strictEqual(fakeProcess.exitCode, 2)
70+
71+
// R-02: hosts committed + Grok integration failure must expose partialState
72+
const { cmdGlobalAdapters: partialCmd } = buildHandler({
73+
packageRoot: ROOT,
74+
process: fakeProcess,
75+
console: logger,
76+
packageJson: require('../package.json'),
77+
applyGlobalHostConfig: () => ({
78+
planDigest: 'deadbeef',
79+
workspaceHostDirectoriesWritten: false,
80+
targets: [
81+
{ host: 'grok', files: { plugin: path.join(home, 'plugin') } },
82+
{ host: 'codex', files: {} }
83+
],
84+
transaction: {
85+
status: 'committed',
86+
hosts: [
87+
{ host: 'grok', status: 'committed', changed: 1 },
88+
{ host: 'codex', status: 'committed', changed: 1 }
89+
]
90+
},
91+
activeRoot: null
92+
}),
93+
syncGrokWorkspacePluginInstallation: () => {
94+
const err = new Error('simulated grok register failure')
95+
err.code = 'GROK_PLUGIN_INSTALL_FAILED'
96+
throw err
97+
}
98+
})
99+
fakeProcess.exitCode = 0
100+
const partial = partialCmd(['apply', '--json', '--home', home])
101+
assert.strictEqual(partial.ok, false)
102+
assert.strictEqual(partial.errorCode, 'GLOBAL_ADAPTERS_HOSTS_COMMITTED_GROK_FAILED')
103+
assert.strictEqual(partial.details.hostsCommitted, true)
104+
assert.ok(partial.details.partialState)
105+
assert.strictEqual(partial.details.partialState.hostsTransaction, 'committed')
106+
assert.strictEqual(partial.details.partialState.grokIntegration, 'failed')
107+
assert.match(String(partial.nextStep || ''), /global-adapters apply|Grok/)
108+
assert.strictEqual(fakeProcess.exitCode, 1)
70109
} finally {
71110
fs.rmSync(home, { recursive: true, force: true })
72111
}

‎scripts/test-host-parity-scorecard.js‎

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,19 @@ assert.strictEqual(partial.hardReady, false)
9494
assert.strictEqual(partial.tier, 'partial')
9595
assert.ok(Array.isArray(partial.failedChecks) && partial.failedChecks.includes('globalKernelAgentsMd'))
9696
assert.ok(Array.isArray(partial.repairSteps) && partial.repairSteps.length >= 1)
97-
assert.ok(partial.repairSteps.some((s) => /npm install -g devcodex/.test(s.command)))
97+
// R1a: source package root must recommend global-adapters apply (not only npm -g)
98+
assert.ok(
99+
partial.repairSteps.some((s) => /devcodex global-adapters apply/.test(s.command)),
100+
'source HostParity repairSteps must recommend global-adapters apply'
101+
)
102+
assert.ok(
103+
!partial.repairSteps.some((s) => s.status === 'failed' && s.command === 'npm install -g devcodex'),
104+
'source HostParity failed repair must not hardcode npm install -g only'
105+
)
98106
assert.match(partial.userVisibleSummary, /partial|failed/i)
107+
assert.match(partial.userVisibleSummary, /global-adapters apply/)
108+
assert.strictEqual(partial.evidence.adapterRefreshSourceCheckout, true)
109+
assert.match(String(partial.evidence.adapterRefreshPrimary || ''), /global-adapters apply/)
99110
assert.ok(Array.isArray(partial.turnChecklist) && partial.turnChecklist.includes('skill-bundle'))
100111
assert.ok(partial.intentSkillBundles && partial.intentSkillBundles.analyze)
101112

@@ -121,9 +132,24 @@ const regOnly = buildGrokRepairSteps({
121132
globalGrokPluginInstalled: true,
122133
globalGrokPluginConfigured: false
123134
})
124-
assert.ok(regOnly.some((s) => s.check === 'globalGrokPluginConfigured' && /npm install -g devcodex/.test(s.command)))
135+
assert.ok(regOnly.some((s) =>
136+
s.check === 'globalGrokPluginConfigured' && /devcodex global-adapters apply/.test(s.command)
137+
))
125138
assert.ok(regOnly.some((s) => s.check === 'full-session-entry' && s.command === 'devcodex grok'))
126139

140+
// published-package guidance path (no .git) still uses npm -g primary
141+
const publishedRepair = buildGrokRepairSteps(
142+
{ globalKernelAgentsMd: false },
143+
{
144+
guidance: {
145+
primary: 'npm update -g devcodex',
146+
nextStepShort: 'npm update -g devcodex',
147+
sourceCheckout: false
148+
}
149+
}
150+
)
151+
assert.ok(publishedRepair.some((s) => s.status === 'failed' && /npm update -g devcodex/.test(s.command)))
152+
127153
// PF-165 negative: claim full Grok workflow without checklist anchors → thin
128154
assert.strictEqual(
129155
classifyGrokTurnOmissionSample('Grok 完整工作流已执行完毕,任务完成。'),

0 commit comments

Comments
 (0)