@blxzer/cursor-trellis 0.3.5 → 0.4.0

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 (156) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +13 -0
  3. package/dist/cli/index.d.ts.map +1 -1
  4. package/dist/cli/index.js +3 -0
  5. package/dist/cli/index.js.map +1 -1
  6. package/dist/commands/goal/index.d.ts +3 -0
  7. package/dist/commands/goal/index.d.ts.map +1 -0
  8. package/dist/commands/goal/index.js +159 -0
  9. package/dist/commands/goal/index.js.map +1 -0
  10. package/dist/commands/goal/review.d.ts +6 -0
  11. package/dist/commands/goal/review.d.ts.map +1 -0
  12. package/dist/commands/goal/review.js +28 -0
  13. package/dist/commands/goal/review.js.map +1 -0
  14. package/dist/commands/sdk/run.d.ts.map +1 -1
  15. package/dist/commands/sdk/run.js +2 -27
  16. package/dist/commands/sdk/run.js.map +1 -1
  17. package/dist/commands/update.d.ts.map +1 -1
  18. package/dist/commands/update.js +5 -1
  19. package/dist/commands/update.js.map +1 -1
  20. package/dist/configurators/workflow.d.ts.map +1 -1
  21. package/dist/configurators/workflow.js +51 -2
  22. package/dist/configurators/workflow.js.map +1 -1
  23. package/dist/constants/paths.d.ts +4 -0
  24. package/dist/constants/paths.d.ts.map +1 -1
  25. package/dist/constants/paths.js +4 -0
  26. package/dist/constants/paths.js.map +1 -1
  27. package/dist/goal/action-packet.d.ts +89 -0
  28. package/dist/goal/action-packet.d.ts.map +1 -0
  29. package/dist/goal/action-packet.js +113 -0
  30. package/dist/goal/action-packet.js.map +1 -0
  31. package/dist/goal/audit.d.ts +14 -0
  32. package/dist/goal/audit.d.ts.map +1 -0
  33. package/dist/goal/audit.js +12 -0
  34. package/dist/goal/audit.js.map +1 -0
  35. package/dist/goal/constants.d.ts +26 -0
  36. package/dist/goal/constants.d.ts.map +1 -0
  37. package/dist/goal/constants.js +24 -0
  38. package/dist/goal/constants.js.map +1 -0
  39. package/dist/goal/paths.d.ts +8 -0
  40. package/dist/goal/paths.d.ts.map +1 -0
  41. package/dist/goal/paths.js +36 -0
  42. package/dist/goal/paths.js.map +1 -0
  43. package/dist/goal/preflight.d.ts +21 -0
  44. package/dist/goal/preflight.d.ts.map +1 -0
  45. package/dist/goal/preflight.js +97 -0
  46. package/dist/goal/preflight.js.map +1 -0
  47. package/dist/goal/review-seam.d.ts +44 -0
  48. package/dist/goal/review-seam.d.ts.map +1 -0
  49. package/dist/goal/review-seam.js +153 -0
  50. package/dist/goal/review-seam.js.map +1 -0
  51. package/dist/goal/reviewer-rules.d.ts +13 -0
  52. package/dist/goal/reviewer-rules.d.ts.map +1 -0
  53. package/dist/goal/reviewer-rules.js +153 -0
  54. package/dist/goal/reviewer-rules.js.map +1 -0
  55. package/dist/goal/reviewer.d.ts +9 -0
  56. package/dist/goal/reviewer.d.ts.map +1 -0
  57. package/dist/goal/reviewer.js +54 -0
  58. package/dist/goal/reviewer.js.map +1 -0
  59. package/dist/goal/runtime.d.ts +32 -0
  60. package/dist/goal/runtime.d.ts.map +1 -0
  61. package/dist/goal/runtime.js +242 -0
  62. package/dist/goal/runtime.js.map +1 -0
  63. package/dist/goal/sdk-client.d.ts +10 -0
  64. package/dist/goal/sdk-client.d.ts.map +1 -0
  65. package/dist/goal/sdk-client.js +34 -0
  66. package/dist/goal/sdk-client.js.map +1 -0
  67. package/dist/goal/state.d.ts +40 -0
  68. package/dist/goal/state.d.ts.map +1 -0
  69. package/dist/goal/state.js +37 -0
  70. package/dist/goal/state.js.map +1 -0
  71. package/dist/goal/task-triggers.d.ts +15 -0
  72. package/dist/goal/task-triggers.d.ts.map +1 -0
  73. package/dist/goal/task-triggers.js +23 -0
  74. package/dist/goal/task-triggers.js.map +1 -0
  75. package/dist/goal/walls.d.ts +12 -0
  76. package/dist/goal/walls.d.ts.map +1 -0
  77. package/dist/goal/walls.js +65 -0
  78. package/dist/goal/walls.js.map +1 -0
  79. package/dist/goal/worker-prompt.d.ts +13 -0
  80. package/dist/goal/worker-prompt.d.ts.map +1 -0
  81. package/dist/goal/worker-prompt.js +41 -0
  82. package/dist/goal/worker-prompt.js.map +1 -0
  83. package/dist/goal/worker.d.ts +42 -0
  84. package/dist/goal/worker.d.ts.map +1 -0
  85. package/dist/goal/worker.js +180 -0
  86. package/dist/goal/worker.js.map +1 -0
  87. package/dist/migrations/manifests/0.3.5.json +9 -0
  88. package/dist/migrations/manifests/0.3.6.json +9 -0
  89. package/dist/migrations/manifests/0.4.0.json +9 -0
  90. package/dist/templates/common/commands/continue.md +15 -3
  91. package/dist/templates/common/skills/check.md +7 -4
  92. package/dist/templates/cursor/agents/cstl-check.md +1 -0
  93. package/dist/templates/cursor/fixtures/expected-rules.d.ts.map +1 -1
  94. package/dist/templates/cursor/fixtures/expected-rules.js +6 -1
  95. package/dist/templates/cursor/fixtures/expected-rules.js.map +1 -1
  96. package/dist/templates/cursor/rules/cstl-cursor-modes.mdc +32 -0
  97. package/dist/templates/cursor/rules/cstl-triage.mdc +2 -4
  98. package/dist/templates/cursor/rules/retrieval-routing.mdc +26 -7
  99. package/dist/templates/markdown/index.d.ts +9 -0
  100. package/dist/templates/markdown/index.d.ts.map +1 -1
  101. package/dist/templates/markdown/index.js +9 -0
  102. package/dist/templates/markdown/index.js.map +1 -1
  103. package/dist/templates/markdown/spec/guides/artifact-locale-guide.md.txt +93 -0
  104. package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +7 -7
  105. package/dist/templates/markdown/spec/guides/cursor-context-injection-guide.md.txt +38 -21
  106. package/dist/templates/markdown/spec/guides/cursor-native-modes-guide.md.txt +125 -0
  107. package/dist/templates/markdown/spec/guides/cursor-subagent-policy.md.txt +10 -8
  108. package/dist/templates/markdown/spec/guides/debug-loop-guide.md.txt +227 -0
  109. package/dist/templates/markdown/spec/guides/goal-release-regression-runbook.md.txt +132 -0
  110. package/dist/templates/markdown/spec/guides/index.md.txt +144 -104
  111. package/dist/templates/markdown/spec/guides/injection-budget-guide.md.txt +111 -0
  112. package/dist/templates/markdown/spec/guides/prototype-guide.md.txt +139 -0
  113. package/dist/templates/markdown/spec/guides/retrieval-daily-guide.md.txt +28 -6
  114. package/dist/templates/markdown/spec/guides/test-discipline-guide.md.txt +138 -0
  115. package/dist/templates/markdown/spec/guides/verification-strength-guide.md.txt +185 -0
  116. package/dist/templates/shared-hooks/session-start.py +10 -0
  117. package/dist/templates/trellis/config.yaml +18 -0
  118. package/dist/templates/trellis/index.d.ts +17 -0
  119. package/dist/templates/trellis/index.d.ts.map +1 -1
  120. package/dist/templates/trellis/index.js +40 -0
  121. package/dist/templates/trellis/index.js.map +1 -1
  122. package/dist/templates/trellis/pool/README.md +103 -0
  123. package/dist/templates/trellis/pool/items/.gitkeep +0 -0
  124. package/dist/templates/trellis/pool/plan.md +26 -0
  125. package/dist/templates/trellis/scripts/common/artifact_locale.py +278 -0
  126. package/dist/templates/trellis/scripts/common/codebase_retrieval_router.py +37 -2
  127. package/dist/templates/trellis/scripts/common/config.py +39 -0
  128. package/dist/templates/trellis/scripts/common/injection_budget.py +317 -0
  129. package/dist/templates/trellis/scripts/common/parent_orchestration.py +161 -5
  130. package/dist/templates/trellis/scripts/common/pool_store.py +702 -0
  131. package/dist/templates/trellis/scripts/common/retrieval_agent_instructions.py +9 -3
  132. package/dist/templates/trellis/scripts/common/session_context.py +32 -4
  133. package/dist/templates/trellis/scripts/common/session_memory.py +26 -0
  134. package/dist/templates/trellis/scripts/common/subagent_dispatch.py +74 -9
  135. package/dist/templates/trellis/scripts/common/task_context.py +18 -0
  136. package/dist/templates/trellis/scripts/common/task_dashboard.py +24 -1
  137. package/dist/templates/trellis/scripts/common/task_dependencies.py +673 -0
  138. package/dist/templates/trellis/scripts/common/task_gates.py +58 -5
  139. package/dist/templates/trellis/scripts/common/task_map.py +40 -8
  140. package/dist/templates/trellis/scripts/common/task_store.py +400 -39
  141. package/dist/templates/trellis/scripts/common/test_depends_mode_block.py +489 -0
  142. package/dist/templates/trellis/scripts/common/test_observable_defaults.py +135 -0
  143. package/dist/templates/trellis/scripts/common/test_pool_store.py +428 -0
  144. package/dist/templates/trellis/scripts/common/test_task_dependencies.py +345 -0
  145. package/dist/templates/trellis/scripts/cursor_retrieval_probe_prompt.md +14 -12
  146. package/dist/templates/trellis/scripts/generate_dispatch_prompt.py +182 -0
  147. package/dist/templates/trellis/scripts/injection_budget_probe.py +69 -0
  148. package/dist/templates/trellis/scripts/pool.py +192 -0
  149. package/dist/templates/trellis/scripts/retrieval_probe_matrix_template.json +3 -3
  150. package/dist/templates/trellis/scripts/spec_health_outcomes.py +160 -0
  151. package/dist/templates/trellis/scripts/task.py +85 -1
  152. package/dist/templates/trellis/scripts/verify_evidence_probe.py +138 -0
  153. package/dist/templates/trellis/tasks/locale/en/default-prd.md +19 -0
  154. package/dist/templates/trellis/tasks/locale/zh/default-prd.md +19 -0
  155. package/dist/templates/trellis/workflow.md +55 -5
  156. package/package.json +2 -2
@@ -0,0 +1,278 @@
1
+ #!/usr/bin/env python3
2
+ """Human-reviewed artifact locale resolution and PRD seed templates."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import re
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ from .config import CONFIG_FILE
11
+ from .io import read_json, write_json
12
+ from .paths import DIR_TASKS, DIR_WORKFLOW, FILE_TASK_JSON, get_repo_root
13
+
14
+ VALID_LOCALES = ("zh", "en")
15
+ DEFAULT_LOCALE = "zh"
16
+
17
+ _ARTIFACT_LOCALE_KEY = "artifact_locale"
18
+ _ARTIFACT_LOCALE_LINE_RE = re.compile(
19
+ r"^(\s*)artifact_locale\s*:\s*.*$",
20
+ re.MULTILINE,
21
+ )
22
+
23
+
24
+ def normalize_locale(raw: object) -> str | None:
25
+ """Return a supported locale code or None for invalid input."""
26
+ if raw is None:
27
+ return None
28
+ value = str(raw).strip().lower()
29
+ if value in VALID_LOCALES:
30
+ return value
31
+ return None
32
+
33
+
34
+ def _warn_invalid_locale(raw: object, *, context: str) -> None:
35
+ print(
36
+ f"[WARN] invalid artifact_locale {raw!r} in {context}; falling back",
37
+ file=sys.stderr,
38
+ )
39
+
40
+
41
+ def _read_workspace_locale_raw(repo_root: Path) -> str | None:
42
+ from .config import _load_config
43
+
44
+ config = _load_config(repo_root)
45
+ return normalize_locale(config.get(_ARTIFACT_LOCALE_KEY))
46
+
47
+
48
+ def get_workspace_artifact_locale(repo_root: Path | None = None) -> str:
49
+ """Return workspace-level artifact locale (default zh)."""
50
+ root = repo_root or get_repo_root()
51
+ return _read_workspace_locale_raw(root) or DEFAULT_LOCALE
52
+
53
+
54
+ def get_task_artifact_locale(task_dir: Path, repo_root: Path | None = None) -> str | None:
55
+ """Return task.json meta override when valid."""
56
+ _ = repo_root
57
+ task_json = task_dir / FILE_TASK_JSON
58
+ if not task_json.is_file():
59
+ return None
60
+ data = read_json(task_json)
61
+ if not isinstance(data, dict):
62
+ return None
63
+ meta = data.get("meta")
64
+ if not isinstance(meta, dict):
65
+ return None
66
+ raw = meta.get(_ARTIFACT_LOCALE_KEY)
67
+ locale = normalize_locale(raw)
68
+ if raw is not None and locale is None:
69
+ _warn_invalid_locale(raw, context=str(task_json))
70
+ return locale
71
+
72
+
73
+ def resolve_artifact_locale(task_dir: Path | None, repo_root: Path | None = None) -> str:
74
+ """Resolve locale: task meta override, then workspace, then default zh."""
75
+ root = repo_root or get_repo_root()
76
+ if task_dir is not None:
77
+ task_locale = get_task_artifact_locale(task_dir, root)
78
+ if task_locale:
79
+ return task_locale
80
+ return get_workspace_artifact_locale(root)
81
+
82
+
83
+ def locale_template_dir(locale: str, repo_root: Path | None = None) -> Path:
84
+ """Directory containing bundled PRD seed templates for a locale."""
85
+ root = repo_root or get_repo_root()
86
+ normalized = normalize_locale(locale) or DEFAULT_LOCALE
87
+ return root / DIR_WORKFLOW / DIR_TASKS / "locale" / normalized
88
+
89
+
90
+ def _builtin_prd_template(locale: str) -> str:
91
+ if locale == "zh":
92
+ return """# {title}
93
+
94
+ ## 目标
95
+
96
+ {goal}
97
+
98
+ ## 需求
99
+
100
+ - 待补充
101
+
102
+ ## 验收标准
103
+
104
+ - [ ] 待补充
105
+
106
+ ## 备注
107
+
108
+ - `prd.md` 聚焦需求、约束与验收标准。
109
+ - 轻量任务可仅保留 PRD。
110
+ - 复杂任务在 `task.py start-execution --check` 前补充 `design.md` 与 `implement.md`。
111
+ """
112
+ return """# {title}
113
+
114
+ ## Goal
115
+
116
+ {goal}
117
+
118
+ ## Requirements
119
+
120
+ - TBD
121
+
122
+ ## Acceptance Criteria
123
+
124
+ - [ ] TBD
125
+
126
+ ## Notes
127
+
128
+ - Keep `prd.md` focused on requirements, constraints, and acceptance criteria.
129
+ - Lightweight tasks can remain PRD-only.
130
+ - For complex tasks, add `design.md` for technical design and `implement.md` for execution planning before `task.py start-execution --check`.
131
+ """
132
+
133
+
134
+ def default_verify_content(
135
+ locale: str | None = None,
136
+ repo_root: Path | None = None,
137
+ task_dir: Path | None = None,
138
+ ) -> str:
139
+ """Return the default verify.md skeleton seeded on task create."""
140
+ root = repo_root or get_repo_root()
141
+ resolved = normalize_locale(locale) or resolve_artifact_locale(task_dir, root)
142
+ template_path = locale_template_dir(resolved, root) / "default-verify.md"
143
+ if template_path.is_file():
144
+ return template_path.read_text(encoding="utf-8")
145
+ if resolved == "zh":
146
+ return """# 验证证据
147
+
148
+ ## Planning check
149
+
150
+ _可选(Full 任务)— 记录规划阶段审查结果。_
151
+
152
+ ## Execution evidence
153
+
154
+ ### Validation commands
155
+
156
+ <!-- 示例:Validation commands: <命令> — <结果> -->
157
+
158
+ ### Acceptance
159
+
160
+ <!-- 示例:Final acceptance evidence: <验收标准达成说明> -->
161
+
162
+ ### Durable learning
163
+
164
+ <!-- 示例:Durable learning decision: no durable learning -->
165
+ """
166
+ return """# Verification Evidence
167
+
168
+ ## Planning check
169
+
170
+ _Optional for Full tasks — record planning review outcomes._
171
+
172
+ ## Execution evidence
173
+
174
+ ### Validation commands
175
+
176
+ <!-- Example: Validation commands: <command> — <outcome> -->
177
+
178
+ ### Acceptance
179
+
180
+ <!-- Example: Final acceptance evidence: <criteria met> -->
181
+
182
+ ### Durable learning
183
+
184
+ <!-- Example: Durable learning decision: no durable learning -->
185
+ """
186
+
187
+
188
+ def default_prd_content(
189
+ title: str,
190
+ description: str | None = None,
191
+ locale: str | None = None,
192
+ repo_root: Path | None = None,
193
+ ) -> str:
194
+ """Return the default PRD skeleton for task create."""
195
+ root = repo_root or get_repo_root()
196
+ resolved = normalize_locale(locale) or resolve_artifact_locale(None, root)
197
+ template_path = locale_template_dir(resolved, root) / "default-prd.md"
198
+ if template_path.is_file():
199
+ template = template_path.read_text(encoding="utf-8")
200
+ else:
201
+ template = _builtin_prd_template(resolved)
202
+
203
+ if resolved == "zh":
204
+ goal = (description or "").strip() or "待补充。"
205
+ heading = title.strip() or "未命名任务"
206
+ else:
207
+ goal = (description or "").strip() or "TBD."
208
+ heading = title.strip() or "Untitled task"
209
+
210
+ return template.replace("{title}", heading).replace("{goal}", goal)
211
+
212
+
213
+ def artifact_locale_summary(repo_root: Path | None = None, task_dir: Path | None = None) -> str:
214
+ """One-line summary for session context."""
215
+ root = repo_root or get_repo_root()
216
+ locale = resolve_artifact_locale(task_dir, root)
217
+ return (
218
+ f"Artifact locale: {locale} — human-reviewed artifacts "
219
+ f"(prd/design/implement/verify/handoff) use this language."
220
+ )
221
+
222
+
223
+ def set_workspace_artifact_locale(locale: str, repo_root: Path | None = None) -> None:
224
+ """Patch .cstl/config.yaml with artifact_locale (non-destructive)."""
225
+ normalized = normalize_locale(locale)
226
+ if not normalized:
227
+ raise ValueError(f"artifact_locale must be one of: {', '.join(VALID_LOCALES)}")
228
+
229
+ root = repo_root or get_repo_root()
230
+ config_path = root / DIR_WORKFLOW / CONFIG_FILE
231
+ replacement = f"artifact_locale: {normalized}"
232
+
233
+ if config_path.is_file():
234
+ content = config_path.read_text(encoding="utf-8")
235
+ if _ARTIFACT_LOCALE_LINE_RE.search(content):
236
+ content = _ARTIFACT_LOCALE_LINE_RE.sub(replacement, content, count=1)
237
+ else:
238
+ section = (
239
+ "\n#-------------------------------------------------------------------------------\n"
240
+ "# Human-reviewed artifact locale (zh | en)\n"
241
+ "#-------------------------------------------------------------------------------\n"
242
+ "# Language for prd/design/implement/verify/handoff and task create seeds.\n"
243
+ "# Does NOT translate CLI UI or existing archived tasks.\n"
244
+ f"\n{replacement}\n"
245
+ )
246
+ content = content.rstrip() + section
247
+ config_path.write_text(content if content.endswith("\n") else content + "\n", encoding="utf-8")
248
+ return
249
+
250
+ config_path.parent.mkdir(parents=True, exist_ok=True)
251
+ config_path.write_text(
252
+ "# Trellis Configuration\n"
253
+ f"{replacement}\n",
254
+ encoding="utf-8",
255
+ )
256
+
257
+
258
+ def set_task_artifact_locale(task_dir: Path, locale: str, repo_root: Path | None = None) -> None:
259
+ """Write task.json meta.artifact_locale."""
260
+ _ = repo_root
261
+ normalized = normalize_locale(locale)
262
+ if not normalized:
263
+ raise ValueError(f"artifact_locale must be one of: {', '.join(VALID_LOCALES)}")
264
+
265
+ task_json = task_dir / FILE_TASK_JSON
266
+ if not task_json.is_file():
267
+ raise FileNotFoundError(f"task.json not found: {task_json}")
268
+
269
+ data = read_json(task_json)
270
+ if not isinstance(data, dict):
271
+ raise ValueError(f"invalid task.json: {task_json}")
272
+
273
+ meta = data.get("meta")
274
+ if not isinstance(meta, dict):
275
+ meta = {}
276
+ meta[_ARTIFACT_LOCALE_KEY] = normalized
277
+ data["meta"] = meta
278
+ write_json(task_json, data)
@@ -671,17 +671,33 @@ def _ordered_routes(
671
671
  ],
672
672
  "rationale": cg_rationale,
673
673
  })
674
+ append({
675
+ "id": "definition-jump-native",
676
+ "role": "exact",
677
+ "sourceFamily": "rg",
678
+ "commands": [
679
+ "rg <symbol> --glob '*.{py,ts,tsx,js}'",
680
+ "Read <file> at matched definition line range",
681
+ ],
682
+ "rationale": (
683
+ "Prefer Cursor native: Grep for named-symbol definition, then Read "
684
+ "to verify (GO_TO_DEFINITION not in Agent tool table per cursor.com/docs/agent/overview)."
685
+ ),
686
+ "platformNative": True,
687
+ })
674
688
  append({
675
689
  "id": "lsp-navigation",
676
690
  "role": "ast",
677
691
  "sourceFamily": "codegraph",
678
692
  "commands": [
693
+ "codegraph_explore <symbol-or-question>",
679
694
  "codegraph_node <symbol> --includeCode",
680
695
  "codegraph_search <symbol>",
681
696
  ],
682
697
  "rationale": (
683
- "Definition/reference via codegraph (Cursor Agent does not expose "
684
- "GO_TO_DEFINITION); corroborate with Read on returned line ranges."
698
+ "Own/codegraph: structural definition/reference when Grep+Read is "
699
+ "ambiguous (cross-package traps, overloads) or blast/caller context "
700
+ "is needed; GO_TO_DEFINITION not exposed in Agent."
685
701
  ),
686
702
  "platformNative": False,
687
703
  })
@@ -707,9 +723,28 @@ def _ordered_routes(
707
723
  others = [r for r in routes if r.get("role") not in ("ast",)]
708
724
  routes = structural + others
709
725
 
726
+ routes = _prefer_native_definition_before_codegraph(routes)
727
+
710
728
  return [{**route, "order": index + 1} for index, route in enumerate(routes)]
711
729
 
712
730
 
731
+ def _prefer_native_definition_before_codegraph(
732
+ routes: list[dict[str, object]],
733
+ ) -> list[dict[str, object]]:
734
+ """Keep definition-jump-native (Grep+Read) before lsp-navigation (codegraph)."""
735
+ ids = {str(r.get("id", "")) for r in routes}
736
+ if "definition-jump-native" not in ids or "lsp-navigation" not in ids:
737
+ return routes
738
+ native = next(r for r in routes if str(r.get("id")) == "definition-jump-native")
739
+ rest = [r for r in routes if str(r.get("id")) != "definition-jump-native"]
740
+ lsp_idx = next(
741
+ (i for i, r in enumerate(rest) if str(r.get("id")) == "lsp-navigation"),
742
+ len(rest),
743
+ )
744
+ rest.insert(lsp_idx, native)
745
+ return rest
746
+
747
+
713
748
  def _fallback_hints(
714
749
  intents: list[dict[str, object]],
715
750
  include_optional_adapters: bool,
@@ -167,6 +167,7 @@ def _next_content_line(lines: list[str], start: int) -> tuple[int, str]:
167
167
  DEFAULT_SESSION_COMMIT_MESSAGE = "chore: record journal"
168
168
  DEFAULT_MAX_JOURNAL_LINES = 2000
169
169
  DEFAULT_SESSION_AUTO_COMMIT = True
170
+ DEFAULT_JOURNAL_SNIPPET_ENABLED = False
170
171
 
171
172
  CONFIG_FILE = "config.yaml"
172
173
 
@@ -243,6 +244,37 @@ def get_session_auto_commit(repo_root: Path | None = None) -> bool:
243
244
  return DEFAULT_SESSION_AUTO_COMMIT
244
245
 
245
246
 
247
+ def get_journal_snippet_enabled(repo_root: Path | None = None) -> bool:
248
+ """Whether get_context should inject a compact recent journal summary.
249
+
250
+ Default: ``False`` (metadata-only JOURNAL FILE section).
251
+ Set ``context_injection.journal_snippet: true`` in ``.cstl/config.yaml`` to
252
+ append a ≤150-token summary from the latest session entry in the active
253
+ journal file.
254
+
255
+ Accepts native YAML booleans and the string aliases
256
+ ``true / false / yes / no / 1 / 0 / on / off`` (case-insensitive).
257
+ Invalid values fall back to ``False`` with a stderr warning.
258
+ """
259
+ config = _load_config(repo_root)
260
+ block = config.get("context_injection")
261
+ if not isinstance(block, dict):
262
+ return DEFAULT_JOURNAL_SNIPPET_ENABLED
263
+ raw = block.get("journal_snippet", DEFAULT_JOURNAL_SNIPPET_ENABLED)
264
+ if isinstance(raw, bool):
265
+ return raw
266
+ s = str(raw).strip().lower()
267
+ if s in ("true", "yes", "1", "on"):
268
+ return True
269
+ if s in ("false", "no", "0", "off"):
270
+ return False
271
+ print(
272
+ f"[WARN] invalid context_injection.journal_snippet value: {raw!r}; using false (default)",
273
+ file=sys.stderr,
274
+ )
275
+ return DEFAULT_JOURNAL_SNIPPET_ENABLED
276
+
277
+
246
278
  def get_smart_search_command_config(repo_root: Path | None = None) -> str | None:
247
279
  """Optional executable name or path from ``smart_search.command`` in config."""
248
280
  config = _load_config(repo_root)
@@ -435,6 +467,13 @@ def resolve_package(
435
467
  return None
436
468
 
437
469
 
470
+ def get_artifact_locale(repo_root: Path | None = None) -> str:
471
+ """Get workspace human-reviewed artifact locale (zh | en, default zh)."""
472
+ from .artifact_locale import get_workspace_artifact_locale
473
+
474
+ return get_workspace_artifact_locale(repo_root)
475
+
476
+
438
477
  def get_spec_scope(repo_root: Path | None = None) -> list[str] | str | None:
439
478
  """Get session.spec_scope configuration.
440
479