@xluos/dev-assets-cli 0.3.1 → 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.
package/README.md CHANGED
@@ -13,6 +13,18 @@ This repository packages a small skill suite for one job: keep development memor
13
13
  Detailed guide:
14
14
 
15
15
  - [docs/dev-asset-skill-suite-guide.md](docs/dev-asset-skill-suite-guide.md)
16
+ - [docs/workspace-mode.md](docs/workspace-mode.md) — multi-repo workspace mode (new)
17
+
18
+ ## Workspace Mode
19
+
20
+ When cwd is not itself a git repo but its first-level subdirectories are (a "workspace" layout hosting multiple cloned repos), the suite switches to **workspace mode** automatically:
21
+
22
+ - `SessionStart`: injects full memory for the **primary repo** (from env `DEV_ASSETS_PRIMARY_REPO`, basename) plus a brief overview of the other repos; when no primary is set, all repos get the brief form.
23
+ - `Stop` / `SessionEnd`: records HEAD for every repo in the workspace (idempotent).
24
+ - `PreCompact`: refreshes working-tree navigation for every repo (Claude only).
25
+ - `dev-assets-sync` / `-context` / `-update`: pass `--repo <basename>` explicitly, or rely on `DEV_ASSETS_PRIMARY_REPO` for the default target.
26
+
27
+ Single-repo cwd behavior is unchanged. See [docs/workspace-mode.md](docs/workspace-mode.md) for the full design.
16
28
 
17
29
  ## Install with `npx skills`
18
30
 
@@ -162,7 +162,34 @@ def detect_repo_identity(repo_root):
162
162
  }
163
163
 
164
164
 
165
+ def _resolve_workspace_repo(repo):
166
+ """If `repo` points to a workspace root (cwd is not a git repo, but first-level
167
+ subdirs are), redirect to the primary repo via `DEV_ASSETS_PRIMARY_REPO` env.
168
+ Single-repo case returns `repo` unchanged. Raises in workspace mode when
169
+ primary is unset or does not match an existing subdir, so callers see a
170
+ clear error instead of git failing later.
171
+ """
172
+ if not detect_workspace_mode(repo):
173
+ return repo
174
+ primary = os.environ.get("DEV_ASSETS_PRIMARY_REPO", "").strip()
175
+ repos_in_ws = list_repos_in_workspace(repo)
176
+ names = [p.name for p in repos_in_ws]
177
+ if not primary:
178
+ raise RuntimeError(
179
+ f"workspace mode detected at '{repo}': pass --repo <basename> explicitly "
180
+ f"(one of: {names}) or set DEV_ASSETS_PRIMARY_REPO env."
181
+ )
182
+ match = next((p for p in repos_in_ws if p.name == primary), None)
183
+ if match is None:
184
+ raise RuntimeError(
185
+ f"workspace mode: DEV_ASSETS_PRIMARY_REPO='{primary}' not found in '{repo}'. "
186
+ f"Available: {names}."
187
+ )
188
+ return str(match)
189
+
190
+
165
191
  def get_branch_paths(repo, context_dir=None, branch=None):
192
+ repo = _resolve_workspace_repo(repo)
166
193
  repo_root = detect_repo_root(repo)
167
194
  branch_name = branch or detect_branch(repo_root)
168
195
  branch_key = sanitize_branch_name(branch_name)
@@ -173,6 +200,53 @@ def get_branch_paths(repo, context_dir=None, branch=None):
173
200
  return repo_root, branch_name, branch_key, storage_root, identity["repo_key"], repo_dir, branch_dir
174
201
 
175
202
 
203
+ def detect_workspace_mode(cwd=None):
204
+ """Return True iff cwd is not inside any git repo yet has first-level
205
+ subdirectories that are git repos. Purely additive — existing single-repo
206
+ behavior (cwd inside a git repo) returns False.
207
+ """
208
+ base = Path(cwd or ".").resolve()
209
+ if not base.exists() or not base.is_dir():
210
+ return False
211
+ probe = run_git(["rev-parse", "--show-toplevel"], cwd=base, check=False)
212
+ if probe.returncode == 0 and probe.stdout.strip():
213
+ return False
214
+ return bool(list_repos_in_workspace(base))
215
+
216
+
217
+ def list_repos_in_workspace(cwd=None):
218
+ """First-level subdirectories of cwd that are git repos. Sorted by name.
219
+ Returns [] when cwd has none or isn't readable. `.git` may be a dir or a
220
+ file (worktree pointer); both count.
221
+ """
222
+ base = Path(cwd or ".").resolve()
223
+ repos = []
224
+ try:
225
+ entries = sorted(base.iterdir(), key=lambda p: p.name)
226
+ except (OSError, PermissionError):
227
+ return []
228
+ for entry in entries:
229
+ if not entry.is_dir():
230
+ continue
231
+ if (entry / ".git").exists():
232
+ repos.append(entry)
233
+ return repos
234
+
235
+
236
+ def get_all_branch_paths(cwd=None, context_dir=None):
237
+ """Batch variant of get_branch_paths() for every repo under a workspace cwd.
238
+ Repos with detached HEAD or other resolution errors are skipped silently.
239
+ Returns [] when not in workspace mode.
240
+ """
241
+ result = []
242
+ for repo_path in list_repos_in_workspace(cwd):
243
+ try:
244
+ result.append(get_branch_paths(str(repo_path), context_dir=context_dir))
245
+ except Exception:
246
+ continue
247
+ return result
248
+
249
+
176
250
  def asset_paths(repo_dir, branch_dir):
177
251
  repo_memory_dir = repo_dir / "repo"
178
252
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xluos/dev-assets-cli",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "CLI for dev-assets hooks and repo-local setup",
5
5
  "license": "MIT",
6
6
  "scripts": {
@@ -14,17 +14,26 @@ LIB_ROOT = PACKAGE_ROOT / "lib"
14
14
  if str(LIB_ROOT) not in sys.path:
15
15
  sys.path.insert(0, str(LIB_ROOT))
16
16
 
17
- from dev_asset_common import AUTO_END, AUTO_START, PLACEHOLDER_MARKERS, asset_paths, get_branch_paths
17
+ from dev_asset_common import (
18
+ AUTO_END,
19
+ AUTO_START,
20
+ PLACEHOLDER_MARKERS,
21
+ asset_paths,
22
+ detect_workspace_mode,
23
+ get_branch_paths,
24
+ list_repos_in_workspace,
25
+ )
18
26
 
19
27
 
20
28
  CONTEXT_SCRIPT = PACKAGE_ROOT / "skills" / "dev-assets-context" / "scripts" / "dev_asset_context.py"
21
29
  SYNC_SCRIPT = PACKAGE_ROOT / "skills" / "dev-assets-sync" / "scripts" / "dev_asset_sync.py"
22
30
 
23
31
 
24
- def run_python(script_path, *args):
32
+ def run_python(script_path, *args, cwd=None):
33
+ work_cwd = cwd if cwd is not None else REPO_ROOT
25
34
  result = subprocess.run(
26
35
  ["python3", str(script_path), *args],
27
- cwd=REPO_ROOT,
36
+ cwd=work_cwd,
28
37
  check=False,
29
38
  capture_output=True,
30
39
  text=True,
@@ -38,10 +47,12 @@ def log(message):
38
47
  print(message, file=sys.stderr)
39
48
 
40
49
 
41
- def resolve_assets():
42
- repo_root, branch_name, branch_key, storage_root, repo_key, repo_dir, branch_dir = get_branch_paths(str(REPO_ROOT))
50
+ def resolve_assets_for(repo_root):
51
+ """Resolve asset paths for an explicit repo root (workspace-mode friendly)."""
52
+ repo_root_str = str(repo_root)
53
+ root, branch_name, branch_key, storage_root, repo_key, repo_dir, branch_dir = get_branch_paths(repo_root_str)
43
54
  return {
44
- "repo_root": repo_root,
55
+ "repo_root": root,
45
56
  "branch_name": branch_name,
46
57
  "branch_key": branch_key,
47
58
  "storage_root": storage_root,
@@ -52,6 +63,24 @@ def resolve_assets():
52
63
  }
53
64
 
54
65
 
66
+ def resolve_assets():
67
+ return resolve_assets_for(REPO_ROOT)
68
+
69
+
70
+ def is_workspace_mode():
71
+ return detect_workspace_mode(str(REPO_ROOT))
72
+
73
+
74
+ def list_workspace_repos():
75
+ return list_repos_in_workspace(str(REPO_ROOT))
76
+
77
+
78
+ def primary_repo_name():
79
+ """Basename of the focus repo from env; None if unset."""
80
+ value = os.environ.get("DEV_ASSETS_PRIMARY_REPO", "").strip()
81
+ return value or None
82
+
83
+
55
84
  def strip_managed_markers(text):
56
85
  return text.replace(AUTO_START, "").replace(AUTO_END, "").replace("_尚未同步_", "").strip()
57
86
 
@@ -87,54 +116,192 @@ def compact_body(text, max_lines=8, max_chars=700):
87
116
  return compacted
88
117
 
89
118
 
119
+ def sync_context_for(repo_root):
120
+ return json.loads(
121
+ run_python(CONTEXT_SCRIPT, "sync", "--repo", str(repo_root), cwd=str(repo_root))
122
+ )
123
+
124
+
125
+ def sync_working_tree_for(repo_root):
126
+ return json.loads(
127
+ run_python(SYNC_SCRIPT, "sync-working-tree", "--repo", str(repo_root), cwd=str(repo_root))
128
+ )
129
+
130
+
131
+ def record_head_for(repo_root):
132
+ return json.loads(
133
+ run_python(SYNC_SCRIPT, "record-head", "--repo", str(repo_root), cwd=str(repo_root))
134
+ )
135
+
136
+
90
137
  def maybe_sync_context():
91
- return json.loads(run_python(CONTEXT_SCRIPT, "sync", "--repo", str(REPO_ROOT)))
138
+ return sync_context_for(REPO_ROOT)
92
139
 
93
140
 
94
141
  def maybe_sync_working_tree():
95
- return json.loads(run_python(SYNC_SCRIPT, "sync-working-tree", "--repo", str(REPO_ROOT)))
142
+ return sync_working_tree_for(REPO_ROOT)
96
143
 
97
144
 
98
145
  def maybe_record_head():
99
- return json.loads(run_python(SYNC_SCRIPT, "record-head", "--repo", str(REPO_ROOT)))
146
+ return record_head_for(REPO_ROOT)
100
147
 
101
148
 
102
- def build_session_start_context():
103
- assets = resolve_assets()
149
+ _FULL_SECTION_KEYS = (
150
+ ("overview", "当前目标"),
151
+ ("overview", "范围边界"),
152
+ ("overview", "当前阶段"),
153
+ ("overview", "关键约束"),
154
+ ("development", "建议优先查看的目录"),
155
+ ("development", "当前进展"),
156
+ ("development", "阻塞与注意点"),
157
+ ("development", "下一步"),
158
+ ("context", "当前有效上下文"),
159
+ ("context", "关键决策与原因"),
160
+ ("context", "后续继续前要注意"),
161
+ ("repo_overview", "长期目标与边界"),
162
+ ("repo_overview", "仓库级关键约束"),
163
+ ("repo_sources", "共享入口"),
164
+ )
165
+
166
+ _BRIEF_SECTION_KEYS = (
167
+ ("overview", "当前目标"),
168
+ ("overview", "当前阶段"),
169
+ ("development", "当前进展"),
170
+ ("development", "下一步"),
171
+ )
172
+
173
+
174
+ def _extract_sections(paths, keys):
175
+ out = []
176
+ for file_key, title in keys:
177
+ body = extract_section(paths[file_key], title)
178
+ out.append((title, body))
179
+ return out
180
+
181
+
182
+ def _build_context_from_assets(assets, *, full=True, heading=None):
104
183
  if not assets["branch_dir"].exists():
105
- return (
106
- "当前仓库尚未初始化 dev-assets 分支记忆。\n"
107
- "如果这是需要跨会话继续的开发线,请先使用 `dev-assets-setup` 建立 repo+branch 存储。"
184
+ if heading is None:
185
+ return (
186
+ "当前仓库尚未初始化 dev-assets 分支记忆。\n"
187
+ "如果这是需要跨会话继续的开发线,请先使用 `dev-assets-setup` 建立 repo+branch 存储。"
188
+ )
189
+ return None
190
+
191
+ paths = assets["paths"]
192
+ keys = _FULL_SECTION_KEYS if full else _BRIEF_SECTION_KEYS
193
+ sections = _extract_sections(paths, keys)
194
+ max_lines, max_chars = (8, 700) if full else (3, 200)
195
+
196
+ parts = []
197
+ if heading is None:
198
+ parts.append(
199
+ f"已加载 dev-assets:repo `{assets['repo_key']}`,branch `{assets['branch_name']}`。"
108
200
  )
201
+ parts.append(f"主存储目录:`{assets['branch_dir']}`")
202
+ else:
203
+ parts.append(heading)
204
+ for title, body in sections:
205
+ if body:
206
+ parts.append(f"{title}:\n{compact_body(body, max_lines=max_lines, max_chars=max_chars)}")
207
+ return "\n\n".join(parts)
208
+
109
209
 
210
+ def build_session_start_context():
211
+ assets = resolve_assets()
110
212
  try:
111
213
  maybe_sync_context()
112
214
  except Exception as exc:
113
215
  log(f"[dev-assets][SessionStart] refresh skipped: {exc}")
216
+ return _build_context_from_assets(assets, full=True)
114
217
 
115
- paths = assets["paths"]
116
- sections = [
117
- ("当前目标", extract_section(paths["overview"], "当前目标")),
118
- ("范围边界", extract_section(paths["overview"], "范围边界")),
119
- ("当前阶段", extract_section(paths["overview"], "当前阶段")),
120
- ("关键约束", extract_section(paths["overview"], "关键约束")),
121
- ("建议优先查看的目录", extract_section(paths["development"], "建议优先查看的目录")),
122
- ("当前进展", extract_section(paths["development"], "当前进展")),
123
- ("阻塞与注意点", extract_section(paths["development"], "阻塞与注意点")),
124
- ("下一步", extract_section(paths["development"], "下一步")),
125
- ("当前有效上下文", extract_section(paths["context"], "当前有效上下文")),
126
- ("关键决策与原因", extract_section(paths["context"], "关键决策与原因")),
127
- ("后续继续前要注意", extract_section(paths["context"], "后续继续前要注意")),
128
- ("仓库级长期目标与边界", extract_section(paths["repo_overview"], "长期目标与边界")),
129
- ("仓库级关键约束", extract_section(paths["repo_overview"], "仓库级关键约束")),
130
- ("共享入口", extract_section(paths["repo_sources"], "共享入口")),
131
- ]
132
218
 
133
- parts = [
134
- f"已加载 dev-assets:repo `{assets['repo_key']}`,branch `{assets['branch_name']}`。",
135
- f"主存储目录:`{assets['branch_dir']}`",
219
+ def build_context_for_repo(repo_path, *, full=True, is_primary=False):
220
+ """Build a per-repo context block for workspace-mode SessionStart injection.
221
+ Returns None when the repo has no initialized branch memory or resolution fails.
222
+ """
223
+ try:
224
+ assets = resolve_assets_for(repo_path)
225
+ except Exception as exc:
226
+ log(f"[dev-assets] resolve failed for {Path(repo_path).name}: {exc}")
227
+ return None
228
+ try:
229
+ sync_context_for(repo_path)
230
+ except Exception as exc:
231
+ log(f"[dev-assets] context sync skipped for {Path(repo_path).name}: {exc}")
232
+ tag = "[PRIMARY] " if is_primary else ""
233
+ heading = (
234
+ f"## {tag}`{Path(repo_path).name}` — repo `{assets['repo_key']}`, branch `{assets['branch_name']}`"
235
+ )
236
+ return _build_context_from_assets(assets, full=full, heading=heading)
237
+
238
+
239
+ def build_workspace_start_context():
240
+ """SessionStart context for workspace mode. Primary repo gets full memory;
241
+ others get a brief overview only. Returns None if no initialized repos.
242
+ """
243
+ repos = list_workspace_repos()
244
+ if not repos:
245
+ return None
246
+ primary = primary_repo_name()
247
+ primary_hit = False
248
+ sections = []
249
+ for repo_path in repos:
250
+ is_primary = (primary is None) or (repo_path.name == primary)
251
+ if is_primary:
252
+ primary_hit = True
253
+ ctx = build_context_for_repo(repo_path, full=is_primary, is_primary=is_primary)
254
+ if ctx:
255
+ sections.append(ctx)
256
+ if not sections:
257
+ return None
258
+ header_parts = [
259
+ f"已加载 dev-assets workspace 模式:共 {len(repos)} 个仓库 @ `{REPO_ROOT}`"
136
260
  ]
137
- for title, body in sections:
138
- if body:
139
- parts.append(f"{title}:\n{compact_body(body)}")
140
- return "\n\n".join(parts)
261
+ if primary:
262
+ status = "命中" if primary_hit else "未在 workspace 中找到,全部按完整模式注入"
263
+ header_parts.append(f"Primary 仓库提示:`{primary}` ({status})")
264
+ header = "\n".join(header_parts)
265
+ return header + "\n\n---\n\n" + "\n\n---\n\n".join(sections)
266
+
267
+
268
+ def record_head_all_repos():
269
+ """Stop/SessionEnd hook helper for workspace mode. Iterates all repos; logs per-repo
270
+ outcome; swallows per-repo failures.
271
+ """
272
+ results = []
273
+ for repo_path in list_workspace_repos():
274
+ try:
275
+ assets = resolve_assets_for(repo_path)
276
+ if not assets["branch_dir"].exists():
277
+ log(f"[dev-assets] {repo_path.name}: branch memory not initialized, skip")
278
+ continue
279
+ payload = record_head_for(repo_path)
280
+ log(
281
+ f"[dev-assets] {repo_path.name}: recorded HEAD "
282
+ f"{payload.get('last_seen_head')} for {payload.get('branch')}"
283
+ )
284
+ results.append((repo_path.name, payload))
285
+ except Exception as exc:
286
+ log(f"[dev-assets] {repo_path.name}: record-head skipped: {exc}")
287
+ return results
288
+
289
+
290
+ def sync_working_tree_all_repos():
291
+ """PreCompact hook helper for workspace mode. Iterates all repos."""
292
+ results = []
293
+ for repo_path in list_workspace_repos():
294
+ try:
295
+ assets = resolve_assets_for(repo_path)
296
+ if not assets["branch_dir"].exists():
297
+ log(f"[dev-assets] {repo_path.name}: branch memory not initialized, skip")
298
+ continue
299
+ payload = sync_working_tree_for(repo_path)
300
+ log(
301
+ f"[dev-assets] {repo_path.name}: refreshed working-tree navigation for "
302
+ f"{payload.get('branch')} ({payload.get('files_considered')} files)"
303
+ )
304
+ results.append((repo_path.name, payload))
305
+ except Exception as exc:
306
+ log(f"[dev-assets] {repo_path.name}: working-tree sync skipped: {exc}")
307
+ return results
@@ -1,10 +1,21 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
- from _common import log, maybe_sync_working_tree, resolve_assets
3
+ from _common import (
4
+ is_workspace_mode,
5
+ log,
6
+ maybe_sync_working_tree,
7
+ resolve_assets,
8
+ sync_working_tree_all_repos,
9
+ )
4
10
 
5
11
 
6
12
  def main():
7
13
  try:
14
+ if is_workspace_mode():
15
+ results = sync_working_tree_all_repos()
16
+ if not results:
17
+ log("[dev-assets][PreCompact] workspace mode: no initialized repos refreshed")
18
+ return 0
8
19
  assets = resolve_assets()
9
20
  if not assets["branch_dir"].exists():
10
21
  log("[dev-assets][PreCompact] branch memory not initialized, skip")
@@ -1,10 +1,21 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
- from _common import log, maybe_record_head, resolve_assets
3
+ from _common import (
4
+ is_workspace_mode,
5
+ log,
6
+ maybe_record_head,
7
+ record_head_all_repos,
8
+ resolve_assets,
9
+ )
4
10
 
5
11
 
6
12
  def main():
7
13
  try:
14
+ if is_workspace_mode():
15
+ results = record_head_all_repos()
16
+ if not results:
17
+ log("[dev-assets][SessionEnd] workspace mode: no initialized repos finalized")
18
+ return 0
8
19
  assets = resolve_assets()
9
20
  if not assets["branch_dir"].exists():
10
21
  log("[dev-assets][SessionEnd] branch memory not initialized, skip")
@@ -3,12 +3,26 @@
3
3
  import json
4
4
  import sys
5
5
 
6
- from _common import build_session_start_context, log
6
+ from _common import (
7
+ build_session_start_context,
8
+ build_workspace_start_context,
9
+ is_workspace_mode,
10
+ log,
11
+ )
12
+
13
+
14
+ def _resolve_context():
15
+ if is_workspace_mode():
16
+ ctx = build_workspace_start_context()
17
+ if ctx:
18
+ return ctx
19
+ return "dev-assets workspace 模式:当前 workspace 下未发现已初始化的仓库记忆。"
20
+ return build_session_start_context()
7
21
 
8
22
 
9
23
  def main():
10
24
  try:
11
- additional_context = build_session_start_context()
25
+ additional_context = _resolve_context()
12
26
  payload = {
13
27
  "hookSpecificOutput": {
14
28
  "hookEventName": "SessionStart",
@@ -1,10 +1,21 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
- from _common import log, maybe_record_head, resolve_assets
3
+ from _common import (
4
+ is_workspace_mode,
5
+ log,
6
+ maybe_record_head,
7
+ record_head_all_repos,
8
+ resolve_assets,
9
+ )
4
10
 
5
11
 
6
12
  def main():
7
13
  try:
14
+ if is_workspace_mode():
15
+ results = record_head_all_repos()
16
+ if not results:
17
+ log("[dev-assets][Stop] workspace mode: no initialized repos recorded")
18
+ return 0
8
19
  assets = resolve_assets()
9
20
  if not assets["branch_dir"].exists():
10
21
  log("[dev-assets][Stop] branch memory not initialized, skip")
@@ -7,6 +7,8 @@ description: Use when starting work in any Git repository conversation on an exi
7
7
 
8
8
  把当前仓库的 branch 记忆作为默认上下文入口恢复出来;repo 共享层只在需要时补读。
9
9
 
10
+ **Workspace mode:** cwd 是多 repo workspace 时,SessionStart 已自动注入 primary 仓库的完整记忆 + 其他仓库的简短概览。当需要切换焦点到非 primary 仓库补读完整记忆时,向脚本传递 `--repo <basename>` 明确指定。
11
+
10
12
  **Announce at start:** 用一句简短的话说明将先恢复当前 branch 记忆,再按需补读 repo 共享记忆。
11
13
 
12
14
  ## Workflow
@@ -7,6 +7,8 @@ description: Use when a branch starts a new requirement stream or when the curre
7
7
 
8
8
  为当前 Git 仓库初始化用户目录下的 repo+branch 开发记忆骨架,并在初始化后主动向用户收集最小但关键的资料。
9
9
 
10
+ **Workspace mode:** 初始化始终针对单个仓库。cwd 是多 repo workspace 时,必须通过 `--repo <basename>` 明确指定目标仓库,每个新仓库分别调用一次;绝不做批量自动初始化,避免用户意外污染不相关的仓库。
11
+
10
12
  **Announce at start:** 用一句简短的话说明将先初始化当前仓库的 repo+branch 记忆目录。
11
13
 
12
14
  ## Workflow
@@ -7,6 +7,8 @@ description: Use when the current conversation reaches a commit-related checkpoi
7
7
 
8
8
  在提交相关检查点,或在当前对话已经形成需要跨会话保留的稳定结论时,把这次会话结束后仍然有价值的信息同步到当前 branch 记忆,并顺带刷新 repo 共享层的轻量元信息。
9
9
 
10
+ **Workspace mode:** 当 cwd 是 workspace 根(不是 git repo,但一级子目录中有多个 git repo)时:向脚本传递 `--repo <basename>` 明确指定目标仓库;若未指定且 `DEV_ASSETS_PRIMARY_REPO` env 已设置,会默认落到 primary 仓库。跨仓库 sync 需要为每个仓库各调用一次。
11
+
10
12
  **Announce at start:** 用一句简短的话说明将先沉淀本次检查点留下的关键信息。
11
13
 
12
14
  ## Workflow
@@ -7,6 +7,8 @@ description: Use when current development memory needs to be corrected, rewritte
7
7
 
8
8
  把当前对话中已经形成且需要保留的新理解,改写到 branch 或 repo 共享层的开发记忆里,而不是只留在对话中。
9
9
 
10
+ **Workspace mode:** cwd 是多 repo workspace 时,改写需要通过 `--repo <basename>` 明确目标仓库;未指定且 `DEV_ASSETS_PRIMARY_REPO` env 已设置则落到 primary 仓库。改写不跨仓库合并。
11
+
10
12
  **Announce at start:** 用一句简短的话说明将先重写相关 section,而不是继续追加历史。
11
13
 
12
14
  ## Trigger Hints