amkernel 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.
- amkernel/__init__.py +7 -0
- amkernel/__main__.py +5 -0
- amkernel/artifact_store.py +1363 -0
- amkernel/cli.py +172 -0
- amkernel/distribution.py +597 -0
- amkernel/output.py +444 -0
- amkernel/protocol.py +1464 -0
- amkernel/py.typed +0 -0
- amkernel/runner.py +1222 -0
- amkernel/runtime.py +581 -0
- amkernel/server.py +774 -0
- amkernel/skill/SKILL.md +124 -0
- amkernel/skill/references/amkernel-appium.md +46 -0
- amkernel/skill/references/official-skills.json +232 -0
- amkernel/skill/references/official-sources.json +21 -0
- amkernel/skill/scripts/verify_official_skills.py +252 -0
- amkernel/structured_output.py +328 -0
- amkernel/supervisor.py +1453 -0
- amkernel/workspace.py +305 -0
- amkernel-0.1.0.dist-info/METADATA +105 -0
- amkernel-0.1.0.dist-info/RECORD +23 -0
- amkernel-0.1.0.dist-info/WHEEL +4 -0
- amkernel-0.1.0.dist-info/entry_points.txt +3 -0
amkernel/workspace.py
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"""Descriptor-confined workspace and environment prelude helpers.
|
|
2
|
+
|
|
3
|
+
These helpers are a safe default for generated code, not a sandbox. User Python
|
|
4
|
+
retains unrestricted access to ordinary same-user filesystem and process APIs.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import re
|
|
11
|
+
import secrets
|
|
12
|
+
import stat
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from typing import Final, cast
|
|
15
|
+
|
|
16
|
+
MAX_READ_BYTES: Final = 67_108_864
|
|
17
|
+
_ENV_KEY = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class WorkspaceError(ValueError):
|
|
21
|
+
"""A confined workspace operation is invalid or unsafe."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ReadError(WorkspaceError):
|
|
25
|
+
"""A confined read could not safely return a regular file."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ReadLimitError(ReadError):
|
|
29
|
+
"""A regular file exceeds the requested read limit."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class WriteError(WorkspaceError):
|
|
33
|
+
"""A confined atomic write could not safely replace a regular file."""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class EnvError(ValueError):
|
|
37
|
+
"""An environment helper argument is invalid."""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class _Missing:
|
|
41
|
+
def __repr__(self) -> str:
|
|
42
|
+
return "<missing>"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
_MISSING = _Missing()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True, slots=True)
|
|
49
|
+
class _RootIdentity:
|
|
50
|
+
device: int
|
|
51
|
+
inode: int
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Workspace:
|
|
55
|
+
"""Pin one launch directory and create fresh callable prelude helpers."""
|
|
56
|
+
|
|
57
|
+
def __init__(self, launch_directory: str) -> None:
|
|
58
|
+
self._require_platform_support()
|
|
59
|
+
canonical = launch_directory
|
|
60
|
+
flags = os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW
|
|
61
|
+
try:
|
|
62
|
+
descriptor = os.open(".", flags)
|
|
63
|
+
metadata = os.fstat(descriptor)
|
|
64
|
+
except OSError as error:
|
|
65
|
+
raise WorkspaceError("workspace initialization failed") from error
|
|
66
|
+
if not stat.S_ISDIR(metadata.st_mode):
|
|
67
|
+
os.close(descriptor)
|
|
68
|
+
raise WorkspaceError("workspace root is not a regular directory")
|
|
69
|
+
self._canonical = canonical
|
|
70
|
+
self._descriptor = descriptor
|
|
71
|
+
self._identity = _RootIdentity(metadata.st_dev, metadata.st_ino)
|
|
72
|
+
|
|
73
|
+
def close(self) -> None:
|
|
74
|
+
descriptor = self._descriptor
|
|
75
|
+
if descriptor < 0:
|
|
76
|
+
return
|
|
77
|
+
self._descriptor = -1
|
|
78
|
+
os.close(descriptor)
|
|
79
|
+
|
|
80
|
+
def helpers(self) -> tuple[ReadHelper, WriteHelper, EnvHelper]:
|
|
81
|
+
return ReadHelper(self), WriteHelper(self), EnvHelper()
|
|
82
|
+
|
|
83
|
+
def read(self, path: object, *, binary: bool, limit: int) -> str | bytes:
|
|
84
|
+
if type(binary) is not bool:
|
|
85
|
+
raise ReadError("binary must be a boolean")
|
|
86
|
+
if type(limit) is not int or not 0 <= limit <= MAX_READ_BYTES:
|
|
87
|
+
raise ReadError("limit must be an integer from 0 through 67108864")
|
|
88
|
+
parent, name = self._open_parent(path, ReadError)
|
|
89
|
+
descriptor = -1
|
|
90
|
+
try:
|
|
91
|
+
descriptor = os.open(
|
|
92
|
+
name,
|
|
93
|
+
os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK,
|
|
94
|
+
dir_fd=parent,
|
|
95
|
+
)
|
|
96
|
+
metadata = os.fstat(descriptor)
|
|
97
|
+
if not stat.S_ISREG(metadata.st_mode):
|
|
98
|
+
raise ReadError("read target is not a regular file")
|
|
99
|
+
remaining = limit + 1
|
|
100
|
+
data = bytearray()
|
|
101
|
+
while remaining:
|
|
102
|
+
chunk = os.read(descriptor, min(65_536, remaining))
|
|
103
|
+
if not chunk:
|
|
104
|
+
break
|
|
105
|
+
data.extend(chunk)
|
|
106
|
+
remaining -= len(chunk)
|
|
107
|
+
if len(data) > limit:
|
|
108
|
+
raise ReadLimitError("read target exceeds limit")
|
|
109
|
+
raw = bytes(data)
|
|
110
|
+
if binary:
|
|
111
|
+
return raw
|
|
112
|
+
try:
|
|
113
|
+
return raw.decode("utf-8", errors="strict")
|
|
114
|
+
except UnicodeDecodeError as error:
|
|
115
|
+
raise ReadError("read target is not valid UTF-8") from error
|
|
116
|
+
except ReadError:
|
|
117
|
+
raise
|
|
118
|
+
except OSError as error:
|
|
119
|
+
raise ReadError("workspace read failed") from error
|
|
120
|
+
finally:
|
|
121
|
+
if descriptor >= 0:
|
|
122
|
+
os.close(descriptor)
|
|
123
|
+
os.close(parent)
|
|
124
|
+
|
|
125
|
+
def write(self, path: object, content: object) -> int:
|
|
126
|
+
data = self._write_bytes(content)
|
|
127
|
+
parent, name = self._open_parent(path, WriteError)
|
|
128
|
+
temporary = f".amkernel-write-{secrets.token_hex(16)}"
|
|
129
|
+
descriptor = -1
|
|
130
|
+
temporary_created = False
|
|
131
|
+
try:
|
|
132
|
+
before = self._destination_identity(parent, name)
|
|
133
|
+
descriptor = os.open(
|
|
134
|
+
temporary,
|
|
135
|
+
os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW,
|
|
136
|
+
0o600,
|
|
137
|
+
dir_fd=parent,
|
|
138
|
+
)
|
|
139
|
+
temporary_created = True
|
|
140
|
+
view = memoryview(data)
|
|
141
|
+
while view:
|
|
142
|
+
written = os.write(descriptor, view)
|
|
143
|
+
if written <= 0:
|
|
144
|
+
raise WriteError("workspace write made no progress")
|
|
145
|
+
view = view[written:]
|
|
146
|
+
os.fchmod(descriptor, 0o600)
|
|
147
|
+
os.close(descriptor)
|
|
148
|
+
descriptor = -1
|
|
149
|
+
if self._destination_identity(parent, name) != before:
|
|
150
|
+
raise WriteError("write destination changed during replacement")
|
|
151
|
+
os.rename(temporary, name, src_dir_fd=parent, dst_dir_fd=parent)
|
|
152
|
+
temporary = ""
|
|
153
|
+
final = os.stat(name, dir_fd=parent, follow_symlinks=False)
|
|
154
|
+
if not stat.S_ISREG(final.st_mode):
|
|
155
|
+
raise WriteError("write destination is not a regular file")
|
|
156
|
+
return len(data)
|
|
157
|
+
except WriteError:
|
|
158
|
+
raise
|
|
159
|
+
except OSError as error:
|
|
160
|
+
raise WriteError("workspace write failed") from error
|
|
161
|
+
finally:
|
|
162
|
+
if descriptor >= 0:
|
|
163
|
+
os.close(descriptor)
|
|
164
|
+
if temporary_created and temporary:
|
|
165
|
+
try:
|
|
166
|
+
os.unlink(temporary, dir_fd=parent)
|
|
167
|
+
except FileNotFoundError:
|
|
168
|
+
pass
|
|
169
|
+
except OSError:
|
|
170
|
+
pass
|
|
171
|
+
os.close(parent)
|
|
172
|
+
|
|
173
|
+
def _open_parent(self, path: object, error_type: type[WorkspaceError]) -> tuple[int, str]:
|
|
174
|
+
components = self._components(path, error_type)
|
|
175
|
+
current = os.dup(self._descriptor)
|
|
176
|
+
try:
|
|
177
|
+
for component in components[:-1]:
|
|
178
|
+
following = os.open(
|
|
179
|
+
component,
|
|
180
|
+
os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW,
|
|
181
|
+
dir_fd=current,
|
|
182
|
+
)
|
|
183
|
+
metadata = os.fstat(following)
|
|
184
|
+
if not stat.S_ISDIR(metadata.st_mode):
|
|
185
|
+
os.close(following)
|
|
186
|
+
raise error_type("workspace parent is not a regular directory")
|
|
187
|
+
os.close(current)
|
|
188
|
+
current = following
|
|
189
|
+
return current, components[-1]
|
|
190
|
+
except error_type:
|
|
191
|
+
os.close(current)
|
|
192
|
+
raise
|
|
193
|
+
except OSError as error:
|
|
194
|
+
os.close(current)
|
|
195
|
+
raise error_type("workspace path resolution failed") from error
|
|
196
|
+
|
|
197
|
+
def _components(self, path: object, error_type: type[WorkspaceError]) -> tuple[str, ...]:
|
|
198
|
+
try:
|
|
199
|
+
raw = os.fspath(cast(str | os.PathLike[str], path))
|
|
200
|
+
except TypeError as error:
|
|
201
|
+
raise error_type("path must be a string or path-like value") from error
|
|
202
|
+
if not isinstance(raw, str) or not raw or "\x00" in raw:
|
|
203
|
+
raise error_type("path must be a nonempty NUL-free text path")
|
|
204
|
+
if os.path.isabs(raw):
|
|
205
|
+
try:
|
|
206
|
+
if os.path.commonpath((self._canonical, raw)) != self._canonical:
|
|
207
|
+
raise error_type("absolute path is outside the launch workspace")
|
|
208
|
+
except ValueError as error:
|
|
209
|
+
raise error_type("absolute path is outside the launch workspace") from error
|
|
210
|
+
relative = raw[len(self._canonical) :]
|
|
211
|
+
if relative.startswith(os.sep):
|
|
212
|
+
relative = relative[1:]
|
|
213
|
+
else:
|
|
214
|
+
relative = raw
|
|
215
|
+
components = tuple(relative.split(os.sep))
|
|
216
|
+
if not components or any(part in {"", ".", ".."} for part in components):
|
|
217
|
+
raise error_type("path contains an empty or traversal component")
|
|
218
|
+
return components
|
|
219
|
+
|
|
220
|
+
@staticmethod
|
|
221
|
+
def _write_bytes(content: object) -> bytes:
|
|
222
|
+
if isinstance(content, str):
|
|
223
|
+
return content.encode("utf-8", errors="strict")
|
|
224
|
+
if isinstance(content, bytes):
|
|
225
|
+
return content
|
|
226
|
+
raise WriteError("content must be text or bytes")
|
|
227
|
+
|
|
228
|
+
@staticmethod
|
|
229
|
+
def _destination_identity(parent: int, name: str) -> tuple[int, int] | None:
|
|
230
|
+
try:
|
|
231
|
+
metadata = os.stat(name, dir_fd=parent, follow_symlinks=False)
|
|
232
|
+
except FileNotFoundError:
|
|
233
|
+
return None
|
|
234
|
+
if not stat.S_ISREG(metadata.st_mode):
|
|
235
|
+
raise WriteError("write destination is not a regular file")
|
|
236
|
+
return metadata.st_dev, metadata.st_ino
|
|
237
|
+
|
|
238
|
+
@staticmethod
|
|
239
|
+
def _require_platform_support() -> None:
|
|
240
|
+
required = (os.open, os.stat, os.unlink)
|
|
241
|
+
if not hasattr(os, "O_DIRECTORY") or not hasattr(os, "O_NOFOLLOW"):
|
|
242
|
+
raise WorkspaceError("descriptor-relative no-follow workspace helpers are unavailable")
|
|
243
|
+
if any(function not in os.supports_dir_fd for function in required):
|
|
244
|
+
raise WorkspaceError("descriptor-relative no-follow workspace helpers are unavailable")
|
|
245
|
+
if os.replace not in os.supports_dir_fd and os.rename not in os.supports_dir_fd:
|
|
246
|
+
raise WorkspaceError("descriptor-relative atomic replacement is unavailable")
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class ReadHelper:
|
|
250
|
+
"""Descriptor-confined convenience read; unrestricted Python is not sandboxed."""
|
|
251
|
+
|
|
252
|
+
def __init__(self, workspace: Workspace) -> None:
|
|
253
|
+
self._workspace = workspace
|
|
254
|
+
|
|
255
|
+
def __call__(
|
|
256
|
+
self, path: object, *, binary: bool = False, limit: int = 1_048_576
|
|
257
|
+
) -> str | bytes:
|
|
258
|
+
return self._workspace.read(path, binary=binary, limit=limit)
|
|
259
|
+
|
|
260
|
+
def __repr__(self) -> str:
|
|
261
|
+
return "<AMKernel helper read>"
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class WriteHelper:
|
|
265
|
+
"""Descriptor-confined atomic write; unrestricted Python is not sandboxed."""
|
|
266
|
+
|
|
267
|
+
def __init__(self, workspace: Workspace) -> None:
|
|
268
|
+
self._workspace = workspace
|
|
269
|
+
|
|
270
|
+
def __call__(self, path: object, content: object) -> int:
|
|
271
|
+
return self._workspace.write(path, content)
|
|
272
|
+
|
|
273
|
+
def __repr__(self) -> str:
|
|
274
|
+
return "<AMKernel helper write>"
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class EnvHelper:
|
|
278
|
+
"""Convenient process environment access; this is not a credential boundary."""
|
|
279
|
+
|
|
280
|
+
def __call__(self, key: object = _MISSING, value: object = _MISSING) -> object:
|
|
281
|
+
if key is _MISSING:
|
|
282
|
+
if value is not _MISSING:
|
|
283
|
+
raise EnvError("value requires a key")
|
|
284
|
+
return dict(os.environ)
|
|
285
|
+
if value is _MISSING:
|
|
286
|
+
name = self._key(key)
|
|
287
|
+
return os.environ.get(name)
|
|
288
|
+
name = self._key(key)
|
|
289
|
+
previous = os.environ.get(name)
|
|
290
|
+
if value is None:
|
|
291
|
+
os.environ.pop(name, None)
|
|
292
|
+
return previous
|
|
293
|
+
if not isinstance(value, str) or "\x00" in value:
|
|
294
|
+
raise EnvError("environment value must be NUL-free text or None")
|
|
295
|
+
os.environ[name] = value
|
|
296
|
+
return previous
|
|
297
|
+
|
|
298
|
+
def __repr__(self) -> str:
|
|
299
|
+
return "<AMKernel helper env>"
|
|
300
|
+
|
|
301
|
+
@staticmethod
|
|
302
|
+
def _key(key: object) -> str:
|
|
303
|
+
if not isinstance(key, str) or _ENV_KEY.fullmatch(key) is None:
|
|
304
|
+
raise EnvError("environment key must be a portable nonempty name")
|
|
305
|
+
return key
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: amkernel
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Persistent Python execution for agentic automation.
|
|
5
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
6
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
9
|
+
Requires-Dist: mcp>=2.3,<3
|
|
10
|
+
Requires-Dist: pillow>=12,<13
|
|
11
|
+
Requires-Dist: pydantic>=2.14,<3
|
|
12
|
+
Requires-Dist: tomlkit>=0.13,<1
|
|
13
|
+
Requires-Python: >=3.11, <3.14
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# AMKernel
|
|
17
|
+
|
|
18
|
+
**AMKernel — Automation MCP Kernel**
|
|
19
|
+
|
|
20
|
+
> Persistent Python execution for agentic automation.
|
|
21
|
+
>
|
|
22
|
+
> 面向 Agent 自动化的持久 Python Execution MCP。
|
|
23
|
+
|
|
24
|
+
AMKernel is a persistent Python execution MCP for agentic automation. Mobile automation through Appium is its first experimental vertical.
|
|
25
|
+
|
|
26
|
+
AMKernel 是一个面向 Agent 自动化的持久 Python Execution MCP。基于 Appium 的移动自动化是它的第一个实验方向。
|
|
27
|
+
|
|
28
|
+
Agents use ordinary Python automation libraries while retaining live objects, imports, and state across tool calls.
|
|
29
|
+
|
|
30
|
+
## Why the name
|
|
31
|
+
|
|
32
|
+
- **Automation** — Appium, Playwright, Windows, macOS, and other automation environments. / Appium、Playwright、Windows、macOS 等自动化场景。
|
|
33
|
+
- **MCP** — the standard interface that attaches AMKernel to different Coding Agents. / 挂载到不同 Coding Agent 的标准接口。
|
|
34
|
+
- **Kernel** — a persistent Python namespace with object reuse, incremental execution, and interrupt recovery. / 持久 Python namespace、对象复用、增量执行和中断恢复。
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
Coding Agent
|
|
38
|
+
→ MCP
|
|
39
|
+
→ persistent Python kernel
|
|
40
|
+
→ automation library
|
|
41
|
+
→ external system
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The first experimental vertical is mobile automation through Appium:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
Coding Agent
|
|
48
|
+
→ AMKernel
|
|
49
|
+
→ Appium Python Client
|
|
50
|
+
→ Appium Server
|
|
51
|
+
→ XCUITest / UiAutomator2
|
|
52
|
+
→ mobile device
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Future experiments may apply the same kernel to Playwright, Selenium, pywinauto, PyAutoGUI, macOS Accessibility, and other Python automation ecosystems. Those platforms do not belong in the core until experiments establish shared requirements.
|
|
56
|
+
|
|
57
|
+
## Project principles
|
|
58
|
+
|
|
59
|
+
- Python execution is the primary Agent interface.
|
|
60
|
+
- State persists across MCP calls.
|
|
61
|
+
- The Agent uses official automation libraries directly.
|
|
62
|
+
- The kernel reports truthful execution and survival state.
|
|
63
|
+
- Text, structured values, images, and large artifacts are first-class outputs.
|
|
64
|
+
- Platform knowledge starts in project skills and experiments, not core abstractions.
|
|
65
|
+
- AMKernel is local stdio code execution first, not a remote multi-tenant sandbox.
|
|
66
|
+
- The core grows from measured experimental needs, not hypothetical platform unification.
|
|
67
|
+
|
|
68
|
+
## Install for a project
|
|
69
|
+
|
|
70
|
+
AMKernel uses one user-level `uv`-managed Runtime and project-scoped Agent activation. From the project directory, run:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
uvx --from amkernel amkernel install
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The installer explains the changes, installs `.agents/skills/amkernel`, writes the detected OMP, Pi, or Codex project MCP entry, and asks you to restart the Coding Agent. It does not modify the project's `.venv`, install Appium Server or platform drivers, start a device, or create a session.
|
|
77
|
+
|
|
78
|
+
Use `amkernel status`, `amkernel doctor`, `amkernel update`, and `amkernel uninstall` for the managed lifecycle. `amkernel uninstall --global` is explicit because it affects every activated project.
|
|
79
|
+
|
|
80
|
+
## Current phase
|
|
81
|
+
|
|
82
|
+
Stage 2 agent-quality output is implemented through the packaged stdio server and the single `python_exec` tool. One persistent Runtime now provides bounded live progress, recoverable stream artifacts, strict JSON and raster displays, explicit artifacts, requested-package diagnostics, descriptor-rooted workspace helpers, hard output-limit intervention, and truthful lifecycle/cleanup results.
|
|
83
|
+
|
|
84
|
+
Artifact resource URIs are ephemeral. Read and verify every manifest part before another large-output call or server shutdown. AMKernel detects Store tampering when a resource is read, but unrestricted same-user Python can access host-visible files; AMKernel is not a sandbox.
|
|
85
|
+
|
|
86
|
+
### Supported environments
|
|
87
|
+
|
|
88
|
+
- CPython 3.11, 3.12, and 3.13.
|
|
89
|
+
- Current GitHub-hosted Ubuntu and macOS environments, where the native lifecycle and installed-wheel official-client matrices are required release evidence.
|
|
90
|
+
|
|
91
|
+
CPython 3.14, Windows, and other operating systems remain unsupported. The support claim is intentionally narrower than generic Linux or macOS package classifiers.
|
|
92
|
+
|
|
93
|
+
Canonical documents:
|
|
94
|
+
|
|
95
|
+
- [`docs/architecture.md`](docs/architecture.md) — generic AMKernel core architecture.
|
|
96
|
+
- [`docs/development.md`](docs/development.md) — agent roles, change ownership, development workflow, and quality gates.
|
|
97
|
+
- [`docs/stage-1-implementation-plan.md`](docs/stage-1-implementation-plan.md) — accepted Stage 1 contracts, dependency order, ownership, behavior scenarios, and risk traceability.
|
|
98
|
+
- [`docs/stage-2-implementation-plan.md`](docs/stage-2-implementation-plan.md) — accepted Stage 2 output, artifact, lifecycle, evidence, and support contracts.
|
|
99
|
+
- [`docs/project-management.md`](docs/project-management.md) — GitHub-backed workflow, Kanban semantics, task contracts, ADR, and risk governance.
|
|
100
|
+
- [GitHub Project](https://github.com/orgs/FSQ-lab/projects/2) — canonical milestone, status, stage, type, priority, and queue.
|
|
101
|
+
- [GitHub Issues](https://github.com/FSQ-lab/amkernel/issues) — canonical work-item, dependency, blocker, risk, and verification records.
|
|
102
|
+
- [`docs/decisions/`](docs/decisions/) — accepted architecture and implementation decisions.
|
|
103
|
+
- [`docs/experiments/mobile-appium.md`](docs/experiments/mobile-appium.md) — first Appium/mobile experiment and success criteria.
|
|
104
|
+
|
|
105
|
+
Implementation follows the interfaces, invariants, and delivery order accepted in these documents.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
amkernel/__init__.py,sha256=NZ2VfLSZOXUHDLzkcMCAEN0-O83vjS0WY5hyZRb0Q6g,126
|
|
2
|
+
amkernel/__main__.py,sha256=kIn6W2lkzwpctvu7ty50ImtePhM1nIj6SLUnN4gvPEc,96
|
|
3
|
+
amkernel/artifact_store.py,sha256=6auaHmX03UKpJvPVhEEESlejOdqhr-bynlFMc3aU38w,57315
|
|
4
|
+
amkernel/cli.py,sha256=SMxSCGeMJcpQ3E4_-i-auy2cMs8Ff6bOJHKx_OB_Y-s,7064
|
|
5
|
+
amkernel/distribution.py,sha256=W4YdHOk11CWugahHeGE4JK23SRITQ6vo2xqrT9XBqzg,23049
|
|
6
|
+
amkernel/output.py,sha256=Rv3H-U9IhXMTjlxG0XPILx9P5DJIXJgwO1pBK49Fovw,16370
|
|
7
|
+
amkernel/protocol.py,sha256=prvE52snNbVpxeyLHXJExc6au0SkRnlGC0Xtv5Plsp8,54431
|
|
8
|
+
amkernel/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
amkernel/runner.py,sha256=FyLRe_Ki9-8u49W0P79tM3ZClRyZq1kc-IubREwPsRg,42279
|
|
10
|
+
amkernel/runtime.py,sha256=_W2lH0ha9SK7p142CiUlyjfT04uqr8ZyGAEgQfTwqus,22227
|
|
11
|
+
amkernel/server.py,sha256=55MNmvsq1G15MssaD6ogF-hxGPAhNsS6FVZrRL7Popk,26755
|
|
12
|
+
amkernel/skill/SKILL.md,sha256=GB-PcyXqXHXSgcAdhYb51BvPSBGLFbXGjOG9TsGWM7Y,10838
|
|
13
|
+
amkernel/skill/references/amkernel-appium.md,sha256=s2yd34H-DcwimEk2CMVuLQIn_-xluSar4BhrBXLlVYw,2462
|
|
14
|
+
amkernel/skill/references/official-skills.json,sha256=W7T8_uhDKyC3KnelqklLNAxAJwjRD859LLQU5mwcMrI,22440
|
|
15
|
+
amkernel/skill/references/official-sources.json,sha256=tbbHj-duz3FQD87FZ8HhoB5gwsa24CTQYyhmA_qQNIU,834
|
|
16
|
+
amkernel/skill/scripts/verify_official_skills.py,sha256=BJ2-nPUgWNVKMEgkJJSwfLFsHMbmAB0OtciNgfG0RuU,9889
|
|
17
|
+
amkernel/structured_output.py,sha256=cjY4AXWhxiRWHrTIbyw_s0OS6HKe_rMDjQjHSFGwoAk,11937
|
|
18
|
+
amkernel/supervisor.py,sha256=PyevwQsgV5AhqKUTWGRnAOPNxpTjx762EtpD7Bf5BN8,54853
|
|
19
|
+
amkernel/workspace.py,sha256=HFMh-FQ-Q6EP38YeeGaYLS9D5WiavKL5NvYDmq8WQP0,11344
|
|
20
|
+
amkernel-0.1.0.dist-info/WHEEL,sha256=4OL6Foqnnp3xRY5wMkjgc25_i5YJC6dKsC6LPcjqEoU,80
|
|
21
|
+
amkernel-0.1.0.dist-info/entry_points.txt,sha256=-kjjp2LXqOgnIRsKrR3WQwd_usST1EGfGx_xAcB30c4,48
|
|
22
|
+
amkernel-0.1.0.dist-info/METADATA,sha256=NY7Qqx25pK5SwCvLz2CNpGiIoOmTkObzWmQ_ksxNGtM,5802
|
|
23
|
+
amkernel-0.1.0.dist-info/RECORD,,
|