agent-notes 2.0.4__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 (162) hide show
  1. agent_notes/VERSION +1 -0
  2. agent_notes/__init__.py +1 -0
  3. agent_notes/__main__.py +4 -0
  4. agent_notes/cli.py +348 -0
  5. agent_notes/commands/__init__.py +27 -0
  6. agent_notes/commands/_install_helpers.py +262 -0
  7. agent_notes/commands/build.py +170 -0
  8. agent_notes/commands/doctor.py +112 -0
  9. agent_notes/commands/info.py +95 -0
  10. agent_notes/commands/install.py +99 -0
  11. agent_notes/commands/list.py +169 -0
  12. agent_notes/commands/memory.py +430 -0
  13. agent_notes/commands/regenerate.py +152 -0
  14. agent_notes/commands/set_role.py +143 -0
  15. agent_notes/commands/uninstall.py +26 -0
  16. agent_notes/commands/update.py +169 -0
  17. agent_notes/commands/validate.py +199 -0
  18. agent_notes/commands/wizard.py +720 -0
  19. agent_notes/config.py +154 -0
  20. agent_notes/data/agents/agents.yaml +352 -0
  21. agent_notes/data/agents/analyst.md +45 -0
  22. agent_notes/data/agents/api-reviewer.md +47 -0
  23. agent_notes/data/agents/architect.md +46 -0
  24. agent_notes/data/agents/coder.md +28 -0
  25. agent_notes/data/agents/database-specialist.md +45 -0
  26. agent_notes/data/agents/debugger.md +47 -0
  27. agent_notes/data/agents/devil.md +47 -0
  28. agent_notes/data/agents/devops.md +38 -0
  29. agent_notes/data/agents/explorer.md +23 -0
  30. agent_notes/data/agents/integrations.md +44 -0
  31. agent_notes/data/agents/lead.md +216 -0
  32. agent_notes/data/agents/performance-profiler.md +44 -0
  33. agent_notes/data/agents/refactorer.md +48 -0
  34. agent_notes/data/agents/reviewer.md +44 -0
  35. agent_notes/data/agents/security-auditor.md +44 -0
  36. agent_notes/data/agents/system-auditor.md +38 -0
  37. agent_notes/data/agents/tech-writer.md +32 -0
  38. agent_notes/data/agents/test-runner.md +36 -0
  39. agent_notes/data/agents/test-writer.md +39 -0
  40. agent_notes/data/cli/claude.yaml +25 -0
  41. agent_notes/data/cli/copilot.yaml +18 -0
  42. agent_notes/data/cli/opencode.yaml +22 -0
  43. agent_notes/data/commands/brainstorm.md +8 -0
  44. agent_notes/data/commands/debug.md +9 -0
  45. agent_notes/data/commands/review.md +10 -0
  46. agent_notes/data/global-claude.md +290 -0
  47. agent_notes/data/global-copilot.md +27 -0
  48. agent_notes/data/global-opencode.md +40 -0
  49. agent_notes/data/hooks/session-context.md.tpl +19 -0
  50. agent_notes/data/models/claude-haiku-4-5.yaml +15 -0
  51. agent_notes/data/models/claude-opus-4-1.yaml +16 -0
  52. agent_notes/data/models/claude-opus-4-5.yaml +16 -0
  53. agent_notes/data/models/claude-opus-4-6.yaml +16 -0
  54. agent_notes/data/models/claude-opus-4-7.yaml +15 -0
  55. agent_notes/data/models/claude-sonnet-4-5.yaml +16 -0
  56. agent_notes/data/models/claude-sonnet-4-6.yaml +15 -0
  57. agent_notes/data/models/claude-sonnet-4.yaml +16 -0
  58. agent_notes/data/pricing.yaml +33 -0
  59. agent_notes/data/roles/orchestrator.yaml +5 -0
  60. agent_notes/data/roles/reasoner.yaml +5 -0
  61. agent_notes/data/roles/scout.yaml +5 -0
  62. agent_notes/data/roles/worker.yaml +5 -0
  63. agent_notes/data/rules/code-quality.md +9 -0
  64. agent_notes/data/rules/safety.md +10 -0
  65. agent_notes/data/scripts/cost-report +211 -0
  66. agent_notes/data/skills/brainstorming/SKILL.md +57 -0
  67. agent_notes/data/skills/code-review/SKILL.md +64 -0
  68. agent_notes/data/skills/debugging-protocol/SKILL.md +51 -0
  69. agent_notes/data/skills/docker-compose/SKILL.md +318 -0
  70. agent_notes/data/skills/docker-compose-advanced/SKILL.md +575 -0
  71. agent_notes/data/skills/docker-dockerfile/SKILL.md +385 -0
  72. agent_notes/data/skills/docker-dockerfile-languages/SKILL.md +293 -0
  73. agent_notes/data/skills/git/SKILL.md +87 -0
  74. agent_notes/data/skills/rails-active-storage/SKILL.md +321 -0
  75. agent_notes/data/skills/rails-broadcasting/SKILL.md +374 -0
  76. agent_notes/data/skills/rails-concerns/SKILL.md +806 -0
  77. agent_notes/data/skills/rails-controllers/SKILL.md +510 -0
  78. agent_notes/data/skills/rails-controllers-advanced/SKILL.md +441 -0
  79. agent_notes/data/skills/rails-helpers/SKILL.md +677 -0
  80. agent_notes/data/skills/rails-initializers/SKILL.md +79 -0
  81. agent_notes/data/skills/rails-javascript/SKILL.md +567 -0
  82. agent_notes/data/skills/rails-jobs/SKILL.md +700 -0
  83. agent_notes/data/skills/rails-kamal/SKILL.md +483 -0
  84. agent_notes/data/skills/rails-lib/SKILL.md +101 -0
  85. agent_notes/data/skills/rails-mailers/SKILL.md +321 -0
  86. agent_notes/data/skills/rails-migrations/SKILL.md +268 -0
  87. agent_notes/data/skills/rails-models/SKILL.md +459 -0
  88. agent_notes/data/skills/rails-models-advanced/SKILL.md +398 -0
  89. agent_notes/data/skills/rails-routes/SKILL.md +804 -0
  90. agent_notes/data/skills/rails-style/SKILL.md +538 -0
  91. agent_notes/data/skills/rails-testing-controllers/SKILL.md +343 -0
  92. agent_notes/data/skills/rails-testing-models/SKILL.md +296 -0
  93. agent_notes/data/skills/rails-testing-system/SKILL.md +375 -0
  94. agent_notes/data/skills/rails-validations/SKILL.md +108 -0
  95. agent_notes/data/skills/rails-view-components/SKILL.md +511 -0
  96. agent_notes/data/skills/rails-view-components-advanced/SKILL.md +376 -0
  97. agent_notes/data/skills/rails-views/SKILL.md +413 -0
  98. agent_notes/data/skills/rails-views-advanced/SKILL.md +450 -0
  99. agent_notes/data/skills/refactoring-protocol/SKILL.md +64 -0
  100. agent_notes/data/skills/tdd/SKILL.md +57 -0
  101. agent_notes/data/templates/__init__.py +1 -0
  102. agent_notes/data/templates/__pycache__/__init__.cpython-314.pyc +0 -0
  103. agent_notes/data/templates/frontmatter/__init__.py +1 -0
  104. agent_notes/data/templates/frontmatter/__pycache__/__init__.cpython-314.pyc +0 -0
  105. agent_notes/data/templates/frontmatter/__pycache__/claude.cpython-314.pyc +0 -0
  106. agent_notes/data/templates/frontmatter/__pycache__/cursor.cpython-314.pyc +0 -0
  107. agent_notes/data/templates/frontmatter/__pycache__/opencode.cpython-314.pyc +0 -0
  108. agent_notes/data/templates/frontmatter/claude.py +44 -0
  109. agent_notes/data/templates/frontmatter/opencode.py +104 -0
  110. agent_notes/doctor_checks.py +189 -0
  111. agent_notes/domain/__init__.py +17 -0
  112. agent_notes/domain/agent.py +34 -0
  113. agent_notes/domain/cli_backend.py +40 -0
  114. agent_notes/domain/diagnostics.py +29 -0
  115. agent_notes/domain/diff.py +44 -0
  116. agent_notes/domain/model.py +27 -0
  117. agent_notes/domain/role.py +13 -0
  118. agent_notes/domain/rule.py +13 -0
  119. agent_notes/domain/skill.py +15 -0
  120. agent_notes/domain/state.py +46 -0
  121. agent_notes/install_state.py +11 -0
  122. agent_notes/registries/__init__.py +16 -0
  123. agent_notes/registries/_base.py +46 -0
  124. agent_notes/registries/agent_registry.py +107 -0
  125. agent_notes/registries/cli_registry.py +89 -0
  126. agent_notes/registries/model_registry.py +85 -0
  127. agent_notes/registries/role_registry.py +64 -0
  128. agent_notes/registries/rule_registry.py +80 -0
  129. agent_notes/registries/skill_registry.py +141 -0
  130. agent_notes/services/__init__.py +8 -0
  131. agent_notes/services/diagnostics/__init__.py +47 -0
  132. agent_notes/services/diagnostics/_checks.py +272 -0
  133. agent_notes/services/diagnostics/_display.py +346 -0
  134. agent_notes/services/diagnostics/_fix.py +169 -0
  135. agent_notes/services/diff.py +349 -0
  136. agent_notes/services/fs.py +195 -0
  137. agent_notes/services/install_state_builder.py +210 -0
  138. agent_notes/services/installer.py +293 -0
  139. agent_notes/services/memory_backend.py +155 -0
  140. agent_notes/services/rendering.py +329 -0
  141. agent_notes/services/session_context.py +23 -0
  142. agent_notes/services/settings_writer.py +79 -0
  143. agent_notes/services/state_store.py +249 -0
  144. agent_notes/services/ui.py +419 -0
  145. agent_notes/services/user_config.py +62 -0
  146. agent_notes/services/validation.py +67 -0
  147. agent_notes/state.py +21 -0
  148. agent_notes-2.0.4.dist-info/METADATA +14 -0
  149. agent_notes-2.0.4.dist-info/RECORD +162 -0
  150. agent_notes-2.0.4.dist-info/WHEEL +5 -0
  151. agent_notes-2.0.4.dist-info/entry_points.txt +2 -0
  152. agent_notes-2.0.4.dist-info/licenses/LICENSE +21 -0
  153. agent_notes-2.0.4.dist-info/top_level.txt +2 -0
  154. tests/conftest.py +20 -0
  155. tests/functional/__init__.py +0 -0
  156. tests/functional/test_build_commands.py +88 -0
  157. tests/functional/test_registries.py +128 -0
  158. tests/integration/__init__.py +0 -0
  159. tests/integration/test_build_output.py +129 -0
  160. tests/plugins/__init__.py +0 -0
  161. tests/plugins/test_agents.py +93 -0
  162. tests/plugins/test_skills.py +77 -0
@@ -0,0 +1,430 @@
1
+ """Manage agent memory stored in ~/.claude/agent-memory/."""
2
+
3
+ import shutil
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+ from ..config import MEMORY_DIR, BACKUP_DIR, Color
8
+
9
+
10
+ def _load_memory_config():
11
+ from ..services.state_store import load_state
12
+ from ..config import memory_dir_for_backend
13
+ state = load_state()
14
+ if state is None:
15
+ return "local", MEMORY_DIR
16
+ backend = state.memory.backend
17
+ path = memory_dir_for_backend(backend, state.memory.path)
18
+ return backend, path
19
+
20
+
21
+ def do_vault() -> None:
22
+ """Show current backend and memory path."""
23
+ backend, path = _load_memory_config()
24
+ if backend == "none":
25
+ print("Memory backend: disabled (none)")
26
+ return
27
+ if backend == "obsidian":
28
+ print(f"Memory backend: obsidian")
29
+ print(f"Vault path: {path}")
30
+ else:
31
+ print(f"Memory backend: local")
32
+ print(f"Memory path: {path}")
33
+ initialized = path is not None and path.exists()
34
+ print(f"Initialized: {'yes' if initialized else 'no — run: agent-notes memory init'}")
35
+
36
+
37
+ def do_init() -> None:
38
+ """Initialize the memory vault — create folder structure and Index.md."""
39
+ backend, path = _load_memory_config()
40
+ if backend == "none":
41
+ print("Memory is disabled. Re-run `agent-notes install` and choose a memory backend.")
42
+ return
43
+ if path is None:
44
+ print("Memory path not configured.")
45
+ return
46
+ if backend == "obsidian":
47
+ from ..services.memory_backend import obsidian_init, OBSIDIAN_CATEGORIES
48
+ obsidian_init(path)
49
+ print(f"{Color.GREEN}Obsidian vault initialised at {path}{Color.NC}")
50
+ print(f" Folders: {', '.join(OBSIDIAN_CATEGORIES)}")
51
+ print(f" Index: {path / 'Index.md'}")
52
+ print(f"\nOpen the folder as a vault in Obsidian to start browsing.")
53
+ else:
54
+ from ..services.memory_backend import local_init
55
+ local_init(path)
56
+ print(f"{Color.GREEN}Memory directory initialised at {path}{Color.NC}")
57
+
58
+
59
+ def do_index() -> None:
60
+ """Regenerate Index.md for the current backend."""
61
+ backend, path = _load_memory_config()
62
+ if backend == "none":
63
+ print("Memory is disabled. Run `agent-notes memory vault` to check configuration.")
64
+ return
65
+ if path is None:
66
+ print("Memory path not configured.")
67
+ return
68
+ if backend == "obsidian":
69
+ from ..services.memory_backend import obsidian_regenerate_index
70
+ obsidian_regenerate_index(path)
71
+ print(f"{Color.GREEN}Index.md regenerated at {path / 'Index.md'}{Color.NC}")
72
+ else:
73
+ from ..services.memory_backend import local_regenerate_index
74
+ local_regenerate_index(path)
75
+ print(f"{Color.GREEN}Index.md regenerated at {path / 'Index.md'}{Color.NC}")
76
+
77
+
78
+ def do_add(title: str, body: str, note_type: str = "context", agent: str = "", project: str = "", tags: Optional[list] = None) -> None:
79
+ """Add a note to memory (obsidian backend only for structured notes)."""
80
+ backend, path = _load_memory_config()
81
+ if backend == "none":
82
+ print("Memory is disabled. Run `agent-notes memory vault` to check configuration.")
83
+ return
84
+ if path is None:
85
+ print("Memory path not configured.")
86
+ return
87
+ if backend == "obsidian":
88
+ from ..services.memory_backend import obsidian_init, obsidian_write_note
89
+ obsidian_init(path)
90
+ note_path = obsidian_write_note(
91
+ path,
92
+ title=title,
93
+ body=body,
94
+ note_type=note_type,
95
+ agent=agent,
96
+ project=project,
97
+ tags=tags or [],
98
+ )
99
+ print(f"{Color.GREEN}Note saved: {note_path}{Color.NC}")
100
+ else:
101
+ print("The `add` subcommand is for the obsidian backend.")
102
+ print("For local backend, write files directly to the agent subdirectory.")
103
+
104
+
105
+ def do_list() -> None:
106
+ """List all agent memories with sizes."""
107
+ backend, path = _load_memory_config()
108
+
109
+ if backend == "none":
110
+ print("Memory is disabled. Run `agent-notes memory backend` to enable it.")
111
+ return
112
+
113
+ if backend == "obsidian":
114
+ if path is None or not path.exists():
115
+ print(f"Obsidian vault not found at {path}")
116
+ return
117
+ from ..services.memory_backend import obsidian_list_notes
118
+ notes = obsidian_list_notes(path)
119
+ if not notes:
120
+ print(f"No notes found in vault {path}")
121
+ return
122
+ print(f"Obsidian vault ({path}):")
123
+ print("")
124
+ current_cat = None
125
+ for note in notes:
126
+ if note["category"] != current_cat:
127
+ current_cat = note["category"]
128
+ print(f" {Color.CYAN}{current_cat}{Color.NC}")
129
+ print(f" {note['file']}")
130
+ return
131
+
132
+ # local backend
133
+ if path is None or not path.exists() or not any(path.iterdir()):
134
+ print(f"No agent memories found in {path}")
135
+ return
136
+
137
+ print(f"Agent memories ({path}):")
138
+ print("")
139
+ print(f" {'AGENT':<25} {'SIZE'}")
140
+ print(f" {'-' * 25} {'-' * 4}")
141
+
142
+ for d in path.iterdir():
143
+ if d.is_dir():
144
+ name = d.name
145
+ size = get_directory_size(d)
146
+ size_str = format_size(size)
147
+ print(f" {name:<25} {size_str}")
148
+
149
+
150
+ def do_size() -> None:
151
+ """Show total memory usage."""
152
+ backend, path = _load_memory_config()
153
+
154
+ if backend == "none":
155
+ print("Memory is disabled.")
156
+ return
157
+
158
+ if path is None or not path.exists():
159
+ print("No agent memories found.")
160
+ return
161
+
162
+ total_size = get_directory_size(path)
163
+ size_str = format_size(total_size)
164
+ print("Total memory usage:")
165
+ print(f" {size_str}")
166
+
167
+
168
+ def do_show(name: str) -> None:
169
+ """Show memory contents for one agent (local) or category (obsidian)."""
170
+ backend, path = _load_memory_config()
171
+
172
+ if backend == "none":
173
+ print("Memory is disabled.")
174
+ return
175
+
176
+ if backend == "obsidian":
177
+ if path is None:
178
+ print("Memory path not configured.")
179
+ return
180
+ cat_dir = path / name
181
+ if not cat_dir.exists():
182
+ print(f"Category '{name}' not found in vault {path}")
183
+ return
184
+ print(f"Notes in category '{name}':")
185
+ print("")
186
+ for f in sorted(cat_dir.glob("*.md")):
187
+ print(f"{Color.CYAN}--- {f.name} ---{Color.NC}")
188
+ try:
189
+ print(f.read_text())
190
+ except (UnicodeDecodeError, OSError):
191
+ print("(binary file or read error)")
192
+ print("")
193
+ return
194
+
195
+ # local backend
196
+ if path is None:
197
+ path = MEMORY_DIR
198
+ agent_dir = path / name
199
+ if not agent_dir.exists():
200
+ print(f"No memory found for agent '{name}'")
201
+ available = sorted(d.name for d in path.iterdir() if d.is_dir()) if path.exists() else []
202
+ if available:
203
+ print(f"Available: {' '.join(available)}")
204
+ exit(1)
205
+
206
+ print(f"Memory for agent '{name}' ({agent_dir}):")
207
+ print("")
208
+
209
+ for f in agent_dir.iterdir():
210
+ if f.is_file():
211
+ print(f"{Color.CYAN}--- {f.name} ---{Color.NC}")
212
+ try:
213
+ content = f.read_text()
214
+ print(content)
215
+ except (UnicodeDecodeError, OSError):
216
+ print("(binary file or read error)")
217
+ print("")
218
+
219
+
220
+ def do_reset(name: Optional[str] = None) -> None:
221
+ """Clear agent memory (all or specific agent)."""
222
+ backend, path = _load_memory_config()
223
+
224
+ if backend == "none":
225
+ print("Memory is disabled.")
226
+ return
227
+
228
+ if path is None:
229
+ path = MEMORY_DIR
230
+
231
+ if name:
232
+ agent_dir = path / name
233
+ if not agent_dir.exists():
234
+ print(f"No memory found for agent '{name}'")
235
+ exit(1)
236
+
237
+ print(f"{Color.YELLOW}This will delete all memory for agent '{name}'.{Color.NC}")
238
+ confirm = input("Continue? [y/N] ")
239
+ if confirm.lower() == 'y':
240
+ shutil.rmtree(agent_dir)
241
+ print(f"{Color.GREEN}Memory for '{name}' cleared.{Color.NC}")
242
+ else:
243
+ print("Cancelled.")
244
+ else:
245
+ if not path.exists() or not any(path.iterdir()):
246
+ print("No agent memories to clear.")
247
+ return
248
+
249
+ print(f"{Color.RED}This will delete ALL agent memories.{Color.NC}")
250
+ print(f"Contents of {path}:")
251
+ for d in path.iterdir():
252
+ if d.is_dir():
253
+ print(f" {d.name}")
254
+ print("")
255
+
256
+ confirm = input("Type 'yes' to confirm: ")
257
+ if confirm == "yes":
258
+ for item in path.iterdir():
259
+ if item.is_dir():
260
+ shutil.rmtree(item)
261
+ else:
262
+ item.unlink()
263
+ print(f"{Color.GREEN}All agent memories cleared.{Color.NC}")
264
+ else:
265
+ print("Cancelled.")
266
+
267
+
268
+ def do_export() -> None:
269
+ """Copy memories to agent-notes/memory-backup/."""
270
+ backend, path = _load_memory_config()
271
+
272
+ if backend == "none":
273
+ print("Memory is disabled.")
274
+ return
275
+
276
+ if path is None:
277
+ path = MEMORY_DIR
278
+
279
+ if not path.exists() or not any(path.iterdir()):
280
+ print("No agent memories to export.")
281
+ return
282
+
283
+ BACKUP_DIR.mkdir(parents=True, exist_ok=True)
284
+
285
+ for item in path.iterdir():
286
+ if item.is_dir():
287
+ dest = BACKUP_DIR / item.name
288
+ if dest.exists():
289
+ shutil.rmtree(dest)
290
+ shutil.copytree(item, dest)
291
+ elif item.is_file():
292
+ shutil.copy2(item, BACKUP_DIR)
293
+
294
+ print(f"{Color.GREEN}Exported to {BACKUP_DIR}{Color.NC}")
295
+ print("Contents:")
296
+ for item in BACKUP_DIR.iterdir():
297
+ print(f" {item.name}")
298
+ print("")
299
+ print(f"{Color.YELLOW}Note: memory-backup/ is in .gitignore — these are personal learnings.{Color.NC}")
300
+
301
+
302
+ def do_import() -> None:
303
+ """Restore from agent-notes/memory-backup/."""
304
+ backend, path = _load_memory_config()
305
+
306
+ if backend == "none":
307
+ print("Memory is disabled.")
308
+ return
309
+
310
+ if path is None:
311
+ path = MEMORY_DIR
312
+
313
+ if not BACKUP_DIR.exists() or not any(BACKUP_DIR.iterdir()):
314
+ print(f"No backup found in {BACKUP_DIR}")
315
+ exit(1)
316
+
317
+ path.mkdir(parents=True, exist_ok=True)
318
+
319
+ for item in BACKUP_DIR.iterdir():
320
+ if item.is_dir():
321
+ dest = path / item.name
322
+ if dest.exists():
323
+ shutil.rmtree(dest)
324
+ shutil.copytree(item, dest)
325
+ elif item.is_file():
326
+ shutil.copy2(item, path)
327
+
328
+ print(f"{Color.GREEN}Imported from {BACKUP_DIR} to {path}{Color.NC}")
329
+ print("Restored agents:")
330
+ for item in path.iterdir():
331
+ if item.is_dir():
332
+ print(f" {item.name}")
333
+
334
+
335
+ def get_directory_size(path: Path) -> int:
336
+ """Calculate total size of directory in bytes."""
337
+ total = 0
338
+ try:
339
+ for item in path.rglob('*'):
340
+ if item.is_file():
341
+ total += item.stat().st_size
342
+ except (OSError, PermissionError):
343
+ pass
344
+ return total
345
+
346
+
347
+ def format_size(size_bytes: int) -> str:
348
+ """Format size in human-readable format."""
349
+ if size_bytes == 0:
350
+ return "0B"
351
+
352
+ original_size = size_bytes
353
+ for unit in ['B', 'K', 'M', 'G', 'T']:
354
+ if original_size < 1024:
355
+ if unit == 'B':
356
+ return f"{original_size}B"
357
+ else:
358
+ return f"{original_size:.1f}{unit}"
359
+ original_size /= 1024
360
+
361
+ return f"{original_size:.1f}P"
362
+
363
+
364
+ def show_help() -> None:
365
+ """Show memory command help."""
366
+ help_text = """Usage: agent-notes memory [command] [args]
367
+
368
+ Manage agent memory.
369
+
370
+ Commands:
371
+ init Create folder structure and Index.md
372
+ list List all agent memories with sizes (default)
373
+ vault Show current backend and memory path
374
+ index Regenerate Index.md for the current backend
375
+ add <title> <body> Add a note (obsidian backend)
376
+ size Total disk usage
377
+ show <name> Show memory contents for one agent/category
378
+ reset Clear ALL memories (requires confirmation)
379
+ reset <name> Clear one agent's memory
380
+ export Back up memories to agent-notes/memory-backup/
381
+ import Restore from agent-notes/memory-backup/
382
+
383
+ Examples:
384
+ agent-notes memory List all memories
385
+ agent-notes memory vault Show backend configuration
386
+ agent-notes memory index Regenerate Index.md
387
+ agent-notes memory show coder View coder agent's memory
388
+ agent-notes memory reset reviewer Clear reviewer's memory
389
+ agent-notes memory export Back up before cleanup"""
390
+
391
+ print(help_text)
392
+
393
+
394
+ def memory(action: str = "list", name: Optional[str] = None, extra: Optional[list] = None) -> None:
395
+ """Manage agent memory."""
396
+ if action == "list":
397
+ do_list()
398
+ elif action == "init":
399
+ do_init()
400
+ elif action == "vault":
401
+ do_vault()
402
+ elif action == "index":
403
+ do_index()
404
+ elif action == "add":
405
+ # name is title, extra[0] is body
406
+ if not name:
407
+ print("Error: add requires a title.")
408
+ exit(1)
409
+ body = extra[0] if extra else ""
410
+ note_type = extra[1] if extra and len(extra) > 1 else "context"
411
+ agent = extra[2] if extra and len(extra) > 2 else ""
412
+ project = extra[3] if extra and len(extra) > 3 else ""
413
+ do_add(name, body, note_type=note_type, agent=agent, project=project)
414
+ elif action == "size":
415
+ do_size()
416
+ elif action == "show":
417
+ if not name:
418
+ print("Error: show requires an agent name.")
419
+ exit(1)
420
+ do_show(name)
421
+ elif action == "reset":
422
+ do_reset(name)
423
+ elif action == "export":
424
+ do_export()
425
+ elif action == "import":
426
+ do_import()
427
+ else:
428
+ print(f"Unknown command: {action}")
429
+ show_help()
430
+ exit(1)
@@ -0,0 +1,152 @@
1
+ """Regenerate agent files from current state.json."""
2
+
3
+ import sys
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+ from ..config import Color
8
+
9
+
10
+ def regenerate(scope: Optional[str] = None, cli: Optional[str] = None, local: bool = False, project_path: Optional[Path] = None) -> None:
11
+ """Rebuild agent/skill/config files from current state.json.
12
+
13
+ Args:
14
+ scope: 'global' or 'local' (auto-detect if omitted)
15
+ cli: Target CLI (regenerate all if omitted)
16
+ local: Shortcut for scope='local'
17
+ project_path: Explicit project path for local scope
18
+ """
19
+ from .. import state as state_mod
20
+ from ..state import get_scope
21
+ from ..config import DATA_DIR
22
+ from .build import generate_agent_files
23
+ from ..registries.cli_registry import load_registry
24
+ from .. import install_state
25
+ from ..config import PKG_DIR
26
+ import yaml
27
+
28
+ # Load state
29
+ current_state = state_mod.load()
30
+ if current_state is None:
31
+ print("No state.json found. Nothing to regenerate.")
32
+ sys.exit(1)
33
+
34
+ # Determine scope
35
+ if local:
36
+ scope = 'local'
37
+ elif scope is None:
38
+ # Auto-detect
39
+ if current_state.global_install:
40
+ scope = 'global'
41
+ elif current_state.local_installs:
42
+ scope = 'local'
43
+ else:
44
+ print("No installation found in state")
45
+ sys.exit(1)
46
+
47
+ if project_path is None and scope == 'local':
48
+ project_path = Path.cwd()
49
+
50
+ scope_state = get_scope(current_state, scope, project_path)
51
+ if scope_state is None:
52
+ print(f"No {scope} installation found")
53
+ sys.exit(1)
54
+
55
+ # Determine target CLIs
56
+ if cli:
57
+ if cli not in scope_state.clis:
58
+ print(f"CLI '{cli}' not in {scope} installation")
59
+ print(f"Installed: {', '.join(scope_state.clis.keys())}")
60
+ sys.exit(1)
61
+ target_clis = [cli]
62
+ else:
63
+ target_clis = list(scope_state.clis.keys())
64
+
65
+ # Load agent config
66
+ agents_yaml = DATA_DIR / "agents" / "agents.yaml"
67
+ if not agents_yaml.exists():
68
+ print(f"Error: {agents_yaml} not found")
69
+ sys.exit(1)
70
+
71
+ with open(agents_yaml) as f:
72
+ agents_data = yaml.safe_load(f)
73
+
74
+ agents_config = agents_data.get('agents', {})
75
+
76
+ # Regenerate per CLI
77
+ registry = load_registry()
78
+
79
+ print(f"Regenerating {scope} installation...")
80
+
81
+ total_files = 0
82
+
83
+ for cli_name in target_clis:
84
+ backend = registry.get(cli_name)
85
+ print(f"\n{backend.label}:")
86
+
87
+ # Generate agents
88
+ if backend.supports("agents"):
89
+ files = generate_agent_files(
90
+ agents_config,
91
+ {}, # empty tiers - state-driven only
92
+ state=current_state,
93
+ scope=scope,
94
+ project_path=project_path
95
+ )
96
+ # Count files for this CLI
97
+ try:
98
+ cli_files = [f for f in files if backend.name in str(f)]
99
+ if cli_files:
100
+ print(f" ✓ {len(cli_files)} agents regenerated")
101
+ total_files += len(cli_files)
102
+ except (TypeError, AttributeError):
103
+ # Handle case where files is mocked or has unexpected structure
104
+ print(f" ✓ agents regenerated")
105
+
106
+ # Regenerate other components as needed
107
+ from ..services import installer
108
+
109
+ # Regenerate rules for backends that support them
110
+ if backend.supports("rules"):
111
+ installer.install_component_for_backend(backend, "rules", scope, scope_state.mode == "copy")
112
+ print(f" ✓ rules regenerated for {backend.label}")
113
+
114
+ # Regenerate global config files
115
+ if backend.layout.get("config"):
116
+ installer.install_component_for_backend(backend, "config", scope, scope_state.mode == "copy")
117
+ print(f" ✓ config regenerated for {backend.label}")
118
+
119
+ # Regenerate skills (static files, just ensure they're synced)
120
+ if backend.supports("skills"):
121
+ installer.install_component_for_backend(backend, "skills", scope, scope_state.mode == "copy")
122
+ print(f" ✓ skills regenerated for {backend.label}")
123
+
124
+ # Update installed manifest to reflect current state
125
+ try:
126
+ # Get current role_models from state to preserve them
127
+ existing_role_models = {}
128
+ for cli_name, backend_state in scope_state.clis.items():
129
+ existing_role_models[cli_name] = backend_state.role_models
130
+
131
+ # Build new state with current files but preserve role_models
132
+ new_state = install_state.build_install_state(
133
+ mode=scope_state.mode,
134
+ scope=scope,
135
+ repo_root=PKG_DIR.parent,
136
+ project_path=project_path,
137
+ role_models=existing_role_models
138
+ )
139
+
140
+ # Merge back into current_state preserving other scopes
141
+ if scope == 'global':
142
+ current_state.global_install = new_state.global_install
143
+ else:
144
+ # Update the specific local install
145
+ if project_path:
146
+ current_state.local_installs[str(project_path.resolve())] = new_state.local_installs[str(project_path.resolve())]
147
+
148
+ install_state.record_install_state(current_state)
149
+ except Exception as e:
150
+ print(f"{Color.YELLOW}Warning: failed to update install state: {e}{Color.NC}")
151
+
152
+ print(f"\n{Color.GREEN}Regenerated {total_files} files.{Color.NC}")