Skip to content

Commit d536d09

Browse files
committed
fix: 补齐任务语言呈现与发布验证消费者
1 parent 6caf1bf commit d536d09

15 files changed

Lines changed: 143 additions & 247 deletions

‎changelogs/releases/v1.20.2.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# v1.20.2 — 意图驱动执行与产物一致性
22

3-
版本日期:2026-09-08。是否已发布以 npm registry、Git tag 与 GitHub Release 为准。
3+
候选日期:2026-09-08。是否已发布以 npm registry、Git tag 与 GitHub Release 为准。
44

55
用户继续原任务、引用其他语言的文本或提供审查材料时,DevCodex 根据结构化意图和已确认的任务偏好继续工作。工具输出、引用里的指令和固定关键词不能自行改写用户意图、回复语言或操作权限。
66

@@ -17,5 +17,6 @@
1717
- 检查优化任务目录,记录有界扫描未覆盖的范围;包含流程简称的正文不能让整个任务跳过检查。
1818
- 实际隔离 Codex CLI 探针核对工具调用、上下文重绑和业务文件;协议探针、自然语言业务结果、模型自述与外部宿主拒绝分别记录。
1919
- 更新用户说明、Profile 功能证据、验证清单和发行包依赖,补充跨会话、跨 generation、缺失回执与错误证据的负向回归。
20+
- 同步公开站点生成数据和运行代次候选日期;更新覆盖率工具链的 brace-expansion 兼容补丁,并补齐移除文本路由后的协议消费者回归。
2021

2122
真实宿主受外部条件限制而未完成的业务验收保持 `UNVERIFIED`,不能从单元测试、协议探针或安装结果推断通过。更新后,已经打开的宿主连接也须单独确认是否加载新运行时。

‎content/duplication-inventory.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"schemaVersion": "ControlContentDuplicationInventoryV1",
3-
"sourceBundleDigest": "0cd04feb20a0440ec4bb4ea78af5dda8c5a2d2495b470dc46e7a71ec6eb7cfc4",
3+
"sourceBundleDigest": "2d01af7b0620191cc4f8697b55961b70a4ba982014ad197479aecc2e7d15c8b4",
44
"thresholds": {
55
"minParagraphChars": 100,
66
"sectionThreshold": 0.94,

‎content/instructions/01a-profile-loading.instructions.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ version: 1.20.2
7979
- 当启用 `workspace-namespace` 且缺少 workspace profile 时,运行时提示必须指向真实路径 `.devcodex/workspace/profile/`。
8080
- 物理项目已绑定但 `.devcodex/<project>/profile/` 整体缺失时必须返回 `PROFILE_MISSING`;只有项目 Profile 根已存在时,单文件缺失才可按 workspace base + project overlay 规则读取 workspace 文件。
8181
- 若已用物理 marker、layout identity 与 allowlist 复证唯一目标项目,后续“继续 / 确认”等消息只有在同一 session(无稳定 session 时为同一 turn)、canonical physicalRoot/activeRoot、layout/root identity、context binding 与 route revision 都未漂移时,才可在短 TTL 内沿用 `ProjectTargetLeaseV2`。session/turn、TTL、marker/layout/root/context/route 任一漂移,命中多个项目、显式目标冲突或用户选择 workspace 时都必须立即撤销并重新判断;V1 仅作 reader compatibility。
82-
- `WorkspaceSessionRouteIndexV1` 只提供项目/task route hint,不提供 CP、artifact、validation 或 mutation authority。完整 `继续<任务名>任务` / `继续 <任务名>` 应先用 `memory_task_resolve` / `TaskResolutionV1` 的 bounded exact resolver 定位项目,再形成该 active namespace 的 ContextReadPlan;该定位阶段不得预读 Profile 正文,且歧义/完成/stale/scale-blocked 不得错误绑定项目、自动 reopen 或恢复写权。
82+
- `WorkspaceSessionRouteIndexV1` 只提供项目/task route hint,不提供 CP、artifact、validation 或 mutation authority。根据完整对话判断用户要续接明确任务后,应先用 `memory_task_resolve` / `TaskResolutionV1` 的 bounded exact resolver 定位项目,再形成该 active namespace 的 ContextReadPlan;不要求固定句式,任务名或稳定 ID 不决定回复语言。该定位阶段不得预读 Profile 正文,且歧义/完成/stale/scale-blocked 不得错误绑定项目、自动 reopen 或恢复写权。
8383

8484
## 项目现实扩展(Project Reality Expansion)
8585

‎content/instructions/01c-intent-expansion.instructions.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ version: 1.20.2
2323

2424
| 检查 | 条件 | 意图 |
2525
|------|------|------|
26-
| 按任务名恢复? | 完整消息符合 `继续<任务名>任务` 或 `继续 <任务名>` | 先用 `memory_task_resolve` 定位;仅 `resolved-active` 进入 `resume` |
27-
| 恢复中断? | 用户说"继续"/"恢复",且今日/昨日任务文件中存在状态为 🔄 的会话 | `resume` → 跳过三问 |
26+
| 按任务名恢复? | 根据完整对话判断用户要续接哪项已有任务,提取任务定位信息 | 先用 `memory_task_resolve` 定位;仅 `resolved-active` 进入 `resume` |
27+
| 恢复中断? | 当前消息在上下文中表达续接目的,且当前会话任务恢复证据有效;短回答或特定词本身不决定路由 | `resume` → 定向复水化,保留已确认范围与授权 |
2828
| 纯问答? | 仅提问/求解释,无文件变更意图 | `chat` → 跳过三问 |
2929

3030
`TaskContinuationIntentGate`:任务名、alias、Hook 命中和派生 index 只负责定位。stable taskId / active displayName / active alias 必须 exact;相似项只提示不自动选中。唯一 active 命中后仍按 `task.json → sessions.md → 当前绑定产物/checkpoint` 定向复证;ambiguous、not-found、completed、rejected、stale-confirmation 或 scale-blocked 均不得静默进入执行。

‎content/instructions/15-memory.instructions.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ daily/SUMMARY 仍是唯一真相源:受管 writer 在文件提交后刷新索
6161

6262
| 场景 | 读取范围 | 执行顺序 |
6363
|------|---------|---------|
64-
| **命名续接 · 首步** | 完整消息为 `继续<任务名>任务` / `继续 <任务名>` 时调用 `memory_task_resolve(name, project?)`;只返回有界 identity/session/CP metadata | 先于通用 resume 查询 |
64+
| **命名续接 · 首步** | 用户意图为续接明确任务时,将任务名或稳定 ID 与项目范围提交给 `memory_task_resolve(name, project?, locale?)`;locale 来自用户意图和已确认语言,任务名不决定语言;只返回有界 identity/session/CP metadata | 先于通用 resume 查询,不要求固定句式 |
6565
| 命名续接 · 唯一 active | 定向读取该任务 `task.json`、`sessions.md`、当前绑定 artifact/checkpoint | resolver 只定位,不替代复水化 |
6666
| **正常会话 · 首步** | `memory_status(limit <= 5)`:今日/昨日 metadata、有限 SUMMARY 行、active 状态与冲突;不返回整文件正文 | 第一读 |
6767
| 正常会话 · 连续性相关 | `memory_summary_query(status: active/unresolved, limit <= 5)` | status 证明需要时再读 |

‎content/skills/memory/SKILL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ status/current/month/day byte-range 分区。该索引不是记忆真相源:
5454

5555
| 场景 | 读取范围 | 执行顺序 |
5656
|------|---------|---------|
57-
| **命名续接 · 首步** | 完整消息为 `继续<任务名>任务` / `继续 <任务名>` 时调用 `memory_task_resolve(name, project?)`;只取 identity/session/CP metadata 与结构化结果 | 先于通用 resume 查询 |
57+
| **命名续接 · 首步** | 用户意图为续接明确任务时,将任务名或稳定 ID 与项目范围提交给 `memory_task_resolve(name, project?, locale?)`;locale 来自当前用户意图和已确认会话语言,不能从任务名字符推断;只取 identity/session/CP metadata 与结构化结果 | 先于通用 resume 查询,不要求固定句式 |
5858
| 命名续接 · 唯一 active | 定向读取该任务 `.memory/task.json`、`.memory/sessions.md`、当前绑定 artifact/checkpoint;执行 SemanticContinuationDiff | resolver 只定位,不替代复水化 |
5959
| **正常会话 · 首步** | `memory_status(limit <= 5)`,只返回今日/昨日 metadata、有限 SUMMARY 行、active 状态与冲突 | 第一读 |
6060
| 正常会话 · 连续性相关 | `memory_summary_query(status: active/unresolved, limit <= 5)` | status 证明需要时再读 |

‎content/skills/portfolio.json‎

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"skillsPattern": "content/skills/*/SKILL.md",
77
"optionalSidecar": "content/skills/*/devcodex.skill.json",
88
"registry": "plugin.json",
9-
"sourceDigest": "e3b0a94d5b0f434995c7a9fcac514f242629f73b12d40e6f8d978739f822360f",
9+
"sourceDigest": "90d516f0dba882fc0b12b46ef7dcc472536a63cfae9d2503a8b8b3a67e20240a",
1010
"sidecarDigest": "774105d7947b340d15d17115fcb8e37b62ae545cf553f06126d529184fd74872",
1111
"pluginDigest": "6ffdd7a19b8b0c09aaab71a6974e4b17fa5c59052f809cf79c0590173d36fced",
1212
"publicTaxonomy": "content/skills/public-taxonomy.json",
@@ -15,8 +15,8 @@
1515
"portfolioEvidenceDigest": "8364a73c66ed2ec9787cdb9300223278d128a45b689a2b8deda0d1f03189f42b",
1616
"consumerInventoryFileCount": 713,
1717
"consumerInventoryDigest": "424ec22febd792e124b514f3a668703849b8d4dee64719c8a7d670f49499c2ca",
18-
"consumerProjectionDigest": "c7fdd9c1aff705e16bbbbd2022aeece58a3c4e2d8f30d6293fd77230b7595ac6",
19-
"portfolioInputDigest": "63b29ee5c7dac4d50418b153b639c89f62d0c6f5d41504a7c2f64356a68c995a"
18+
"consumerProjectionDigest": "888ebbd8cf584f95025396c95e9df15412e81148b8ce7afdabf7758eba7b84b1",
19+
"portfolioInputDigest": "bc5a2bdbccfe1a0d3612bb8d7f2efd88093b58bc0f93f8e4f91d0a6fe0b7ce9f"
2020
},
2121
"ordering": "skills.id asc; graph edges from/to asc",
2222
"summary": {
@@ -11743,8 +11743,8 @@
1174311743
"content/skills/memory/SKILL.md"
1174411744
],
1174511745
"source": "content/skills/memory/SKILL.md",
11746-
"hash": "1d0104d516106b5e6e2a652cb091637f1b5349dbbd8c11a7d5f33cd829cef2e4",
11747-
"sourceBytes": 33822,
11746+
"hash": "badf85e46500adcfb207a68bab41b7e8e634bc2f28725cfea4aacb3b21c0a261",
11747+
"sourceBytes": 33960,
1174811748
"version": "1.20.2",
1174911749
"lifecycleState": "active",
1175011750
"publicCategory": "runtime-governance",
@@ -16936,10 +16936,6 @@
1693616936
"path": "scripts/test-review-execution-contract.js",
1693716937
"role": "current"
1693816938
},
16939-
{
16940-
"path": "scripts/test-session-route-consumers.js",
16941-
"role": "current"
16942-
},
1694316939
{
1694416940
"path": "scripts/test-skill-portfolio.js",
1694516941
"role": "current"
@@ -17044,7 +17040,6 @@
1704417040
"scripts/test-requirement-artifacts.js",
1704517041
"scripts/test-requirement-parallel-orchestration.js",
1704617042
"scripts/test-review-execution-contract.js",
17047-
"scripts/test-session-route-consumers.js",
1704817043
"scripts/test-skill-portfolio.js",
1704917044
"scripts/test-skill-route-state.js",
1705017045
"scripts/test-skill-sidecar-contract.js",
@@ -17146,7 +17141,6 @@
1714617141
"scripts/test-requirement-artifacts.js",
1714717142
"scripts/test-requirement-parallel-orchestration.js",
1714817143
"scripts/test-review-execution-contract.js",
17149-
"scripts/test-session-route-consumers.js",
1715017144
"scripts/test-skill-portfolio.js",
1715117145
"scripts/test-skill-route-state.js",
1715217146
"scripts/test-skill-sidecar-contract.js",

‎hooks/_runtime/evidence/codex-skill-route-pass.v1.json‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,24 @@
55
"hostVariant": "codex-cli/exec-user-global-local-stdio",
66
"testedVersion": "codex-cli 0.153.4",
77
"protocol": "MCP 2024-11-05",
8-
"runtimeContractDigest": "151d66d6ce51871771e376dc97a49623de712989190632a3ec1c9f14982bb1f4",
8+
"runtimeContractDigest": "2632c548b611e3bfe6d0eeaea4cdb61d2b9c0406f994d257a80ae384405ad729",
99
"hostAdapterDigest": "1095efd90a80d223c5990a70a8503f5cbbe818e4d2f5253b944bce92536873a3",
10-
"sourceEvidenceDigest": "2749b302af29f5049b813a136ca09b17f32f3b71f2767927b4059f3302ddcb0b",
10+
"sourceEvidenceDigest": "caf00f55882dbb5d8938f40662631b1313b85afab3db4c7d428b71d89a212a1f",
1111
"sourceProbe": {
1212
"schemaVersion": "SkillRouteS15EvidenceV1",
13-
"probeRunId": "s15-codex-probe-8dfb19d1-3cfa-41af-a019-3ff591cc941b",
13+
"probeRunId": "s15-codex-probe-17c1fdfd-201f-4717-915b-3c093c1095a7",
1414
"authorizationSource": "isolated-probe-authority",
1515
"contextSource": "host-hooks",
1616
"observationMode": "hook-post-history",
1717
"receiptStatus": "relevant-complete",
18-
"completedAt": "2026-09-08T16:04:57.020Z"
18+
"completedAt": "2026-09-08T17:19:52.426Z"
1919
},
2020
"runtimeBinding": {
2121
"source": "isolated-source-candidate",
22-
"expectedDigest": "151d66d6ce51871771e376dc97a49623de712989190632a3ec1c9f14982bb1f4",
23-
"generationDigest": "151d66d6ce51871771e376dc97a49623de712989190632a3ec1c9f14982bb1f4",
24-
"modeReceiptDigest": "151d66d6ce51871771e376dc97a49623de712989190632a3ec1c9f14982bb1f4",
25-
"routeEnvelopeDigest": "151d66d6ce51871771e376dc97a49623de712989190632a3ec1c9f14982bb1f4"
22+
"expectedDigest": "2632c548b611e3bfe6d0eeaea4cdb61d2b9c0406f994d257a80ae384405ad729",
23+
"generationDigest": "2632c548b611e3bfe6d0eeaea4cdb61d2b9c0406f994d257a80ae384405ad729",
24+
"modeReceiptDigest": "2632c548b611e3bfe6d0eeaea4cdb61d2b9c0406f994d257a80ae384405ad729",
25+
"routeEnvelopeDigest": "2632c548b611e3bfe6d0eeaea4cdb61d2b9c0406f994d257a80ae384405ad729"
2626
},
2727
"probe": {
2828
"schemaVersion": "SkillRouteProbeSummaryV1",

‎hooks/_runtime/host-skill-route-capabilities.v1.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
"status": "PASS",
1414
"testedVersion": "codex-cli 0.153.4",
1515
"protocol": "MCP 2024-11-05",
16-
"runtimeContractDigest": "151d66d6ce51871771e376dc97a49623de712989190632a3ec1c9f14982bb1f4",
16+
"runtimeContractDigest": "2632c548b611e3bfe6d0eeaea4cdb61d2b9c0406f994d257a80ae384405ad729",
1717
"hostAdapterDigest": "1095efd90a80d223c5990a70a8503f5cbbe818e4d2f5253b944bce92536873a3",
1818
"evidenceRef": "hooks/_runtime/evidence/codex-skill-route-pass.v1.json",
19-
"evidenceDigest": "421033d53f7ddbff7a6f79aec506ec1677011ed2d8048a7601ae847e497f3963",
19+
"evidenceDigest": "aac58d8cc0f41aabeb3af4e6ad61d09154a3427730a93500265f1ced4afed802",
2020
"entrySurface": "codex exec --ephemeral",
2121
"bootstrapDelivery": "stable user-global Hook launcher UserPromptSubmit or profile_context_plan fallback",
2222
"defaultEligible": true

‎mcp/memory-server.js‎

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,7 @@ const TOOLS = [
584584
name: { type: 'string', minLength: 1, maxLength: 300, description: '精确任务名、alias 或稳定 taskId' },
585585
project: { ...PROJECT_NAMESPACE_INPUT_SCHEMA, description: '可选项目命名空间;提供后限制为 project scope' },
586586
scope: { type: 'string', enum: ['project', 'workspace'], description: '可选;默认按 cwd/project 推断' },
587+
locale: { type: 'string', minLength: 2, maxLength: 32, description: '按当前用户意图选择的提示语言;省略时沿用已确认任务语言,不从任务名称猜测' },
587588
persistIndex: { type: 'boolean', description: '是否持久化可重建索引;默认 true' }
588589
}
589590
}
@@ -4769,6 +4770,26 @@ function handleMemorySummaryAppend(args) {
47694770
}
47704771
}
47714772

4773+
function taskPresentationLocale(args, candidate, persistedLanguage) {
4774+
const requested = normalizeLanguageTag(args.locale)
4775+
if (requested) return requested
4776+
let language = persistedLanguage
4777+
if (language === undefined && candidate?.taskId && candidate.project) {
4778+
try {
4779+
const target = taskMemoryTransactionTarget({ project: candidate.project, scope: 'project' })
4780+
const identity = { activeRoot: target.activeRoot, project: target.project, taskId: candidate.taskId, taskStatus: 'active' }
4781+
const recovery = readTaskRecoveryState({ metaDir: resolveTaskRecoveryMetaDir(identity), identity }, { fs })
4782+
if (recovery.status === 'fresh') language = recovery.state?.languageContext
4783+
} catch {
4784+
// Presentation has no authority to alter a missing or invalid task state.
4785+
}
4786+
}
4787+
const durable = language?.durableProvisional !== true
4788+
? normalizeLanguageTag(language?.durablePrimaryLocale || language?.primaryLanguage || language?.responseLanguage)
4789+
: ''
4790+
return durable || 'en-US'
4791+
}
4792+
47724793
function handleMemoryTaskResolve(args) {
47734794
if (!String(args.name || '').trim()) throw new TaskContinuationError('TASK_NAME_REQUIRED', 'name is required')
47744795
const resolution = resolveTaskContinuation({
@@ -4779,8 +4800,7 @@ function handleMemoryTaskResolve(args) {
47794800
persistIndex: args.persistIndex !== false
47804801
})
47814802
const candidates = resolution.candidates || resolution.suggestions || []
4782-
const languageEvidence = `${String(args.name || '')}${String(resolution.candidate?.displayName || '')}${candidates.map(item => item.displayName || '').join('')}`
4783-
const chinese = /[\u3400-\u9fff]/u.test(languageEvidence)
4803+
const chinese = taskPresentationLocale(args, resolution.candidate).startsWith('zh')
47844804
let humanText
47854805
if (resolution.status === 'resolved-active') {
47864806
humanText = chinese
@@ -4901,15 +4921,8 @@ function handleMemoryTaskContinuityViewV1(args) {
49014921
activeRoot: target?.activeRoot || '',
49024922
priorDisambiguationReceiptDigest: args.priorDisambiguationReceiptDigest || null
49034923
})
4904-
const persistedLanguage = recoveryRead.state?.languageContext || {}
4905-
const requestedLocale = normalizeLanguageTag(args.locale)
4906-
const durableLocale = normalizeLanguageTag(
4907-
persistedLanguage.responseLanguage ||
4908-
persistedLanguage.primaryLanguage ||
4909-
persistedLanguage.durablePrimaryLocale
4910-
)
4911-
const languageEvidence = `${args.name || ''}${candidate?.displayName || ''}`
4912-
const locale = requestedLocale || durableLocale || (/\p{Script=Han}/u.test(languageEvidence) ? 'zh-CN' : 'en-US')
4924+
const locale = taskPresentationLocale(args, candidate,
4925+
recoveryRead.status === 'fresh' ? recoveryRead.state?.languageContext || {} : {})
49134926
return {
49144927
content: [{ type: 'text', text: renderTaskContinuityViewHuman(view, { locale }) }],
49154928
structuredContent: view,

0 commit comments

Comments
 (0)