agentic-development-framework 0.1.0__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.
- agentic_development_framework-0.1.0.dist-info/METADATA +175 -0
- agentic_development_framework-0.1.0.dist-info/RECORD +27 -0
- agentic_development_framework-0.1.0.dist-info/WHEEL +4 -0
- agentic_development_framework-0.1.0.dist-info/entry_points.txt +2 -0
- agentic_development_framework-0.1.0.dist-info/licenses/LICENSE +22 -0
- agentic_framework/__init__.py +6 -0
- agentic_framework/__main__.py +7 -0
- agentic_framework/adapters/__init__.py +5 -0
- agentic_framework/adapters/generator.py +139 -0
- agentic_framework/cli.py +264 -0
- agentic_framework/core/__init__.py +2 -0
- agentic_framework/core/command_registry.py +16 -0
- agentic_framework/core/config.py +146 -0
- agentic_framework/core/initialization.py +241 -0
- agentic_framework/core/project.py +93 -0
- agentic_framework/core/workflow.py +94 -0
- agentic_framework/exceptions.py +22 -0
- agentic_framework/logging.py +70 -0
- agentic_framework/requirements_manager/__init__.py +8 -0
- agentic_framework/requirements_manager/comparison.py +129 -0
- agentic_framework/requirements_manager/extraction.py +43 -0
- agentic_framework/requirements_manager/ingestion.py +367 -0
- agentic_framework/requirements_manager/traceability.py +65 -0
- agentic_framework/state/__init__.py +5 -0
- agentic_framework/state/database.py +231 -0
- agentic_framework/templates/__init__.py +1 -0
- agentic_framework/templates/skills/agentic-commands/SKILL.md +22 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""Transactional SQLite state store with snapshots and checkpoints."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import sqlite3
|
|
9
|
+
from collections.abc import Iterator
|
|
10
|
+
from contextlib import contextmanager
|
|
11
|
+
from datetime import UTC, datetime
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from agentic_framework.exceptions import StateError
|
|
16
|
+
|
|
17
|
+
_SCHEMA = """
|
|
18
|
+
CREATE TABLE IF NOT EXISTS project (
|
|
19
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
20
|
+
root TEXT NOT NULL,
|
|
21
|
+
initialized_at TEXT NOT NULL
|
|
22
|
+
);
|
|
23
|
+
CREATE TABLE IF NOT EXISTS workflow (
|
|
24
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
25
|
+
status TEXT NOT NULL,
|
|
26
|
+
current_stage TEXT NOT NULL,
|
|
27
|
+
updated_at TEXT NOT NULL
|
|
28
|
+
);
|
|
29
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
30
|
+
task_id TEXT PRIMARY KEY,
|
|
31
|
+
agent_id TEXT NOT NULL,
|
|
32
|
+
title TEXT NOT NULL,
|
|
33
|
+
description TEXT NOT NULL,
|
|
34
|
+
dependencies_json TEXT NOT NULL DEFAULT '[]',
|
|
35
|
+
expected_outputs_json TEXT NOT NULL DEFAULT '[]',
|
|
36
|
+
acceptance_criteria_json TEXT NOT NULL DEFAULT '[]',
|
|
37
|
+
status TEXT NOT NULL
|
|
38
|
+
);
|
|
39
|
+
CREATE TABLE IF NOT EXISTS approvals (
|
|
40
|
+
stage_id TEXT PRIMARY KEY,
|
|
41
|
+
status TEXT NOT NULL,
|
|
42
|
+
feedback TEXT,
|
|
43
|
+
updated_at TEXT NOT NULL
|
|
44
|
+
);
|
|
45
|
+
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
46
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
47
|
+
reason TEXT NOT NULL,
|
|
48
|
+
state_json TEXT NOT NULL,
|
|
49
|
+
sha256 TEXT NOT NULL,
|
|
50
|
+
created_at TEXT NOT NULL
|
|
51
|
+
);
|
|
52
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
53
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
54
|
+
event_type TEXT NOT NULL,
|
|
55
|
+
payload_json TEXT NOT NULL,
|
|
56
|
+
created_at TEXT NOT NULL
|
|
57
|
+
);
|
|
58
|
+
PRAGMA user_version = 1;
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _now() -> str:
|
|
63
|
+
return datetime.now(UTC).isoformat()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class RuntimeStore:
|
|
67
|
+
"""Own the runtime database, inspection snapshots, and JSONL event history."""
|
|
68
|
+
|
|
69
|
+
def __init__(self, project_root: Path) -> None:
|
|
70
|
+
self.project_root = project_root.expanduser().resolve()
|
|
71
|
+
self.runtime = self.project_root / ".agentic"
|
|
72
|
+
self.database_path = self.runtime / "database" / "agentic.db"
|
|
73
|
+
self.state_dir = self.runtime / "state"
|
|
74
|
+
self.events_path = self.runtime / "logs" / "events.jsonl"
|
|
75
|
+
|
|
76
|
+
@contextmanager
|
|
77
|
+
def connect(self) -> Iterator[sqlite3.Connection]:
|
|
78
|
+
connection = sqlite3.connect(self.database_path)
|
|
79
|
+
connection.row_factory = sqlite3.Row
|
|
80
|
+
connection.execute("PRAGMA foreign_keys = ON")
|
|
81
|
+
try:
|
|
82
|
+
yield connection
|
|
83
|
+
connection.commit()
|
|
84
|
+
except Exception:
|
|
85
|
+
connection.rollback()
|
|
86
|
+
raise
|
|
87
|
+
finally:
|
|
88
|
+
connection.close()
|
|
89
|
+
|
|
90
|
+
def initialize(self) -> None:
|
|
91
|
+
self.database_path.parent.mkdir(parents=True, exist_ok=True)
|
|
92
|
+
self.state_dir.mkdir(parents=True, exist_ok=True)
|
|
93
|
+
self.events_path.parent.mkdir(parents=True, exist_ok=True)
|
|
94
|
+
timestamp = _now()
|
|
95
|
+
with self.connect() as connection:
|
|
96
|
+
connection.executescript(_SCHEMA)
|
|
97
|
+
connection.execute(
|
|
98
|
+
"INSERT OR IGNORE INTO project(id, root, initialized_at) VALUES(1, ?, ?)",
|
|
99
|
+
(str(self.project_root), timestamp),
|
|
100
|
+
)
|
|
101
|
+
connection.execute(
|
|
102
|
+
"INSERT OR IGNORE INTO workflow(id, status, current_stage, updated_at) "
|
|
103
|
+
"VALUES(1, 'not_started', 'project_initialization', ?)",
|
|
104
|
+
(timestamp,),
|
|
105
|
+
)
|
|
106
|
+
self.write_snapshots()
|
|
107
|
+
self.events_path.touch(exist_ok=True)
|
|
108
|
+
|
|
109
|
+
def workflow(self) -> dict[str, Any]:
|
|
110
|
+
with self.connect() as connection:
|
|
111
|
+
row = connection.execute(
|
|
112
|
+
"SELECT status, current_stage, updated_at FROM workflow WHERE id = 1"
|
|
113
|
+
).fetchone()
|
|
114
|
+
if row is None:
|
|
115
|
+
raise StateError("runtime workflow state is missing; run adt init")
|
|
116
|
+
return dict(row)
|
|
117
|
+
|
|
118
|
+
def tasks(self) -> list[dict[str, Any]]:
|
|
119
|
+
with self.connect() as connection:
|
|
120
|
+
rows = connection.execute(
|
|
121
|
+
"SELECT task_id, agent_id, title, status FROM tasks ORDER BY task_id"
|
|
122
|
+
).fetchall()
|
|
123
|
+
return [dict(row) for row in rows]
|
|
124
|
+
|
|
125
|
+
def approvals(self, status: str | None = None) -> list[dict[str, Any]]:
|
|
126
|
+
query = "SELECT stage_id, status, feedback, updated_at FROM approvals"
|
|
127
|
+
parameters: tuple[str, ...] = ()
|
|
128
|
+
if status:
|
|
129
|
+
query += " WHERE status = ?"
|
|
130
|
+
parameters = (status,)
|
|
131
|
+
query += " ORDER BY stage_id"
|
|
132
|
+
with self.connect() as connection:
|
|
133
|
+
rows = connection.execute(query, parameters).fetchall()
|
|
134
|
+
return [dict(row) for row in rows]
|
|
135
|
+
|
|
136
|
+
def approval_status(self, stage_id: str) -> str | None:
|
|
137
|
+
with self.connect() as connection:
|
|
138
|
+
row = connection.execute(
|
|
139
|
+
"SELECT status FROM approvals WHERE stage_id = ?", (stage_id,)
|
|
140
|
+
).fetchone()
|
|
141
|
+
return row["status"] if row else None
|
|
142
|
+
|
|
143
|
+
def set_approval(self, stage_id: str, status: str, feedback: str | None = None) -> None:
|
|
144
|
+
with self.connect() as connection:
|
|
145
|
+
connection.execute(
|
|
146
|
+
"INSERT INTO approvals(stage_id, status, feedback, updated_at) VALUES(?, ?, ?, ?) "
|
|
147
|
+
"ON CONFLICT(stage_id) DO UPDATE SET status=excluded.status, "
|
|
148
|
+
"feedback=excluded.feedback, updated_at=excluded.updated_at",
|
|
149
|
+
(stage_id, status, feedback, _now()),
|
|
150
|
+
)
|
|
151
|
+
self.record_event("approval_updated", {"stage_id": stage_id, "status": status})
|
|
152
|
+
self.write_snapshots()
|
|
153
|
+
|
|
154
|
+
def update_workflow(self, status: str, current_stage: str) -> None:
|
|
155
|
+
with self.connect() as connection:
|
|
156
|
+
connection.execute(
|
|
157
|
+
"UPDATE workflow SET status = ?, current_stage = ?, updated_at = ? WHERE id = 1",
|
|
158
|
+
(status, current_stage, _now()),
|
|
159
|
+
)
|
|
160
|
+
self.record_event("workflow_updated", {"status": status, "stage": current_stage})
|
|
161
|
+
self.write_snapshots()
|
|
162
|
+
|
|
163
|
+
def record_event(self, event_type: str, payload: dict[str, Any]) -> None:
|
|
164
|
+
timestamp = _now()
|
|
165
|
+
serialized = json.dumps(payload, sort_keys=True, separators=(",", ":"))
|
|
166
|
+
with self.connect() as connection:
|
|
167
|
+
connection.execute(
|
|
168
|
+
"INSERT INTO events(event_type, payload_json, created_at) VALUES(?, ?, ?)",
|
|
169
|
+
(event_type, serialized, timestamp),
|
|
170
|
+
)
|
|
171
|
+
entry = json.dumps(
|
|
172
|
+
{"timestamp": timestamp, "type": event_type, "payload": payload},
|
|
173
|
+
sort_keys=True,
|
|
174
|
+
separators=(",", ":"),
|
|
175
|
+
)
|
|
176
|
+
with self.events_path.open("a", encoding="utf-8") as event_log:
|
|
177
|
+
event_log.write(entry + "\n")
|
|
178
|
+
|
|
179
|
+
def create_checkpoint(self, reason: str) -> int:
|
|
180
|
+
state = {"workflow": self.workflow(), "tasks": self.tasks()}
|
|
181
|
+
serialized = json.dumps(state, sort_keys=True, separators=(",", ":"))
|
|
182
|
+
checksum = hashlib.sha256(serialized.encode()).hexdigest()
|
|
183
|
+
with self.connect() as connection:
|
|
184
|
+
cursor = connection.execute(
|
|
185
|
+
"INSERT INTO checkpoints(reason, state_json, sha256, created_at) "
|
|
186
|
+
"VALUES(?, ?, ?, ?)",
|
|
187
|
+
(reason, serialized, checksum, _now()),
|
|
188
|
+
)
|
|
189
|
+
checkpoint_id = int(cursor.lastrowid)
|
|
190
|
+
self.record_event("checkpoint_created", {"id": checkpoint_id, "reason": reason})
|
|
191
|
+
return checkpoint_id
|
|
192
|
+
|
|
193
|
+
def validate_latest_checkpoint(self) -> dict[str, Any] | None:
|
|
194
|
+
with self.connect() as connection:
|
|
195
|
+
row = connection.execute(
|
|
196
|
+
"SELECT state_json, sha256 FROM checkpoints ORDER BY id DESC LIMIT 1"
|
|
197
|
+
).fetchone()
|
|
198
|
+
if row is None:
|
|
199
|
+
return None
|
|
200
|
+
checksum = hashlib.sha256(row["state_json"].encode()).hexdigest()
|
|
201
|
+
if checksum != row["sha256"]:
|
|
202
|
+
raise StateError("latest checkpoint failed integrity validation")
|
|
203
|
+
try:
|
|
204
|
+
value = json.loads(row["state_json"])
|
|
205
|
+
except json.JSONDecodeError as exc:
|
|
206
|
+
raise StateError("latest checkpoint contains invalid state") from exc
|
|
207
|
+
workflow = value.get("workflow") if isinstance(value, dict) else None
|
|
208
|
+
if (
|
|
209
|
+
not isinstance(workflow, dict)
|
|
210
|
+
or not isinstance(workflow.get("status"), str)
|
|
211
|
+
or not isinstance(workflow.get("current_stage"), str)
|
|
212
|
+
):
|
|
213
|
+
raise StateError("latest checkpoint has an invalid structure")
|
|
214
|
+
return value
|
|
215
|
+
|
|
216
|
+
@staticmethod
|
|
217
|
+
def _atomic_json(path: Path, value: object) -> None:
|
|
218
|
+
temporary = path.with_suffix(path.suffix + ".tmp")
|
|
219
|
+
temporary.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
220
|
+
os.replace(temporary, path)
|
|
221
|
+
|
|
222
|
+
def write_snapshots(self) -> None:
|
|
223
|
+
self.state_dir.mkdir(parents=True, exist_ok=True)
|
|
224
|
+
workflow = self.workflow()
|
|
225
|
+
tasks = self.tasks()
|
|
226
|
+
self._atomic_json(self.state_dir / "workflow.json", workflow)
|
|
227
|
+
self._atomic_json(self.state_dir / "tasks.json", tasks)
|
|
228
|
+
self._atomic_json(
|
|
229
|
+
self.state_dir / "execution.json",
|
|
230
|
+
{"status": workflow["status"], "updated_at": workflow["updated_at"]},
|
|
231
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Packaged canonical template assets."""
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agentic-commands
|
|
3
|
+
description: Interpret ADF workflow commands such as /start, /status, /pause, /resume, /continue, /stop, /review, /approve, /revise, /requirements, and /tasks in an initialized project.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ADF command interpretation
|
|
7
|
+
|
|
8
|
+
When the user sends an ADF slash command, run the matching `adt` command from the project root so
|
|
9
|
+
the operation is persisted outside the conversation. Show the command result without claiming
|
|
10
|
+
work occurred that the CLI did not record.
|
|
11
|
+
|
|
12
|
+
Use these mappings:
|
|
13
|
+
|
|
14
|
+
- `/start`, `/status`, `/pause`, `/resume`, `/continue`, `/stop`, `/review`, `/requirements`, and
|
|
15
|
+
`/tasks` map directly to the same `adt` subcommand.
|
|
16
|
+
- `/approve <stage>` maps to `adt approve <stage>`.
|
|
17
|
+
- `/revise <stage> <feedback>` maps to `adt revise <stage> <feedback>`.
|
|
18
|
+
- `/help` maps to `adt help`.
|
|
19
|
+
|
|
20
|
+
Do not advance through an approval gate without an explicit recorded approval. If the CLI reports
|
|
21
|
+
invalid state or checkpoint corruption, stop and present that error rather than reconstructing or
|
|
22
|
+
overwriting state from conversation memory.
|