Skip to content

Commit 1145a80

Browse files
committed
fix: expand discipline probes and complete RQ-4~8 definitions
Add UniqueNextStepRecommendationGate and control-plane classifiers (PF-138/139/140/172), wire them into compliance/cp-gate/report skills, and fill missing audit-requirements RQ dimensions for full RQ-1~8 coverage.
1 parent 10b3821 commit 1145a80

9 files changed

Lines changed: 303 additions & 11 deletions

File tree

instructions/17-compliance.instructions.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ version: 1.15.2
147147
| FC4 | 文件名/路径合规(`NN--` 双横杠开头;本轮无报告产物时标 N/A) |
148148
| FC5 | `ArtifactDeliveryManifestV1` 完整对账;`UserFacingArtifactSetV1` required hidden=0、计数守恒;semantic name/action/order 与 capability renderer 有效 |
149149
| FC6 | 新增 DevCodex 规范资产 `.md` 行数检查(instructions / skills / prompts / templates / 规范源等超 500 行须按 C13 拆分;业务项目需求、技术方案、报告和正式项目文档不因 C13 强制拆分) |
150-
| FC7 | 用户决策选项与报告决策点必带推荐 + 理由:所有 AskUserQuestion / 多选项呈现 / CP 范围选择 / 方案对比 / analyze-audit 报告决策点必须有且仅有 1 个 🟢 推荐项(首位置 + 标签含"(推荐)"或表格标 ⭐),并附一句话推荐理由;没有可推荐动作时必须显式写 `推荐:无后续动作` 与原因 |
150+
| FC7 | 用户决策选项与报告决策点必带推荐 + 理由:所有 AskUserQuestion / 多选项呈现 / CP 范围选择 / 方案对比 / analyze-audit 报告决策点必须有且仅有 1 个 🟢 推荐项(首位置 + 标签含"(推荐)"或表格标 ⭐),并附一句话推荐理由;**完成态「下一步/后续建议」适用 UniqueNextStepRecommendationGate**(禁止用「或/或者」并列 ≥2 条可执行路径;探针 `classifyNextStepOrForkSample`);没有可推荐动作时必须显式写 `推荐:无后续动作` 与原因 |
151151

152152
> ℹ️ **层次说明**:FC 关注“写入与输出格式是否完整落盘”,T 层关注“任务目标是否最终达成”。看似相近的检查项(如 FC1 vs T3、FC2 vs T2)属于不同验证层,前者防止漏写,后者确认闭环完成。
153153
@@ -159,7 +159,7 @@ version: 1.15.2
159159
| SC2 | 代码已诊断(无未处理 error) | dev/fix 🔴 |
160160
| SC3 | 修复已全局扫描(同类错误模式全局+数据联动+grep零残留) | fix 🔴 |
161161
| SC4 | 关联文件已同步(含 profile 中定义的 dev 模式专属同步命令,如 `devcodex update`| dev/fix/self-fix 🔴 |
162-
| SC5 | 后续建议与推荐结论已输出;报告含多个建议/路径时必须有 `推荐结论` / `推荐方案`无待跟进时显式标注“推荐:无后续动作” | 全工作流 |
162+
| SC5 | 后续建议与推荐结论已输出;报告含多个建议/路径时必须有 `推荐结论` / `推荐方案`**唯一主动作**;禁止完成态 free-text「A 或 B」同级推荐;无待跟进时显式标注“推荐:无后续动作” | 全工作流 |
163163
| SC6 | Agent SUMMARY 已更新(写入动作已发生) | 全工作流 🔴 |
164164
| SC7 | 全局 SUMMARY 关键决策已追加 | 有关键决策时 🔴 |
165165
| SC8 | 上次待跟进已查阅 | 全工作流 🔴 |

scripts/lib/discipline-execution-probe.js

Lines changed: 148 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
'use strict'
22

33
/**
4-
* Discipline cluster probe (VL-053~061 family) — lightweight classifiers for
5-
* unauthorized push, preference menus after convergence, and CI-regression stop-without-self-fix.
4+
* Discipline cluster probe (VL-053~061 + PF-172 + control-plane absorb PF-138/139/140 family).
5+
* Lightweight classifiers for unauthorized push, preference menus after convergence,
6+
* CI-regression stop-without-self-fix, completion free-text next-step "A 或 B" forks,
7+
* CP artifact-before-confirm, CodeTruth matrix at CP, control-plane digest binding,
8+
* and author-self-review vs independent review boundary.
69
* Extends existing CP/release/expert gates; does not invent a parallel Skill.
710
*/
811

@@ -35,6 +38,76 @@ function classifyPreferenceMenuAfterConvergenceSample(sample) {
3538
return 'ok'
3639
}
3740

41+
/**
42+
* CP confirm request after convergence must point at on-disk artifact (PF-138 / GR-045).
43+
* @param {string} sample
44+
* @returns {'ok'|'missing-cp-artifact'|'not-cp-confirm'}
45+
*/
46+
function classifyCpArtifactBeforeConfirmSample(sample) {
47+
const text = String(sample || '')
48+
const asksConfirm = /\s*CP[123]|\s*CP[123]|\s*CP|\s*(||)|CP1\s*|CP2\s*/i.test(text)
49+
if (!asksConfirm) return 'not-cp-confirm'
50+
const hasArtifact =
51+
/01-\.md|01-\.md|01-\.md|02-\.md|04-\.md|00-\.md|00-\.md|artifactPath|artifactSha256|CpArtifactBeforeConfirmGate/i.test(
52+
text
53+
)
54+
if (!hasArtifact) return 'missing-cp-artifact'
55+
return 'ok'
56+
}
57+
58+
/**
59+
* Control-plane / optimization CP draft must carry CodeTruth matrix evidence (PF-139 / GR-046).
60+
* @param {string} sample
61+
* @returns {'ok'|'missing-code-truth-matrix'|'not-control-plane-cp'}
62+
*/
63+
function classifyCodeTruthMatrixAtCpSample(sample) {
64+
const text = String(sample || '')
65+
const controlCp =
66+
/|MCP|Hook|CLI|validate\.js|||workflow-capabilities|CodeTruth|CP2\s*|/i.test(
67+
text
68+
) && /CP[12]||||稿/i.test(text)
69+
if (!controlCp) return 'not-control-plane-cp'
70+
const hasMatrix =
71+
/CodeTruthEvidenceMatrix|CodeTruthEvidenceMatrixGate|repoPath|currentBehavior|negativeProbe|/i.test(text)
72+
const claimsReady = /\s*CP||稿|PR-1\s*|.*/i.test(text)
73+
if (claimsReady && !hasMatrix) return 'missing-code-truth-matrix'
74+
if (!hasMatrix && /|MCP|Hook|CLI/.test(text) && /稿||\s*=/i.test(text)) {
75+
return 'missing-code-truth-matrix'
76+
}
77+
return 'ok'
78+
}
79+
80+
/**
81+
* Control-plane CP confirmation must bind digest (PF-140 / ConfirmBindingGate residual).
82+
* @param {string} sample
83+
* @returns {'ok'|'missing-digest'|'not-control-plane-confirm'}
84+
*/
85+
function classifyControlPlaneDigestSample(sample) {
86+
const text = String(sample || '')
87+
const controlConfirm =
88+
/|Hook|MCP|CLI||/i.test(text) && /\s*CP|CP[123]\s*[|]|memory_cp_confirm/i.test(text)
89+
if (!controlConfirm) return 'not-control-plane-confirm'
90+
const hasDigest = /artifactSha256|sha256|digest|ConfirmBindingGate|artifactPath/i.test(text)
91+
if (!hasDigest) return 'missing-digest'
92+
return 'ok'
93+
}
94+
95+
/**
96+
* Author self-check must not be labeled independent design review (PF-140 / VL-057 residual).
97+
* @param {string} sample
98+
* @returns {'ok'|'author-self-review-as-independent'|'not-review-claim'}
99+
*/
100+
function classifyAuthorSelfReviewBoundarySample(sample) {
101+
const text = String(sample || '')
102+
const claimsIndependent = /||independent\s*review||/i.test(text)
103+
if (!claimsIndependent) return 'not-review-claim'
104+
const onlySelf = /|self-review|||.*/i.test(text)
105+
const hasIndependentEvidence = /||Codex.*|Grok.*|audit\s*session| Agent/i.test(text)
106+
if (onlySelf && !hasIndependentEvidence) return 'author-self-review-as-independent'
107+
if (/|/i.test(text)) return 'author-self-review-as-independent'
108+
return 'ok'
109+
}
110+
38111
/**
39112
* @param {string} sample
40113
* @returns {'ok'|'stop-without-self-fix'|'not-ci-regression'}
@@ -49,18 +122,90 @@ function classifyOwnIntroducedRegressionSample(sample) {
49122
return 'ok'
50123
}
51124

125+
/**
126+
* Strip quoted / meta discussion of the word 或 so rules about forbidding "或"
127+
* do not false-positive as dual-action forks.
128+
* @param {string} text
129+
* @returns {string}
130+
*/
131+
function stripOrMetaNoise(text) {
132+
return String(text || '')
133+
.replace(/[^]*/g, ' ')
134+
.replace(/[^]*/g, ' ')
135+
.replace(/"[^"]*"/g, ' ')
136+
.replace(/'[^']*'/g, ' ')
137+
.replace(/[^\n;]{0,40}(?:|)/g, ' ')
138+
.replace(/[^\n;]{0,40}(?:|)/g, ' ')
139+
.replace(/[^\n;]{0,40}(?:|)/g, ' ')
140+
.replace(/forbid[^\n.]{0,40}\bor\b/gi, ' ')
141+
.replace(/do\s+not[^\n.]{0,40}\bor\b/gi, ' ')
142+
}
143+
144+
/**
145+
* Primary recommendation surface only — alternatives under 不推荐 are allowed.
146+
* @param {string} text
147+
* @returns {string}
148+
*/
149+
function primaryRecommendationSurface(text) {
150+
return String(text || '').split(/(?:^|\n)\s*(?:|\s*[(]||)/)[0]
151+
}
152+
153+
/**
154+
* Completion / free-text next-step must be a single primary action (PF-172 / PI-151 / VL-077).
155+
* @param {string} sample
156+
* @returns {'ok'|'or-fork'|'not-next-step'}
157+
*/
158+
function classifyNextStepOrForkSample(sample) {
159+
const text = String(sample || '')
160+
if (/\s*[:]\s*/.test(text)) return 'ok'
161+
const isNext = /|||||##\s*|##\s*|\s*[:]/i.test(text)
162+
if (!isNext) return 'not-next-step'
163+
164+
const primary = stripOrMetaNoise(primaryRecommendationSurface(text))
165+
166+
if (/(?:||||)[^\n]*\n\s*[-*]\s*.+\n\s*[-*]\s*.+/i.test(primary)) {
167+
return 'or-fork'
168+
}
169+
if (/(?:||||)[^\n]{0,160}?(?:|)/.test(primary)) {
170+
return 'or-fork'
171+
}
172+
if (/\s*[:][^\n]{0,120}?(?:|)/.test(primary)) {
173+
return 'or-fork'
174+
}
175+
if (
176+
/(?:||||||||)[^\n]{0,48}(?:|)[^\n]{0,48}(?:|||||||)/.test(primary) &&
177+
/(?:||)/.test(primary)
178+
) {
179+
return 'or-fork'
180+
}
181+
182+
return 'ok'
183+
}
184+
52185
function buildDisciplineProbeReceipt(sample) {
53186
return {
54187
schemaVersion: 'DisciplineExecutionProbeReceiptV1',
55188
push: classifyPushAuthorizationSample(sample),
56189
preferenceMenu: classifyPreferenceMenuAfterConvergenceSample(sample),
57-
ciRegression: classifyOwnIntroducedRegressionSample(sample)
190+
ciRegression: classifyOwnIntroducedRegressionSample(sample),
191+
nextStepOrFork: classifyNextStepOrForkSample(sample),
192+
cpArtifactBeforeConfirm: classifyCpArtifactBeforeConfirmSample(sample),
193+
codeTruthAtCp: classifyCodeTruthMatrixAtCpSample(sample),
194+
controlPlaneDigest: classifyControlPlaneDigestSample(sample),
195+
authorSelfReviewBoundary: classifyAuthorSelfReviewBoundarySample(sample)
58196
}
59197
}
60198

61199
module.exports = {
62200
classifyPushAuthorizationSample,
63201
classifyPreferenceMenuAfterConvergenceSample,
202+
classifyCpArtifactBeforeConfirmSample,
203+
classifyCodeTruthMatrixAtCpSample,
204+
classifyControlPlaneDigestSample,
205+
classifyAuthorSelfReviewBoundarySample,
64206
classifyOwnIntroducedRegressionSample,
207+
classifyNextStepOrForkSample,
208+
stripOrMetaNoise,
209+
primaryRecommendationSurface,
65210
buildDisciplineProbeReceipt
66211
}

scripts/test-discipline-execution-probe.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ const {
66
classifyPushAuthorizationSample,
77
classifyPreferenceMenuAfterConvergenceSample,
88
classifyOwnIntroducedRegressionSample,
9+
classifyNextStepOrForkSample,
10+
classifyCpArtifactBeforeConfirmSample,
11+
classifyCodeTruthMatrixAtCpSample,
12+
classifyControlPlaneDigestSample,
13+
classifyAuthorSelfReviewBoundarySample,
914
buildDisciplineProbeReceipt
1015
} = require('./lib/discipline-execution-probe')
1116

@@ -34,7 +39,75 @@ assert.strictEqual(
3439
'ok'
3540
)
3641

42+
// PF-172 next-step or-fork
43+
assert.strictEqual(classifyNextStepOrForkSample('只改本地文件'), 'not-next-step')
44+
assert.strictEqual(
45+
classifyNextStepOrForkSample('推荐下一步:对齐 VL-010,或挑 PF-138 做 Intake'),
46+
'or-fork'
47+
)
48+
assert.strictEqual(
49+
classifyNextStepOrForkSample(
50+
'推荐下一步:实施 PF-172。\n不推荐:先洗 VL 表(不修本逃逸面)。'
51+
),
52+
'ok'
53+
)
54+
assert.strictEqual(classifyNextStepOrForkSample('推荐:无后续动作'), 'ok')
55+
56+
// PF-138 CpArtifactBeforeConfirm
57+
assert.strictEqual(classifyCpArtifactBeforeConfirmSample('随便聊聊'), 'not-cp-confirm')
58+
assert.strictEqual(
59+
classifyCpArtifactBeforeConfirmSample('方案已收敛。请确认 CP1'),
60+
'missing-cp-artifact'
61+
)
62+
assert.strictEqual(
63+
classifyCpArtifactBeforeConfirmSample(
64+
'请确认 CP1;产物已落盘 `01-需求确认.md`(CpArtifactBeforeConfirmGate)'
65+
),
66+
'ok'
67+
)
68+
69+
// PF-139 CodeTruth at CP
70+
assert.strictEqual(classifyCodeTruthMatrixAtCpSample('修个文案'), 'not-control-plane-cp')
71+
assert.strictEqual(
72+
classifyCodeTruthMatrixAtCpSample('控制面 MCP 方案已定稿,可确认 CP2,推荐方案=Wave'),
73+
'missing-code-truth-matrix'
74+
)
75+
assert.strictEqual(
76+
classifyCodeTruthMatrixAtCpSample(
77+
'控制面 CP2 技术方案;CodeTruthEvidenceMatrixGate repoPath=mcp/profile-server.js currentBehavior=… negativeProbe=…;可确认 CP2'
78+
),
79+
'ok'
80+
)
81+
82+
// PF-140 digest + author self-review
83+
assert.strictEqual(classifyControlPlaneDigestSample('确认普通需求'), 'not-control-plane-confirm')
84+
assert.strictEqual(
85+
classifyControlPlaneDigestSample('控制面 Hook 变更,确认 CP2 已完成'),
86+
'missing-digest'
87+
)
88+
assert.strictEqual(
89+
classifyControlPlaneDigestSample(
90+
'控制面 CLI 变更;确认 CP2;artifactPath=02-技术方案.md artifactSha256=abc ConfirmBindingGate'
91+
),
92+
'ok'
93+
)
94+
assert.strictEqual(classifyAuthorSelfReviewBoundarySample('无审查声明'), 'not-review-claim')
95+
assert.strictEqual(
96+
classifyAuthorSelfReviewBoundarySample('独立审查已通过;作者自审自检通过,无需他人'),
97+
'author-self-review-as-independent'
98+
)
99+
assert.strictEqual(
100+
classifyAuthorSelfReviewBoundarySample('独立审查已通过;Codex 审查报告 + audit session 证据'),
101+
'ok'
102+
)
103+
37104
const r = buildDisciplineProbeReceipt('hello')
38105
assert.strictEqual(r.push, 'not-release-action')
106+
assert.strictEqual(r.nextStepOrFork, 'not-next-step')
107+
assert.strictEqual(r.cpArtifactBeforeConfirm, 'not-cp-confirm')
108+
assert.strictEqual(r.codeTruthAtCp, 'not-control-plane-cp')
109+
110+
const bad = buildDisciplineProbeReceipt('推荐下一步:做 A 或做 B')
111+
assert.strictEqual(bad.nextStepOrFork, 'or-fork')
39112

40113
console.log('discipline-execution-probe tests passed')

skills/audit-requirements/SKILL.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,50 @@ description: 需求文档审查维度 RQ-1~RQ-8 — 需求定义/功能描述/
4141
- 需求应包含足够的正例、反例、边界例、异常与回退口径;缺失项应进入待确认问题
4242
- 技术验收 / 测试方案类文档才要求可执行验证用例、正向/负向场景和通过标准
4343

44+
**RQ-4 需求一致性 🔴**
45+
- 同一需求包内编号、术语、范围、优先级与阶段声明不得互相矛盾;同名功能不得给出相反行为
46+
- 变更需求必须锚定原需求基线(路径/版本/章节),并显式列出 retained / changed / removed / deferred
47+
- 正例:变更确认稿可追溯到原 F-01,且“不做什么”与正文功能列表无冲突
48+
- 反例:同一 F-id 在概况写“必须登录”、在确认写“匿名可用”且无裁决记录 → 阻断
49+
- 必要证据:冲突对(摘录 A/B)、裁决位置(确认稿/报告)、未决冲突清单(可为空)
50+
- 验证路线:交叉阅读 `00/01` + 功能清单 + 排除范围;无冲突则写 `RQ-4=PASS`,有未决冲突则 `FAIL` 并禁止进入 CP2 编码
51+
52+
**RQ-5 影响分析完整性 🟡**
53+
- 当需求触及外部系统、共享契约、多模块、数据迁移、权限边界或发布面时,必须说明影响面与回滚/降级口径;纯文案/单页说明可标 `N/A + skipReason`
54+
- 影响面至少覆盖:直接消费者、间接调用链/数据依赖、破坏性兼容风险、需同步的 Profile/文档/配置(若适用)
55+
- 正例:新增公开 API 列出 SDK/网站/旧客户端三类消费者与兼容策略
56+
- 反例:引入跨服务写路径却只写“影响后端”且无消费者与回滚 → `FAIL`
57+
- 必要证据:ImpactSurface 列表(或 N/A 理由)、兼容/回滚一句、关联模块路径
58+
- 验证路线:对照变更类型检查是否触发外部依赖/共享契约;无外部依赖时 `RQ-5=N/A`
59+
60+
**RQ-6 约束条件明确性 🟡**
61+
- 业务、合规、性能、安全、环境与时间窗口等约束须可观察、可判定,禁止仅写“尽量快/安全/兼容”
62+
- 约束写清:主体(谁遵守)、条件(何时生效)、边界(上限/下限/禁止项)、例外(若有)
63+
- 正例:“管理端导出单次 ≤ 1 万行;超限返回 400 与可操作提示”
64+
- 反例:“系统应高性能”且无指标/场景/失败表现 → `FAIL` 或降为待确认
65+
- 必要证据:约束条目表(主体/条件/边界/例外)或明确“无额外约束”
66+
- 验证路线:扫描非功能/约束章节;技术量化指标可下沉 CP2,但业务边界须在需求层可判定
67+
68+
**RQ-7 版本与变更追溯 💡/🔴**
69+
- 有版本号、修订日期或变更记录时,当前有效需求必须可指向唯一 current 真相源;历史版本须标记 historical/superseded
70+
- 需求变更路径:概况 → 确认 → 回写目标需求真相源;缺失回写锚点时不得宣称“已同步主需求”
71+
- 正例:`01-需求确认.md` 文首 `version/status/supersedes`,旧稿 status=superseded
72+
- 反例:多份 active 确认稿并行、无 supersedes → 阻断(🔴);仅缺 changelog 小节但 current 唯一 → 💡 改进
73+
- 必要证据:current 路径、version/status、supersedes 链(可空)、变更回写位置
74+
- 验证路线:列任务目录全部需求稿 status;current 计数必须 = 1
75+
76+
**RQ-8 项目上下文一致性 🟡**
77+
- 需求中的技术栈、目录边界、脚本、环境、发布面与 active Profile / 仓库现实一致;不一致须标注假设或待确认,不得静默当作事实
78+
- 无项目 Profile 时 `RQ-8=N/A`;有 Profile 时至少核对 01 项目信息中的栈/阶段/关键路径是否与需求假设冲突
79+
- 正例:需求声明 Node 20 + monorepo 包名与 Profile 01 一致
80+
- 反例:需求写“必须改 Django 中间件”而 Profile/仓库为纯 Node CLI → `FAIL` 或待确认
81+
- 必要证据:Profile 对照表(字段/需求声明/一致或差异)、差异处置(改需求/改 Profile/待确认)
82+
- 验证路线:定向读取 active Profile 与需求假设句;差异进入待确认或阻断扩展范围
83+
84+
**RequirementDimensionCompletenessGate(声明完整性)**
85+
- 凡 Skill/报告/清单声明覆盖 `RQ-1~RQ-8`,正文必须为每一维提供可执行通过条件、至少一类失败反例、必要证据字段与验证路线(或显式 `N/A + skipReason` 边界)
86+
- 仅维度总览表/索引而无独立定义 → 本审查层与 validate 探针均视为不完整,不得标 RQ 全覆盖 PASS
87+
4488
**PhaseDeliverySemanticGate(条件)**
4589
- 多阶段需求、路线图或“全部纳入某阶段”必须为每阶段标记 `phaseKind=planning-only / design-ready / implementation / release`,分别说明 planning coverage 与 source delivery。
4690
- 建立 `PhaseDeliverySemanticMatrix`:originalIntent、phaseKind、inScope、sourceDelivery、entry、exit、carryOver、closeRule、confirmationText 必须一致。

0 commit comments

Comments
 (0)