monoco-toolkit 0.3.6__py3-none-any.whl → 0.3.9__py3-none-any.whl

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 (58) hide show
  1. monoco/cli/workspace.py +1 -1
  2. monoco/core/config.py +51 -0
  3. monoco/core/hooks/__init__.py +19 -0
  4. monoco/core/hooks/base.py +104 -0
  5. monoco/core/hooks/builtin/__init__.py +11 -0
  6. monoco/core/hooks/builtin/git_cleanup.py +266 -0
  7. monoco/core/hooks/builtin/logging_hook.py +78 -0
  8. monoco/core/hooks/context.py +131 -0
  9. monoco/core/hooks/registry.py +222 -0
  10. monoco/core/integrations.py +6 -0
  11. monoco/core/registry.py +2 -0
  12. monoco/core/setup.py +1 -1
  13. monoco/core/skills.py +226 -42
  14. monoco/features/{scheduler → agent}/__init__.py +4 -2
  15. monoco/features/{scheduler → agent}/cli.py +134 -80
  16. monoco/features/{scheduler → agent}/config.py +17 -3
  17. monoco/features/agent/defaults.py +55 -0
  18. monoco/features/agent/flow_skills.py +281 -0
  19. monoco/features/{scheduler → agent}/manager.py +39 -2
  20. monoco/features/{scheduler → agent}/models.py +6 -3
  21. monoco/features/{scheduler → agent}/reliability.py +1 -1
  22. monoco/features/agent/resources/skills/flow_engineer/SKILL.md +94 -0
  23. monoco/features/agent/resources/skills/flow_manager/SKILL.md +88 -0
  24. monoco/features/agent/resources/skills/flow_reviewer/SKILL.md +114 -0
  25. monoco/features/{scheduler → agent}/session.py +36 -1
  26. monoco/features/{scheduler → agent}/worker.py +2 -2
  27. monoco/features/i18n/resources/skills/i18n_scan_workflow/SKILL.md +105 -0
  28. monoco/features/issue/commands.py +427 -21
  29. monoco/features/issue/core.py +100 -0
  30. monoco/features/issue/criticality.py +553 -0
  31. monoco/features/issue/domain/models.py +28 -2
  32. monoco/features/issue/engine/machine.py +70 -13
  33. monoco/features/issue/git_service.py +185 -0
  34. monoco/features/issue/linter.py +291 -62
  35. monoco/features/issue/models.py +49 -2
  36. monoco/features/issue/resources/en/SKILL.md +48 -0
  37. monoco/features/issue/resources/skills/issue_lifecycle_workflow/SKILL.md +159 -0
  38. monoco/features/issue/resources/zh/SKILL.md +50 -0
  39. monoco/features/issue/validator.py +185 -65
  40. monoco/features/memo/__init__.py +2 -1
  41. monoco/features/memo/adapter.py +32 -0
  42. monoco/features/memo/cli.py +36 -14
  43. monoco/features/memo/core.py +59 -0
  44. monoco/features/memo/resources/skills/note_processing_workflow/SKILL.md +140 -0
  45. monoco/features/memo/resources/zh/AGENTS.md +8 -0
  46. monoco/features/memo/resources/zh/SKILL.md +75 -0
  47. monoco/features/spike/resources/skills/research_workflow/SKILL.md +121 -0
  48. monoco/main.py +2 -3
  49. {monoco_toolkit-0.3.6.dist-info → monoco_toolkit-0.3.9.dist-info}/METADATA +1 -1
  50. {monoco_toolkit-0.3.6.dist-info → monoco_toolkit-0.3.9.dist-info}/RECORD +55 -37
  51. monoco/features/scheduler/defaults.py +0 -54
  52. monoco/features/skills/__init__.py +0 -0
  53. monoco/features/skills/core.py +0 -102
  54. /monoco/core/{hooks.py → githooks.py} +0 -0
  55. /monoco/features/{scheduler → agent}/engines.py +0 -0
  56. {monoco_toolkit-0.3.6.dist-info → monoco_toolkit-0.3.9.dist-info}/WHEEL +0 -0
  57. {monoco_toolkit-0.3.6.dist-info → monoco_toolkit-0.3.9.dist-info}/entry_points.txt +0 -0
  58. {monoco_toolkit-0.3.6.dist-info → monoco_toolkit-0.3.9.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,159 @@
1
+ ---
2
+ name: issue-lifecycle-workflow
3
+ description: Issue 生命周期工作流 (Flow Skill)。定义从创建到关闭的完整 Issue 管理流程,确保任务追踪和流程合规。
4
+ type: flow
5
+ domain: issue
6
+ version: 1.0.0
7
+ ---
8
+
9
+ # Issue Lifecycle Workflow
10
+
11
+ Issue 生命周期的标准化工作流,确保 "Open → Start → Develop → Submit → Review → Close" 流程。
12
+
13
+ ## 工作流状态机
14
+
15
+ ```mermaid
16
+ stateDiagram-v2
17
+ [*] --> Open: 创建 Issue
18
+
19
+ Open --> Start: 准备开发
20
+ Open --> Open: 需求不清<br/>(等待澄清)
21
+
22
+ Start --> Develop: 分支创建
23
+ Start --> Start: 分支冲突<br/>(解决冲突)
24
+
25
+ Develop --> Submit: 开发完成
26
+ Develop --> Develop: 测试失败<br/>(修复代码)
27
+
28
+ state "Oracle Loop" as ReviewLoop {
29
+ Submit --> Review: 提交评审
30
+ Review --> Fix: 拒绝
31
+ Fix --> Submit: 重新提交
32
+ }
33
+
34
+ Review --> Close: 批准合并
35
+
36
+ Close --> [*]: 清理完成
37
+ ```
38
+
39
+ ## 执行步骤
40
+
41
+ ### 1. Open (创建)
42
+
43
+ - **目标**: 创建清晰、可执行的 Issue
44
+ - **输入**: 需求描述、类型、优先级
45
+ - **输出**: Issue Ticket 文件
46
+ - **检查点**:
47
+ - [ ] 使用 `monoco issue create <type> -t "标题"`
48
+ - [ ] 选择合适的类型(epic/feature/chore/fix)
49
+ - [ ] 编写清晰的描述和验收标准
50
+ - [ ] 设置依赖关系(如需要)
51
+ - [ ] 确保至少 2 个 Checkbox
52
+
53
+ ### 2. Start (启动)
54
+
55
+ - **目标**: 准备开发环境,创建功能分支
56
+ - **检查点**:
57
+ - [ ] 运行 `monoco issue start <ID> --branch`
58
+ - [ ] 确认分支已创建并切换
59
+ - [ ] 验证当前不在 main/master 分支
60
+ - [ ] 检查依赖 Issue 是否已完成
61
+
62
+ ### 3. Develop (开发)
63
+
64
+ - **目标**: 实现功能或修复缺陷
65
+ - **策略**: 迭代开发,持续测试
66
+ - **检查点**:
67
+ - [ ] 遵循项目代码规范
68
+ - [ ] 编写/更新单元测试
69
+ - [ ] 运行测试套件,确保通过
70
+ - [ ] 定期提交代码(小步提交)
71
+ - [ ] 更新文件追踪(`monoco issue sync-files`)
72
+
73
+ ### 4. Submit (提交)
74
+
75
+ - **目标**: 准备代码评审
76
+ - **检查点**:
77
+ - [ ] 运行 `monoco issue lint` 检查合规性
78
+ - [ ] 修复所有 Lint 错误
79
+ - [ ] 更新任务清单状态
80
+ - [ ] 运行 `monoco issue submit <ID>`
81
+ - [ ] 编写变更摘要
82
+
83
+ ### 5. Review (评审)
84
+
85
+ - **目标**: 代码质量和流程合规检查
86
+ - **检查点**:
87
+ - [ ] 功能是否正确实现
88
+ - [ ] 代码是否符合设计规范
89
+ - [ ] 测试是否充分
90
+ - [ ] 文档是否同步更新
91
+ - [ ] 是否遵循项目规范
92
+
93
+ ### 6. Close (关闭)
94
+
95
+ - **目标**: 完成 Issue,清理环境
96
+ - **检查点**:
97
+ - [ ] 代码已合并到主分支
98
+ - [ ] 运行 `monoco issue close <ID> --solution completed --prune`
99
+ - [ ] 验证分支已清理
100
+ - [ ] 更新 Review Comments(如需要)
101
+
102
+ ## 决策分支
103
+
104
+ | 条件 | 动作 |
105
+ |------|------|
106
+ | 需求不清晰 | 返回 Open,请求澄清 |
107
+ | 分支创建失败 | 检查 Git 状态,解决冲突 |
108
+ | 测试失败 | 返回 Develop,修复代码 |
109
+ | Lint 失败 | 修复合规性问题,重新 Submit |
110
+ | 评审拒绝 | 返回 Develop,按反馈修改 |
111
+ | 评审通过 | 进入 Close,合并并清理 |
112
+
113
+ ## 合规要求
114
+
115
+ - **禁止**: 在 main/master 分支直接修改代码
116
+ - **必须**: 使用 `monoco issue start --branch` 创建功能分支
117
+ - **必须**: 所有单元测试通过后才能 Submit
118
+ - **必须**: 每个 Issue 至少 2 个 Checkbox
119
+ - **必须**: Review/Done 阶段必须包含 Review Comments
120
+ - **建议**: 小步提交,频繁同步文件追踪
121
+
122
+ ## 相关命令
123
+
124
+ ```bash
125
+ # 创建 Issue
126
+ monoco issue create feature -t "标题"
127
+
128
+ # 启动开发
129
+ monoco issue start FEAT-0001 --branch
130
+
131
+ # 同步文件追踪
132
+ monoco issue sync-files
133
+
134
+ # 检查合规性
135
+ monoco issue lint
136
+
137
+ # 提交评审
138
+ monoco issue submit FEAT-0001
139
+
140
+ # 关闭 Issue
141
+ monoco issue close FEAT-0001 --solution completed --prune
142
+ ```
143
+
144
+ ## Issue 类型指南
145
+
146
+ | 类型 | 用途 | 前缀 | Mindset |
147
+ |------|------|------|---------|
148
+ | Epic | 宏大目标、愿景容器 | EPIC- | Architect |
149
+ | Feature | 用户价值增量 | FEAT- | Product Owner |
150
+ | Chore | 工程性事务 | CHORE- | Builder |
151
+ | Fix | 缺陷修复 | FIX- | Debugger |
152
+
153
+ ## 与 flow_engineer 的关系
154
+
155
+ 此工作流与 `flow_engineer` 互补:
156
+ - `issue-lifecycle-workflow`: 关注 Issue 管理流程
157
+ - `flow_engineer`: 关注代码实现流程
158
+
159
+ Engineer 在执行 Develop 阶段时,应遵循 `flow_engineer` 的 Investigate → Code → Test → Report → Submit 流程。
@@ -67,6 +67,50 @@ Monoco 强制采用 **Feature Branch** 模式。
67
67
  - **禁止主干开发**: **严禁** 直接在 `main`, `master`, `production` 分支上修改代码。Linter 会拦截此类行为。
68
68
  - **Submit**: 在提交 PR 前,运行 `monoco issue submit <ID>` 进行清理和预发布检查。
69
69
 
70
+ ## 标准化工作流 (Standardized Workflow)
71
+
72
+ 本指南引导 Agent 遵循 Monoco 标准 Issue 工作流。
73
+
74
+ ### 工作流图示
75
+
76
+ ```mermaid
77
+ stateDiagram-v2
78
+ [*] --> Plan
79
+ Plan --> Build: monoco issue start
80
+ Build --> Submit: monoco issue submit
81
+ state "Oracle Loop" as Oracle {
82
+ Submit --> Review: Auto/Manual Review
83
+ Review --> Fix: Reject
84
+ Fix --> Submit: Retry
85
+ }
86
+ Review --> Merge: Approve
87
+ Merge --> [*]: monoco issue close
88
+ ```
89
+
90
+ ### 执行步骤
91
+
92
+ 1. **Plan (计划阶段)**:
93
+ - 确保 Issue 已创作且处于 `Open` 状态。
94
+ - 验证需求描述与任务清单 (Acceptance Criteria)。
95
+
96
+ 2. **Build (构建阶段)**:
97
+ - 运行 `monoco issue start <ID> --branch` (强制分支隔离)。
98
+ - 实现功能或修复缺陷。
99
+ - 运行 `monoco issue sync-files` 更新修改文件追踪。
100
+
101
+ 3. **Submit (提交阶段 - Oracle 循环)**:
102
+ - 运行测试确保质量。
103
+ - 运行 `monoco issue lint` 检查合规性。
104
+ - 运行 `monoco issue submit <ID>` 触发评审。
105
+ - **如果** 收到报错或反馈:
106
+ - 修复问题。
107
+ - 重新运行测试。
108
+ - 重新运行提交。
109
+
110
+ 4. **Merge (合并/关闭阶段)**:
111
+ - 一旦获得批准 (人工或自动):
112
+ - 运行 `monoco issue close <ID> --solution completed --prune` 清理环境并下线。
113
+
70
114
  ### 2. 文件追踪 (File Tracking)
71
115
 
72
116
  为了保证上下文的自包含性 (Self-Contained Context),Agent 必须记录修改过的文件。
@@ -136,3 +180,9 @@ Monoco 强制采用 **Feature Branch** 模式。
136
180
  Linter 包含环境感知防护:
137
181
 
138
182
  - 🛑 **Dirty Main Protection**: 当检测到处于受保护分支 (`main`/`master`) 且存在未提交变更时,Lint 将失败并阻止操作。
183
+
184
+ ### 6. ID 格式与层级 (ID Format & Hierarchy)
185
+
186
+ - **ID 规范**: Issue ID 必须严格遵循 `TYPE-XXXX` 格式,其中 `XXXX` 必须是 4 位数字(示例: `FEAT-0001`, `FIX-9999`)。
187
+ - **禁止后缀**: 禁止使用类似 `FEAT-0001-1` 这样带后缀的 ID。
188
+ - **层级表达**: 子功能或子任务应通过 `parent` 字段(在 Front Matter 中)来关联父级 Issue,严禁通过 ID 命名约定(如加分级后缀)来表达层级关系。
@@ -27,6 +27,7 @@ class IssueValidator:
27
27
  all_issue_ids: Set[str] = set(),
28
28
  current_project: Optional[str] = None,
29
29
  workspace_root: Optional[str] = None,
30
+ valid_domains: Set[str] = set(),
30
31
  ) -> List[Diagnostic]:
31
32
  """
32
33
  Validate an issue and return diagnostics.
@@ -77,7 +78,11 @@ class IssueValidator:
77
78
  diagnostics.extend(self._validate_references(meta, content, all_issue_ids))
78
79
 
79
80
  # 5.5 Domain Integrity
80
- diagnostics.extend(self._validate_domains(meta, content, all_issue_ids))
81
+ diagnostics.extend(
82
+ self._validate_domains(
83
+ meta, content, all_issue_ids, valid_domains=valid_domains
84
+ )
85
+ )
81
86
 
82
87
  # 6. Time Consistency
83
88
  diagnostics.extend(self._validate_time_consistency(meta, content))
@@ -88,6 +93,9 @@ class IssueValidator:
88
93
  # 8. Language Consistency
89
94
  diagnostics.extend(self._validate_language_consistency(meta, content))
90
95
 
96
+ # 9. Placeholder Detection
97
+ diagnostics.extend(self._validate_placeholders(meta, content))
98
+
91
99
  return diagnostics
92
100
 
93
101
  def _validate_language_consistency(
@@ -431,7 +439,7 @@ class IssueValidator:
431
439
  )
432
440
  resolver = ReferenceResolver(context)
433
441
 
434
- # Malformed ID Check
442
+ # 1. Malformed ID Check (Syntax)
435
443
  if meta.parent and meta.parent.startswith("#"):
436
444
  line = self._get_field_line(content, "parent")
437
445
  diagnostics.append(
@@ -466,7 +474,63 @@ class IssueValidator:
466
474
  )
467
475
  )
468
476
 
469
- if not all_ids or not resolver:
477
+ # 2. Body Reference Check (Format and Existence)
478
+ lines = content.split("\n")
479
+ in_fm = False
480
+ fm_end = 0
481
+ for i, line in enumerate(lines):
482
+ if line.strip() == "---":
483
+ if not in_fm:
484
+ in_fm = True
485
+ else:
486
+ fm_end = i
487
+ break
488
+
489
+ for i, line in enumerate(lines):
490
+ if i <= fm_end:
491
+ continue # Skip frontmatter
492
+
493
+ # Find all matches, including those with invalid suffixes to report them properly
494
+ matches = re.finditer(r"\b((?:EPIC|FEAT|CHORE|FIX)-\d{4}(?:-\d+)?)\b", line)
495
+ for match in matches:
496
+ full_raw_id = match.group(1)
497
+
498
+ # Check if it has an invalid suffix (e.g. FEAT-0099-1)
499
+ if len(full_raw_id.split("-")) > 2:
500
+ diagnostics.append(
501
+ self._create_diagnostic(
502
+ f"Invalid ID Format: '{full_raw_id}' has an invalid suffix. Use 'parent' field for hierarchy.",
503
+ DiagnosticSeverity.Warning,
504
+ line=i,
505
+ )
506
+ )
507
+ continue
508
+
509
+ ref_id = full_raw_id
510
+
511
+ # Knowledge Check (Only if resolver is available)
512
+ if resolver:
513
+ # Check for namespaced ID before this match?
514
+ full_match = re.search(
515
+ r"\b(?:([a-z0-9_-]+)::)?(" + re.escape(ref_id) + r")\b",
516
+ line[max(0, match.start() - 50) : match.end()],
517
+ )
518
+
519
+ check_id = ref_id
520
+ if full_match and full_match.group(1):
521
+ check_id = f"{full_match.group(1)}::{ref_id}"
522
+
523
+ if ref_id != meta.id and not resolver.is_valid_reference(check_id):
524
+ diagnostics.append(
525
+ self._create_diagnostic(
526
+ f"Broken Reference: Issue '{check_id}' not found.",
527
+ DiagnosticSeverity.Warning,
528
+ line=i,
529
+ )
530
+ )
531
+
532
+ # 3. Hierarchy and Graph Integrity (Requires Resolver)
533
+ if not resolver:
470
534
  return diagnostics
471
535
 
472
536
  # Logic: Epics must have a parent (unless it is the Sink Root EPIC-0000)
@@ -506,49 +570,6 @@ class IssueValidator:
506
570
  )
507
571
  )
508
572
 
509
- # Body Reference Check
510
- # Regex for generic issue ID: (EPIC|FEAT|CHORE|FIX)-\d{4}
511
- # We scan line by line to get line numbers
512
- lines = content.split("\n")
513
- # Skip frontmatter for body check to avoid double counting (handled above)
514
- in_fm = False
515
- fm_end = 0
516
- for i, line in enumerate(lines):
517
- if line.strip() == "---":
518
- if not in_fm:
519
- in_fm = True
520
- else:
521
- fm_end = i
522
- break
523
-
524
- for i, line in enumerate(lines):
525
- if i <= fm_end:
526
- continue # Skip frontmatter
527
-
528
- # Find all matches
529
- matches = re.finditer(r"\b((?:EPIC|FEAT|CHORE|FIX)-\d{4})\b", line)
530
- for match in matches:
531
- ref_id = match.group(1)
532
- # Check for namespaced ID before this match?
533
- # The regex above only catches the ID part.
534
- # Let's adjust regex to optionally catch namespace::
535
- full_match = re.search(
536
- r"\b(?:([a-z0-9_-]+)::)?(" + re.escape(ref_id) + r")\b",
537
- line[max(0, match.start() - 50) : match.end()],
538
- )
539
-
540
- check_id = ref_id
541
- if full_match and full_match.group(1):
542
- check_id = f"{full_match.group(1)}::{ref_id}"
543
-
544
- if ref_id != meta.id and not resolver.is_valid_reference(check_id):
545
- diagnostics.append(
546
- self._create_diagnostic(
547
- f"Broken Reference: Issue '{check_id}' not found.",
548
- DiagnosticSeverity.Warning,
549
- line=i,
550
- )
551
- )
552
573
  return diagnostics
553
574
  return diagnostics
554
575
 
@@ -603,7 +624,11 @@ class IssueValidator:
603
624
  return diagnostics
604
625
 
605
626
  def _validate_domains(
606
- self, meta: IssueMetadata, content: str, all_ids: Set[str] = set()
627
+ self,
628
+ meta: IssueMetadata,
629
+ content: str,
630
+ all_ids: Set[str] = set(),
631
+ valid_domains: Set[str] = set(),
607
632
  ) -> List[Diagnostic]:
608
633
  diagnostics = []
609
634
  # Check if 'domains' field exists in frontmatter text
@@ -650,38 +675,79 @@ class IssueValidator:
650
675
  )
651
676
 
652
677
  # Domain Content Validation
653
- from .domain_service import DomainService
654
-
655
- service = DomainService()
656
-
678
+ # If valid_domains is provided (from file scan), use it as strict source of truth
657
679
  if hasattr(meta, "domains") and meta.domains:
658
- for domain in meta.domains:
659
- if service.is_alias(domain):
660
- canonical = service.get_canonical(domain)
661
- diagnostics.append(
662
- self._create_diagnostic(
663
- f"Domain Alias: '{domain}' is an alias for '{canonical}'. Preference: Canonical.",
664
- DiagnosticSeverity.Warning,
665
- line=field_line,
680
+ if valid_domains:
681
+ # Use File-based validation
682
+ for domain in meta.domains:
683
+ # 1. Format Check: PascalCase
684
+ is_pascal = re.match(r"^[A-Z][a-zA-Z0-9]+$", domain) is not None
685
+
686
+ if not is_pascal:
687
+ # Suggest conversion
688
+ normalized = "".join(
689
+ word.capitalize()
690
+ for word in re.findall(r"[a-zA-Z0-9]+", domain)
666
691
  )
667
- )
668
- elif not service.is_defined(domain):
669
- if service.config.strict:
692
+ if normalized in valid_domains:
693
+ diagnostics.append(
694
+ self._create_diagnostic(
695
+ f"Domain Format Error: '{domain}' must be PascalCase (e.g., '{normalized}').",
696
+ DiagnosticSeverity.Error,
697
+ line=field_line,
698
+ )
699
+ )
700
+ else:
701
+ diagnostics.append(
702
+ self._create_diagnostic(
703
+ f"Domain Format Error: '{domain}' must be PascalCase (no spaces/symbols).",
704
+ DiagnosticSeverity.Error,
705
+ line=field_line,
706
+ )
707
+ )
708
+ continue
709
+
710
+ # 2. Existence Check
711
+ if domain not in valid_domains:
670
712
  diagnostics.append(
671
713
  self._create_diagnostic(
672
- f"Unknown Domain: '{domain}' is not defined in domain ontology.",
714
+ f"Unknown Domain: '{domain}' not found. Available: {', '.join(sorted(valid_domains))}",
673
715
  DiagnosticSeverity.Error,
674
716
  line=field_line,
675
717
  )
676
718
  )
677
- else:
719
+ else:
720
+ # Fallback to legacy DomainService (hardcoded list)
721
+ from .domain_service import DomainService
722
+
723
+ service = DomainService()
724
+ for domain in meta.domains:
725
+ if service.is_alias(domain):
726
+ canonical = service.get_canonical(domain)
678
727
  diagnostics.append(
679
728
  self._create_diagnostic(
680
- f"Unknown Domain: '{domain}' is not defined in domain ontology.",
729
+ f"Domain Alias: '{domain}' is an alias for '{canonical}'. Preference: Canonical.",
681
730
  DiagnosticSeverity.Warning,
682
731
  line=field_line,
683
732
  )
684
733
  )
734
+ elif not service.is_defined(domain):
735
+ if service.config.strict:
736
+ diagnostics.append(
737
+ self._create_diagnostic(
738
+ f"Unknown Domain: '{domain}' is not defined in domain ontology.",
739
+ DiagnosticSeverity.Error,
740
+ line=field_line,
741
+ )
742
+ )
743
+ else:
744
+ diagnostics.append(
745
+ self._create_diagnostic(
746
+ f"Unknown Domain: '{domain}' is not defined in domain ontology.",
747
+ DiagnosticSeverity.Warning,
748
+ line=field_line,
749
+ )
750
+ )
685
751
 
686
752
  return diagnostics
687
753
 
@@ -716,3 +782,57 @@ class IssueValidator:
716
782
  )
717
783
 
718
784
  return diagnostics
785
+
786
+ def _validate_placeholders(
787
+ self, meta: IssueMetadata, content: str
788
+ ) -> List[Diagnostic]:
789
+ """
790
+ Detect uncleared placeholders in issue content.
791
+
792
+ Placeholders are template hints that should be removed before submission.
793
+ Examples:
794
+ - <!-- Required for Review/Done stage. Record review feedback here. -->
795
+ - <!-- TODO: Add implementation details -->
796
+ - <!-- Placeholder: ... -->
797
+
798
+ Severity depends on stage:
799
+ - review/done: ERROR (must be cleared before submission)
800
+ - draft/open/doing: WARNING (should be cleared)
801
+ """
802
+ diagnostics = []
803
+
804
+ # Define placeholder patterns
805
+ placeholder_patterns = [
806
+ # HTML comments with common placeholder keywords
807
+ (r"<!--\s*Required for Review/Done stage.*?-->", "Review/Done placeholder"),
808
+ (r"<!--\s*TODO:.*?-->", "TODO placeholder"),
809
+ (r"<!--\s*FIXME:.*?-->", "FIXME placeholder"),
810
+ (r"<!--\s*Placeholder:.*?-->", "Generic placeholder"),
811
+ (r"<!--\s*Template:.*?-->", "Template placeholder"),
812
+ (r"<!--\s*Example:.*?-->", "Example placeholder"),
813
+ # Generic instruction patterns (English and Chinese)
814
+ (r"<!--\s*Record review feedback here.*?-->", "Review placeholder"),
815
+ (r"<!--\s*在此记录评审反馈.*?-->", "Review placeholder (Chinese)"),
816
+ ]
817
+
818
+ lines = content.splitlines()
819
+
820
+ # Determine severity based on stage
821
+ if meta.stage in ["review", "done"]:
822
+ severity = DiagnosticSeverity.Error
823
+ else:
824
+ severity = DiagnosticSeverity.Warning
825
+
826
+ for line_idx, line in enumerate(lines):
827
+ for pattern, desc in placeholder_patterns:
828
+ if re.search(pattern, line, re.IGNORECASE):
829
+ diagnostics.append(
830
+ self._create_diagnostic(
831
+ f"Uncleared Placeholder: {desc} found. Remove template hints before submission.",
832
+ severity,
833
+ line_idx,
834
+ )
835
+ )
836
+ break # Only report once per line
837
+
838
+ return diagnostics
@@ -1,3 +1,4 @@
1
1
  from .cli import app
2
+ from .adapter import MemoFeature
2
3
 
3
- __all__ = ["app"]
4
+ __all__ = ["app", "MemoFeature"]
@@ -0,0 +1,32 @@
1
+ from pathlib import Path
2
+ from typing import Dict
3
+ from monoco.core.feature import MonocoFeature, IntegrationData
4
+
5
+
6
+ class MemoFeature(MonocoFeature):
7
+ @property
8
+ def name(self) -> str:
9
+ return "memo"
10
+
11
+ def initialize(self, root: Path, config: Dict) -> None:
12
+ # Memo feature doesn't require explicit initialization
13
+ # The inbox is created on first use
14
+ pass
15
+
16
+ def integrate(self, root: Path, config: Dict) -> IntegrationData:
17
+ # Determine language from config, default to 'en'
18
+ lang = config.get("i18n", {}).get("source_lang", "en")
19
+
20
+ # Resource path: monoco/features/memo/resources/{lang}/AGENTS.md
21
+ base_dir = Path(__file__).parent / "resources"
22
+
23
+ # Try specific language, fallback to 'en'
24
+ prompt_file = base_dir / lang / "AGENTS.md"
25
+ if not prompt_file.exists():
26
+ prompt_file = base_dir / "en" / "AGENTS.md"
27
+
28
+ content = ""
29
+ if prompt_file.exists():
30
+ content = prompt_file.read_text(encoding="utf-8").strip()
31
+
32
+ return IntegrationData(system_prompts={"Memo (Fleeting Notes)": content})
@@ -4,25 +4,16 @@ from typing import Optional
4
4
  from rich.console import Console
5
5
  from rich.table import Table
6
6
  from monoco.core.config import get_config
7
- from .core import add_memo, list_memos, get_inbox_path
7
+ from .core import add_memo, list_memos, delete_memo, get_inbox_path, validate_content_language
8
8
 
9
9
  app = typer.Typer(help="Manage memos (fleeting notes).")
10
10
  console = Console()
11
11
 
12
12
 
13
- def get_issues_root() -> Path:
14
- config = get_config()
13
+ def get_issues_root(config=None) -> Path:
14
+ if config is None:
15
+ config = get_config()
15
16
  # Resolve absolute path for issues
16
- root = Path(config.paths.root).resolve()
17
- # If config.paths.root is '.', it means current or discovered root.
18
- # We should trust get_config's loading mechanism, but find_monoco_root might be safer to base off.
19
- # Update: config is loaded relative to where it was found.
20
- # Let's rely on config.paths.root if it's absolute, or relative to CWD?
21
- # Actually, the ConfigLoader doesn't mutate paths.root based on location.
22
- # It defaults to "."
23
-
24
- # Better approach:
25
- # Use find_monoco_root() to get base, then append config.paths.issues
26
17
  from monoco.core.config import find_monoco_root
27
18
 
28
19
  project_root = find_monoco_root()
@@ -35,11 +26,26 @@ def add_command(
35
26
  context: Optional[str] = typer.Option(
36
27
  None, "--context", "-c", help="Context reference (e.g. file:line)."
37
28
  ),
29
+ force: bool = typer.Option(
30
+ False, "--force", "-f", help="Bypass i18n language validation."
31
+ ),
38
32
  ):
39
33
  """
40
34
  Capture a new idea or thought into the Memo Inbox.
41
35
  """
42
- issues_root = get_issues_root()
36
+ config = get_config()
37
+ issues_root = get_issues_root(config)
38
+
39
+ # Language Validation
40
+ source_lang = config.i18n.source_lang
41
+ if not force and not validate_content_language(content, source_lang):
42
+ console.print(
43
+ f"[red]Error: Content language mismatch.[/red] Content does not match configured source language: [bold]{source_lang}[/bold]."
44
+ )
45
+ console.print(
46
+ "[yellow]Tip: Use --force to bypass this check if you really want to add this content.[/yellow]"
47
+ )
48
+ raise typer.Exit(code=1)
43
49
 
44
50
  uid = add_memo(issues_root, content, context)
45
51
 
@@ -88,3 +94,19 @@ def open_command():
88
94
  return
89
95
 
90
96
  typer.launch(str(inbox_path))
97
+
98
+
99
+ @app.command("delete")
100
+ def delete_command(
101
+ memo_id: str = typer.Argument(..., help="The ID of the memo to delete.")
102
+ ):
103
+ """
104
+ Delete a memo from the inbox by its ID.
105
+ """
106
+ issues_root = get_issues_root()
107
+
108
+ if delete_memo(issues_root, memo_id):
109
+ console.print(f"[green]✔ Memo [bold]{memo_id}[/bold] deleted successfully.[/green]")
110
+ else:
111
+ console.print(f"[red]Error: Memo with ID [bold]{memo_id}[/bold] not found.[/red]")
112
+ raise typer.Exit(code=1)