devmemory-cli 0.1.0.dev0__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.
- devmemory/__about__.py +3 -0
- devmemory/__init__.py +14 -0
- devmemory/__main__.py +6 -0
- devmemory/adapters/__init__.py +6 -0
- devmemory/adapters/databricks.py +346 -0
- devmemory/adapters/entire.py +444 -0
- devmemory/adapters/git.py +408 -0
- devmemory/adapters/graph.py +251 -0
- devmemory/adapters/metrics.py +150 -0
- devmemory/adapters/tests.py +227 -0
- devmemory/analysis/__init__.py +19 -0
- devmemory/analysis/base.py +128 -0
- devmemory/analysis/chain.py +53 -0
- devmemory/analysis/llm.py +236 -0
- devmemory/analysis/rules.py +110 -0
- devmemory/api/__init__.py +10 -0
- devmemory/api/app.py +390 -0
- devmemory/api/mappers.py +187 -0
- devmemory/api/schemas.py +201 -0
- devmemory/cli/__init__.py +1 -0
- devmemory/cli/_errors.py +36 -0
- devmemory/cli/_render.py +79 -0
- devmemory/cli/analytics.py +136 -0
- devmemory/cli/analyze.py +58 -0
- devmemory/cli/app.py +163 -0
- devmemory/cli/checkpoint.py +199 -0
- devmemory/cli/compare.py +104 -0
- devmemory/cli/doctor.py +151 -0
- devmemory/cli/history.py +56 -0
- devmemory/cli/impact.py +95 -0
- devmemory/cli/init.py +91 -0
- devmemory/cli/mcp.py +66 -0
- devmemory/cli/memory.py +70 -0
- devmemory/cli/restore.py +91 -0
- devmemory/cli/search.py +48 -0
- devmemory/cli/serve.py +64 -0
- devmemory/cli/show.py +139 -0
- devmemory/cli/status.py +72 -0
- devmemory/cli/task.py +333 -0
- devmemory/config.py +302 -0
- devmemory/domain/__init__.py +5 -0
- devmemory/domain/enums.py +151 -0
- devmemory/domain/errors.py +188 -0
- devmemory/domain/models.py +452 -0
- devmemory/domain/taskloop.py +212 -0
- devmemory/environment.py +67 -0
- devmemory/logging.py +148 -0
- devmemory/mcp/__init__.py +12 -0
- devmemory/mcp/server.py +225 -0
- devmemory/paths.py +112 -0
- devmemory/pipeline/__init__.py +7 -0
- devmemory/pipeline/checkpoint.py +443 -0
- devmemory/pipeline/feature_detect.py +53 -0
- devmemory/pipeline/regression.py +141 -0
- devmemory/pipeline/runlog.py +73 -0
- devmemory/pipeline/status_rules.py +44 -0
- devmemory/py.typed +0 -0
- devmemory/services/__init__.py +9 -0
- devmemory/services/agent_context.py +287 -0
- devmemory/services/analysis.py +116 -0
- devmemory/services/analytics.py +328 -0
- devmemory/services/brief.py +53 -0
- devmemory/services/context.py +88 -0
- devmemory/services/databricks_sync.py +121 -0
- devmemory/services/features.py +85 -0
- devmemory/services/impact.py +47 -0
- devmemory/services/memory.py +212 -0
- devmemory/services/projects.py +226 -0
- devmemory/services/restore.py +194 -0
- devmemory/services/taskloop/__init__.py +39 -0
- devmemory/services/taskloop/collectors.py +263 -0
- devmemory/services/taskloop/engine.py +426 -0
- devmemory/services/taskloop/requirements.py +358 -0
- devmemory/services/trace.py +152 -0
- devmemory/services/versions.py +287 -0
- devmemory/storage/__init__.py +9 -0
- devmemory/storage/artifacts.py +113 -0
- devmemory/storage/db.py +205 -0
- devmemory/storage/graph_impacts.py +63 -0
- devmemory/storage/migrations/0001_init.sql +15 -0
- devmemory/storage/migrations/0002_versions.sql +210 -0
- devmemory/storage/migrations/0003_graph.sql +14 -0
- devmemory/storage/migrations/0004_taskloop.sql +82 -0
- devmemory/storage/migrations/0005_project_brief.sql +12 -0
- devmemory/storage/repositories.py +286 -0
- devmemory/storage/tasks.py +342 -0
- devmemory/storage/versions.py +604 -0
- devmemory/web/static/assets/index-CbV5njRH.js +78 -0
- devmemory/web/static/assets/index-DD-7ceZx.css +1 -0
- devmemory/web/static/index.html +18 -0
- devmemory_cli-0.1.0.dev0.dist-info/METADATA +174 -0
- devmemory_cli-0.1.0.dev0.dist-info/RECORD +95 -0
- devmemory_cli-0.1.0.dev0.dist-info/WHEEL +4 -0
- devmemory_cli-0.1.0.dev0.dist-info/entry_points.txt +3 -0
- devmemory_cli-0.1.0.dev0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Enumerations shared across the domain.
|
|
2
|
+
|
|
3
|
+
All are ``str`` enums so they serialize transparently to JSON and SQLite.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from enum import StrEnum
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class VersionStatus(StrEnum):
|
|
12
|
+
"""The development result a version represents - not merely whether a commit exists."""
|
|
13
|
+
|
|
14
|
+
SUCCESS = "SUCCESS"
|
|
15
|
+
PARTIAL_SUCCESS = "PARTIAL_SUCCESS"
|
|
16
|
+
ERROR = "ERROR"
|
|
17
|
+
REGRESSION = "REGRESSION"
|
|
18
|
+
IN_PROGRESS = "IN_PROGRESS"
|
|
19
|
+
NEEDS_REVIEW = "NEEDS_REVIEW"
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def is_adverse(self) -> bool:
|
|
23
|
+
return self in {VersionStatus.ERROR, VersionStatus.REGRESSION}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class FeatureStatus(StrEnum):
|
|
27
|
+
"""Roll-up health of a logical feature area across its versions."""
|
|
28
|
+
|
|
29
|
+
COMPLETE = "COMPLETE"
|
|
30
|
+
PARTIAL = "PARTIAL"
|
|
31
|
+
IN_PROGRESS = "IN_PROGRESS"
|
|
32
|
+
FAILED = "FAILED"
|
|
33
|
+
NOT_STARTED = "NOT_STARTED"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ChangeType(StrEnum):
|
|
37
|
+
"""How a single file changed between two commits (git name-status)."""
|
|
38
|
+
|
|
39
|
+
ADDED = "added"
|
|
40
|
+
MODIFIED = "modified"
|
|
41
|
+
DELETED = "deleted"
|
|
42
|
+
RENAMED = "renamed"
|
|
43
|
+
COPIED = "copied"
|
|
44
|
+
TYPE_CHANGED = "type_changed"
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def from_git_status(cls, code: str) -> ChangeType:
|
|
48
|
+
"""Map a git ``--name-status`` letter (``A``/``M``/``D``/``R``/``C``/``T``)."""
|
|
49
|
+
return {
|
|
50
|
+
"A": cls.ADDED,
|
|
51
|
+
"M": cls.MODIFIED,
|
|
52
|
+
"D": cls.DELETED,
|
|
53
|
+
"R": cls.RENAMED,
|
|
54
|
+
"C": cls.COPIED,
|
|
55
|
+
"T": cls.TYPE_CHANGED,
|
|
56
|
+
}.get(code[:1].upper(), cls.MODIFIED)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class MetricDirection(StrEnum):
|
|
60
|
+
"""Which way a metric should move to count as an improvement."""
|
|
61
|
+
|
|
62
|
+
HIGHER_IS_BETTER = "higher_is_better"
|
|
63
|
+
LOWER_IS_BETTER = "lower_is_better"
|
|
64
|
+
NEUTRAL = "neutral"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class AssociationMethod(StrEnum):
|
|
68
|
+
"""How a checkpoint was linked to a commit, ordered by decreasing confidence."""
|
|
69
|
+
|
|
70
|
+
TRAILER = "trailer"
|
|
71
|
+
ENTIRE_CLI = "entire_cli"
|
|
72
|
+
GIT_REF = "git_ref"
|
|
73
|
+
HEURISTIC_TIME = "heuristic_time"
|
|
74
|
+
MANUAL = "manual"
|
|
75
|
+
NONE = "none"
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def default_confidence(self) -> float:
|
|
79
|
+
return {
|
|
80
|
+
AssociationMethod.TRAILER: 1.0,
|
|
81
|
+
AssociationMethod.ENTIRE_CLI: 1.0,
|
|
82
|
+
AssociationMethod.GIT_REF: 1.0,
|
|
83
|
+
AssociationMethod.HEURISTIC_TIME: 0.5,
|
|
84
|
+
AssociationMethod.MANUAL: 0.9,
|
|
85
|
+
AssociationMethod.NONE: 0.0,
|
|
86
|
+
}[self]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class TaskStatus(StrEnum):
|
|
90
|
+
"""Where a task stands in the state-aware coding loop.
|
|
91
|
+
|
|
92
|
+
Evidence-based, never a claim of certainty: ``READY`` means the collected
|
|
93
|
+
evidence supports completion, not that completion is proven.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
IN_PROGRESS = "IN_PROGRESS"
|
|
97
|
+
NEEDS_WORK = "NEEDS_WORK"
|
|
98
|
+
READY = "READY"
|
|
99
|
+
BLOCKED = "BLOCKED"
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def is_terminal(self) -> bool:
|
|
103
|
+
"""``READY``/``BLOCKED`` end the loop; the agent must stop or ask a human."""
|
|
104
|
+
return self in {TaskStatus.READY, TaskStatus.BLOCKED}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class RequirementStatus(StrEnum):
|
|
108
|
+
"""Whether one normalized requirement is satisfied by the evidence."""
|
|
109
|
+
|
|
110
|
+
COMPLETE = "COMPLETE"
|
|
111
|
+
PARTIAL = "PARTIAL"
|
|
112
|
+
INCOMPLETE = "INCOMPLETE"
|
|
113
|
+
UNKNOWN = "UNKNOWN"
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def is_satisfied(self) -> bool:
|
|
117
|
+
return self is RequirementStatus.COMPLETE
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class IssueStatus(StrEnum):
|
|
121
|
+
"""An unresolved item raised by the agent or the engine."""
|
|
122
|
+
|
|
123
|
+
OPEN = "OPEN"
|
|
124
|
+
RESOLVED = "RESOLVED"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class TestRunStatus(StrEnum):
|
|
128
|
+
"""Outcome of the configured test command for one state refresh.
|
|
129
|
+
|
|
130
|
+
``FAILED_TO_PARSE`` is deliberate: the command ran but its output could not
|
|
131
|
+
be parsed, so counts are unknown rather than invented.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
NOT_RUN = "NOT_RUN"
|
|
135
|
+
PASSED = "PASSED"
|
|
136
|
+
FAILED = "FAILED"
|
|
137
|
+
FAILED_TO_PARSE = "FAILED_TO_PARSE"
|
|
138
|
+
ERROR = "ERROR"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
__all__ = [
|
|
142
|
+
"AssociationMethod",
|
|
143
|
+
"ChangeType",
|
|
144
|
+
"FeatureStatus",
|
|
145
|
+
"IssueStatus",
|
|
146
|
+
"MetricDirection",
|
|
147
|
+
"RequirementStatus",
|
|
148
|
+
"TaskStatus",
|
|
149
|
+
"TestRunStatus",
|
|
150
|
+
"VersionStatus",
|
|
151
|
+
]
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Typed error taxonomy.
|
|
2
|
+
|
|
3
|
+
Every failure DevMemory raises carries a human-readable ``message`` and, where we
|
|
4
|
+
can, a ``hint`` telling the user what to do next. The CLI renders these; adapters
|
|
5
|
+
and services raise them instead of leaking ``subprocess`` / ``sqlite3`` details.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
_GIT_INIT_HINT = "DevMemory records versions against real git commits. Run `git init` first."
|
|
11
|
+
_ENTIRE_INSTALL_HINT = (
|
|
12
|
+
"Install it from https://entire.io or set `entire.binary` in .devmemory/config.json. "
|
|
13
|
+
"Use `--allow-no-entire` to record a version without checkpoint context."
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DevMemoryError(Exception):
|
|
18
|
+
"""Base class for every error DevMemory raises deliberately.
|
|
19
|
+
|
|
20
|
+
``exit_code`` is used by the CLI as the process exit status. ``hint`` is an
|
|
21
|
+
optional next-step suggestion shown to the user beneath the message.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
exit_code: int = 1
|
|
25
|
+
|
|
26
|
+
def __init__(self, message: str, *, hint: str | None = None) -> None:
|
|
27
|
+
super().__init__(message)
|
|
28
|
+
self.message = message
|
|
29
|
+
self.hint = hint
|
|
30
|
+
|
|
31
|
+
def __str__(self) -> str: # pragma: no cover - trivial
|
|
32
|
+
return self.message
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# --- configuration / project lifecycle -------------------------------------------------
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ConfigError(DevMemoryError):
|
|
39
|
+
"""The DevMemory configuration is missing, malformed, or invalid."""
|
|
40
|
+
|
|
41
|
+
exit_code = 2
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ProjectNotInitializedError(DevMemoryError):
|
|
45
|
+
"""No ``.devmemory/`` directory was found at or above the working directory."""
|
|
46
|
+
|
|
47
|
+
exit_code = 3
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
message: str = "DevMemory is not initialized in this directory.",
|
|
52
|
+
*,
|
|
53
|
+
hint: str | None = "Run `devmemory init` in your project root.",
|
|
54
|
+
) -> None:
|
|
55
|
+
super().__init__(message, hint=hint)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ProjectAlreadyInitializedError(DevMemoryError):
|
|
59
|
+
"""``devmemory init`` was run where DevMemory is already set up."""
|
|
60
|
+
|
|
61
|
+
exit_code = 3
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# --- storage --------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class StorageError(DevMemoryError):
|
|
68
|
+
"""A local persistence operation failed."""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class MigrationError(StorageError):
|
|
72
|
+
"""A schema migration could not be applied."""
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# --- git -----------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class GitError(DevMemoryError):
|
|
79
|
+
"""A git operation failed."""
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class GitRepositoryNotFoundError(GitError):
|
|
83
|
+
"""The target path is not inside a git repository."""
|
|
84
|
+
|
|
85
|
+
exit_code = 4
|
|
86
|
+
|
|
87
|
+
def __init__(
|
|
88
|
+
self,
|
|
89
|
+
message: str = "This directory is not inside a git repository.",
|
|
90
|
+
*,
|
|
91
|
+
hint: str | None = _GIT_INIT_HINT,
|
|
92
|
+
) -> None:
|
|
93
|
+
super().__init__(message, hint=hint)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# --- entire --------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class EntireError(DevMemoryError):
|
|
100
|
+
"""An Entire integration operation failed."""
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class EntireNotInstalledError(EntireError):
|
|
104
|
+
"""The Entire CLI could not be located."""
|
|
105
|
+
|
|
106
|
+
exit_code = 5
|
|
107
|
+
|
|
108
|
+
def __init__(
|
|
109
|
+
self,
|
|
110
|
+
message: str = "The Entire CLI was not found on PATH.",
|
|
111
|
+
*,
|
|
112
|
+
hint: str | None = _ENTIRE_INSTALL_HINT,
|
|
113
|
+
) -> None:
|
|
114
|
+
super().__init__(message, hint=hint)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class EntireNotEnabledError(EntireError):
|
|
118
|
+
"""Entire is installed but not enabled in this repository."""
|
|
119
|
+
|
|
120
|
+
exit_code = 5
|
|
121
|
+
|
|
122
|
+
def __init__(
|
|
123
|
+
self,
|
|
124
|
+
message: str = "Entire is not enabled in this repository.",
|
|
125
|
+
*,
|
|
126
|
+
hint: str | None = "Run `entire enable` so AI sessions are captured as checkpoints.",
|
|
127
|
+
) -> None:
|
|
128
|
+
super().__init__(message, hint=hint)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class CheckpointNotFoundError(EntireError):
|
|
132
|
+
"""No Entire checkpoint could be associated with the target commit."""
|
|
133
|
+
|
|
134
|
+
exit_code = 6
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class CheckpointAmbiguousError(EntireError):
|
|
138
|
+
"""More than one checkpoint plausibly matches and no explicit link exists."""
|
|
139
|
+
|
|
140
|
+
exit_code = 6
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# --- collection / analysis ----------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class CollectionError(DevMemoryError):
|
|
147
|
+
"""A test or metric collection step failed to run (not: the tests failed)."""
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class AnalysisError(DevMemoryError):
|
|
151
|
+
"""The analysis provider could not produce a result."""
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# --- restore ----------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class RestoreSafetyError(DevMemoryError):
|
|
158
|
+
"""A restore was refused because it would risk losing uncommitted work."""
|
|
159
|
+
|
|
160
|
+
exit_code = 7
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# --- external services ----------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class DatabricksError(DevMemoryError):
|
|
167
|
+
"""A Databricks operation failed. Never fatal to local history."""
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
__all__ = [
|
|
171
|
+
"AnalysisError",
|
|
172
|
+
"CheckpointAmbiguousError",
|
|
173
|
+
"CheckpointNotFoundError",
|
|
174
|
+
"CollectionError",
|
|
175
|
+
"ConfigError",
|
|
176
|
+
"DatabricksError",
|
|
177
|
+
"DevMemoryError",
|
|
178
|
+
"EntireError",
|
|
179
|
+
"EntireNotEnabledError",
|
|
180
|
+
"EntireNotInstalledError",
|
|
181
|
+
"GitError",
|
|
182
|
+
"GitRepositoryNotFoundError",
|
|
183
|
+
"MigrationError",
|
|
184
|
+
"ProjectAlreadyInitializedError",
|
|
185
|
+
"ProjectNotInitializedError",
|
|
186
|
+
"RestoreSafetyError",
|
|
187
|
+
"StorageError",
|
|
188
|
+
]
|