feat(intent): match rdev document names when the user asks to generate
生成项目建议书 now resolves to rd_proposal. Longer titles win, so 系统设计方案 is not mistaken for the shorter business-stage phrase, and an explicit generate command is no longer swallowed by a progress question. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -49,7 +49,7 @@ updated: 2026-09-19
|
||||
| FE App.tsx | 部分。登录壳不再静态引用导出引擎、文件预览、管理台、块编辑器和图表。入口包 2.83 MB → 1.00 MB(br 626 KB → 235 KB)。`loginShellImports.test.ts`。工作台主体仍在 `App.tsx`,未按 hook 拆文件 |
|
||||
| H1-del | 关闭理由:`allowLegacyTestPrincipal` 仍被 `AllowInMemory && ContextDbHost==""` 的 dev/test 内存测试使用。删除会把这批测试变成未登录 401。保留告警,删除另开、先改测试夹具 |
|
||||
|
||||
仍开放:rdev P3 三样本真 PI、P2 前端、P4 键名空间化、首次加载 U1/U6/U7、dev 容器重建与治理库 PG。U4 已去掉登录时的全项目成员扇出,并让会话拉取不再跟着项目数组重跑(`initFanout.test.ts`);浏览器请求次数证据还没有。S2 已完成。S3 的 overlay 与 R2/R3 门禁已落地;P3 真 PI 报告未做。U3 登录壳拆包与 U5 身份复用仍有效。H2 批注/审计的项目成员检查在 Postgres 上走 `RequireProjectAccess`;内存授权器在无库时把任意正用户 id 视为成员,这是 `TestAuthorizeDocRevision_ReviewerAndNonMemberCannotWrite` 的隔离前提,不能改成默认拒绝。H2 的 HTTP 负例因此要在 `TEST_DATABASE_URL` 上补,本机未设置该变量。M7 的投影意图已随账本提交(goose `00053`);容器 schema 仍是 52,要等占槽重建后才升到 53。
|
||||
仍开放:rdev P2 前端规划 UI、P3 三样本真 PI、P4 键名空间化、首次加载 U1/U6/U7、dev 容器重建与治理库 PG。P2 的对话点名已接:`生成项目建议书` 命中 `rd_proposal`,长标题压过短标题。H2 批注/审计的项目成员检查在 Postgres 上走 `RequireProjectAccess`;内存授权器在无库时把任意正用户 id 视为成员,这是 `TestAuthorizeDocRevision_ReviewerAndNonMemberCannotWrite` 的隔离前提,不能改成默认拒绝。H2 的 HTTP 负例因此要在 `TEST_DATABASE_URL` 上补,本机未设置该变量。M7 的投影意图已随账本提交(goose `00053`);容器 schema 仍是 52,要等占槽重建后才升到 53。
|
||||
|
||||
红线关闭(不是实现):
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
| 对话数据治理(D7) | 留存/删除口径、库·备份·日志控制、敏感字段加密边界 | deferred | deferred_enhancement(违规则升 blocker) | [计划](../remediation/2026-09-16-feature-matrix-production-readiness-plan.md) Scope Boundaries;[收口 §7.1](../remediation/2026-09-16-partial-deferred-completion-plan.md);[D7 ADR 草案](../adr/2026-09-17-d7-chat-data-retention.md);[W7 盘点](../audit/2026-09-17-d7-data-inventory.md);[报告](../test-reports/2026-09-17-w7-d7-inventory-test-report.md) | **after W7**:只读盘点完成(sessions/messages/KB/日志/备份/event)。**未发现**已确认基线违规,未写 gap-matrix。口径仍 `pending-compliance-signoff`;禁止按 365/180/30 物理删除。本行不升 done | `chat` |
|
||||
| 智能体小队 | 角色-任务-工作区-黑板 | done | non_blocker | [agent-squad](../system-design/agent-squad/);[test 2026-09-07](../test-reports/2026-09-07-dynamic-squad-planning-test-report.md) | 非关键步骤降级(A5/A6)属 backlog | `squad` |
|
||||
| 工作流治理 | 三正式工作流、设置页/DutyMode、动作级鉴权、真实 PI 三工作流 E2E、full/none 混合长链 | done | non_blocker | [三工作流整改](../remediation/2026-09-12-three-workflows-strict-governance-remediation.md);[409/职责](../test-reports/2026-09-14-workflow-plan-409-frozen-and-optional-duties-test-report.md);[鉴权](../test-reports/2026-09-14-governance-action-level-authz-test-report.md);[PI E2E](../test-reports/2026-09-16-pi-three-workflow-e2e-test-report.md);[Wave2 full/none](../test-reports/2026-09-16-wave2-real-pi-evidence-test-report.md) | full 跨用户审批 + none 免审批不阻塞已签。纯软件自举仍独立 planned | `workflow` |
|
||||
| 研发设计控制(rdev) | 医疗第 4 工作流:13×60 manifest/模板、`squad.rdev_doc.v1` kind 编排、main-completed 硬 precheck、StageKey 动态化;不含前端/真 PI/键名空间化 | partial | deferred_enhancement | [设计](../system-design/project-workflow/07-研发工作流rdev接入-设计与实施计划.md);[short-term](../archive/ops/2026-09-17-rdev-workflow-integration.md);[模板深度](../archive/ops/2026-09-17-rdev-template-depth.md);[S0+S1 首批报告](../test-reports/2026-09-17-rdev-template-depth-test-report.md);[S1 续批报告](../test-reports/2026-09-17-rdev-template-depth-s1-continued-test-report.md);[写作规范](../system-design/project-workflow/08-rdev模板写作规范.md);[backlog](../backlog/2026-09-17-rdev-phase2-backlog.md) | **P0+P1 done**。**模板深度 S0+S1 首批 + S1 续批 done**:60/60 模板已补强;真 PI 抽样 PASS(含 `rd_planning_review` + `rd_design_vv`,项目 6446)。剩余:S3 已接 overlay(按角色/阶段/复杂度写入 `OVERLAY.md`)与 R2/R3 确定性门禁(`TestRDevOverlayFiltersRoleAndComplexity`、`TestRDevQualityGates`)。P3 三样本真 PI E2E、P2 前端、P4 键名空间化仍未做 | `workflow-def` / `rdev-templates` |
|
||||
| 研发设计控制(rdev) | 医疗第 4 工作流:13×60 manifest/模板、`squad.rdev_doc.v1` kind 编排、main-completed 硬 precheck、StageKey 动态化;不含前端/真 PI/键名空间化 | partial | deferred_enhancement | [设计](../system-design/project-workflow/07-研发工作流rdev接入-设计与实施计划.md);[short-term](../archive/ops/2026-09-17-rdev-workflow-integration.md);[模板深度](../archive/ops/2026-09-17-rdev-template-depth.md);[S0+S1 首批报告](../test-reports/2026-09-17-rdev-template-depth-test-report.md);[S1 续批报告](../test-reports/2026-09-17-rdev-template-depth-s1-continued-test-report.md);[写作规范](../system-design/project-workflow/08-rdev模板写作规范.md);[backlog](../backlog/2026-09-17-rdev-phase2-backlog.md) | **P0+P1 done**。**模板深度 S0+S1 首批 + S1 续批 done**:60/60 模板已补强;真 PI 抽样 PASS(含 `rd_planning_review` + `rd_design_vv`,项目 6446)。剩余:S3 overlay 与 R2/R3 已接。P2 名称匹配已接:`生成项目建议书` → `rd_proposal`,更长标题优先(`TestIntentRecognition_RDevProposalAndLongerTitleWins`)。P2 前端规划 UI、P3 三样本真 PI E2E、P4 键名空间化仍未做 | `workflow-def` / `rdev-templates` |
|
||||
| 纯软件项目自举 | 软件项目创建、Bootstrap、Delivery、受控工程交接 | partial | deferred_enhancement(明确不挡首发) | [设计](../system-design/2026-09-14-software-project-bootstrap-workflow.md);[ADR](../adr/2026-09-16-software-project-bootstrap-keys.md);[L1 报告](../test-reports/2026-09-16-software-project-bootstrap-test-report.md);[W5a](../test-reports/2026-09-17-w5a-software-bootstrap-ui-pi-test-report.md) | **before**:L1 + UI 仍克隆医疗模板。**after W5a**:catalog 驱动创建、软件不挂医疗 key、docs_only 真 PI 建议 delivery 且不自动激活。**SWBOOT-10 仍 stub**(W5b);本行不升 done | `workflow-design` |
|
||||
| IAM / 鉴权 | 项目级 API 鉴权 + RBAC;会话/阶段授权 | done | 加固 | [v3 full inventory](../audit/authorization-matrix-v3-full.md);[session 中断](../test-reports/2026-09-16-iam-route-inventory-test-report.md);[fail-closed](../test-reports/2026-09-16-wave1-iam-fail-closed-test-report.md);[410 策略](../audit/2026-09-16-bug11-410-retire-decision.md);[R13](../audit/2026-09-16-r13-data-safety-checklist.md) | 全路由 inventory + session 中断负例 + 410 冻结 + R13 基线已签 | `authz` |
|
||||
| 文档治理 | 法定文档生命周期 + 租约 + 受控正文读取 | done | non_blocker | [docgov](../test-reports/2026-09-08-docgov-lifecycle-and-claiming-framework-test-report.md);[正文 UI](../test-reports/2026-09-14-controlled-content-ui-acceptance.md);[canonical hash](../remediation/2026-09-14-controlled-content-canonical-hash-remediation.md) | 项目 143 只读;不终止 | `docgov` |
|
||||
|
||||
@@ -4,10 +4,13 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"chat-agent-system/internal/maturity"
|
||||
"chat-agent-system/internal/squad"
|
||||
)
|
||||
|
||||
// Recognizer 意图识别器接口
|
||||
@@ -1123,6 +1126,9 @@ func matchSupplementInfo(input string) *CandidateIntent {
|
||||
}
|
||||
|
||||
func matchQueryProgress(input string) *CandidateIntent {
|
||||
if matchTriggerTask(input) != nil {
|
||||
return nil
|
||||
}
|
||||
if isWorkflowControlInput(input) || isRunControlInput(input) {
|
||||
return nil
|
||||
}
|
||||
@@ -1168,6 +1174,10 @@ func matchQueryProgress(input string) *CandidateIntent {
|
||||
}
|
||||
|
||||
func matchTriggerTask(input string) *CandidateIntent {
|
||||
type phrase struct {
|
||||
text string
|
||||
key string
|
||||
}
|
||||
taskMap := map[string]string{
|
||||
"需求收集表": "intake",
|
||||
"需求表": "intake",
|
||||
@@ -1180,16 +1190,34 @@ func matchTriggerTask(input string) *CandidateIntent {
|
||||
"报价单": "cdo_quote",
|
||||
"报价": "cdo_quote",
|
||||
}
|
||||
|
||||
phrases := make([]phrase, 0, len(taskMap)+60)
|
||||
for kw, tKey := range taskMap {
|
||||
if strings.Contains(input, kw) && (strings.Contains(input, "生成") || strings.Contains(input, "做") || strings.Contains(input, "跑") || strings.Contains(input, "出")) {
|
||||
phrases = append(phrases, phrase{text: kw, key: tKey})
|
||||
}
|
||||
for _, item := range squad.RDevTriggerPhrases() {
|
||||
phrases = append(phrases, phrase{text: item.Text, key: item.TaskKey})
|
||||
}
|
||||
sort.Slice(phrases, func(i, j int) bool {
|
||||
li := utf8.RuneCountInString(phrases[i].text)
|
||||
lj := utf8.RuneCountInString(phrases[j].text)
|
||||
if li != lj {
|
||||
return li > lj
|
||||
}
|
||||
return phrases[i].text < phrases[j].text
|
||||
})
|
||||
|
||||
if !(strings.Contains(input, "生成") || strings.Contains(input, "做") || strings.Contains(input, "跑") || strings.Contains(input, "出")) {
|
||||
return nil
|
||||
}
|
||||
for _, item := range phrases {
|
||||
if strings.Contains(input, item.text) {
|
||||
return &CandidateIntent{
|
||||
Intent: IntentTriggerTask,
|
||||
Confidence: 0.86,
|
||||
FilledSlots: map[string]string{
|
||||
SlotTargetTask: tKey,
|
||||
SlotTargetTask: item.key,
|
||||
},
|
||||
Reasoning: fmt.Sprintf("用户要求显式生成交付任务: %s (%s)", kw, tKey),
|
||||
Reasoning: fmt.Sprintf("用户要求显式生成交付任务: %s (%s)", item.text, item.key),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"chat-agent-system/internal/maturity"
|
||||
"chat-agent-system/internal/intent"
|
||||
"chat-agent-system/internal/maturity"
|
||||
)
|
||||
|
||||
func TestIntentRecognition_QueryProgress(t *testing.T) {
|
||||
@@ -91,6 +91,43 @@ func TestIntentRecognition_TriggerTask(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntentRecognition_RDevProposalAndLongerTitleWins(t *testing.T) {
|
||||
rec := intent.NewRecognizer()
|
||||
ctx := &intent.ContextSnapshot{SnapshotID: "ctx_rdev_trigger"}
|
||||
|
||||
proposal, err := rec.Recognize(context.Background(), "请生成项目建议书", ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if proposal.TopIntent.FilledSlots[intent.SlotTargetTask] != "rd_proposal" {
|
||||
t.Fatalf("项目建议书 want rd_proposal got %s", proposal.TopIntent.FilledSlots[intent.SlotTargetTask])
|
||||
}
|
||||
|
||||
design, err := rec.Recognize(context.Background(), "生成设计方案", ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if design.TopIntent.FilledSlots[intent.SlotTargetTask] != "prelim_design" {
|
||||
t.Fatalf("设计方案 must stay prelim_design, got %s", design.TopIntent.FilledSlots[intent.SlotTargetTask])
|
||||
}
|
||||
|
||||
system, err := rec.Recognize(context.Background(), "生成系统设计方案", ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if system.TopIntent.FilledSlots[intent.SlotTargetTask] != "rd_system_design" {
|
||||
t.Fatalf("系统设计方案 want rd_system_design got %s", system.TopIntent.FilledSlots[intent.SlotTargetTask])
|
||||
}
|
||||
|
||||
intake, err := rec.Recognize(context.Background(), "生成用户需求收集表", ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if intake.TopIntent.FilledSlots[intent.SlotTargetTask] != "intake" {
|
||||
t.Fatalf("用户需求收集表 must stay intake, got %s", intake.TopIntent.FilledSlots[intent.SlotTargetTask])
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntentRecognition_CasualChat(t *testing.T) {
|
||||
rec := intent.NewRecognizer()
|
||||
res, err := rec.Recognize(context.Background(), "你好,请问你是谁?", nil)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package squad
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// RDevArtifactKind 研发文档产物类别(对齐参考项目 artifact_kind)。
|
||||
type RDevArtifactKind string
|
||||
@@ -351,7 +354,51 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// LookupRDevTask 按任务键或中文别名查找 rdev 目录项。
|
||||
// RDevTriggerPhrase 对话里用来点名研发任务的标题或别名。
|
||||
type RDevTriggerPhrase struct {
|
||||
Text string
|
||||
TaskKey string
|
||||
}
|
||||
|
||||
// RDevTriggerPhrases 返回可用于「生成/出」匹配的研发文档名。
|
||||
// 过短的英文缩写不进入,避免 UN/BOM 这类记号命中无关句子。
|
||||
func RDevTriggerPhrases() []RDevTriggerPhrase {
|
||||
seen := map[string]string{}
|
||||
var out []RDevTriggerPhrase
|
||||
add := func(text, key string) {
|
||||
text = strings.TrimSpace(text)
|
||||
if text == "" || rdevTriggerAliasUnsafe(text) {
|
||||
return
|
||||
}
|
||||
if prev, ok := seen[text]; ok && prev != key {
|
||||
return
|
||||
}
|
||||
if _, ok := seen[text]; ok {
|
||||
return
|
||||
}
|
||||
seen[text] = key
|
||||
out = append(out, RDevTriggerPhrase{Text: text, TaskKey: key})
|
||||
}
|
||||
for _, item := range RDevCatalog {
|
||||
add(item.Title, item.Key)
|
||||
for _, alias := range item.Aliases {
|
||||
add(alias, item.Key)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func rdevTriggerAliasUnsafe(text string) bool {
|
||||
if utf8.RuneCountInString(text) < 2 {
|
||||
return true
|
||||
}
|
||||
for _, r := range text {
|
||||
if r > 127 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
func LookupRDevTask(taskKey string) (RDevTaskMeta, bool) {
|
||||
if t, ok := rdevCatalogByKey[taskKey]; ok {
|
||||
return t, true
|
||||
|
||||
Reference in New Issue
Block a user