agent-codinglanguage-mapper 1.1.0__tar.gz → 1.1.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 (74) hide show
  1. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/PKG-INFO +14 -17
  2. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/README.md +13 -16
  3. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/pyproject.toml +1 -1
  4. agent_codinglanguage_mapper-1.1.1/src/agent_codinglanguage_mapper/_version.py +1 -0
  5. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/cli.py +8 -1
  6. agent_codinglanguage_mapper-1.1.1/src/agent_codinglanguage_mapper/integration.py +212 -0
  7. agent_codinglanguage_mapper-1.1.1/src/agent_codinglanguage_mapper/templates/opencode/agents/agent-codinglanguage-mapper.md +16 -0
  8. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/templates/skill/SKILL.md +1 -1
  9. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper.egg-info/PKG-INFO +14 -17
  10. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper.egg-info/SOURCES.txt +1 -0
  11. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/tests/test_cli.py +3 -1
  12. agent_codinglanguage_mapper-1.1.1/tests/test_integration_install.py +299 -0
  13. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/tests/test_opencode_e2e_harness.py +11 -11
  14. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/tests/test_package_contract.py +2 -2
  15. agent_codinglanguage_mapper-1.1.0/src/agent_codinglanguage_mapper/_version.py +0 -1
  16. agent_codinglanguage_mapper-1.1.0/src/agent_codinglanguage_mapper/integration.py +0 -187
  17. agent_codinglanguage_mapper-1.1.0/tests/test_integration_install.py +0 -196
  18. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/LICENSE +0 -0
  19. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/setup.cfg +0 -0
  20. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/__init__.py +0 -0
  21. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/__main__.py +0 -0
  22. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/adapters/__init__.py +0 -0
  23. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/adapters/delphi.py +0 -0
  24. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/adapters/delphi_frontend/__init__.py +0 -0
  25. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/adapters/delphi_frontend/comment_builder.py +0 -0
  26. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/adapters/delphi_frontend/consts.py +0 -0
  27. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/adapters/delphi_frontend/grammar.py +0 -0
  28. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/adapters/delphi_frontend/lark_builder.py +0 -0
  29. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/adapters/delphi_frontend/lark_tokens.py +0 -0
  30. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/adapters/delphi_frontend/lsp_server.py +0 -0
  31. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/adapters/delphi_frontend/nodes.py +0 -0
  32. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/adapters/delphi_frontend/parser.py +0 -0
  33. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/adapters/delphi_frontend/preprocessor.py +0 -0
  34. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/adapters/delphi_frontend/project_discovery.py +0 -0
  35. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/adapters/delphi_frontend/project_indexer.py +0 -0
  36. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/adapters/delphi_frontend/semantic.py +0 -0
  37. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/adapters/delphi_frontend/semantic_builder.py +0 -0
  38. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/adapters/delphi_frontend/source_reader.py +0 -0
  39. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/adapters/delphi_frontend/workspace.py +0 -0
  40. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/adapters/tree_sitter.py +0 -0
  41. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/discovery.py +0 -0
  42. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/formatting.py +0 -0
  43. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/guidelines.py +0 -0
  44. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/indexer.py +0 -0
  45. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/lsp_server.py +0 -0
  46. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/lsp_service.py +0 -0
  47. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/mapper.py +0 -0
  48. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/models.py +0 -0
  49. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/protocol.py +0 -0
  50. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/rendering.py +0 -0
  51. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/templates/opencode/plugins/codebase_map.ts +0 -0
  52. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/worker.py +0 -0
  53. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper/workspace.py +0 -0
  54. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper.egg-info/dependency_links.txt +0 -0
  55. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper.egg-info/entry_points.txt +0 -0
  56. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper.egg-info/requires.txt +0 -0
  57. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/src/agent_codinglanguage_mapper.egg-info/top_level.txt +0 -0
  58. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/tests/test_adapters.py +0 -0
  59. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/tests/test_delphi_adapter.py +0 -0
  60. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/tests/test_discovery.py +0 -0
  61. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/tests/test_ffi.py +0 -0
  62. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/tests/test_guideline_formatting.py +0 -0
  63. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/tests/test_guidelines.py +0 -0
  64. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/tests/test_indexer.py +0 -0
  65. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/tests/test_language_matrix.py +0 -0
  66. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/tests/test_large_files.py +0 -0
  67. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/tests/test_lsp_server.py +0 -0
  68. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/tests/test_lsp_service.py +0 -0
  69. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/tests/test_mapper_details.py +0 -0
  70. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/tests/test_performance_harness.py +0 -0
  71. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/tests/test_protocol.py +0 -0
  72. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/tests/test_rendering.py +0 -0
  73. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/tests/test_worker.py +0 -0
  74. {agent_codinglanguage_mapper-1.1.0 → agent_codinglanguage_mapper-1.1.1}/tests/test_workspace.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-codinglanguage-mapper
3
- Version: 1.1.0
3
+ Version: 1.1.1
4
4
  Summary: Agent-focused semantic mapping and language services for mixed-language codebases.
5
5
  Author: Dark Light
6
6
  License-Expression: MPL-2.0
@@ -319,40 +319,37 @@ Example generic client configuration:
319
319
 
320
320
  ## OpenCode Integration
321
321
 
322
- Install the packaged skill, plugin, and restricted agent configuration into a repository:
322
+ Install the package-named skill, Markdown agent, and plugin into a repository:
323
323
 
324
324
  ```bash
325
- codinglanguage-mapper opencode install --target . --write
325
+ codinglanguage-mapper opencode install --target .
326
326
  ```
327
327
 
328
328
  This creates:
329
329
 
330
330
  ```text
331
- .agents/skills/mixed-codebase-navigator/SKILL.md
331
+ .agents/skills/agent-codinglanguage-mapper/SKILL.md
332
+ .opencode/agents/agent-codinglanguage-mapper.md
332
333
  .opencode/plugins/codebase_map.ts
333
- opencode.json
334
334
  ```
335
335
 
336
- The generated `mixed-codebase-navigator` agent denies all tools by default and permits only `skill`, `codebase_map`, and `code_guidelines`. The plugin maps the active OpenCode directory, serializes Protocol 3 requests through one worker, applies a hard timeout, reports bounded worker `stderr`, restarts after failure, and preserves focus plus warm parser indexes across short idle gaps. It stops the worker when the session is deleted or after 60 seconds without another mapper request.
336
+ The generated `agent-codinglanguage-mapper` agent denies all tools by default and permits only its package-named skill, `codebase_map`, and `code_guidelines`. The plugin maps the active OpenCode directory, serializes Protocol 3 requests through one worker, applies a hard timeout, reports bounded worker `stderr`, restarts after failure, and preserves focus plus warm parser indexes across short idle gaps. It stops the worker when the session is deleted or after 60 seconds without another mapper request.
337
+
338
+ The installer never reads or changes `opencode.json`; user configuration remains
339
+ user-owned. The deprecated `--write`, `--write-config`, and `--write-agent`
340
+ compatibility flags are accepted, but the Markdown agent is installed by
341
+ default and those flags do not enable configuration writes.
337
342
 
338
343
  `code_guidelines` exposes four actions: `detect`, `audit`, `patch`, and `apply`. The first three are read-only. `apply` is rejected unless its request contains `write: true`, so the agent must inspect a patch before it can opt into mutation.
339
344
 
340
- ```json
341
- {
342
- "permission": {
343
- "*": "deny",
344
- "skill": "allow",
345
- "codebase_map": "allow",
346
- "code_guidelines": "allow"
347
- }
348
- }
349
- ```
345
+ The agent's YAML frontmatter contains its restricted permissions; no matching
346
+ JSON agent block is required.
350
347
 
351
348
  Run it with:
352
349
 
353
350
  ```bash
354
351
  opencode run \
355
- --agent mixed-codebase-navigator \
352
+ --agent agent-codinglanguage-mapper \
356
353
  "Find PaymentService, inspect its implementation, and cite the callers. Use only the installed skill and codebase_map."
357
354
  ```
358
355
 
@@ -272,40 +272,37 @@ Example generic client configuration:
272
272
 
273
273
  ## OpenCode Integration
274
274
 
275
- Install the packaged skill, plugin, and restricted agent configuration into a repository:
275
+ Install the package-named skill, Markdown agent, and plugin into a repository:
276
276
 
277
277
  ```bash
278
- codinglanguage-mapper opencode install --target . --write
278
+ codinglanguage-mapper opencode install --target .
279
279
  ```
280
280
 
281
281
  This creates:
282
282
 
283
283
  ```text
284
- .agents/skills/mixed-codebase-navigator/SKILL.md
284
+ .agents/skills/agent-codinglanguage-mapper/SKILL.md
285
+ .opencode/agents/agent-codinglanguage-mapper.md
285
286
  .opencode/plugins/codebase_map.ts
286
- opencode.json
287
287
  ```
288
288
 
289
- The generated `mixed-codebase-navigator` agent denies all tools by default and permits only `skill`, `codebase_map`, and `code_guidelines`. The plugin maps the active OpenCode directory, serializes Protocol 3 requests through one worker, applies a hard timeout, reports bounded worker `stderr`, restarts after failure, and preserves focus plus warm parser indexes across short idle gaps. It stops the worker when the session is deleted or after 60 seconds without another mapper request.
289
+ The generated `agent-codinglanguage-mapper` agent denies all tools by default and permits only its package-named skill, `codebase_map`, and `code_guidelines`. The plugin maps the active OpenCode directory, serializes Protocol 3 requests through one worker, applies a hard timeout, reports bounded worker `stderr`, restarts after failure, and preserves focus plus warm parser indexes across short idle gaps. It stops the worker when the session is deleted or after 60 seconds without another mapper request.
290
+
291
+ The installer never reads or changes `opencode.json`; user configuration remains
292
+ user-owned. The deprecated `--write`, `--write-config`, and `--write-agent`
293
+ compatibility flags are accepted, but the Markdown agent is installed by
294
+ default and those flags do not enable configuration writes.
290
295
 
291
296
  `code_guidelines` exposes four actions: `detect`, `audit`, `patch`, and `apply`. The first three are read-only. `apply` is rejected unless its request contains `write: true`, so the agent must inspect a patch before it can opt into mutation.
292
297
 
293
- ```json
294
- {
295
- "permission": {
296
- "*": "deny",
297
- "skill": "allow",
298
- "codebase_map": "allow",
299
- "code_guidelines": "allow"
300
- }
301
- }
302
- ```
298
+ The agent's YAML frontmatter contains its restricted permissions; no matching
299
+ JSON agent block is required.
303
300
 
304
301
  Run it with:
305
302
 
306
303
  ```bash
307
304
  opencode run \
308
- --agent mixed-codebase-navigator \
305
+ --agent agent-codinglanguage-mapper \
309
306
  "Find PaymentService, inspect its implementation, and cite the callers. Use only the installed skill and codebase_map."
310
307
  ```
311
308
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "agent-codinglanguage-mapper"
7
- version = "1.1.0"
7
+ version = "1.1.1"
8
8
  description = "Agent-focused semantic mapping and language services for mixed-language codebases."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10,<3.15"
@@ -84,7 +84,14 @@ def build_parser() -> argparse.ArgumentParser:
84
84
  opencode_install.add_argument("--target", type=Path, default=Path("."))
85
85
  opencode_install.add_argument("--python", default=sys.executable)
86
86
  opencode_install.add_argument("--force", action="store_true")
87
- opencode_install.add_argument("--write", "--write-config", dest="write_config", action="store_true")
87
+ opencode_install.add_argument(
88
+ "--write-agent",
89
+ "--write",
90
+ "--write-config",
91
+ dest="write_config",
92
+ action="store_true",
93
+ help="deprecated compatibility flag; the Markdown agent is always installed",
94
+ )
88
95
  return parser
89
96
 
90
97
 
@@ -0,0 +1,212 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+ import os
6
+ import stat
7
+ from pathlib import Path
8
+ from tempfile import NamedTemporaryFile
9
+ from typing import Final
10
+
11
+ PACKAGE_NAME: Final[str] = "agent-codinglanguage-mapper"
12
+
13
+ SKILL_RELATIVE_PATH: Final[Path] = Path(
14
+ f".agents/skills/{PACKAGE_NAME}/SKILL.md"
15
+ )
16
+ LEGACY_SKILL_RELATIVE_PATH: Final[Path] = Path(
17
+ ".agents/skills/mixed-codebase-navigator/SKILL.md"
18
+ )
19
+ LEGACY_SKILL_SHA256: Final[str] = (
20
+ "bbacc23782ca97e54b2f65ddea9fb1bf816f23397dc8572e3d1ef7a60e2709d4"
21
+ )
22
+ OPENCODE_PLUGIN_RELATIVE_PATH: Final[Path] = Path(".opencode/plugins/codebase_map.ts")
23
+ OPENCODE_AGENT_RELATIVE_PATH: Final[Path] = Path(
24
+ f".opencode/agents/{PACKAGE_NAME}.md"
25
+ )
26
+
27
+ SKILL_TEMPLATE_PATH: Final[Path] = Path("skill", "SKILL.md")
28
+ OPENCODE_PLUGIN_TEMPLATE_PATH: Final[Path] = Path("opencode", "plugins", "codebase_map.ts")
29
+ OPENCODE_AGENT_TEMPLATE_PATH: Final[Path] = Path(
30
+ "opencode", "agents", f"{PACKAGE_NAME}.md"
31
+ )
32
+ _TEMPLATES_ROOT: Final[Path] = Path(__file__).resolve().parent / "templates"
33
+
34
+
35
+ def install_skill(target: Path, force: bool = False) -> Path:
36
+ target_root = _normalize_target(target)
37
+ legacy_path = _preflight_legacy_skill(target_root, force=force)
38
+ skill_path = _safe_join(target_root, SKILL_RELATIVE_PATH)
39
+ _preflight_overwrite((skill_path,), force=force)
40
+ snapshots = _snapshots(skill_path, legacy_path)
41
+ try:
42
+ _write_atomic_text(skill_path, _load_template(SKILL_TEMPLATE_PATH), force=True)
43
+ _remove_legacy_skill(legacy_path)
44
+ except BaseException:
45
+ _restore_snapshots(snapshots)
46
+ raise
47
+ return skill_path
48
+
49
+
50
+ def install_opencode(
51
+ target: Path,
52
+ python_executable: str | os.PathLike[str],
53
+ force: bool = False,
54
+ write_config: bool = False,
55
+ ) -> tuple[Path, Path, Path]:
56
+ target_root = _normalize_target(target)
57
+ legacy_path = _preflight_legacy_skill(target_root, force=force)
58
+ skill_path = _safe_join(target_root, SKILL_RELATIVE_PATH)
59
+ plugin_path = _safe_join(target_root, OPENCODE_PLUGIN_RELATIVE_PATH)
60
+ agent_path = _safe_join(target_root, OPENCODE_AGENT_RELATIVE_PATH)
61
+ _preflight_overwrite((skill_path, plugin_path, agent_path), force=force)
62
+ rendered_skill = _load_template(SKILL_TEMPLATE_PATH)
63
+ rendered_plugin = _render_plugin(str(python_executable))
64
+ rendered_agent = _load_template(OPENCODE_AGENT_TEMPLATE_PATH)
65
+ snapshots = _snapshots(skill_path, plugin_path, agent_path, legacy_path)
66
+
67
+ try:
68
+ _write_atomic_text(skill_path, rendered_skill, force=True)
69
+ _write_atomic_text(plugin_path, rendered_plugin, force=True)
70
+ _write_atomic_text(agent_path, rendered_agent, force=True)
71
+ _remove_legacy_skill(legacy_path)
72
+ except BaseException:
73
+ _restore_snapshots(snapshots)
74
+ raise
75
+ _ = write_config # Deprecated compatibility input; user configuration is never touched.
76
+ return skill_path, plugin_path, agent_path
77
+
78
+
79
+ def _preflight_legacy_skill(target: Path, *, force: bool) -> Path | None:
80
+ legacy_path = target / LEGACY_SKILL_RELATIVE_PATH
81
+ legacy_path.relative_to(target)
82
+ _reject_symlink_components(legacy_path, target)
83
+ if not legacy_path.exists():
84
+ return None
85
+ if not legacy_path.is_file():
86
+ raise FileExistsError(f"Legacy skill path is not a file: {legacy_path}")
87
+ digest = hashlib.sha256(legacy_path.read_bytes()).hexdigest()
88
+ if digest != LEGACY_SKILL_SHA256 and not force:
89
+ raise FileExistsError(
90
+ f"Refusing to remove modified legacy skill without force: {legacy_path}"
91
+ )
92
+ return legacy_path
93
+
94
+
95
+ def _remove_legacy_skill(path: Path | None) -> None:
96
+ if path is None:
97
+ return
98
+ path.unlink()
99
+ try:
100
+ path.parent.rmdir()
101
+ except OSError:
102
+ pass
103
+
104
+
105
+ def _normalize_target(target: Path) -> Path:
106
+ normalized = target.expanduser().resolve()
107
+ if normalized.exists() and not normalized.is_dir():
108
+ raise NotADirectoryError(f"Target must be a directory: {normalized}")
109
+ normalized.mkdir(parents=True, exist_ok=True)
110
+ return normalized
111
+
112
+
113
+ def _safe_join(target: Path, relative: Path) -> Path:
114
+ destination = target / relative
115
+ destination.relative_to(target)
116
+ _reject_symlink_components(destination, target)
117
+ return destination
118
+
119
+
120
+ def _reject_symlink_components(path: Path, target: Path) -> None:
121
+ current = path
122
+ while current != target:
123
+ if current.is_symlink():
124
+ raise FileExistsError(f"Generated path must not contain a symbolic link: {current}")
125
+ current = current.parent
126
+
127
+
128
+ def _preflight_overwrite(paths: tuple[Path, ...], *, force: bool) -> None:
129
+ for path in paths:
130
+ if path.is_symlink():
131
+ raise FileExistsError(f"Refusing to replace symbolic link: {path}")
132
+ if path.exists() and not path.is_file():
133
+ raise FileExistsError(f"Generated destination is not a file: {path}")
134
+ if path.exists() and not force:
135
+ raise FileExistsError(f"Refusing to overwrite existing file: {path}")
136
+
137
+
138
+ _FileSnapshot = tuple[bytes, int] | None
139
+
140
+
141
+ def _snapshots(*paths: Path | None) -> dict[Path, _FileSnapshot]:
142
+ snapshots: dict[Path, _FileSnapshot] = {}
143
+ for path in paths:
144
+ if path is None or path in snapshots:
145
+ continue
146
+ snapshots[path] = (
147
+ (path.read_bytes(), stat.S_IMODE(path.stat().st_mode))
148
+ if path.exists()
149
+ else None
150
+ )
151
+ return snapshots
152
+
153
+
154
+ def _restore_snapshots(snapshots: dict[Path, _FileSnapshot]) -> None:
155
+ for path, snapshot in reversed(tuple(snapshots.items())):
156
+ if snapshot is None:
157
+ if path.is_symlink() or path.is_file():
158
+ path.unlink()
159
+ continue
160
+ content, mode = snapshot
161
+ _write_atomic_bytes(path, content, force=True, mode=mode)
162
+
163
+
164
+ def _load_template(relative_path: Path) -> str:
165
+ template = _TEMPLATES_ROOT / relative_path
166
+ return template.read_text(encoding="utf-8")
167
+
168
+
169
+ def _render_plugin(python_executable: str) -> str:
170
+ template = _load_template(OPENCODE_PLUGIN_TEMPLATE_PATH)
171
+ escaped_python_executable = json.dumps(str(python_executable))
172
+ return template.replace("{{PYTHON_EXECUTABLE}}", escaped_python_executable)
173
+
174
+
175
+ def _write_atomic_text(path: Path, content: str, force: bool = False) -> None:
176
+ _write_atomic_bytes(path, content.encode(), force=force)
177
+
178
+
179
+ def _write_atomic_bytes(
180
+ path: Path,
181
+ content: bytes,
182
+ *,
183
+ force: bool = False,
184
+ mode: int | None = None,
185
+ ) -> None:
186
+ if path.is_symlink():
187
+ raise FileExistsError(f"Refusing to replace symbolic link: {path}")
188
+ if path.exists() and not force:
189
+ raise FileExistsError(f"Refusing to overwrite existing file: {path}")
190
+ if path.exists() and not path.is_file():
191
+ raise FileExistsError(f"Generated destination is not a file: {path}")
192
+
193
+ path.parent.mkdir(parents=True, exist_ok=True)
194
+ installed_mode = (
195
+ mode
196
+ if mode is not None
197
+ else stat.S_IMODE(path.stat().st_mode)
198
+ if path.exists()
199
+ else 0o644
200
+ )
201
+ tmp_path: Path | None = None
202
+ try:
203
+ with NamedTemporaryFile("wb", dir=path.parent, delete=False) as handle:
204
+ handle.write(content)
205
+ handle.flush()
206
+ os.fsync(handle.fileno())
207
+ tmp_path = Path(handle.name)
208
+ os.chmod(tmp_path, installed_mode)
209
+ os.replace(tmp_path, path)
210
+ finally:
211
+ if tmp_path is not None and tmp_path.exists():
212
+ tmp_path.unlink()
@@ -0,0 +1,16 @@
1
+ ---
2
+ description: Navigate mixed-language codebases through agent-codinglanguage-mapper.
3
+ mode: subagent
4
+ permission:
5
+ "*": deny
6
+ skill:
7
+ "*": deny
8
+ agent-codinglanguage-mapper: allow
9
+ codebase_map: allow
10
+ code_guidelines: allow
11
+ ---
12
+
13
+ Load `agent-codinglanguage-mapper` first. Use only `skill`, `codebase_map`, and
14
+ `code_guidelines` for source inspection and coding-guideline work. Preserve
15
+ returned citations exactly, keep uncertainty explicit, and never replace
16
+ semantic tool evidence with raw filesystem inspection.
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: mixed-codebase-navigator
2
+ name: agent-codinglanguage-mapper
3
3
  description: Inspect Python, Rust, C#, Delphi, C++, and mixed-language workspaces through a bounded semantic codebase map without raw filesystem search tools.
4
4
  license: MPL-2.0
5
5
  compatibility: opencode
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-codinglanguage-mapper
3
- Version: 1.1.0
3
+ Version: 1.1.1
4
4
  Summary: Agent-focused semantic mapping and language services for mixed-language codebases.
5
5
  Author: Dark Light
6
6
  License-Expression: MPL-2.0
@@ -319,40 +319,37 @@ Example generic client configuration:
319
319
 
320
320
  ## OpenCode Integration
321
321
 
322
- Install the packaged skill, plugin, and restricted agent configuration into a repository:
322
+ Install the package-named skill, Markdown agent, and plugin into a repository:
323
323
 
324
324
  ```bash
325
- codinglanguage-mapper opencode install --target . --write
325
+ codinglanguage-mapper opencode install --target .
326
326
  ```
327
327
 
328
328
  This creates:
329
329
 
330
330
  ```text
331
- .agents/skills/mixed-codebase-navigator/SKILL.md
331
+ .agents/skills/agent-codinglanguage-mapper/SKILL.md
332
+ .opencode/agents/agent-codinglanguage-mapper.md
332
333
  .opencode/plugins/codebase_map.ts
333
- opencode.json
334
334
  ```
335
335
 
336
- The generated `mixed-codebase-navigator` agent denies all tools by default and permits only `skill`, `codebase_map`, and `code_guidelines`. The plugin maps the active OpenCode directory, serializes Protocol 3 requests through one worker, applies a hard timeout, reports bounded worker `stderr`, restarts after failure, and preserves focus plus warm parser indexes across short idle gaps. It stops the worker when the session is deleted or after 60 seconds without another mapper request.
336
+ The generated `agent-codinglanguage-mapper` agent denies all tools by default and permits only its package-named skill, `codebase_map`, and `code_guidelines`. The plugin maps the active OpenCode directory, serializes Protocol 3 requests through one worker, applies a hard timeout, reports bounded worker `stderr`, restarts after failure, and preserves focus plus warm parser indexes across short idle gaps. It stops the worker when the session is deleted or after 60 seconds without another mapper request.
337
+
338
+ The installer never reads or changes `opencode.json`; user configuration remains
339
+ user-owned. The deprecated `--write`, `--write-config`, and `--write-agent`
340
+ compatibility flags are accepted, but the Markdown agent is installed by
341
+ default and those flags do not enable configuration writes.
337
342
 
338
343
  `code_guidelines` exposes four actions: `detect`, `audit`, `patch`, and `apply`. The first three are read-only. `apply` is rejected unless its request contains `write: true`, so the agent must inspect a patch before it can opt into mutation.
339
344
 
340
- ```json
341
- {
342
- "permission": {
343
- "*": "deny",
344
- "skill": "allow",
345
- "codebase_map": "allow",
346
- "code_guidelines": "allow"
347
- }
348
- }
349
- ```
345
+ The agent's YAML frontmatter contains its restricted permissions; no matching
346
+ JSON agent block is required.
350
347
 
351
348
  Run it with:
352
349
 
353
350
  ```bash
354
351
  opencode run \
355
- --agent mixed-codebase-navigator \
352
+ --agent agent-codinglanguage-mapper \
356
353
  "Find PaymentService, inspect its implementation, and cite the callers. Use only the installed skill and codebase_map."
357
354
  ```
358
355
 
@@ -43,6 +43,7 @@ src/agent_codinglanguage_mapper/adapters/delphi_frontend/semantic.py
43
43
  src/agent_codinglanguage_mapper/adapters/delphi_frontend/semantic_builder.py
44
44
  src/agent_codinglanguage_mapper/adapters/delphi_frontend/source_reader.py
45
45
  src/agent_codinglanguage_mapper/adapters/delphi_frontend/workspace.py
46
+ src/agent_codinglanguage_mapper/templates/opencode/agents/agent-codinglanguage-mapper.md
46
47
  src/agent_codinglanguage_mapper/templates/opencode/plugins/codebase_map.ts
47
48
  src/agent_codinglanguage_mapper/templates/skill/SKILL.md
48
49
  tests/test_adapters.py
@@ -26,6 +26,7 @@ def test_parser_exposes_mapper_commands_and_layer_choices() -> None:
26
26
  assert parser.parse_args(["skill", "install"]).skill_command == "install"
27
27
  assert parser.parse_args(["opencode", "install", "--write-config"]).write_config is True
28
28
  assert parser.parse_args(["opencode", "install", "--write"]).write_config is True
29
+ assert parser.parse_args(["opencode", "install", "--write-agent"]).write_config is True
29
30
 
30
31
 
31
32
  def test_cli_executes_view_index_doctor_and_installers(tmp_path: Path, capsys) -> None:
@@ -48,7 +49,8 @@ def test_cli_executes_view_index_doctor_and_installers(tmp_path: Path, capsys) -
48
49
  assert main(["opencode", "install", "--target", str(target), "--write"]) == 0
49
50
  installed = json.loads(capsys.readouterr().out)["installed"]
50
51
  assert any(path.endswith("codebase_map.ts") for path in installed)
51
- assert (target / "opencode.json").is_file()
52
+ assert any(path.endswith("agents/agent-codinglanguage-mapper.md") for path in installed)
53
+ assert not (target / "opencode.json").exists()
52
54
 
53
55
 
54
56
  def test_cli_guidelines_detect_audit_patch_and_write(tmp_path: Path, capsys) -> None: