autosnippet 3.2.1 → 3.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +23 -0
  2. package/config/default.json +7 -0
  3. package/dashboard/dist/assets/{icons-18VxiaCT.js → icons-pSac4wYO.js} +101 -96
  4. package/dashboard/dist/assets/{index-CRH5Umim.js → index-6itPuGFl.js} +45 -45
  5. package/dashboard/dist/assets/index-DNOHYBhy.css +1 -0
  6. package/dashboard/dist/index.html +3 -3
  7. package/lib/cli/SetupService.js +245 -46
  8. package/lib/domain/knowledge/KnowledgeEntry.js +11 -0
  9. package/lib/domain/task/Task.js +32 -2
  10. package/lib/domain/task/TaskDependency.js +1 -0
  11. package/lib/external/mcp/McpServer.js +180 -6
  12. package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +2 -1
  13. package/lib/external/mcp/handlers/decide.js +109 -0
  14. package/lib/external/mcp/handlers/ready.js +42 -0
  15. package/lib/external/mcp/handlers/system.js +12 -0
  16. package/lib/external/mcp/handlers/task.js +7 -19
  17. package/lib/external/mcp/tools.js +83 -42
  18. package/lib/http/routes/knowledge.js +10 -10
  19. package/lib/http/routes/task.js +81 -1
  20. package/lib/http/utils/routeHelpers.js +30 -0
  21. package/lib/infrastructure/config/Paths.js +18 -8
  22. package/lib/repository/task/TaskRepository.impl.js +3 -1
  23. package/lib/service/cursor/AgentInstructionsGenerator.js +6 -4
  24. package/lib/service/knowledge/KnowledgeService.js +12 -1
  25. package/lib/service/task/TaskGraphService.js +243 -3
  26. package/package.json +1 -1
  27. package/skills/autosnippet-intent/SKILL.md +3 -1
  28. package/skills/autosnippet-recipes/SKILL.md +3 -1
  29. package/templates/claude-hooks.yaml +1 -0
  30. package/templates/copilot-instructions.md +48 -13
  31. package/templates/cursor-rules/autosnippet-conventions.mdc +11 -0
  32. package/templates/cursor-rules/autosnippet-workflow.mdc +16 -7
  33. package/templates/guard-ci.yml +22 -0
  34. package/templates/pre-commit-guard.sh +2 -1
  35. package/dashboard/dist/assets/index-BJiuaVPD.css +0 -1
@@ -5,7 +5,8 @@
5
5
  *
6
6
  * Step 1 .autosnippet/ 运行时目录 + config.json + .gitignore
7
7
  * Step 2 AutoSnippet/ 子仓库(核心数据 + 权限能力 + skills/)
8
- * Step 3 IDE 集成(VSCode MCP + Cursor MCP + copilot-instructions + cursor-rules + skills-template)
8
+ * Step 3 IDE 集成(VSCode MCP + Cursor MCP + copilot-instructions + cursor-rules
9
+ * + skills-template + cursor-workflow + claude-hooks + guard-ci + pre-commit-hook)
9
10
  * Step 4 SQLite 数据库 + V1 数据迁移
10
11
  * Step 5 平台相关初始化(macOS → Xcode Snippets)
11
12
  *
@@ -432,24 +433,27 @@ export class SetupService {
432
433
  /* ═══ Step 3: IDE 集成 ═══════════════════════════════ */
433
434
 
434
435
  stepIDE() {
436
+ const configured = [];
437
+
438
+ // MCP 配置(总是写入/合并,不会跳过)
435
439
  this._configureVSCodeMCP();
440
+ configured.push('vscode-mcp');
436
441
  this._configureCursorMCP();
437
- this._copyCopilotInstructions();
438
- this._generateAgentInstructionFiles();
439
- this._copyCursorRules();
440
- this._copySkillsTemplate();
442
+ configured.push('cursor-mcp');
443
+
444
+ // 模板文件(可能因已存在/用户文件而跳过)
445
+ if (this._copyCopilotInstructions()) configured.push('copilot-instructions');
446
+ this._ensureTaskGraphInstructions();
447
+ if (this._generateAgentInstructionFiles()) configured.push('agent-instructions');
448
+ if (this._copyCursorRules()) configured.push('cursor-rules');
449
+ if (this._copySkillsTemplate()) configured.push('skills-template');
450
+ if (this._copyCursorWorkflow()) configured.push('cursor-workflow');
451
+ if (this._copyClaudeHooks()) configured.push('claude-hooks');
452
+ if (this._copyGuardCI()) configured.push('guard-ci');
453
+ if (this._installPreCommitHook()) configured.push('pre-commit-hook');
441
454
  // NOTE: .qoder/ .trae/ 不再自动创建,用户可通过 `asd mirror` 按需生成
442
455
 
443
456
  const extResult = this._installVSCodeExtension();
444
-
445
- const configured = [
446
- 'vscode-mcp',
447
- 'cursor-mcp',
448
- 'copilot-instructions',
449
- 'agent-instructions',
450
- 'cursor-rules',
451
- 'skills-template',
452
- ];
453
457
  if (extResult) {
454
458
  configured.push(...extResult);
455
459
  }
@@ -711,23 +715,79 @@ export class SetupService {
711
715
  _copyCopilotInstructions() {
712
716
  const src = join(REPO_ROOT, 'templates', 'copilot-instructions.md');
713
717
  if (!existsSync(src)) {
714
- return;
718
+ return false;
715
719
  }
716
720
 
717
721
  const destDir = join(this.projectRoot, '.github');
718
722
  const dest = join(destDir, 'copilot-instructions.md');
719
723
  if (existsSync(dest) && !this.force) {
720
- return;
724
+ return false;
721
725
  }
722
726
 
723
727
  // 即使 --force,也不覆盖用户原有的非 AutoSnippet 文件
724
728
  const { canWrite } = checkWriteSafety(dest);
725
729
  if (!canWrite) {
726
- return;
730
+ return false;
727
731
  }
728
732
 
729
733
  mkdirSync(destDir, { recursive: true });
730
734
  copyFileSync(src, dest);
735
+ return true;
736
+ }
737
+
738
+ /** @private TaskGraph 决策记忆激活段落注入 */
739
+ _ensureTaskGraphInstructions() {
740
+ const githubDir = join(this.projectRoot, '.github');
741
+ const filePath = join(githubDir, 'copilot-instructions.md');
742
+
743
+ // 使用 HTML 注释标记界定段落,避免标题被编辑后重复追加(D3)
744
+ const BEGIN_MARKER = '<!-- autosnippet:begin -->';
745
+ const END_MARKER = '<!-- autosnippet:end -->';
746
+
747
+ const autosnippetSection = `
748
+ ${BEGIN_MARKER}
749
+ ## AutoSnippet
750
+
751
+ This project uses AutoSnippet for knowledge management and decision tracking.
752
+ In VS Code Agent Mode, type \`#asd\` before your message to activate project memory.
753
+
754
+ ### ⚠️ MANDATORY — Call \`autosnippet_ready\` FIRST
755
+
756
+ Before doing ANY work, you MUST call \`autosnippet_ready()\` at the start of every conversation.
757
+ This loads **active team decisions** and **pending tasks** that you must respect.
758
+ Skipping this step will cause you to contradict team agreements.
759
+
760
+ \`\`\`
761
+ autosnippet_ready() → Load decisions + context (ALWAYS FIRST)
762
+ autosnippet_decide(record) → Save new team agreement
763
+ autosnippet_task(create/close) → Task lifecycle
764
+ \`\`\`
765
+ ${END_MARKER}
766
+ `;
767
+
768
+ if (!existsSync(githubDir)) {
769
+ mkdirSync(githubDir, { recursive: true });
770
+ }
771
+
772
+ if (existsSync(filePath)) {
773
+ const existing = readFileSync(filePath, 'utf8');
774
+ // 通过 HTML 注释标记检测,不依赖可编辑的标题文本
775
+ if (existing.includes(BEGIN_MARKER)) {
776
+ // 替换现有段落(支持升级)
777
+ const updated = existing.replace(
778
+ new RegExp(`${BEGIN_MARKER}[\\s\\S]*?${END_MARKER}`),
779
+ autosnippetSection.trim()
780
+ );
781
+ writeFileSync(filePath, updated);
782
+ return { action: 'updated' };
783
+ }
784
+ // 追加到末尾
785
+ writeFileSync(filePath, existing + '\n' + autosnippetSection);
786
+ return { action: 'appended' };
787
+ }
788
+
789
+ writeFileSync(filePath, autosnippetSection.trimStart());
790
+ return { action: 'created' };
731
791
  }
732
792
 
733
793
  /**
@@ -737,6 +797,7 @@ export class SetupService {
737
797
  */
738
798
  _generateAgentInstructionFiles() {
739
799
  const projectName = this.projectRoot.split('/').pop();
800
+ let wrote = false;
740
801
 
741
802
  // AGENTS.md
742
803
  const agentsPath = join(this.projectRoot, 'AGENTS.md');
@@ -746,31 +807,35 @@ export class SetupService {
746
807
  const agentsContent = [
747
808
  `# ${projectName} — Agent Instructions`,
748
809
  '',
749
- '> Auto-generated by AutoSnippet. Will be enriched after `asd bootstrap`.',
810
+ '> Auto-generated by AutoSnippet.',
750
811
  '',
751
- '## Project Knowledge Base',
812
+ '## AutoSnippet Integration',
752
813
  '',
753
- 'This project uses **AutoSnippet** for knowledge management.',
754
- 'Run `asd bootstrap` to populate the knowledge base, then this file will be',
755
- 'regenerated with coding standards, patterns, and tool references.',
814
+ 'This project uses **AutoSnippet** for knowledge management and decision tracking.',
756
815
  '',
757
- '## MCP Tools',
816
+ '### MCP Tools',
758
817
  '',
759
- 'Use these MCP tools to access the full knowledge base:',
760
- '- `autosnippet_search` — Search knowledge (mode: auto/context/keyword/semantic)',
761
- '- `autosnippet_knowledge` — Browse/get recipes (operation: list/get/insights)',
762
- '- `autosnippet_submit_knowledge` — Submit candidate (strict validation)',
818
+ '- `autosnippet_search` Search knowledge',
819
+ '- `autosnippet_knowledge` — Browse/get recipes',
820
+ '- `autosnippet_submit_knowledge` — Submit candidate',
763
821
  '- `autosnippet_guard` — Code compliance check',
764
822
  '- `autosnippet_health` — Service health & KB stats',
823
+ '- `autosnippet_ready` — Session entry point (loads decisions + tasks)',
824
+ '- `autosnippet_decide` — Decision management (record/revise/unpin)',
825
+ '- `autosnippet_task` — Task CRUD (create/claim/close/fail/defer)',
765
826
  '',
766
- '## Mandatory Constraints',
827
+ '### VS Code Agent Mode',
767
828
  '',
768
- '1. **Do NOT modify** knowledge base files directly (`AutoSnippet/`, `.autosnippet/`).',
769
- '2. Create or update knowledge **only** through MCP tools.',
770
- '3. **Prefer Recipes** as project standards; source code is supplementary.',
829
+ 'Type `#asd` before your message in Agent Mode to activate project memory.',
830
+ '',
831
+ '### Constraints',
832
+ '',
833
+ '1. Do NOT modify knowledge base files directly.',
834
+ '2. Create or update knowledge only through MCP tools.',
771
835
  '',
772
836
  ].join('\n');
773
837
  writeFileSync(agentsPath, agentsContent);
838
+ wrote = true;
774
839
  } else {
775
840
  }
776
841
  }
@@ -783,64 +848,198 @@ export class SetupService {
783
848
  const claudeContent = [
784
849
  `# ${projectName} — Claude Code Instructions`,
785
850
  '',
786
- '> Auto-generated by AutoSnippet. Will be enriched after `asd bootstrap`.',
851
+ '> Auto-generated by AutoSnippet.',
852
+ '',
853
+ '## AutoSnippet Integration',
787
854
  '',
788
- '## MCP Integration',
855
+ 'This project uses AutoSnippet for knowledge management and decision tracking.',
789
856
  '',
790
- 'This project has an AutoSnippet MCP server configured.',
791
- 'Use the following tools to access project knowledge:',
792
- '- `autosnippet_search` — Search knowledge (mode: auto/context/keyword/semantic)',
793
- '- `autosnippet_knowledge` — Browse/get recipes (operation: list/get/insights)',
794
- '- `autosnippet_submit_knowledge` — Submit candidate (strict validation)',
857
+ '### MCP Tools',
858
+ '',
859
+ '- `autosnippet_search` — Search knowledge',
860
+ '- `autosnippet_knowledge` — Browse/get recipes',
861
+ '- `autosnippet_submit_knowledge` — Submit candidate',
795
862
  '- `autosnippet_guard` — Code compliance check',
796
863
  '- `autosnippet_health` — Service health & KB stats',
864
+ '- `autosnippet_ready` — Session entry point (loads decisions + tasks)',
865
+ '- `autosnippet_decide` — Decision management (record/revise/unpin)',
866
+ '- `autosnippet_task` — Task CRUD (create/claim/close/fail/defer)',
867
+ '',
868
+ '### Claude + VS Code',
869
+ '',
870
+ 'Claude Code uses MCP tools directly. Run `asd server` to start the API server.',
871
+ 'In VS Code Agent Mode, type `#asd` before your message to activate project memory.',
797
872
  '',
798
- '## Mandatory Constraints',
873
+ '### Constraints',
799
874
  '',
800
- '1. **Do NOT modify** knowledge base files directly (`AutoSnippet/`, `.autosnippet/`).',
801
- '2. Create or update knowledge **only** through MCP tools.',
802
- '3. **Prefer Recipes** as project standards; source code is supplementary.',
875
+ '1. Do NOT modify knowledge base files directly.',
876
+ '2. Create or update knowledge only through MCP tools.',
803
877
  '',
804
878
  ].join('\n');
805
879
  writeFileSync(claudePath, claudeContent);
880
+ wrote = true;
806
881
  } else {
807
882
  }
808
883
  }
884
+ return wrote;
809
885
  }
810
886
 
811
887
  /** @private .cursor/rules/autosnippet-conventions.mdc */
812
888
  _copyCursorRules() {
813
889
  const src = join(REPO_ROOT, 'templates', 'cursor-rules', 'autosnippet-conventions.mdc');
814
890
  if (!existsSync(src)) {
815
- return;
891
+ return false;
816
892
  }
817
893
 
818
894
  const destDir = join(this.projectRoot, '.cursor', 'rules');
819
895
  const dest = join(destDir, 'autosnippet-conventions.mdc');
820
896
  if (existsSync(dest) && !this.force) {
821
- return;
897
+ return false;
822
898
  }
823
899
 
824
900
  mkdirSync(destDir, { recursive: true });
825
901
  copyFileSync(src, dest);
902
+ return true;
826
903
  }
827
904
 
828
905
  /** @private .cursor/rules/autosnippet-skills.mdc — Project Skills 索引模板 */
829
906
  _copySkillsTemplate() {
830
907
  const src = join(REPO_ROOT, 'templates', 'cursor-rules', 'autosnippet-skills.mdc');
831
908
  if (!existsSync(src)) {
832
- return;
909
+ return false;
833
910
  }
834
911
 
835
912
  const destDir = join(this.projectRoot, '.cursor', 'rules');
836
913
  const dest = join(destDir, 'autosnippet-skills.mdc');
837
914
  if (existsSync(dest) && !this.force) {
838
- return;
915
+ return false;
839
916
  }
840
917
 
841
918
  mkdirSync(destDir, { recursive: true });
842
919
  copyFileSync(src, dest);
920
+ return true;
843
921
  }
922
+ /** @private .cursor/rules/autosnippet-workflow.mdc — TaskGraph & Guard 工作流 */
923
+ _copyCursorWorkflow() {
924
+ const src = join(REPO_ROOT, 'templates', 'cursor-rules', 'autosnippet-workflow.mdc');
925
+ if (!existsSync(src)) {
926
+ return false;
927
+ }
928
+
929
+ const destDir = join(this.projectRoot, '.cursor', 'rules');
930
+ const dest = join(destDir, 'autosnippet-workflow.mdc');
931
+ if (existsSync(dest) && !this.force) {
932
+ return false;
933
+ }
934
+
935
+ mkdirSync(destDir, { recursive: true });
936
+ copyFileSync(src, dest);
937
+ return true;
938
+ }
939
+
940
+ /**
941
+ * @private .claude/hooks.yaml — Claude Code SessionStart hook
942
+ * 会话开始时自动 prime TaskGraph 上下文
943
+ */
944
+ _copyClaudeHooks() {
945
+ const src = join(REPO_ROOT, 'templates', 'claude-hooks.yaml');
946
+ if (!existsSync(src)) {
947
+ return false;
948
+ }
949
+
950
+ const claudeDir = join(this.projectRoot, '.claude');
951
+ const dest = join(claudeDir, 'hooks.yaml');
952
+ if (existsSync(dest) && !this.force) {
953
+ return false;
954
+ }
955
+
956
+ // 即使 --force,也不覆盖用户自己的 hooks 文件
957
+ const { canWrite } = checkWriteSafety(dest);
958
+ if (!canWrite) {
959
+ return false;
960
+ }
961
+
962
+ mkdirSync(claudeDir, { recursive: true });
963
+ copyFileSync(src, dest);
964
+ return true;
965
+ }
966
+
967
+ /**
968
+ * @private .github/workflows/autosnippet-guard.yml — Guard CI/CD workflow
969
+ * 在 push/PR 时自动运行 Guard 合规检查
970
+ */
971
+ _copyGuardCI() {
972
+ const src = join(REPO_ROOT, 'templates', 'guard-ci.yml');
973
+ if (!existsSync(src)) {
974
+ return false;
975
+ }
976
+
977
+ const destDir = join(this.projectRoot, '.github', 'workflows');
978
+ const dest = join(destDir, 'autosnippet-guard.yml');
979
+ if (existsSync(dest) && !this.force) {
980
+ return false;
981
+ }
982
+
983
+ const { canWrite } = checkWriteSafety(dest);
984
+ if (!canWrite) {
985
+ return false;
986
+ }
987
+
988
+ mkdirSync(destDir, { recursive: true });
989
+ copyFileSync(src, dest);
990
+ return true;
991
+ }
992
+
993
+ /**
994
+ * @private .git/hooks/pre-commit — Guard pre-commit hook
995
+ * 如果项目使用 husky,则安装到 .husky/pre-commit
996
+ * 否则安装到 .git/hooks/pre-commit
997
+ * 已存在 pre-commit hook 时不覆盖(避免破坏用户现有 hook)
998
+ */
999
+ _installPreCommitHook() {
1000
+ const src = join(REPO_ROOT, 'templates', 'pre-commit-guard.sh');
1001
+ if (!existsSync(src)) {
1002
+ return false;
1003
+ }
1004
+
1005
+ // 优先检查 husky
1006
+ const huskyDir = join(this.projectRoot, '.husky');
1007
+ const gitHooksDir = join(this.projectRoot, '.git', 'hooks');
1008
+
1009
+ let dest;
1010
+ if (existsSync(huskyDir)) {
1011
+ // 项目使用 husky — 安装到 .husky/pre-commit
1012
+ dest = join(huskyDir, 'pre-commit');
1013
+ } else if (existsSync(join(this.projectRoot, '.git'))) {
1014
+ // 普通 git 项目 — 安装到 .git/hooks/pre-commit
1015
+ dest = join(gitHooksDir, 'pre-commit');
1016
+ mkdirSync(gitHooksDir, { recursive: true });
1017
+ } else {
1018
+ // 非 git 项目,跳过
1019
+ return false;
1020
+ }
1021
+
1022
+ // 已存在 pre-commit hook 时不覆盖(可能是用户自己的 hook)
1023
+ if (existsSync(dest) && !this.force) {
1024
+ return false;
1025
+ }
1026
+
1027
+ // 即使 --force,也不覆盖用户自己的 pre-commit hook
1028
+ const { canWrite } = checkWriteSafety(dest);
1029
+ if (!canWrite) {
1030
+ return false;
1031
+ }
1032
+
1033
+ copyFileSync(src, dest);
1034
+ // 确保可执行
1035
+ try {
1036
+ execSync(`chmod +x "${dest}"`, { stdio: 'pipe' });
1037
+ } catch {
1038
+ /* Windows 不需要 chmod */
1039
+ }
1040
+ return true;
1041
+ }
1042
+
844
1043
  /** @private 镜像 .cursor/rules/ 中的 autosnippet-* 文件到目标 IDE 目录(Qoder / Trae 兼容)
845
1044
  * 只复制 autosnippet- 前缀的文件,不触碰用户自己创建的规则 */
846
1045
  _mirrorCursorToIDE(targetDirName) {
@@ -203,10 +203,21 @@ export class KnowledgeEntry {
203
203
  return [...regexRules, ...astRules];
204
204
  }
205
205
 
206
+ /* ═══ 系统标签 ═══════════════════════════════════ */
207
+
208
+ /** 系统内部标签前缀 — 内部元数据,不应暴露给最终用户 */
209
+ static SYSTEM_TAG_PREFIXES = ['dimension:', 'bootstrap:', 'internal:', 'system:'];
210
+
211
+ /** 判断是否为系统内部标签 */
212
+ static isSystemTag(tag) {
213
+ return KnowledgeEntry.SYSTEM_TAG_PREFIXES.some((p) => tag.startsWith(p));
214
+ }
215
+
206
216
  /* ═══ 序列化 ═══════════════════════════════════════ */
207
217
 
208
218
  /**
209
219
  * Domain → JSON (camelCase 直出,全链路统一)
220
+ * 注意: tags 保留原始值(含系统标签),对外 API 使用 sanitizeForAPI() 过滤
210
221
  */
211
222
  toJSON() {
212
223
  return {
@@ -60,12 +60,18 @@ export class Task {
60
60
  if (this.status === 'closed') {
61
61
  throw new Error('Cannot claim a closed task');
62
62
  }
63
+ if (this.status === 'pinned') {
64
+ throw new Error('Cannot claim a pinned decision. Use unpin_decision or revise_decision instead.');
65
+ }
63
66
  this.status = 'in_progress';
64
67
  this.assignee = assignee;
65
68
  this.updatedAt = Math.floor(Date.now() / 1000);
66
69
  }
67
70
 
68
71
  close(reason = 'Completed') {
72
+ if (this.status === 'pinned') {
73
+ throw new Error('Cannot close a pinned decision directly. Use unpin_decision or revise_decision instead.');
74
+ }
69
75
  this.status = 'closed';
70
76
  this.closeReason = reason;
71
77
  this.closedAt = Math.floor(Date.now() / 1000);
@@ -79,7 +85,28 @@ export class Task {
79
85
  this.updatedAt = Math.floor(Date.now() / 1000);
80
86
  }
81
87
 
88
+ pin() {
89
+ if (this.status === 'closed') {
90
+ throw new Error('Cannot pin a closed task');
91
+ }
92
+ this.status = 'pinned';
93
+ this.updatedAt = Math.floor(Date.now() / 1000);
94
+ }
95
+
96
+ unpin(reason = '') {
97
+ if (this.status !== 'pinned') {
98
+ throw new Error('Can only unpin a pinned task');
99
+ }
100
+ this.status = 'closed';
101
+ this.closeReason = reason || 'Unpinned by user';
102
+ this.closedAt = Math.floor(Date.now() / 1000);
103
+ this.updatedAt = this.closedAt;
104
+ }
105
+
82
106
  defer(reason = '') {
107
+ if (this.status === 'pinned') {
108
+ throw new Error('Cannot defer a pinned decision. Use unpin_decision or revise_decision instead.');
109
+ }
83
110
  this.status = 'deferred';
84
111
  if (reason) {
85
112
  this.notes = `[deferred] ${reason}`;
@@ -91,6 +118,9 @@ export class Task {
91
118
  if (this.status === 'closed') {
92
119
  throw new Error('Cannot fail a closed task');
93
120
  }
121
+ if (this.status === 'pinned') {
122
+ throw new Error('Cannot fail a pinned decision. Use unpin_decision or revise_decision instead.');
123
+ }
94
124
  this.status = 'open';
95
125
  this.failCount += 1;
96
126
  this.lastFailReason = reason || 'Unknown failure';
@@ -120,11 +150,11 @@ export class Task {
120
150
  if (typeof this.priority !== 'number' || !Number.isInteger(this.priority) || this.priority < 0 || this.priority > 4) {
121
151
  throw new Error(`priority must be an integer between 0 and 4 (got ${this.priority})`);
122
152
  }
123
- const validStatuses = ['open', 'in_progress', 'deferred', 'closed'];
153
+ const validStatuses = ['open', 'in_progress', 'deferred', 'closed', 'pinned'];
124
154
  if (!validStatuses.includes(this.status)) {
125
155
  throw new Error(`invalid status: ${this.status}`);
126
156
  }
127
- const validTypes = ['epic', 'task', 'bug', 'chore'];
157
+ const validTypes = ['epic', 'task', 'bug', 'chore', 'decision'];
128
158
  if (!validTypes.includes(this.taskType)) {
129
159
  throw new Error(`invalid task type: ${this.taskType}`);
130
160
  }
@@ -19,6 +19,7 @@ export const DepType = Object.freeze({
19
19
  DISCOVERED_FROM: 'discovered-from',
20
20
  RELATED: 'related',
21
21
  KNOWLEDGE_REF: 'knowledge-ref', // AutoSnippet 独有:关联知识条目
22
+ SUPERSEDES: 'supersedes', // 决策演化链:新决策取代旧决策
22
23
  });
23
24
 
24
25
  /** 所有合法的依赖类型值列表 */