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,129 @@
1
+ """Integration tests that verify real build output in dist/."""
2
+ import pytest
3
+ from pathlib import Path
4
+
5
+
6
+ def _parse_frontmatter(text: str) -> dict:
7
+ """Parse YAML frontmatter from markdown text. Returns dict of key/value strings."""
8
+ lines = text.split("\n")
9
+ if not lines or lines[0].strip() != "---":
10
+ return {}
11
+ end = None
12
+ for i in range(1, len(lines)):
13
+ if lines[i].strip() == "---":
14
+ end = i
15
+ break
16
+ if end is None:
17
+ return {}
18
+ result = {}
19
+ for line in lines[1:end]:
20
+ if ":" in line:
21
+ key, _, value = line.partition(":")
22
+ result[key.strip()] = value.strip()
23
+ return result
24
+
25
+
26
+ # --- Directory structure ---
27
+
28
+ def test_claude_agents_dir_exists(built_dist):
29
+ assert (built_dist / "claude" / "agents").is_dir()
30
+
31
+
32
+ def test_claude_agents_has_enough_files(built_dist):
33
+ files = list((built_dist / "claude" / "agents").glob("*.md"))
34
+ assert len(files) >= 15
35
+
36
+
37
+ def test_opencode_agents_dir_exists(built_dist):
38
+ assert (built_dist / "opencode" / "agents").is_dir()
39
+
40
+
41
+ def test_opencode_agents_has_files(built_dist):
42
+ files = list((built_dist / "opencode" / "agents").glob("*.md"))
43
+ assert len(files) > 0
44
+
45
+
46
+ def test_skills_dir_exists(built_dist):
47
+ assert (built_dist / "skills").is_dir()
48
+
49
+
50
+ def test_skills_has_enough_subdirs(built_dist):
51
+ subdirs = [p for p in (built_dist / "skills").iterdir() if p.is_dir()]
52
+ assert len(subdirs) >= 30
53
+
54
+
55
+ def test_rules_dir_exists(built_dist):
56
+ assert (built_dist / "rules").is_dir()
57
+
58
+
59
+ def test_rules_has_markdown_files(built_dist):
60
+ files = list((built_dist / "rules").glob("*.md"))
61
+ assert len(files) >= 1
62
+
63
+
64
+ # --- Scripts ---
65
+
66
+ def test_cost_report_script_exists(built_dist):
67
+ assert (built_dist / "scripts" / "cost-report").exists()
68
+
69
+
70
+ def test_cost_report_is_executable(built_dist):
71
+ import stat
72
+ script = built_dist / "scripts" / "cost-report"
73
+ mode = script.stat().st_mode
74
+ assert mode & stat.S_IXUSR
75
+
76
+
77
+ def test_cost_report_contains_pricing_json(built_dist):
78
+ content = (built_dist / "scripts" / "cost-report").read_text()
79
+ assert '"providers"' in content
80
+
81
+
82
+ def test_cost_report_has_no_placeholder(built_dist):
83
+ content = (built_dist / "scripts" / "cost-report").read_text()
84
+ assert "{{PRICING}}" not in content
85
+
86
+
87
+ # --- Specific agent files ---
88
+
89
+ def test_coder_md_exists(built_dist):
90
+ assert (built_dist / "claude" / "agents" / "coder.md").exists()
91
+
92
+
93
+ def test_reviewer_md_exists(built_dist):
94
+ assert (built_dist / "claude" / "agents" / "reviewer.md").exists()
95
+
96
+
97
+ def test_coder_frontmatter_name(built_dist):
98
+ text = (built_dist / "claude" / "agents" / "coder.md").read_text()
99
+ fm = _parse_frontmatter(text)
100
+ assert fm.get("name") == "coder"
101
+
102
+
103
+ def test_coder_frontmatter_description(built_dist):
104
+ text = (built_dist / "claude" / "agents" / "coder.md").read_text()
105
+ fm = _parse_frontmatter(text)
106
+ assert fm.get("description", "").strip()
107
+
108
+
109
+ def test_coder_frontmatter_model(built_dist):
110
+ text = (built_dist / "claude" / "agents" / "coder.md").read_text()
111
+ fm = _parse_frontmatter(text)
112
+ assert fm.get("model", "").strip()
113
+
114
+
115
+ def test_coder_frontmatter_tools(built_dist):
116
+ text = (built_dist / "claude" / "agents" / "coder.md").read_text()
117
+ fm = _parse_frontmatter(text)
118
+ assert fm.get("tools", "").strip()
119
+
120
+
121
+ # --- Global config file ---
122
+
123
+ def test_claude_md_exists(built_dist):
124
+ assert (built_dist / "claude" / "CLAUDE.md").exists()
125
+
126
+
127
+ def test_claude_md_is_non_empty(built_dist):
128
+ content = (built_dist / "claude" / "CLAUDE.md").read_text()
129
+ assert len(content.strip()) > 0
File without changes
@@ -0,0 +1,93 @@
1
+ """Parametrized tests for every agent file in dist/claude/agents/."""
2
+ import pytest
3
+ from pathlib import Path
4
+
5
+ from agent_notes.config import DIST_DIR
6
+
7
+ _VALID_MODEL_KEYWORDS = {"haiku", "sonnet", "opus", "claude", "gpt", "gemini"}
8
+
9
+ _CLAUDE_AGENTS_DIR = DIST_DIR / "claude" / "agents"
10
+
11
+
12
+ def _agent_files():
13
+ if not _CLAUDE_AGENTS_DIR.is_dir():
14
+ return []
15
+ return sorted(_CLAUDE_AGENTS_DIR.glob("*.md"))
16
+
17
+
18
+ def _parse_frontmatter(text: str) -> dict:
19
+ lines = text.split("\n")
20
+ if not lines or lines[0].strip() != "---":
21
+ return {}
22
+ end = None
23
+ for i in range(1, len(lines)):
24
+ if lines[i].strip() == "---":
25
+ end = i
26
+ break
27
+ if end is None:
28
+ return {}
29
+ result = {}
30
+ for line in lines[1:end]:
31
+ if ":" in line:
32
+ key, _, value = line.partition(":")
33
+ result[key.strip()] = value.strip().strip('"\'')
34
+ return result
35
+
36
+
37
+ AGENT_FILES = _agent_files()
38
+
39
+
40
+ @pytest.fixture(scope="module", autouse=True)
41
+ def require_built_dist(built_dist):
42
+ """Ensure the build has run before collecting agent files."""
43
+ pass
44
+
45
+
46
+ @pytest.mark.parametrize("agent_file", AGENT_FILES, ids=[f.name for f in AGENT_FILES])
47
+ def test_agent_file_non_empty(agent_file):
48
+ assert agent_file.stat().st_size > 0, f"{agent_file.name} is empty"
49
+
50
+
51
+ @pytest.mark.parametrize("agent_file", AGENT_FILES, ids=[f.name for f in AGENT_FILES])
52
+ def test_agent_has_frontmatter_markers(agent_file):
53
+ text = agent_file.read_text()
54
+ lines = text.split("\n")
55
+ assert lines[0].strip() == "---", f"{agent_file.name} does not start with ---"
56
+ has_close = any(line.strip() == "---" for line in lines[1:])
57
+ assert has_close, f"{agent_file.name} has no closing ---"
58
+
59
+
60
+ @pytest.mark.parametrize("agent_file", AGENT_FILES, ids=[f.name for f in AGENT_FILES])
61
+ def test_agent_frontmatter_has_name(agent_file):
62
+ text = agent_file.read_text()
63
+ fm = _parse_frontmatter(text)
64
+ assert fm.get("name", "").strip(), f"{agent_file.name} missing 'name' in frontmatter"
65
+
66
+
67
+ @pytest.mark.parametrize("agent_file", AGENT_FILES, ids=[f.name for f in AGENT_FILES])
68
+ def test_agent_frontmatter_has_description(agent_file):
69
+ text = agent_file.read_text()
70
+ fm = _parse_frontmatter(text)
71
+ assert fm.get("description", "").strip(), f"{agent_file.name} missing 'description' in frontmatter"
72
+
73
+
74
+ @pytest.mark.parametrize("agent_file", AGENT_FILES, ids=[f.name for f in AGENT_FILES])
75
+ def test_agent_frontmatter_model_is_valid(agent_file):
76
+ text = agent_file.read_text()
77
+ fm = _parse_frontmatter(text)
78
+ model = fm.get("model", "").strip().lower()
79
+ assert model, f"{agent_file.name} missing 'model' in frontmatter"
80
+ assert any(kw in model for kw in _VALID_MODEL_KEYWORDS), (
81
+ f"{agent_file.name}: model '{model}' does not match any known keyword"
82
+ )
83
+
84
+
85
+ @pytest.mark.parametrize("agent_file", AGENT_FILES, ids=[f.name for f in AGENT_FILES])
86
+ def test_agent_frontmatter_has_tools(agent_file):
87
+ text = agent_file.read_text()
88
+ fm = _parse_frontmatter(text)
89
+ has_tools = fm.get("tools", "").strip()
90
+ has_disallowed = fm.get("disallowedTools", "").strip()
91
+ assert has_tools or has_disallowed, (
92
+ f"{agent_file.name} missing both 'tools' and 'disallowedTools' in frontmatter"
93
+ )
@@ -0,0 +1,77 @@
1
+ """Parametrized tests for every skill directory in data/skills/."""
2
+ import pytest
3
+ from pathlib import Path
4
+
5
+ from agent_notes.config import SKILLS_DIR
6
+
7
+
8
+ def _skill_dirs():
9
+ if not SKILLS_DIR.is_dir():
10
+ return []
11
+ return sorted(d for d in SKILLS_DIR.iterdir() if d.is_dir())
12
+
13
+
14
+ def _parse_frontmatter(text: str) -> dict:
15
+ lines = text.split("\n")
16
+ if not lines or lines[0].strip() != "---":
17
+ return {}
18
+ end = None
19
+ for i in range(1, len(lines)):
20
+ if lines[i].strip() == "---":
21
+ end = i
22
+ break
23
+ if end is None:
24
+ return {}
25
+ result = {}
26
+ for line in lines[1:end]:
27
+ if ":" in line:
28
+ key, _, value = line.partition(":")
29
+ result[key.strip()] = value.strip().strip('"\'')
30
+ return result
31
+
32
+
33
+ SKILL_DIRS = _skill_dirs()
34
+
35
+
36
+ @pytest.mark.parametrize("skill_dir", SKILL_DIRS, ids=[d.name for d in SKILL_DIRS])
37
+ def test_skill_md_exists(skill_dir):
38
+ assert (skill_dir / "SKILL.md").exists(), f"SKILL.md missing in {skill_dir.name}"
39
+
40
+
41
+ @pytest.mark.parametrize("skill_dir", SKILL_DIRS, ids=[d.name for d in SKILL_DIRS])
42
+ def test_skill_has_frontmatter(skill_dir):
43
+ text = (skill_dir / "SKILL.md").read_text()
44
+ lines = text.split("\n")
45
+ assert lines[0].strip() == "---", f"{skill_dir.name}/SKILL.md does not start with ---"
46
+ has_close = any(line.strip() == "---" for line in lines[1:])
47
+ assert has_close, f"{skill_dir.name}/SKILL.md has no closing ---"
48
+
49
+
50
+ @pytest.mark.parametrize("skill_dir", SKILL_DIRS, ids=[d.name for d in SKILL_DIRS])
51
+ def test_skill_frontmatter_has_name(skill_dir):
52
+ text = (skill_dir / "SKILL.md").read_text()
53
+ fm = _parse_frontmatter(text)
54
+ assert fm.get("name", "").strip(), f"{skill_dir.name}/SKILL.md missing non-empty 'name'"
55
+
56
+
57
+ @pytest.mark.parametrize("skill_dir", SKILL_DIRS, ids=[d.name for d in SKILL_DIRS])
58
+ def test_skill_frontmatter_has_description(skill_dir):
59
+ text = (skill_dir / "SKILL.md").read_text()
60
+ fm = _parse_frontmatter(text)
61
+ assert fm.get("description", "").strip(), f"{skill_dir.name}/SKILL.md missing non-empty 'description'"
62
+
63
+
64
+ @pytest.mark.parametrize("skill_dir", SKILL_DIRS, ids=[d.name for d in SKILL_DIRS])
65
+ def test_skill_frontmatter_has_group(skill_dir):
66
+ text = (skill_dir / "SKILL.md").read_text()
67
+ fm = _parse_frontmatter(text)
68
+ assert fm.get("group", "").strip(), f"{skill_dir.name}/SKILL.md missing non-empty 'group'"
69
+
70
+
71
+ @pytest.mark.parametrize("skill_dir", SKILL_DIRS, ids=[d.name for d in SKILL_DIRS])
72
+ def test_skill_name_matches_dir(skill_dir):
73
+ text = (skill_dir / "SKILL.md").read_text()
74
+ fm = _parse_frontmatter(text)
75
+ assert fm.get("name") == skill_dir.name, (
76
+ f"{skill_dir.name}/SKILL.md: 'name' field is '{fm.get('name')}', expected '{skill_dir.name}'"
77
+ )