mycode-aiagent 0.4.0__tar.gz → 0.4.1__tar.gz

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 (27) hide show
  1. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/PKG-INFO +31 -3
  2. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/README.md +30 -2
  3. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/cli.py +15 -0
  4. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/mcp_client.py +26 -0
  5. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/mycode_aiagent.egg-info/PKG-INFO +31 -3
  6. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/mycode_aiagent.egg-info/SOURCES.txt +2 -1
  7. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/pyproject.toml +1 -1
  8. mycode_aiagent-0.4.1/tests/test_server.py +138 -0
  9. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/__init__.py +0 -0
  10. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/__main__.py +0 -0
  11. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/analyzer.py +0 -0
  12. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/backends/__init__.py +0 -0
  13. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/backends/base.py +0 -0
  14. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/backends/claude_backend.py +0 -0
  15. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/backends/mcp_backend.py +0 -0
  16. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/backends/openai_backend.py +0 -0
  17. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/backends/ricky_backend.py +0 -0
  18. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/generator.py +0 -0
  19. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/server.py +0 -0
  20. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/utils/__init__.py +0 -0
  21. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/utils/prompts.py +0 -0
  22. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/mycode_aiagent.egg-info/dependency_links.txt +0 -0
  23. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/mycode_aiagent.egg-info/entry_points.txt +0 -0
  24. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/mycode_aiagent.egg-info/requires.txt +0 -0
  25. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/mycode_aiagent.egg-info/top_level.txt +0 -0
  26. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/setup.cfg +0 -0
  27. {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/tests/test_library.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mycode-aiagent
3
- Version: 0.4.0
3
+ Version: 0.4.1
4
4
  Summary: Style-aware code generation — analyze any codebase and generate new code that matches its style
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/RyanAbbottData/MyCode
@@ -93,7 +93,9 @@ MyCode delegates inference to a pluggable backend. Choose one based on what you
93
93
  | Local LLM | `--backend llama` (default) | MCP server running at `localhost:8000` |
94
94
  | Anthropic Claude | `--backend claude` | `ANTHROPIC_API_KEY` env var or `--api-key` |
95
95
  | OpenAI | `--backend openai` | `OPENAI_API_KEY` env var or `--api-key` |
96
- | Custom MCP server | `--backend mcp` | Any MCP server at `--mcp-url` |
96
+ | MyCode server | `--backend mcp` | A running MyCode MCP server at `--mcp-url` |
97
+
98
+ When `--backend mcp` is used with `analyze` or `generate`, the CLI delegates the entire operation to the running MyCode server — it calls the server's `analyze_codebase` or `generate_code` tool directly instead of running the pipeline locally. This is the recommended way to use MyCode in a multi-project or agentic setup.
97
99
 
98
100
  ### Setting up a local LLM
99
101
 
@@ -203,6 +205,11 @@ The profile is saved to `style_profile.json` by default. Specify a different pat
203
205
  my-code analyze ./path/to/codebase --profile ./profiles/my_team.json
204
206
  ```
205
207
 
208
+ Delegating to a running MyCode server:
209
+ ```bash
210
+ my-code --backend mcp --mcp-url http://localhost:8000/mcp analyze ./path/to/codebase
211
+ ```
212
+
206
213
  ### Step 2 — Generate code
207
214
 
208
215
  ```bash
@@ -220,6 +227,9 @@ my-code --backend claude generate "write a binary search function"
220
227
 
221
228
  # Override the model
222
229
  my-code --backend claude --model claude-sonnet-4-6 generate "write a rate limiter"
230
+
231
+ # Delegate to a running MyCode server
232
+ my-code --backend mcp --mcp-url http://localhost:8000/mcp generate "write a rate limiter"
223
233
  ```
224
234
 
225
235
  ---
@@ -364,6 +374,24 @@ my-code stop --pid-file mycode-8080.pid
364
374
  my-code stop --pid-file mycode-8081.pid
365
375
  ```
366
376
 
377
+ ### Using the server from the CLI
378
+
379
+ Start a MyCode server as a daemon, then point `analyze` and `generate` at it with `--backend mcp`. The CLI calls the server's tools directly — the server handles all analysis and generation using whichever LLM it was started with.
380
+
381
+ ```bash
382
+ # Start the server (uses a local LLM at port 8001 as its brain)
383
+ my-code --backend mcp --mcp-url http://localhost:8001/mcp serve --daemon --port 8000
384
+
385
+ # Analyze a codebase via the server
386
+ my-code --backend mcp --mcp-url http://localhost:8000/mcp analyze ./my_project
387
+
388
+ # Generate code via the server (loads style_profile.json locally and sends it)
389
+ my-code --backend mcp --mcp-url http://localhost:8000/mcp generate "write a retry decorator"
390
+
391
+ # Stop the server
392
+ my-code stop
393
+ ```
394
+
367
395
  ### Connecting from an MCP consumer
368
396
 
369
397
  Add the printed snippet to your consumer's MCP config file (e.g. `.mcp.json`):
@@ -443,7 +471,7 @@ Both test suites use a `MockBackend` — no live AI backend required.
443
471
  my_code/
444
472
  ├── analyzer.py # StyleAnalyzer — scans files, builds style profile
445
473
  ├── generator.py # generate_code() — formats prompt and calls backend
446
- ├── mcp_client.py # Generic MCP client (Streamable HTTP transport)
474
+ ├── mcp_client.py # MCPClient (raw LLM wrapper) + MyCodeClient (server delegation)
447
475
  ├── server.py # MCPServer — exposes analyze/generate as MCP tools
448
476
  ├── cli.py # CLI entry point (my-code command)
449
477
  ├── backends/
@@ -64,7 +64,9 @@ MyCode delegates inference to a pluggable backend. Choose one based on what you
64
64
  | Local LLM | `--backend llama` (default) | MCP server running at `localhost:8000` |
65
65
  | Anthropic Claude | `--backend claude` | `ANTHROPIC_API_KEY` env var or `--api-key` |
66
66
  | OpenAI | `--backend openai` | `OPENAI_API_KEY` env var or `--api-key` |
67
- | Custom MCP server | `--backend mcp` | Any MCP server at `--mcp-url` |
67
+ | MyCode server | `--backend mcp` | A running MyCode MCP server at `--mcp-url` |
68
+
69
+ When `--backend mcp` is used with `analyze` or `generate`, the CLI delegates the entire operation to the running MyCode server — it calls the server's `analyze_codebase` or `generate_code` tool directly instead of running the pipeline locally. This is the recommended way to use MyCode in a multi-project or agentic setup.
68
70
 
69
71
  ### Setting up a local LLM
70
72
 
@@ -174,6 +176,11 @@ The profile is saved to `style_profile.json` by default. Specify a different pat
174
176
  my-code analyze ./path/to/codebase --profile ./profiles/my_team.json
175
177
  ```
176
178
 
179
+ Delegating to a running MyCode server:
180
+ ```bash
181
+ my-code --backend mcp --mcp-url http://localhost:8000/mcp analyze ./path/to/codebase
182
+ ```
183
+
177
184
  ### Step 2 — Generate code
178
185
 
179
186
  ```bash
@@ -191,6 +198,9 @@ my-code --backend claude generate "write a binary search function"
191
198
 
192
199
  # Override the model
193
200
  my-code --backend claude --model claude-sonnet-4-6 generate "write a rate limiter"
201
+
202
+ # Delegate to a running MyCode server
203
+ my-code --backend mcp --mcp-url http://localhost:8000/mcp generate "write a rate limiter"
194
204
  ```
195
205
 
196
206
  ---
@@ -335,6 +345,24 @@ my-code stop --pid-file mycode-8080.pid
335
345
  my-code stop --pid-file mycode-8081.pid
336
346
  ```
337
347
 
348
+ ### Using the server from the CLI
349
+
350
+ Start a MyCode server as a daemon, then point `analyze` and `generate` at it with `--backend mcp`. The CLI calls the server's tools directly — the server handles all analysis and generation using whichever LLM it was started with.
351
+
352
+ ```bash
353
+ # Start the server (uses a local LLM at port 8001 as its brain)
354
+ my-code --backend mcp --mcp-url http://localhost:8001/mcp serve --daemon --port 8000
355
+
356
+ # Analyze a codebase via the server
357
+ my-code --backend mcp --mcp-url http://localhost:8000/mcp analyze ./my_project
358
+
359
+ # Generate code via the server (loads style_profile.json locally and sends it)
360
+ my-code --backend mcp --mcp-url http://localhost:8000/mcp generate "write a retry decorator"
361
+
362
+ # Stop the server
363
+ my-code stop
364
+ ```
365
+
338
366
  ### Connecting from an MCP consumer
339
367
 
340
368
  Add the printed snippet to your consumer's MCP config file (e.g. `.mcp.json`):
@@ -414,7 +442,7 @@ Both test suites use a `MockBackend` — no live AI backend required.
414
442
  my_code/
415
443
  ├── analyzer.py # StyleAnalyzer — scans files, builds style profile
416
444
  ├── generator.py # generate_code() — formats prompt and calls backend
417
- ├── mcp_client.py # Generic MCP client (Streamable HTTP transport)
445
+ ├── mcp_client.py # MCPClient (raw LLM wrapper) + MyCodeClient (server delegation)
418
446
  ├── server.py # MCPServer — exposes analyze/generate as MCP tools
419
447
  ├── cli.py # CLI entry point (my-code command)
420
448
  ├── backends/
@@ -17,6 +17,7 @@ from pathlib import Path
17
17
  from .analyzer import StyleAnalyzer
18
18
  from .backends import make_backend
19
19
  from .generator import generate_code
20
+ from .mcp_client import MyCodeClient
20
21
  from .server import MCPServer
21
22
 
22
23
  DEFAULT_PROFILE = Path("style_profile.json")
@@ -27,6 +28,13 @@ def cmd_analyze(args):
27
28
  if not root.exists():
28
29
  sys.exit(f"Error: directory not found: {root}")
29
30
 
31
+ if args.backend == "mcp":
32
+ client = MyCodeClient(args.mcp_url, args.timeout)
33
+ print(f"Analyzing codebase at {root} via MyCode server ...")
34
+ profile = client.analyze_codebase(str(root))
35
+ StyleAnalyzer.save_profile(profile, Path(args.profile))
36
+ return
37
+
30
38
  backend = make_backend(
31
39
  backend=args.backend,
32
40
  api_key=args.api_key,
@@ -52,6 +60,13 @@ def cmd_generate(args):
52
60
  "Run 'analyze' first."
53
61
  )
54
62
 
63
+ if args.backend == "mcp":
64
+ profile = json.loads(profile_path.read_text(encoding="utf-8"))
65
+ client = MyCodeClient(args.mcp_url, args.timeout)
66
+ print("Generating code ...\n")
67
+ print(client.generate_code(args.task, profile=profile))
68
+ return
69
+
55
70
  backend = make_backend(
56
71
  backend=args.backend,
57
72
  api_key=args.api_key,
@@ -137,3 +137,29 @@ class MCPClient:
137
137
  "arguments": {self._analyze_arg: prompt},
138
138
  })
139
139
  return self._extract_text(result)
140
+
141
+
142
+ class MyCodeClient(MCPClient):
143
+ """Client for a running MyCode MCP server. Calls semantic tools directly."""
144
+
145
+ def __init__(self, url: str = "http://localhost:8000/mcp", timeout: int = 120):
146
+ self.url = url
147
+ self._timeout = timeout
148
+ self._session_id = None
149
+ self._initialize()
150
+
151
+ def analyze_codebase(self, path: str, save_to: str | None = None) -> dict:
152
+ args = {"path": path}
153
+ if save_to:
154
+ args["save_to"] = save_to
155
+ result = self._post("tools/call", {"name": "analyze_codebase", "arguments": args})
156
+ return json.loads(self._extract_text(result))
157
+
158
+ def generate_code(self, task: str, profile: dict | None = None, profile_path: str | None = None) -> str:
159
+ args = {"task": task}
160
+ if profile is not None:
161
+ args["profile"] = profile
162
+ elif profile_path:
163
+ args["profile_path"] = profile_path
164
+ result = self._post("tools/call", {"name": "generate_code", "arguments": args})
165
+ return self._extract_text(result)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mycode-aiagent
3
- Version: 0.4.0
3
+ Version: 0.4.1
4
4
  Summary: Style-aware code generation — analyze any codebase and generate new code that matches its style
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/RyanAbbottData/MyCode
@@ -93,7 +93,9 @@ MyCode delegates inference to a pluggable backend. Choose one based on what you
93
93
  | Local LLM | `--backend llama` (default) | MCP server running at `localhost:8000` |
94
94
  | Anthropic Claude | `--backend claude` | `ANTHROPIC_API_KEY` env var or `--api-key` |
95
95
  | OpenAI | `--backend openai` | `OPENAI_API_KEY` env var or `--api-key` |
96
- | Custom MCP server | `--backend mcp` | Any MCP server at `--mcp-url` |
96
+ | MyCode server | `--backend mcp` | A running MyCode MCP server at `--mcp-url` |
97
+
98
+ When `--backend mcp` is used with `analyze` or `generate`, the CLI delegates the entire operation to the running MyCode server — it calls the server's `analyze_codebase` or `generate_code` tool directly instead of running the pipeline locally. This is the recommended way to use MyCode in a multi-project or agentic setup.
97
99
 
98
100
  ### Setting up a local LLM
99
101
 
@@ -203,6 +205,11 @@ The profile is saved to `style_profile.json` by default. Specify a different pat
203
205
  my-code analyze ./path/to/codebase --profile ./profiles/my_team.json
204
206
  ```
205
207
 
208
+ Delegating to a running MyCode server:
209
+ ```bash
210
+ my-code --backend mcp --mcp-url http://localhost:8000/mcp analyze ./path/to/codebase
211
+ ```
212
+
206
213
  ### Step 2 — Generate code
207
214
 
208
215
  ```bash
@@ -220,6 +227,9 @@ my-code --backend claude generate "write a binary search function"
220
227
 
221
228
  # Override the model
222
229
  my-code --backend claude --model claude-sonnet-4-6 generate "write a rate limiter"
230
+
231
+ # Delegate to a running MyCode server
232
+ my-code --backend mcp --mcp-url http://localhost:8000/mcp generate "write a rate limiter"
223
233
  ```
224
234
 
225
235
  ---
@@ -364,6 +374,24 @@ my-code stop --pid-file mycode-8080.pid
364
374
  my-code stop --pid-file mycode-8081.pid
365
375
  ```
366
376
 
377
+ ### Using the server from the CLI
378
+
379
+ Start a MyCode server as a daemon, then point `analyze` and `generate` at it with `--backend mcp`. The CLI calls the server's tools directly — the server handles all analysis and generation using whichever LLM it was started with.
380
+
381
+ ```bash
382
+ # Start the server (uses a local LLM at port 8001 as its brain)
383
+ my-code --backend mcp --mcp-url http://localhost:8001/mcp serve --daemon --port 8000
384
+
385
+ # Analyze a codebase via the server
386
+ my-code --backend mcp --mcp-url http://localhost:8000/mcp analyze ./my_project
387
+
388
+ # Generate code via the server (loads style_profile.json locally and sends it)
389
+ my-code --backend mcp --mcp-url http://localhost:8000/mcp generate "write a retry decorator"
390
+
391
+ # Stop the server
392
+ my-code stop
393
+ ```
394
+
367
395
  ### Connecting from an MCP consumer
368
396
 
369
397
  Add the printed snippet to your consumer's MCP config file (e.g. `.mcp.json`):
@@ -443,7 +471,7 @@ Both test suites use a `MockBackend` — no live AI backend required.
443
471
  my_code/
444
472
  ├── analyzer.py # StyleAnalyzer — scans files, builds style profile
445
473
  ├── generator.py # generate_code() — formats prompt and calls backend
446
- ├── mcp_client.py # Generic MCP client (Streamable HTTP transport)
474
+ ├── mcp_client.py # MCPClient (raw LLM wrapper) + MyCodeClient (server delegation)
447
475
  ├── server.py # MCPServer — exposes analyze/generate as MCP tools
448
476
  ├── cli.py # CLI entry point (my-code command)
449
477
  ├── backends/
@@ -21,4 +21,5 @@ mycode_aiagent.egg-info/dependency_links.txt
21
21
  mycode_aiagent.egg-info/entry_points.txt
22
22
  mycode_aiagent.egg-info/requires.txt
23
23
  mycode_aiagent.egg-info/top_level.txt
24
- tests/test_library.py
24
+ tests/test_library.py
25
+ tests/test_server.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mycode-aiagent"
7
- version = "0.4.0"
7
+ version = "0.4.1"
8
8
  description = "Style-aware code generation — analyze any codebase and generate new code that matches its style"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1,138 @@
1
+ """
2
+ Tests for the MyCode MCP server.
3
+ Spins up a real ThreadingHTTPServer against a MockBackend and exercises the protocol.
4
+ No live AI backend required.
5
+ """
6
+ import json
7
+ import socket
8
+ import threading
9
+ import unittest
10
+ from pathlib import Path
11
+
12
+ import requests
13
+
14
+ from my_code.server import MCPServer
15
+ from my_code import AIBackend
16
+
17
+ # ── Mock backend (same contract as the one in test_library.py) ────────────────
18
+
19
+ MOCK_PROFILE = {
20
+ "naming": {"functions": "snake_case", "classes": "PascalCase"},
21
+ "comments": {"docstring_style": "plain"},
22
+ "representative_snippets": ["def foo(x: int) -> str:\n return str(x)"],
23
+ }
24
+
25
+ class MockBackend(AIBackend):
26
+ max_file_chars = 6000
27
+
28
+ def ask_to_analyze(self, prompt: str) -> str:
29
+ return json.dumps(MOCK_PROFILE)
30
+
31
+ def ask_for_code(self, prompt: str) -> str:
32
+ return "def hello(name: str) -> str:\n return f'Hello, {name}'"
33
+
34
+
35
+ def _free_port() -> int:
36
+ with socket.socket() as s:
37
+ s.bind(("127.0.0.1", 0))
38
+ return s.getsockname()[1]
39
+
40
+
41
+ def _post(url: str, method: str, params: dict, session_id: str | None = None) -> requests.Response:
42
+ headers = {
43
+ "Content-Type": "application/json",
44
+ "Accept": "application/json, text/event-stream",
45
+ }
46
+ if session_id:
47
+ headers["Mcp-Session-Id"] = session_id
48
+ return requests.post(
49
+ url,
50
+ json={"jsonrpc": "2.0", "id": "1", "method": method, "params": params},
51
+ headers=headers,
52
+ timeout=10,
53
+ )
54
+
55
+
56
+ def _parse(resp: requests.Response) -> dict:
57
+ for line in resp.text.splitlines():
58
+ if line.startswith("data:"):
59
+ return json.loads(line[5:].strip())
60
+ raise AssertionError(f"No SSE data line in response: {resp.text[:300]}")
61
+
62
+
63
+ class TestMCPServer(unittest.TestCase):
64
+ @classmethod
65
+ def setUpClass(cls):
66
+ port = _free_port()
67
+ cls.url = f"http://127.0.0.1:{port}/mcp"
68
+ server = MCPServer(MockBackend(), host="127.0.0.1", port=port)
69
+ cls.httpd = server.start()
70
+ t = threading.Thread(target=cls.httpd.serve_forever, daemon=True)
71
+ t.start()
72
+
73
+ @classmethod
74
+ def tearDownClass(cls):
75
+ cls.httpd.shutdown()
76
+
77
+ def test_initialize(self):
78
+ resp = _post(self.url, "initialize", {
79
+ "protocolVersion": "2024-11-05",
80
+ "capabilities": {},
81
+ "clientInfo": {"name": "test", "version": "1"},
82
+ })
83
+ self.assertEqual(resp.status_code, 200)
84
+ self.assertIn("Mcp-Session-Id", resp.headers)
85
+ msg = _parse(resp)
86
+ self.assertIn("result", msg)
87
+ self.assertEqual(msg["result"]["serverInfo"]["name"], "mycode")
88
+
89
+ def test_tools_list(self):
90
+ resp = _post(self.url, "tools/list", {})
91
+ self.assertEqual(resp.status_code, 200)
92
+ msg = _parse(resp)
93
+ tools = {t["name"] for t in msg["result"]["tools"]}
94
+ self.assertEqual(tools, {"analyze_codebase", "generate_code"})
95
+
96
+ def test_analyze_codebase(self):
97
+ path = str(Path(__file__).parent.parent / "my_code")
98
+ resp = _post(self.url, "tools/call", {
99
+ "name": "analyze_codebase",
100
+ "arguments": {"path": path},
101
+ })
102
+ self.assertEqual(resp.status_code, 200)
103
+ msg = _parse(resp)
104
+ text = msg["result"]["content"][0]["text"]
105
+ profile = json.loads(text)
106
+ self.assertIn("naming", profile)
107
+
108
+ def test_generate_code(self):
109
+ resp = _post(self.url, "tools/call", {
110
+ "name": "generate_code",
111
+ "arguments": {"task": "write a greeting function", "profile": MOCK_PROFILE},
112
+ })
113
+ self.assertEqual(resp.status_code, 200)
114
+ msg = _parse(resp)
115
+ code = msg["result"]["content"][0]["text"]
116
+ self.assertTrue(len(code) > 0)
117
+ self.assertIn("def", code)
118
+
119
+ def test_unknown_method(self):
120
+ resp = _post(self.url, "nonexistent/method", {})
121
+ self.assertEqual(resp.status_code, 200)
122
+ msg = _parse(resp)
123
+ self.assertIn("error", msg)
124
+ self.assertEqual(msg["error"]["code"], -32601)
125
+
126
+ def test_unknown_tool(self):
127
+ resp = _post(self.url, "tools/call", {
128
+ "name": "does_not_exist",
129
+ "arguments": {},
130
+ })
131
+ self.assertEqual(resp.status_code, 200)
132
+ msg = _parse(resp)
133
+ self.assertIn("error", msg)
134
+ self.assertEqual(msg["error"]["code"], -32602)
135
+
136
+
137
+ if __name__ == "__main__":
138
+ unittest.main()
File without changes