Skip to content

Commit eda3286

Browse files
committed
fix: harden workspace and skill route lifecycle
1 parent 57a56cd commit eda3286

36 files changed

Lines changed: 2027 additions & 311 deletions

‎CHANGELOG.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# 变更日志 (CHANGELOG)
22

33
> **说明**: 版本概览摘要。最新版本的详细变更见下方表格首行的 `changelogs/releases/vX.Y.Z.md` 链接;历史版本见对应详细变更文件
4-
> **最后更新**: 2026-08-03
5-
> **当前未发布**: v1.16.2 已归档;后续未发布实现继续记录到 [`changelogs/unreleased.md`](./changelogs/unreleased.md)。
4+
> **最后更新**: 2026-08-04
5+
> **当前未发布**: v1.16.2 已归档;F1~F14 的 Profile 目标、ContextRead/SkillRoute、索引证据与发布门禁修复仍是本地候选,详见 [`changelogs/unreleased.md`](./changelogs/unreleased.md)。
66
77
---
88

‎README.md‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,15 @@ devcodex init
157157
devcodex init --profile api
158158
```
159159

160-
`api` 必须能唯一匹配 workspace 中已经存在的项目;如果重名,请使用完整相对名称,例如 `apps/api`。DevCodex 会根据该项目已有的包、测试、构建与公开接口选择合适的 Profile 档位,并且只生成缺失文件,不覆盖已经编辑过的内容。
160+
`api` 必须能唯一匹配 workspace 中已经存在的项目目录;`.devcodex` 尚不存在并不影响首次执行。如果多个项目使用相同末段名称,请改用从 workspace 根目录开始的相对名称,例如 `apps/api`。DevCodex 只从真实项目目录解析目标,不会把旧运行记录或构建产物误认成项目;目标不存在或不唯一时会直接报错,并且不会创建 `.devcodex`。
161+
162+
需要先确认目标和计划路径时,可加 `--dry-run`:
163+
164+
```bash
165+
devcodex init --profile api --dry-run
166+
```
167+
168+
这个预览不会创建目录、Profile、备份或运行态文件。确认后去掉 `--dry-run` 即可;正式执行会根据项目已有的包、测试、构建与公开接口选择合适的 Profile 档位,并且只生成缺失文件,不覆盖已经编辑过的内容。
161169

162170
`devcodex update` 只刷新运行态,不会自动创建、升级或降级 Profile。需要手动预览或指定档位时,才使用高级命令 `devcodex profile plan` / `devcodex profile init`。
163171

‎changelogs/unreleased.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
> **当前**: v1.16.2 已归档到 `changelogs/releases/v1.16.2.md`;本文件仅保留历史锚点索引与镜像供 validate 探针消费。发版后新实现再写入下方「当前未发布」。
55
66
## 当前未发布实现候选
7+
8+
- **Profile 目标、ContextRead/SkillRoute 与发布真相闭环修复(F1~F14)**:定向 `init --profile` 只解析真实项目且 `--dry-run` 全程零写入;生命周期改为 session-first 主动协调,ContextRead observation、阶段义务与 status/execute 共用身份和前置条件;memory 索引区分 freshness/trust/coverage/repair,report index 诚实降级为维护预览;Codex PASS 改用包内可复算真实宿主证据,相关组合负例接入 changed/full/package-release;F14 审计阻断将 website 已有 `brace-expansion` override 从 5.0.8 提升到官方修复版 5.0.9,不新增审计例外。当前为本地未发布候选,不代表 npm、GitHub、tag 或远端 CI 已更新。
79
- **v1.16.2 已归档**:ContextRead 来源回执耐久化、空会话标识重放修复、README 常见任务教程与 CLI 只读帮助已进入 `changelogs/releases/v1.16.2.md`。
810
- **v1.16.1 已归档**:长会话提示有界化、会话治理状态隔离、项目绑定稳定性与唯一短名解析修复已进入 `changelogs/releases/v1.16.1.md`。
911
- **v1.16.0 已归档**:首次初始化、项目 Profile、用户语言与 workspace runtime-state 一致性已进入 `changelogs/releases/v1.16.0.md`。

‎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": "521b6b99a2759923536cca8f71a6a36c817c716c8a831927b2025b8bd3e2527d",
3+
"sourceBundleDigest": "e9e6c6a4c3b3e4a58a1b1402fe82b4c1497e88f5ef70a744a43980020946b1c5",
44
"thresholds": {
55
"minParagraphChars": 100,
66
"sectionThreshold": 0.94,

‎content/skills/portfolio.json‎

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66
"skillsPattern": "content/skills/*/SKILL.md",
77
"optionalSidecar": "content/skills/*/devcodex.skill.json",
88
"registry": "plugin.json",
9-
"sourceDigest": "1d59afaa9a6de34be4f108b2935d8b8fc09be3eac5601b3e5d3fc7773167b0f8",
9+
"sourceDigest": "bcbcb34c670c6c52b180bfb44929c5bc0e5c7eb2fada6d62d660f48f61019192",
1010
"sidecarDigest": "774105d7947b340d15d17115fcb8e37b62ae545cf553f06126d529184fd74872",
1111
"pluginDigest": "b305f2e18310fc9ee6eca5a13f5454c463dc98ca2add76bf39e78e4a32085a11",
1212
"portfolioEvidence": "content/skills/portfolio-evidence.json",
1313
"portfolioEvidenceDigest": "8364a73c66ed2ec9787cdb9300223278d128a45b689a2b8deda0d1f03189f42b",
14-
"consumerInventoryFileCount": 469,
15-
"consumerInventoryDigest": "816035cd3178ee7a0333aa017434c7f9188af86c095534040b95cff30cb6a400",
16-
"consumerProjectionDigest": "b241444bd8b6ece6591a9519ba98a65105b7cc587986d7ea8aa75a35ded938f0",
17-
"portfolioInputDigest": "ede35889bd2d9f3e59b9a7d5fac9a7185221a4dba5f3b5d8977161dca8814ca0"
14+
"consumerInventoryFileCount": 471,
15+
"consumerInventoryDigest": "395741c255efa9c452af5af40a62a66db1ee7defc65d55bd25e0e6fc0dedfd45",
16+
"consumerProjectionDigest": "33a51834e6b78df70ca18492538a95856c7cfc68b054d48da4debec40cc1be37",
17+
"portfolioInputDigest": "243d91c7c40fa6da5db81d7216b851ec70f2b538ec0ee3397056e080d97bb456"
1818
},
1919
"ordering": "skills.id asc; graph edges from/to asc",
2020
"summary": {
@@ -11072,6 +11072,10 @@
1107211072
"path": "hooks/_runtime/context-source-observation.cjs",
1107311073
"role": "current"
1107411074
},
11075+
{
11076+
"path": "hooks/_runtime/evidence/codex-skill-route-pass.v1.json",
11077+
"role": "current"
11078+
},
1107511079
{
1107611080
"path": "hooks/_runtime/lifecycle-bootstrap-state.cjs",
1107711081
"role": "current"
@@ -11084,6 +11088,10 @@
1108411088
"path": "hooks/_runtime/lifecycle-namespace-state.cjs",
1108511089
"role": "current"
1108611090
},
11091+
{
11092+
"path": "hooks/_runtime/lifecycle-skill-route-coordinator.cjs",
11093+
"role": "current"
11094+
},
1108711095
{
1108811096
"path": "hooks/_runtime/lifecycle-stop-gate.cjs",
1108911097
"role": "current"
@@ -11520,6 +11528,10 @@
1152011528
"path": "scripts/test-validate-profile.js",
1152111529
"role": "current"
1152211530
},
11531+
{
11532+
"path": "scripts/test-validation-dag.js",
11533+
"role": "current"
11534+
},
1152311535
{
1152411536
"path": "scripts/test-workflow-completion-contract.js",
1152511537
"role": "current"
@@ -11573,6 +11585,7 @@
1157311585
"scripts/test-skills-deploy-mode.js",
1157411586
"scripts/test-task-continuation.js",
1157511587
"scripts/test-validate-profile.js",
11588+
"scripts/test-validation-dag.js",
1157611589
"scripts/test-workflow-completion-contract.js",
1157711590
"scripts/validate-profile.js",
1157811591
"scripts/validate.js"
@@ -11655,6 +11668,7 @@
1165511668
"scripts/test-skills-deploy-mode.js",
1165611669
"scripts/test-task-continuation.js",
1165711670
"scripts/test-validate-profile.js",
11671+
"scripts/test-validation-dag.js",
1165811672
"scripts/test-workflow-completion-contract.js",
1165911673
"scripts/validate-profile.js",
1166011674
"scripts/validate.js"
@@ -12059,6 +12073,10 @@
1205912073
"path": "hooks/_runtime/devcodex-md-entry.cjs",
1206012074
"role": "current"
1206112075
},
12076+
{
12077+
"path": "hooks/_runtime/evidence/codex-skill-route-pass.v1.json",
12078+
"role": "current"
12079+
},
1206212080
{
1206312081
"path": "hooks/_runtime/lifecycle-bootstrap-state.cjs",
1206412082
"role": "current"
@@ -12071,6 +12089,10 @@
1207112089
"path": "hooks/_runtime/lifecycle-host-adapters.cjs",
1207212090
"role": "current"
1207312091
},
12092+
{
12093+
"path": "hooks/_runtime/lifecycle-skill-route-coordinator.cjs",
12094+
"role": "current"
12095+
},
1207412096
{
1207512097
"path": "hooks/_runtime/lifecycle-stop-gate.cjs",
1207612098
"role": "current"
@@ -12583,6 +12605,10 @@
1258312605
"path": "scripts/test-skill-route-contracts.js",
1258412606
"role": "current"
1258512607
},
12608+
{
12609+
"path": "scripts/test-skill-route-lifecycle.js",
12610+
"role": "current"
12611+
},
1258612612
{
1258712613
"path": "scripts/test-skill-route-state.js",
1258812614
"role": "current"
@@ -12658,6 +12684,7 @@
1265812684
"scripts/test-skill-resolution-hook-paths.js",
1265912685
"scripts/test-skill-resolve.js",
1266012686
"scripts/test-skill-route-contracts.js",
12687+
"scripts/test-skill-route-lifecycle.js",
1266112688
"scripts/test-skill-route-state.js",
1266212689
"scripts/test-skills-deploy-mode.js",
1266312690
"scripts/test-validation-dag.js",
@@ -12752,6 +12779,7 @@
1275212779
"scripts/test-skill-resolution-hook-paths.js",
1275312780
"scripts/test-skill-resolve.js",
1275412781
"scripts/test-skill-route-contracts.js",
12782+
"scripts/test-skill-route-lifecycle.js",
1275512783
"scripts/test-skill-route-state.js",
1275612784
"scripts/test-skills-deploy-mode.js",
1275712785
"scripts/test-validation-dag.js",
@@ -14360,8 +14388,8 @@
1436014388
"content/skills/report/SKILL.md"
1436114389
],
1436214390
"source": "content/skills/report/SKILL.md",
14363-
"hash": "bf205291ec6a9389ca3ee605dd0fe76b8918cb14efa507b142e946bc39780358",
14364-
"sourceBytes": 21304,
14391+
"hash": "002dcfdaff342f0a0bf296b0678a9fbf2403e0155d6b8f73174d8884f80aa39a",
14392+
"sourceBytes": 21360,
1436514393
"version": "1.16.2",
1436614394
"lifecycleState": "active",
1436714395
"dependencies": [
@@ -14740,6 +14768,10 @@
1474014768
"path": "scripts/lib/skill-portfolio-utils.js",
1474114769
"role": "current"
1474214770
},
14771+
{
14772+
"path": "scripts/lib/test-hooks-runtime-bootstrap-layout.js",
14773+
"role": "current"
14774+
},
1474314775
{
1474414776
"path": "scripts/lib/test-hooks-runtime-visibility.js",
1474514777
"role": "current"
@@ -14872,6 +14904,10 @@
1487214904
"path": "scripts/test-completion-report-ecr-check.js",
1487314905
"role": "current"
1487414906
},
14907+
{
14908+
"path": "scripts/test-context-read-contract.js",
14909+
"role": "current"
14910+
},
1487514911
{
1487614912
"path": "scripts/test-control-plane-contracts.js",
1487714913
"role": "current"
@@ -15010,6 +15046,7 @@
1501015046
"scripts/test-cli-behavior.js",
1501115047
"scripts/test-client-contracts.js",
1501215048
"scripts/test-completion-report-ecr-check.js",
15049+
"scripts/test-context-read-contract.js",
1501315050
"scripts/test-control-plane-contracts.js",
1501415051
"scripts/test-delivery-honesty.js",
1501515052
"scripts/test-ecr-closure.js",
@@ -15097,6 +15134,7 @@
1509715134
"scripts/test-cli-behavior.js",
1509815135
"scripts/test-client-contracts.js",
1509915136
"scripts/test-completion-report-ecr-check.js",
15137+
"scripts/test-context-read-contract.js",
1510015138
"scripts/test-control-plane-contracts.js",
1510115139
"scripts/test-delivery-honesty.js",
1510215140
"scripts/test-ecr-closure.js",
@@ -17740,6 +17778,10 @@
1774017778
"path": "hooks/_runtime/lifecycle-governance-intake.cjs",
1774117779
"role": "current"
1774217780
},
17781+
{
17782+
"path": "hooks/_runtime/lifecycle-skill-route-coordinator.cjs",
17783+
"role": "current"
17784+
},
1774317785
{
1774417786
"path": "hooks/_runtime/lifecycle-stop-gate.cjs",
1774517787
"role": "current"

‎content/skills/report/SKILL.md‎

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -32,27 +32,28 @@ reports/<子目录>/<agent>/YYYYMMDD/NN--<简述>.md
3232
> 路径详细规范见 [`02-output-paths.instructions.md`](../../instructions/02-output-paths.instructions.md)。
3333
> 当 `<工作区根>/.devcodex/layout.json` 启用 `workspace-namespace` 时,本文中的任务目录与项目级 `reports/...` 均以当前 **`<active-root>`** 为根。
3434
35-
## 报告索引导航
36-
37-
需要按任务、日期或类别发现历史报告时,优先使用
38-
`scripts/lib/report-index.js#queryReportIndex` 的 metadata/pointer 结果,禁止先把整个
39-
report corpus 正文装入上下文。该索引不改变报告 Markdown 真相源,也不新增
40-
CLI/MCP surface。
41-
42-
- discovery 只允许 `<active-root>/reports/**` 与
43-
`<active-root>/{requirements,bugs,optimizations,scenario-tests}/<task>/reports/**`。
44-
- 默认只返回 `primary-report`;`evidence`、`artifact`、`generated-copy` 与
45-
`unknown` 只有显式筛选时才返回,unknown 必须保留 warning。
46-
- `fresh` 索引可用于 metadata 导航;pointer 损坏、陈旧或出现未登记文件时,
47-
只做内存中的 path/stat reconcile 并标记 `fallback`,不得隐式写索引。
48-
- 多页宽查询使用返回的 `snapshotCursor` / `snapshotCursorEncoded` 绑定 immutable
49-
manifest,下一页同时传 `nextPointer.offset`;旧 offset-only 查询继续兼容。
50-
- 正文只能在选定具体 pointer 后通过 `hydrateReportEntry` 或
51-
`hydrateReportEntries` 有界读取;截断正文只能声明 `metadata-reconciled`,不得
52-
声明全文内容已验证。metadata-only 场景可用 `projection: "compact"` 返回瘦身
53-
字段。
54-
- 只有显式维护/benchmark 路径可调用 `rebuildReportIndex`;查询、resume、ECR
55-
和普通报告写入必须保持 zero-write。
35+
## 报告索引维护预览
36+
37+
`scripts/lib/report-index.js` 当前只有测试与 benchmark 消费者,尚未接入正式的
38+
CLI/MCP reader 或 writer,因此属于 **maintenance/preview**,不能作为普通报告、
39+
resume、ECR 或 ContextRead 的已发布依赖,也不能据其状态要求用户刷新上下文。
40+
41+
普通工作流应在下列白名单根内做有界的 canonical Markdown 路径盘点,选定具体文件后
42+
再读取正文:`<active-root>/reports/**` 与
43+
`<active-root>/{requirements,bugs,optimizations,scenario-tests}/<task>/reports/**`。
44+
不得先把整个 report corpus 正文装入上下文。
45+
46+
只有显式的索引维护、测试或 benchmark 可以直接调用内部模块,并遵守以下预览契约:
47+
48+
- 默认只发现 `primary-report`;`evidence`、`artifact`、`generated-copy` 与
49+
`unknown` 必须显式筛选,unknown 保留 warning。
50+
- 查询和 hydration 保持 zero-write;只有维护路径可调用 `rebuildReportIndex`。
51+
- `fresh` 仅表示 metadata 路径已协调,不代表正文已验证;pointer 损坏、陈旧或
52+
出现未登记文件时,只允许内存中的 path/stat reconcile。
53+
- 多页查询必须绑定 `snapshotCursor`;截断正文只能声明
54+
`metadata-reconciled`,不得声明全文内容已验证。
55+
- 在正式 reader、writer、repair owner、宿主契约与 package 验证全部落地前,
56+
Profile 和用户文档只能把该能力标为 maintenance/preview。
5657

5758
## 头部必填
5859

‎hooks/_runtime/context-read-contract.cjs‎

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const CONTEXT_READ_CONTRACT = Object.freeze({
4646
risks: Object.freeze(['normal', 'high', 'critical']),
4747
actionClasses: Object.freeze([
4848
'context-read', 'analysis-read', 'test-execution', 'docs-mutation',
49-
'source-mutation', 'release', 'dangerous'
49+
'source-mutation', 'workflow-closeout', 'release', 'dangerous'
5050
]),
5151
verificationModes: Object.freeze(['structured-plan', 'path-observable', 'instruction-only']),
5252
receiptStatuses: Object.freeze([
@@ -702,7 +702,10 @@ function selectProfilePrefixes(intent, changeTypes) {
702702

703703
function deriveActionEnvelope(intent, changeTypes, riskHint) {
704704
const allowed = new Set(['context-read', 'analysis-read'])
705-
if (intent !== 'chat') allowed.add('test-execution')
705+
if (intent !== 'chat') {
706+
allowed.add('test-execution')
707+
allowed.add('workflow-closeout')
708+
}
706709
const mutationIntent = ['dev', 'fix', 'self-fix', 'other'].includes(intent)
707710
const docsMutation = mutationIntent && changeTypes.some(item => ['docs', 'public-contract'].includes(item))
708711
const sourceMutation = mutationIntent &&
@@ -713,7 +716,7 @@ function deriveActionEnvelope(intent, changeTypes, riskHint) {
713716
if (changeTypes.includes('destructive')) allowed.add('dangerous')
714717
return {
715718
allowedActionClasses: [...allowed].sort(),
716-
mutationExpected: allowed.has('docs-mutation') || allowed.has('source-mutation') || allowed.has('release') || allowed.has('dangerous'),
719+
mutationExpected: allowed.has('docs-mutation') || allowed.has('source-mutation') || allowed.has('workflow-closeout') || allowed.has('release') || allowed.has('dangerous'),
717720
riskCeiling: riskHint
718721
}
719722
}
@@ -1162,8 +1165,10 @@ function outcomePayload(raw, path = 'root', depth = 0) {
11621165
: { payload: raw, variant: path, observable: raw.length > 0 }
11631166
}
11641167
if (typeof raw !== 'object') return { payload: raw, variant: path, observable: true }
1165-
const localFailed = raw.success === false || raw.is_error === true || raw.isError === true || !!raw.error
1166-
const localError = localFailed ? String(raw.error?.message || raw.error || 'tool outcome failed') : null
1168+
const localFailed = raw.success === false || raw.ok === false || raw.is_error === true || raw.isError === true || !!raw.error
1169+
const localError = localFailed
1170+
? String(raw.errorCode || raw.error?.message || raw.error || 'tool outcome failed')
1171+
: null
11671172
if ([CONTEXT_READ_CONTRACT.schemas.plan, CONTEXT_READ_CONTRACT.schemas.planV1].includes(raw.schemaVersion) ||
11681173
/^Memory.+V1$/.test(String(raw.schemaVersion || ''))) {
11691174
return { payload: raw, variant: path, observable: true, failed: localFailed, error: localError }
@@ -1186,7 +1191,15 @@ function outcomePayload(raw, path = 'root', depth = 0) {
11861191
function normalizeContextToolOutcome(raw = {}) {
11871192
const extracted = outcomePayload(raw)
11881193
const source = raw && typeof raw === 'object' ? raw : {}
1189-
const failed = source.success === false || source.is_error === true || source.isError === true || !!source.error || extracted.failed === true
1194+
const semantic = parseExactJson(extracted.payload) ||
1195+
(extracted.payload && typeof extracted.payload === 'object' && !Array.isArray(extracted.payload)
1196+
? extracted.payload
1197+
: {})
1198+
const failed = source.success === false || source.ok === false ||
1199+
source.is_error === true || source.isError === true || !!source.error ||
1200+
semantic.ok === false || semantic.success === false ||
1201+
semantic.is_error === true || semantic.isError === true || !!semantic.error ||
1202+
extracted.failed === true
11901203
let serialized = ''
11911204
if (extracted.observable) {
11921205
try {
@@ -1207,7 +1220,19 @@ function normalizeContextToolOutcome(raw = {}) {
12071220
payload: extracted.payload,
12081221
text: typeof extracted.payload === 'string' ? extracted.payload : serialized,
12091222
resultDigest: extracted.observable ? stableDigest(extracted.payload) : null,
1210-
error: failed ? String(source.error?.message || source.error || extracted.error || 'tool outcome failed') : null,
1223+
ok: typeof semantic.ok === 'boolean'
1224+
? semantic.ok
1225+
: (typeof source.ok === 'boolean' ? source.ok : null),
1226+
errorCode: String(semantic.errorCode || source.errorCode || '') || null,
1227+
stateChanged: typeof semantic.stateChanged === 'boolean'
1228+
? semantic.stateChanged
1229+
: (typeof source.stateChanged === 'boolean' ? source.stateChanged : null),
1230+
error: failed
1231+
? String(
1232+
semantic.errorCode || source.errorCode || semantic.error?.message || semantic.error ||
1233+
source.error?.message || source.error || extracted.error || 'tool outcome failed'
1234+
)
1235+
: null,
12111236
telemetry
12121237
}
12131238
}

0 commit comments

Comments
 (0)