✨ feat(spx): issue marker 支持第三种类型 prototype

- --type 三选一 spec|plan|prototype,marker 行写 <!-- spx:prototype=路径 -->,
  与 spec/plan 共用同一套增量更新逻辑;类型→state 字段的映射收成一张表
- 同步写 state JSON 的 prototypeFile(pattern 要求 dir/.../x.json)
- 顺手补上 b62e58d 漏掉的 prototypeSessionId / prototypeProfilePath:
  schema 与 knownStateFields 都没有它们,带原型会话的工单一 merge
  就会被 additionalProperties 拒掉

Claude-Session: https://claude.ai/code/session_01Kgmb1LvrPmFX3NoUDB4zZC
This commit is contained in:
cruldra committed 2026-09-15 00:08:31 +08:00
1 parent 814e0e8f5a
commit def5f4e88c
5 files changed
+89 -8

No files matched your search

+24 -7
View File
@@ -111,6 +111,20 @@ func buildRootCmd() *cobra.Command {
return root
}
// markerTypeToStateField maps a `spx issue marker --type` value to the state
// JSON field it mirrors. All three types share one incremental-update path;
// this table is the single source of truth — adding a type only touches it
// (plus markerTypesHint below, which is what the user sees).
var markerTypeToStateField = map[string]string{
"spec": "specFile",
"plan": "planFile",
"prototype": "prototypeFile",
}
// markerTypesHint is the human-readable enumeration used in help text and the
// "bad --type" error, kept next to the table so the two can't drift.
const markerTypesHint = "spec、plan 或 prototype"
func buildIssueCmd() *cobra.Command {
issue := &cobra.Command{
Use: "issue",
@@ -215,17 +229,17 @@ func buildIssueCmd() *cobra.Command {
markerCmd := &cobra.Command{
Use: "marker",
Short: "增量更新现有工单的 spec/plan marker,并同步合并 specFile/planFile 进 state JSON",
Short: "增量更新现有工单的 spec/plan/prototype marker,并同步合并 specFile/planFile/prototypeFile 进 state JSON",
RunE: func(cmd *cobra.Command, args []string) error {
if imIssue <= 0 {
return fmt.Errorf("--issue 必须是正整数")
}
if imType != "spec" && imType != "plan" {
return fmt.Errorf("--type 必须是 spec 或 plan,得到 %q", imType)
fieldName, ok := markerTypeToStateField[imType]
if !ok {
return fmt.Errorf("--type 必须是 %s,得到 %q", markerTypesHint, imType)
}
// Fail fast on a bad path before we touch the network — the
// schema's specFile/planFile pattern doubles as input check.
fieldName := map[string]string{"spec": "specFile", "plan": "planFile"}[imType]
// schema's pattern for this field doubles as input check.
patch := map[string]any{fieldName: imValue}
if err := state.ValidateValue(patch); err != nil {
return err
@@ -263,8 +277,8 @@ func buildIssueCmd() *cobra.Command {
},
}
markerCmd.Flags().IntVar(&imIssue, "issue", 0, "工单编号(必填)")
markerCmd.Flags().StringVar(&imType, "type", "", "marker 类型:spec 或 plan(必填)")
markerCmd.Flags().StringVar(&imValue, "value", "", "marker 值(路径,必填,同步写入 state JSON 的 specFile/planFile)")
markerCmd.Flags().StringVar(&imType, "type", "", "marker 类型:"+markerTypesHint+"(必填)")
markerCmd.Flags().StringVar(&imValue, "value", "", "marker 值(路径,必填,同步写入 state JSON 的 specFile/planFile/prototypeFile)")
_ = markerCmd.MarkFlagRequired("issue")
_ = markerCmd.MarkFlagRequired("type")
_ = markerCmd.MarkFlagRequired("value")
@@ -394,11 +408,14 @@ var knownStateFields = map[string]struct{}{
"implementSessionId": {},
"reviewSessionId": {},
"testSessionId": {},
"prototypeSessionId": {},
"profilePath": {},
"brainstormProfilePath": {},
"testProfilePath": {},
"prototypeProfilePath": {},
"specFile": {},
"planFile": {},
"prototypeFile": {},
"prDiffFile": {},
"pr": {},
"prMerged": {},