devex-cli 0.24.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.
Files changed (115) hide show
  1. agent_experience/__init__.py +24 -0
  2. agent_experience/__main__.py +4 -0
  3. agent_experience/backends/__init__.py +0 -0
  4. agent_experience/backends/acp/__init__.py +0 -0
  5. agent_experience/backends/acp/probe.py +9 -0
  6. agent_experience/backends/capabilities/acp.yaml +7 -0
  7. agent_experience/backends/capabilities/claude-code.yaml +4 -0
  8. agent_experience/backends/capabilities/codex.yaml +7 -0
  9. agent_experience/backends/capabilities/copilot.yaml +7 -0
  10. agent_experience/backends/claude_code/__init__.py +0 -0
  11. agent_experience/backends/claude_code/probe.py +97 -0
  12. agent_experience/backends/codex/__init__.py +0 -0
  13. agent_experience/backends/codex/probe.py +16 -0
  14. agent_experience/backends/copilot/__init__.py +0 -0
  15. agent_experience/backends/copilot/probe.py +9 -0
  16. agent_experience/cli.py +485 -0
  17. agent_experience/commands/__init__.py +0 -0
  18. agent_experience/commands/doctor/SKILL.md +41 -0
  19. agent_experience/commands/doctor/__init__.py +0 -0
  20. agent_experience/commands/doctor/assets/report.md.j2 +39 -0
  21. agent_experience/commands/doctor/references/design.md +36 -0
  22. agent_experience/commands/doctor/scripts/__init__.py +0 -0
  23. agent_experience/commands/doctor/scripts/doctor.py +394 -0
  24. agent_experience/commands/explain/SKILL.md +26 -0
  25. agent_experience/commands/explain/__init__.py +0 -0
  26. agent_experience/commands/explain/assets/topics/agex.md +37 -0
  27. agent_experience/commands/explain/references/.gitkeep +0 -0
  28. agent_experience/commands/explain/scripts/__init__.py +0 -0
  29. agent_experience/commands/explain/scripts/explain.py +64 -0
  30. agent_experience/commands/gamify/SKILL.md +31 -0
  31. agent_experience/commands/gamify/__init__.py +0 -0
  32. agent_experience/commands/gamify/assets/hooks/claude-code.json +28 -0
  33. agent_experience/commands/gamify/references/.gitkeep +0 -0
  34. agent_experience/commands/gamify/scripts/__init__.py +0 -0
  35. agent_experience/commands/gamify/scripts/install.py +203 -0
  36. agent_experience/commands/hook/SKILL.md +31 -0
  37. agent_experience/commands/hook/__init__.py +0 -0
  38. agent_experience/commands/hook/assets/table.md.j2 +17 -0
  39. agent_experience/commands/hook/references/.gitkeep +0 -0
  40. agent_experience/commands/hook/scripts/__init__.py +0 -0
  41. agent_experience/commands/hook/scripts/read.py +53 -0
  42. agent_experience/commands/hook/scripts/write.py +25 -0
  43. agent_experience/commands/learn/SKILL.md +21 -0
  44. agent_experience/commands/learn/__init__.py +0 -0
  45. agent_experience/commands/learn/assets/menu.md.j2 +7 -0
  46. agent_experience/commands/learn/assets/topics/cicd/SKILL.md +103 -0
  47. agent_experience/commands/learn/assets/topics/gamify/SKILL.md +35 -0
  48. agent_experience/commands/learn/assets/topics/gamify/assets/skill-template/claude-code/SKILL.md +22 -0
  49. agent_experience/commands/learn/assets/topics/introspect/SKILL.md +41 -0
  50. agent_experience/commands/learn/assets/topics/introspect/assets/skill-template/claude-code/SKILL.md +22 -0
  51. agent_experience/commands/learn/assets/topics/levelup/SKILL.md +31 -0
  52. agent_experience/commands/learn/assets/topics/levelup/assets/skill-template/claude-code/SKILL.md +22 -0
  53. agent_experience/commands/learn/assets/topics/visualize/SKILL.md +27 -0
  54. agent_experience/commands/learn/assets/topics/visualize/assets/skill-template/claude-code/SKILL.md +19 -0
  55. agent_experience/commands/learn/references/.gitkeep +0 -0
  56. agent_experience/commands/learn/scripts/__init__.py +0 -0
  57. agent_experience/commands/learn/scripts/learn.py +73 -0
  58. agent_experience/commands/overview/SKILL.md +31 -0
  59. agent_experience/commands/overview/__init__.py +0 -0
  60. agent_experience/commands/overview/assets/backends/acp.yaml +7 -0
  61. agent_experience/commands/overview/assets/backends/claude-code.yaml +7 -0
  62. agent_experience/commands/overview/assets/backends/codex.yaml +7 -0
  63. agent_experience/commands/overview/assets/backends/copilot.yaml +7 -0
  64. agent_experience/commands/overview/assets/sections.md.j2 +52 -0
  65. agent_experience/commands/overview/references/.gitkeep +0 -0
  66. agent_experience/commands/overview/scripts/__init__.py +0 -0
  67. agent_experience/commands/overview/scripts/overview.py +40 -0
  68. agent_experience/commands/pr/SKILL.md +90 -0
  69. agent_experience/commands/pr/__init__.py +0 -0
  70. agent_experience/commands/pr/assets/__init__.py +0 -0
  71. agent_experience/commands/pr/assets/backends/__init__.py +0 -0
  72. agent_experience/commands/pr/assets/backends/acp.yaml +21 -0
  73. agent_experience/commands/pr/assets/backends/claude-code.yaml +21 -0
  74. agent_experience/commands/pr/assets/backends/codex.yaml +21 -0
  75. agent_experience/commands/pr/assets/backends/copilot.yaml +21 -0
  76. agent_experience/commands/pr/assets/rules/__init__.py +0 -0
  77. agent_experience/commands/pr/assets/rules/lint_rules.py +79 -0
  78. agent_experience/commands/pr/assets/rules/next_step_rules.py +78 -0
  79. agent_experience/commands/pr/assets/templates/__init__.py +0 -0
  80. agent_experience/commands/pr/assets/templates/delta.md.j2 +32 -0
  81. agent_experience/commands/pr/assets/templates/footer.md.j2 +2 -0
  82. agent_experience/commands/pr/assets/templates/lint_result.md.j2 +19 -0
  83. agent_experience/commands/pr/assets/templates/pr_briefing.md.j2 +69 -0
  84. agent_experience/commands/pr/assets/templates/pr_open_result.md.j2 +17 -0
  85. agent_experience/commands/pr/assets/templates/pr_reply_result.md.j2 +15 -0
  86. agent_experience/commands/pr/assets/templates/pr_review_result.md.j2 +5 -0
  87. agent_experience/commands/pr/scripts/__init__.py +0 -0
  88. agent_experience/commands/pr/scripts/_footer.py +32 -0
  89. agent_experience/commands/pr/scripts/_journal.py +21 -0
  90. agent_experience/commands/pr/scripts/_qodo.py +147 -0
  91. agent_experience/commands/pr/scripts/_readiness.py +76 -0
  92. agent_experience/commands/pr/scripts/_sonar.py +29 -0
  93. agent_experience/commands/pr/scripts/await_.py +156 -0
  94. agent_experience/commands/pr/scripts/delta.py +84 -0
  95. agent_experience/commands/pr/scripts/lint.py +72 -0
  96. agent_experience/commands/pr/scripts/open_.py +104 -0
  97. agent_experience/commands/pr/scripts/read.py +151 -0
  98. agent_experience/commands/pr/scripts/reply.py +160 -0
  99. agent_experience/commands/pr/scripts/review.py +59 -0
  100. agent_experience/core/__init__.py +0 -0
  101. agent_experience/core/backend.py +80 -0
  102. agent_experience/core/capabilities.py +44 -0
  103. agent_experience/core/config.py +46 -0
  104. agent_experience/core/github.py +355 -0
  105. agent_experience/core/hook_io.py +95 -0
  106. agent_experience/core/journal.py +90 -0
  107. agent_experience/core/paths.py +26 -0
  108. agent_experience/core/prog.py +44 -0
  109. agent_experience/core/render.py +42 -0
  110. agent_experience/core/skill_loader.py +36 -0
  111. devex_cli-0.24.0.dist-info/METADATA +55 -0
  112. devex_cli-0.24.0.dist-info/RECORD +115 -0
  113. devex_cli-0.24.0.dist-info/WHEEL +4 -0
  114. devex_cli-0.24.0.dist-info/entry_points.txt +3 -0
  115. devex_cli-0.24.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,203 @@
1
+ import json
2
+ from datetime import datetime, timezone
3
+ from importlib.resources import files
4
+ from importlib.resources.abc import Traversable
5
+ from pathlib import Path
6
+
7
+ from agent_experience.core.backend import Backend
8
+ from agent_experience.core.config import load as load_config
9
+ from agent_experience.core.config import save as save_config
10
+ from agent_experience.core.paths import ensure_init
11
+ from agent_experience.core.prog import error_prefix, prog_name
12
+
13
+
14
+ def _fragments_file() -> Traversable:
15
+ return files("agent_experience.commands.gamify").joinpath("assets", "hooks", "claude-code.json")
16
+
17
+
18
+ def _fragments_for(backend: Backend) -> list[dict]:
19
+ if backend != Backend.CLAUDE_CODE:
20
+ return []
21
+ data = json.loads(_fragments_file().read_text(encoding="utf-8"))
22
+ return data["fragments"]
23
+
24
+
25
+ def _hooks_file_for(backend: Backend, project_dir: Path) -> Path | None:
26
+ if backend == Backend.CLAUDE_CODE:
27
+ return project_dir / ".claude" / "hooks.json"
28
+ return None
29
+
30
+
31
+ def _refuse(path: Path, reason: str) -> ValueError:
32
+ return ValueError(
33
+ f"{path} {reason}; refusing to overwrite. " "Fix or remove the file before re-running."
34
+ )
35
+
36
+
37
+ def _load_hooks_file(path: Path) -> dict:
38
+ # Malformed or unexpectedly-shaped files are NEVER silently overwritten —
39
+ # the caller gets a ValueError, surfaces it as exit 2, and the user's file
40
+ # stays on disk untouched.
41
+ if not path.exists():
42
+ return {}
43
+ try:
44
+ data = json.loads(path.read_text(encoding="utf-8"))
45
+ except json.JSONDecodeError as e:
46
+ raise _refuse(path, f"is not valid JSON ({e})") from e
47
+ if not isinstance(data, dict):
48
+ raise _refuse(path, "is not a JSON object at the top level")
49
+ for entries in data.values():
50
+ if not isinstance(entries, list) or not all(isinstance(e, dict) for e in entries):
51
+ raise _refuse(path, "is not a mapping of event → list of hook objects")
52
+ return data
53
+
54
+
55
+ def _write_hooks_file(path: Path, data: dict) -> None:
56
+ # Sonar pythonsecurity:S2083: `path` derives from
57
+ # Path.cwd()/".claude/hooks.json"; the backend is enum-validated by
58
+ # parse_backend() before reaching here. Full rationale lives in
59
+ # sonar-project.properties. The suppression tag below is the
60
+ # load-bearing one under SonarCloud Automatic Analysis.
61
+ path.parent.mkdir(parents=True, exist_ok=True)
62
+ path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") # NOSONAR
63
+
64
+
65
+ def _merge_fragments(hooks: dict, fragments: list[dict]) -> tuple[list[str], int]:
66
+ """Append each fragment's {id, hook} under its event if the id isn't already
67
+ there. Returns (all_fragment_ids_in_insertion_order, added_count)."""
68
+ written_ids: list[str] = []
69
+ added = 0
70
+ for frag in fragments:
71
+ event = frag["event"]
72
+ entry = {"id": frag["id"], "hook": frag["hook"]}
73
+ hooks.setdefault(event, [])
74
+ if not any(e.get("id") == frag["id"] for e in hooks[event]):
75
+ hooks[event].append(entry)
76
+ added += 1
77
+ written_ids.append(frag["id"])
78
+ return written_ids, added
79
+
80
+
81
+ def _remove_ids_from_hooks(hooks: dict, ids_to_remove: set[str]) -> int:
82
+ """Filter entries matching ids_to_remove out of each event. Event keys whose
83
+ arrays become empty as a result of removal are deleted. Pre-existing empty
84
+ event arrays are left intact. Returns total entries removed."""
85
+ removed = 0
86
+ for event in list(hooks.keys()):
87
+ original = hooks[event]
88
+ filtered = [e for e in original if e.get("id") not in ids_to_remove]
89
+ event_removed = len(original) - len(filtered)
90
+ if event_removed == 0:
91
+ continue
92
+ removed += event_removed
93
+ if filtered:
94
+ hooks[event] = filtered
95
+ else:
96
+ del hooks[event]
97
+ return removed
98
+
99
+
100
+ def install(backend: Backend) -> tuple[str, int, str]:
101
+ ensure_init()
102
+ project_dir = Path.cwd()
103
+ hooks_file = _hooks_file_for(backend, project_dir)
104
+ if hooks_file is None:
105
+ return (_unsupported_notice(backend), 0, "")
106
+
107
+ fragments = _fragments_for(backend)
108
+ if not fragments:
109
+ return (_unsupported_notice(backend), 0, "")
110
+
111
+ try:
112
+ hooks = _load_hooks_file(hooks_file)
113
+ except ValueError as e:
114
+ return ("", 2, error_prefix(str(e)))
115
+
116
+ written_ids, added_count = _merge_fragments(hooks, fragments)
117
+ if added_count:
118
+ _write_hooks_file(hooks_file, hooks)
119
+
120
+ cfg = load_config()
121
+ previous = cfg.installed.get("gamify", {})
122
+ if previous.get("hook_fragment_ids") != written_ids:
123
+ cfg.installed["gamify"] = {
124
+ "at": datetime.now(tz=timezone.utc).isoformat(),
125
+ "hook_fragment_ids": written_ids,
126
+ }
127
+ save_config(cfg)
128
+
129
+ rel = hooks_file.relative_to(project_dir)
130
+ if added_count:
131
+ status_line = (
132
+ f"- Added {added_count} hook fragment(s); "
133
+ f"ensured {len(written_ids)} present in `{rel}`."
134
+ )
135
+ else:
136
+ status_line = (
137
+ f"- Ensured {len(written_ids)} hook fragment(s) already present "
138
+ f"in `{rel}` (no changes)."
139
+ )
140
+ lines = [
141
+ f"# Gamify installed — {backend.value}",
142
+ "",
143
+ status_line,
144
+ "- Fragment IDs: " + ", ".join(f"`{i}`" for i in written_ids),
145
+ "",
146
+ f"Next: run `{prog_name()} learn gamify --agent {backend.value}`"
147
+ " to set up the levelup skill.",
148
+ "",
149
+ ]
150
+ return ("\n".join(lines), 0, "")
151
+
152
+
153
+ def uninstall(backend: Backend) -> tuple[str, int, str]:
154
+ ensure_init()
155
+ project_dir = Path.cwd()
156
+ hooks_file = _hooks_file_for(backend, project_dir)
157
+ if hooks_file is None:
158
+ return (_unsupported_notice(backend), 0, "")
159
+
160
+ cfg = load_config()
161
+ installed = cfg.installed.get("gamify", {})
162
+ ids_to_remove = set(installed.get("hook_fragment_ids", []))
163
+ if not ids_to_remove:
164
+ return (f"# Gamify uninstalled — nothing to remove on {backend.value}.\n", 0, "")
165
+
166
+ rel = hooks_file.relative_to(project_dir)
167
+ # If the user already removed the hooks file, just drop the config record.
168
+ # Don't re-create the file with an empty object.
169
+ if not hooks_file.exists():
170
+ cfg.installed.pop("gamify", None)
171
+ save_config(cfg)
172
+ return (
173
+ f"# Gamify uninstalled — `{rel}` was already gone; " "cleared config record.\n",
174
+ 0,
175
+ "",
176
+ )
177
+
178
+ try:
179
+ hooks = _load_hooks_file(hooks_file)
180
+ except ValueError as e:
181
+ return ("", 2, error_prefix(str(e)))
182
+
183
+ removed_count = _remove_ids_from_hooks(hooks, ids_to_remove)
184
+ if removed_count:
185
+ _write_hooks_file(hooks_file, hooks)
186
+
187
+ cfg.installed.pop("gamify", None)
188
+ save_config(cfg)
189
+
190
+ return (
191
+ f"# Gamify uninstalled — removed {removed_count} fragment(s) from `{rel}`.\n",
192
+ 0,
193
+ "",
194
+ )
195
+
196
+
197
+ def _unsupported_notice(backend: Backend) -> str:
198
+ return (
199
+ f"## `gamify` is not supported on {backend.value}\n\n"
200
+ f"Hooks are required to track usage events, and {backend.value} does not expose "
201
+ f"a hook interface {prog_name()} can write to.\n\n"
202
+ "Want this supported? Open an issue: <https://github.com/agentculture/devex/issues>\n"
203
+ )
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: hook
3
+ description: Write and read agex tracking events.
4
+ type: command
5
+ ---
6
+
7
+ # `agex hook write <event> [key=value ...]` / `agex hook read --agent <backend>`
8
+
9
+ ## `write`
10
+
11
+ Called by installed hooks (see `agex gamify`, Phase 7). Appends a JSON line to `.agex/data/<event>.json`. Silent. Safe for concurrent invocation (file locking via `portalocker`).
12
+
13
+ ```bash
14
+ agex hook write post-tool-use tool=Read
15
+ ```
16
+
17
+ ## `read`
18
+
19
+ Renders tracked events as a markdown table. Prints the source JSON path for deeper inspection.
20
+
21
+ ```bash
22
+ agex hook read --agent claude-code
23
+ ```
24
+
25
+ ## Notes
26
+
27
+ - Event names are free-form; conventional names: `post-tool-use`, `user-prompt`, `stop`, `sessions`.
28
+ - Extra positional `key=value` pairs are captured into the payload. Empty keys (e.g., `=foo`) are dropped.
29
+ - Timestamp (`ts`) is attached automatically; a positional `ts=<value>` overrides it (useful for replays).
30
+ - The positional `<event>` name is authoritative — it always wins over any `event=...` pair in args.
31
+ - Malformed JSON lines in `.agex/data/*.json` (e.g., from a partial write) are skipped with a warning on `hook read`, not raised.
File without changes
@@ -0,0 +1,17 @@
1
+ # Hook events — {{ backend }}
2
+
3
+ **Source:** `{{ source }}`
4
+
5
+ {% for stream in streams %}
6
+ ## `{{ stream.name }}` ({{ stream.events|length }})
7
+
8
+ {% if stream.events -%}
9
+ | ts | event | details |
10
+ |---|---|---|
11
+ {% for e in stream.events -%}
12
+ | {{ e.ts }} | {{ stream.name }} | {{ e.details }} |
13
+ {% endfor %}
14
+ {%- else -%}
15
+ _no events_
16
+ {%- endif %}
17
+ {% endfor %}
File without changes
File without changes
@@ -0,0 +1,53 @@
1
+ from importlib.resources import files
2
+ from importlib.resources.abc import Traversable
3
+
4
+ from agent_experience.core.backend import Backend
5
+ from agent_experience.core.hook_io import load_events
6
+ from agent_experience.core import journal as _journal
7
+ from agent_experience.core.paths import data_dir, ensure_init
8
+ from agent_experience.core.render import render_string
9
+
10
+ KNOWN_STREAMS = ["post-tool-use", "user-prompt", "stop", "sessions"]
11
+
12
+
13
+ def _assets_root() -> Traversable:
14
+ # Anchor on the `commands` package (which has __init__.py) and navigate in.
15
+ # Avoids relying on namespace-package semantics for `assets/`, which is a
16
+ # data directory, not a package. Matches overview.py / learn.py pattern.
17
+ return files("agent_experience.commands").joinpath("hook", "assets")
18
+
19
+
20
+ def _summarize(events):
21
+ return [
22
+ {
23
+ "ts": e.get("ts", ""),
24
+ "details": ", ".join(f"{k}={v}" for k, v in e.items() if k not in ("ts", "event")),
25
+ }
26
+ for e in events
27
+ ]
28
+
29
+
30
+ def run(backend: Backend) -> tuple[str, int, str]:
31
+ ensure_init()
32
+ streams = []
33
+
34
+ # Flat streams: data/<name>.json
35
+ for name in KNOWN_STREAMS:
36
+ events = load_events(name)
37
+ streams.append({"name": name, "events": _summarize(events)})
38
+
39
+ # Nested streams: data/<subdir>/<name>.jsonl
40
+ root = data_dir()
41
+ if root.exists():
42
+ for subdir in sorted(p for p in root.iterdir() if p.is_dir()):
43
+ for jsonl_file in sorted(subdir.glob("*.jsonl")):
44
+ stream_name = f"{subdir.name}/{jsonl_file.stem}"
45
+ events = _journal.load_events(stream_name)
46
+ streams.append({"name": stream_name, "events": _summarize(events)})
47
+
48
+ template_text = _assets_root().joinpath("table.md.j2").read_text(encoding="utf-8")
49
+ out = render_string(
50
+ template_text,
51
+ {"backend": backend.value, "source": str(root), "streams": streams},
52
+ )
53
+ return (out, 0, "")
@@ -0,0 +1,25 @@
1
+ from datetime import datetime, timezone
2
+
3
+ from agent_experience.core.hook_io import append_event
4
+ from agent_experience.core.paths import ensure_init
5
+ from agent_experience.core.prog import error_prefix
6
+
7
+
8
+ def run(event: str, args: list[str]) -> tuple[str, int, str]:
9
+ ensure_init()
10
+ payload: dict = {"ts": datetime.now(tz=timezone.utc).isoformat()}
11
+ for arg in args:
12
+ if "=" in arg:
13
+ k, v = arg.split("=", 1)
14
+ if k:
15
+ payload[k] = v
16
+ # Positional event name is authoritative — it overrides any `event=...`
17
+ # pair in args so hook scripts can't misattribute events.
18
+ payload["event"] = event
19
+ try:
20
+ append_event(event, payload)
21
+ except ValueError as e:
22
+ # append_event → _stream_path rejects names that don't match the
23
+ # `^[a-z][a-z0-9-]*$` slug whitelist (path-traversal guard).
24
+ return ("", 2, error_prefix(str(e)))
25
+ return ("", 0, "")
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: learn
3
+ description: Show available lessons, or teach one.
4
+ type: command
5
+ ---
6
+
7
+ # `agex learn [topic] --agent <backend>`
8
+
9
+ Without a topic, lists the lessons available for your backend. With a topic, teaches it — emits a markdown lesson body plus inline skill-template code blocks you can write into your project.
10
+
11
+ ## From your shell tool
12
+
13
+ ```bash
14
+ agex learn --agent claude-code
15
+ agex learn introspect --agent claude-code
16
+ ```
17
+
18
+ ## Notes
19
+
20
+ - Lessons gated on a backend feature (e.g., `gamify` needs hooks) may still appear in the list, but they are not currently annotated as unsupported in the menu — Phase 8 adds that capability-based routing.
21
+ - v0.1 emits inline code blocks only. A future `--write` flag is tracked as an open issue.
File without changes
@@ -0,0 +1,7 @@
1
+ # Lessons — {{ backend }}
2
+
3
+ Run `{{ prog }} learn <topic> --agent {{ backend }}` to learn one.
4
+
5
+ {% for topic in topics -%}
6
+ - **`{{ topic.name }}`** — {{ topic.description }}{% if topic.unsupported %} _(unsupported on {{ backend }}: {{ topic.unsupported }})_{% endif %}
7
+ {% endfor %}
@@ -0,0 +1,103 @@
1
+ ---
2
+ name: cicd
3
+ description: How to use `agex pr` to ship a PR end-to-end — lint, open with auto-signature, fetch the unified briefing (status + comments + readiness), batch-reply with thread resolution, and run alignment-delta when needed.
4
+ type: lesson
5
+ ---
6
+
7
+ # Lesson — CI/CD with `agex pr` for {{ backend }}
8
+
9
+ The full PR loop boils down to six commands. Each one ends with a
10
+ **Next step:** footer that names the right next command — chain it.
11
+
12
+ ## Standard happy path
13
+
14
+ ```bash
15
+ git checkout -b feat/<desc>
16
+ # ... edit ...
17
+ agex pr lint --agent {{ backend }} # portability + alignment
18
+ git commit -am "..." && git push -u origin <branch>
19
+
20
+ agex pr open --agent {{ backend }} \
21
+ --title "..." --body-file ./body.md \
22
+ --delayed-read # creates PR + waits 180s + briefing
23
+
24
+ # briefing arrived; triage and prepare replies.jsonl, then:
25
+ agex pr reply <PR> --agent {{ backend }} < replies.jsonl
26
+
27
+ # fix anything, push, then:
28
+ agex pr read <PR> --agent {{ backend }} --wait 180
29
+ # repeat until reviewers quiet + CI green
30
+ # wait for human merge — never merge yourself
31
+ ```
32
+
33
+ ## `read --wait` vs. `await`
34
+
35
+ Both poll the same readiness loop. They differ on what they do with
36
+ the result:
37
+
38
+ - `agex pr read <PR> --wait 180` — always exits 0. Renders the briefing
39
+ and lets the agent decide. Use when you want the unified view.
40
+ - `agex pr await <PR>` — exits **1** on SonarCloud gate `ERROR`,
41
+ unresolved review threads, or failing CI checks; **0** otherwise
42
+ (clean state or timeout). Use when you want to gate the next command
43
+ on PR health (e.g., in a shell loop that should fail if Sonar or CI
44
+ is red).
45
+
46
+ ## When CLAUDE.md / culture.yaml / .claude/skills change
47
+
48
+ `agex pr lint` flags this and points you at:
49
+
50
+ ```bash
51
+ agex pr delta --agent {{ backend }}
52
+ ```
53
+
54
+ Read each sibling's CLAUDE.md head + culture.yaml, decide whether each
55
+ needs a follow-up PR, and mention any drift in your reply.
56
+
57
+ ## JSONL reply shape
58
+
59
+ Each line of stdin to `agex pr reply <PR>`:
60
+
61
+ ```json
62
+ {"in_reply_to": 123456, "thread_id": "T_kw...", "body": "Fixed in <commit>."}
63
+ ```
64
+
65
+ - `in_reply_to` is the inline review-comment id. Omit for top-level conversation comments.
66
+ - `thread_id` triggers `resolveReviewThread` after the post.
67
+ - `body` is auto-signed with `- <nick> (Claude)` if the signature is missing. `<nick>` comes from the first agent's `suffix` in `culture.yaml`, falling back to the repo basename.
68
+
69
+ ## Side effects
70
+
71
+ Network: every command except `lint` and `delta` talks to GitHub via `gh`.
72
+ Disk: `pr open`, `pr read`, and `pr reply` append events to
73
+ `.agex/data/pr/events.jsonl` for retrospective tooling.
74
+
75
+ ## When something goes wrong
76
+
77
+ - `gh` not installed → `agex: install gh — https://cli.github.com/ — then rerun`
78
+ - `gh` not authenticated → `agex: run 'gh auth login' then rerun`
79
+ - `pr reply` partial failure → stderr names the line slice to resubmit; the
80
+ command stops at the first failure to keep recovery surgical.
81
+ - `pr read --wait` / `pr await` timeout → exit 0 with a "Still waiting on:
82
+ <reviewers>" banner; rerun the same command to keep waiting.
83
+
84
+ ## Long waits (5-minute cache TTL)
85
+
86
+ Anthropic's prompt cache has a 5-minute TTL. If you expect to wait
87
+ longer than that — for example, polling a slow CI gate with
88
+ `agex pr read <PR> --wait 600` or `agex pr await <PR> --max-wait 1800`
89
+ — run the wait inside a subagent and triage the result when it fires.
90
+ The parent session keeps its cache warm; only the subagent pays the
91
+ per-iteration cost.
92
+
93
+ Pattern (Claude Code): spawn an `Agent(..., run_in_background=true)`
94
+ that runs the command and echoes the final headline + exit code.
95
+ Continue with unrelated work; act on the notification when it
96
+ arrives. This is exactly how steward's `cicd` workflow handles its
97
+ `workflow.sh await <PR>` chains.
98
+
99
+ ## Reply etiquette
100
+
101
+ Every comment gets a reply — no silent fixes. Always include a
102
+ `thread_id` so the thread closes automatically. Reference the fix-up
103
+ commit SHA in the reply body where relevant.
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: gamify
3
+ description: Install usage tracking hooks and build a levelup skill to advise the user.
4
+ type: lesson
5
+ ---
6
+
7
+ # Lesson — set up gamification for {{ backend }}
8
+
9
+ Two parts:
10
+
11
+ ## Part 1 — install the tracking hooks
12
+
13
+ Run in your shell tool:
14
+
15
+ ```bash
16
+ agex gamify --agent {{ backend }}
17
+ ```
18
+
19
+ This writes backend-native hook fragments that call `agex hook write <event>` whenever you use a tool, submit a prompt, or stop. The events land in `.agex/data/`.
20
+
21
+ To uninstall: `agex gamify --uninstall --agent {{ backend }}`.
22
+
23
+ ## Part 2 — build the `levelup` skill
24
+
25
+ The hook data is inert without something to surface it. Build the levelup skill described in `agex learn levelup --agent {{ backend }}`, or copy its skill template directly:
26
+
27
+ ### `.claude/skills/levelup/SKILL.md`
28
+
29
+ ```markdown
30
+ {{ skill_template_body }}
31
+ ```
32
+
33
+ ## After both parts
34
+
35
+ Use your runtime normally for a few sessions. Then invoke `/levelup` — it will read the tracking data via `agex hook read` and advise the user.
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: levelup
3
+ description: Read agex usage tracking data and suggest a next-feature-to-learn for the user.
4
+ type: command
5
+ ---
6
+
7
+ # Level up
8
+
9
+ > **Note:** Requires `agex hook read`, which ships in a future phase (Phase 6). Until it lands, this skill has no data source — treat it as a scaffold placeholder.
10
+
11
+ Invoke when the user asks for what's next, or opportunistically after a long session.
12
+
13
+ ## Process
14
+
15
+ 1. Run in your shell tool: `agex hook read --agent claude-code`
16
+ 2. Count occurrences per event type.
17
+ 3. Pick **one** feature area the user is under-using (e.g., they have MCP servers configured but `post-tool-use` shows zero MCP tool calls).
18
+ 4. Suggest one concrete next step in 3-4 sentences.
19
+
20
+ ## Rule
21
+
22
+ One suggestion per invocation. If nothing stands out, say so — don't invent.
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: introspect
3
+ description: Build an agent-native introspect skill that audits your project setup and suggests next steps.
4
+ type: lesson
5
+ ---
6
+
7
+ # Lesson — build an `introspect` skill for {{ backend }}
8
+
9
+ ## What you'll end up with
10
+
11
+ A backend-native skill (for {{ backend }}) that:
12
+ 1. Calls `agex overview --agent {{ backend }}` to read the project's current state.
13
+ 2. Identifies gaps (missing CLAUDE.md, no skills, no hooks, etc.).
14
+ 3. Suggests the next one or two improvements you could apply.
15
+ 4. Is small enough that invoking it doesn't blow your context budget.
16
+
17
+ ## Why build it instead of shipping it
18
+
19
+ Two reasons: (1) each agent backend has its own native skill format, so no shipped skill fits perfectly; (2) you and the user stay in control of what gets installed into the project.
20
+
21
+ ## Step 1 — review the `agex overview` output
22
+
23
+ Run `agex overview --agent {{ backend }}` now. Note which sections are empty — those are your candidate gaps.
24
+
25
+ ## Step 2 — create the skill file
26
+
27
+ Write the file shown below to the path noted above its fence. Adjust the prose/tone to your project's voice.
28
+
29
+ ### `.claude/skills/introspect/SKILL.md`
30
+
31
+ ```markdown
32
+ {{ skill_template_body }}
33
+ ```
34
+
35
+ ## Step 3 — try it
36
+
37
+ Invoke `/introspect` (or equivalent) in your runtime. Read the output, apply one suggestion.
38
+
39
+ ## Why one suggestion at a time
40
+
41
+ Agents that shove 10 recommendations into one turn overwhelm the user. The skill you just built is explicitly capped at two — if you want more later, iterate.
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: introspect
3
+ description: Audit the current project's agent setup and suggest 1-2 next improvements.
4
+ type: command
5
+ ---
6
+
7
+ # Introspect
8
+
9
+ When the user asks to audit the agent setup, improve it, or "what should I add", invoke this skill.
10
+
11
+ ## Process
12
+
13
+ 1. Run in your shell tool: `agex overview --agent claude-code`
14
+ 2. Read the output. Count what exists under each section (skills, hooks, MCP, settings).
15
+ 3. Identify the two weakest sections — the ones most likely to unblock a real workflow next.
16
+ 4. Emit a short markdown reply: what's missing, why it matters, what adding it costs.
17
+
18
+ ## Rules
19
+
20
+ - Cap suggestions at 2.
21
+ - No "nice-to-haves." Only suggestions that unblock something concrete.
22
+ - Don't install anything. Advise only.
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: levelup
3
+ description: Build a skill that reads agex usage data and advises the user.
4
+ type: lesson
5
+ ---
6
+
7
+ # Lesson — build the `levelup` skill for {{ backend }}
8
+
9
+ > **Preview:** This lesson depends on `agex gamify` (Phase 7) and `agex hook read` (Phase 6); neither is available in agex 0.3.0. Treat the steps below as a design preview — the emitted skill template won't have real data to read until those commands ship.
10
+
11
+ Prerequisite: you've run `agex gamify --agent {{ backend }}` so there's data to read.
12
+
13
+ ## Step 1 — understand the data source
14
+
15
+ Run `agex hook read --agent {{ backend }}` now. You'll see a JSON list of events (tool calls, prompts submitted, stops). The levelup skill will parse this and suggest one area for improvement.
16
+
17
+ ## Step 2 — create the skill file
18
+
19
+ Write the file shown below to the path noted above its fence. This skill reads the tracking data and offers one concrete suggestion per invocation.
20
+
21
+ ### Skill template — `.claude/skills/levelup/SKILL.md`
22
+
23
+ ```markdown
24
+ {{ skill_template_body }}
25
+ ```
26
+
27
+ ## Step 3 — try it after a few sessions
28
+
29
+ Use your runtime normally for a few turns. Then invoke `/levelup` to see what the skill suggests.
30
+
31
+ See also: `agex learn gamify --agent {{ backend }}` (bundles the full setup).
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: levelup
3
+ description: Read agex usage tracking data and suggest a next-feature-to-learn for the user.
4
+ type: command
5
+ ---
6
+
7
+ # Level up
8
+
9
+ > **Note:** Requires `agex hook read`, which ships in a future phase (Phase 6). Until it lands, this skill has no data source — treat it as a scaffold placeholder.
10
+
11
+ Invoke when the user asks for what's next, or opportunistically after a long session.
12
+
13
+ ## Process
14
+
15
+ 1. Run in your shell tool: `agex hook read --agent claude-code`
16
+ 2. Count occurrences per event type.
17
+ 3. Pick **one** feature area the user is under-using (e.g., they have MCP servers configured but `post-tool-use` shows zero MCP tool calls).
18
+ 4. Suggest one concrete next step in 3-4 sentences.
19
+
20
+ ## Rule
21
+
22
+ One suggestion per invocation. If nothing stands out, say so — don't invent.