Skip to content

Commit 8583ab4

Browse files
committed
fix: converge profile truth validation entrypoints
1 parent 60b74be commit 8583ab4

6 files changed

Lines changed: 83 additions & 18 deletions

File tree

‎RULES.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ DevCodex 同时支持六宿主的用户级加载路径,规则语义保持一
3535

3636
## 正式需求与执行模板边界
3737

38-
- **正式需求信源**:当前项目内的正式需求写在 `website/docs/versions/v1/<active-version>/requirements/`。
38+
- **正式需求信源**:当前执行中的 dev/fix/audit 任务以 workspace active-root 下的 `.devcodex/<project>/{requirements,bugs,optimizations,scenario-tests}/...` 产物为准;维护者本地完整 `website/docs/versions/v1/<active-version>/requirements/` 存在时可作为版本化需求源站/历史文档,不存在时不得把 `website` 缺省误判为需求缺失。
3939
- **执行模板职责**:`prompts/*.prompt.md` 负责约束 CP1 / CP2 / CP3 产物结构,是默认执行模板,不替代当前项目的正式 requirement 入口。
4040
- **项目规则优先**:如果项目已经定义自定义 requirement 规范,则项目规范优先,prompt 只提供通用骨架。
4141
- **阶段边界**:CP1 看需求目标、用户交互与业务结果;CP2 看实现流程、公共契约、兼容性与边界问题;CP3 看实施顺序、验证、风险与回滚。

‎package.json‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"global-adapters:remove": "node index.js global-adapters remove --apply",
1414
"global-adapters:remove:dry-run": "node index.js global-adapters remove --dry-run",
1515
"test:global-adapters-cli": "node scripts/test-global-adapters-cli.js",
16-
"test": "node scripts/run-validation.js --route changed",
16+
"test": "node scripts/run-validation.js --route changed --actor human-cli --plan",
1717
"test:fast": "node scripts/run-validation.js --route fast",
1818
"test:full": "node scripts/run-validation.js --route full",
1919
"test:all": "npm run test:full",
@@ -148,7 +148,8 @@
148148
"test:ci-validation-plan": "node scripts/test-ci-validation-plan.js",
149149
"test:validation-authority": "node scripts/test-validation-execution-authority.js && node scripts/test-validation-budget-control.js && node scripts/test-validation-convergence.js",
150150
"test:artifact-authority": "node scripts/test-artifact-mutation-authority.js && npm run test:artifact-slot-template-coverage",
151-
"test:changed": "node scripts/run-validation.js --route changed",
151+
"test:changed": "node scripts/run-validation.js --route changed --actor human-cli --plan",
152+
"test:changed:ai": "node scripts/run-validation.js --route changed --actor ai-hook",
152153
"test:delivery": "node scripts/run-validation.js --route delivery",
153154
"test:boundary": "node scripts/run-validation.js --route boundary",
154155
"test:profile-deploy": "node scripts/run-validation.js --route profile-deploy",

‎scripts/run-validation.js‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ function printHelp() {
241241
' --release-authorized Compatibility request flag; never grants execution authority',
242242
' --approve-plan <digest> Confirm the exact BudgetCardV1 digest',
243243
' --actor <type> ai-hook|human-cli|trusted-ci|release-pipeline',
244+
' With --plan, human-cli may be requested from an AI host for zero-execution local planning.',
244245
' --authority-source <ref> Current message, TTY attestation, CI policy or release policy reference',
245246
' --source-message-digest <d> Required for ai-hook authority',
246247
' --policy-digest <digest> Required for trusted-ci and release-pipeline authority',
@@ -443,8 +444,11 @@ function detectedActorType(env = process.env) {
443444
return 'human-cli'
444445
}
445446

446-
function resolveActorType(requested, env = process.env) {
447+
function resolveActorType(requested, env = process.env, options = {}) {
447448
const detected = detectedActorType(env)
449+
if (options.allowPlanOnlyDowngrade && requested === 'human-cli' && detected === 'ai-hook') {
450+
return 'human-cli'
451+
}
448452
if (requested && requested !== detected) {
449453
throw new ValidationDagError(
450454
'VALIDATION_ACTOR_SPOOF_REJECTED',
@@ -2106,7 +2110,9 @@ async function main(argv = process.argv.slice(2)) {
21062110
return 0
21072111
}
21082112
const manifest = readValidationManifest(options.manifestPath)
2109-
const actorType = resolveActorType(options.actorType)
2113+
const actorType = resolveActorType(options.actorType, process.env, {
2114+
allowPlanOnlyDowngrade: options.planOnly
2115+
})
21102116
const activeRoot = process.env.DEVCODEX_VALIDATION_ACTIVE_ROOT
21112117
? path.resolve(process.env.DEVCODEX_VALIDATION_ACTIVE_ROOT)
21122118
: resolveActiveRuntimeRoot(ROOT)

‎scripts/test-validation-dag.js‎

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2166,11 +2166,13 @@ function run() {
21662166
assert(!aggregateInvocations.includes('aggregate-dependent'))
21672167

21682168
const packageJson = require('../package.json')
2169-
assert.strictEqual(packageJson.scripts.test, 'node scripts/run-validation.js --route changed')
2169+
assert.strictEqual(packageJson.scripts.test, 'node scripts/run-validation.js --route changed --actor human-cli --plan')
21702170
assert.strictEqual(packageJson.scripts['test:fast'], 'node scripts/run-validation.js --route fast')
21712171
assert.strictEqual(packageJson.scripts['test:full'], 'node scripts/run-validation.js --route full')
21722172
assert.strictEqual(packageJson.scripts['test:delivery'], 'node scripts/run-validation.js --route delivery')
21732173
assert.strictEqual(packageJson.scripts['test:boundary'], 'node scripts/run-validation.js --route boundary')
2174+
assert.strictEqual(packageJson.scripts['test:changed'], 'node scripts/run-validation.js --route changed --actor human-cli --plan')
2175+
assert.strictEqual(packageJson.scripts['test:changed:ai'], 'node scripts/run-validation.js --route changed --actor ai-hook')
21742176
assert.strictEqual(packageJson.scripts['test:actual-candidate-evidence'], 'node scripts/test-actual-candidate-evidence.js')
21752177
assert.strictEqual(packageJson.scripts['test:dangerous-command-context'], 'node scripts/test-dangerous-command-context.js')
21762178
assert.strictEqual(packageJson.scripts['test:session-route-consumers'], 'node scripts/test-session-route-consumers.js')
@@ -2243,6 +2245,33 @@ function run() {
22432245
assert.strictEqual(planEnvelope.data.plan.javascriptCommandCount, 0)
22442246
assert.strictEqual(planEnvelope.data.plan.fullFallback, null)
22452247

2248+
const aiPlanOnlyEnv = {
2249+
...cliHumanEnv,
2250+
CODEX_THREAD_ID: 'fixture-ai-thread'
2251+
}
2252+
const aiHostHumanPlan = spawnSync(process.execPath, [
2253+
'scripts/run-validation.js',
2254+
'--route', 'changed',
2255+
'--changed', 'README.md',
2256+
'--actor', 'human-cli',
2257+
'--plan',
2258+
'--json'
2259+
], { cwd: ROOT, encoding: 'utf8', windowsHide: true, env: aiPlanOnlyEnv })
2260+
assert.strictEqual(aiHostHumanPlan.status, 0, aiHostHumanPlan.stderr)
2261+
const aiHostHumanPlanEnvelope = JSON.parse(aiHostHumanPlan.stdout)
2262+
assert.strictEqual(aiHostHumanPlanEnvelope.ok, true)
2263+
assert.strictEqual(aiHostHumanPlanEnvelope.data.plan.routeResolved, 'changed')
2264+
2265+
const aiHostHumanExecute = spawnSync(process.execPath, [
2266+
'scripts/run-validation.js',
2267+
'--route', 'changed',
2268+
'--actor', 'human-cli',
2269+
'--json'
2270+
], { cwd: ROOT, encoding: 'utf8', windowsHide: true, env: aiPlanOnlyEnv })
2271+
assert.strictEqual(aiHostHumanExecute.status, 2)
2272+
const aiHostHumanExecuteEnvelope = JSON.parse(aiHostHumanExecute.stdout)
2273+
assert.strictEqual(aiHostHumanExecuteEnvelope.error.code, 'VALIDATION_ACTOR_SPOOF_REJECTED')
2274+
22462275
const highHookPlan = spawnSync(process.execPath, [
22472276
'scripts/run-validation.js',
22482277
'--route', 'changed',

‎scripts/validate-all-profiles.js‎

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -101,26 +101,48 @@ if (!targets.length) {
101101
process.exit(0)
102102
}
103103

104-
let errorCount = 0
105-
let warningCount = 0
106104
const results = targets.map(runTarget)
105+
const sourceNamespace = path.basename(ROOT)
107106

108-
for (const result of results) {
107+
function resultKind(result) {
108+
if (result.status === 1) return 'error'
109+
if (result.status === 2) return 'warning'
110+
return 'ok'
111+
}
112+
113+
function countKinds(items) {
114+
return items.reduce((acc, result) => {
115+
acc[resultKind(result)] += 1
116+
return acc
117+
}, { ok: 0, warning: 0, error: 0 })
118+
}
119+
120+
function printResult(result, prefix = '[profile-all]') {
109121
if (result.status === 1) {
110-
errorCount += 1
111-
console.error(`[profile-all] ${result.namespace}: error`)
122+
console.log(`${prefix} ${result.namespace}: error`)
112123
const detail = indent(result.output)
113-
if (detail) console.error(detail)
124+
if (detail) console.log(detail)
114125
} else if (result.status === 2) {
115-
warningCount += 1
116-
console.warn(`[profile-all] ${result.namespace}: warning`)
126+
console.log(`${prefix} ${result.namespace}: warning`)
117127
const detail = indent(result.output)
118-
if (detail) console.warn(detail)
128+
if (detail) console.log(detail)
119129
} else {
120-
console.log(`[profile-all] ${result.namespace}: ok`)
130+
console.log(`${prefix} ${result.namespace}: ok`)
121131
}
122132
}
123133

124-
console.log(`[profile-all] checked=${results.length} errors=${errorCount} warnings=${warningCount} strictWarnings=${strictWarnings}`)
125-
if (errorCount || (strictWarnings && warningCount)) process.exit(1)
134+
const currentProject = results.filter(result => result.namespace === sourceNamespace)
135+
const workspaceDebt = results.filter(result => result.namespace !== sourceNamespace)
136+
const currentCounts = countKinds(currentProject)
137+
const workspaceCounts = countKinds(workspaceDebt)
138+
const allCounts = countKinds(results)
139+
140+
console.log(`[profile-all] current-project=${sourceNamespace} checked=${currentProject.length} errors=${currentCounts.error} warnings=${currentCounts.warning}`)
141+
for (const result of currentProject) printResult(result, '[profile-all][current-project]')
142+
143+
console.log(`[profile-all] workspace-debt checked=${workspaceDebt.length} errors=${workspaceCounts.error} warnings=${workspaceCounts.warning}`)
144+
for (const result of workspaceDebt) printResult(result, '[profile-all][workspace-debt]')
145+
146+
console.log(`[profile-all] checked=${results.length} errors=${allCounts.error} warnings=${allCounts.warning} strictWarnings=${strictWarnings}`)
147+
if (allCounts.error || (strictWarnings && allCounts.warning)) process.exit(1)
126148
process.exit(0)

‎website/README.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# DevCodex maintainer website pointer
2+
3+
`website/` 是维护者本地可选的完整文档源站目录,不进入公开仓和 npm 包。
4+
5+
当前公开用户文档位于 `public-site/`,当前执行中的需求、修复、优化和审查任务以 workspace active-root 下的 `.devcodex/<project>/` 产物为准。
6+
7+
当维护者本地存在完整 `website/docs` 时,它可以作为版本化需求源站和历史文档源;当公开 checkout 中只有本指针文件时,验证器和工作流不得把缺省 `website/docs` 解释为当前需求缺失。

0 commit comments

Comments
 (0)