@ethlete/agent-rules 0.1.0-next.31 → 0.1.0-next.33
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.
- package/CHANGELOG.md +50 -0
- package/README.md +7 -1
- package/content/hooks/background-design-commands.py +178 -0
- package/content/hooks/builder-chat-words.py +269 -0
- package/content/hooks/context-warning.py +47 -19
- package/content/hooks/git-safety.py +208 -0
- package/content/mods/plugins/e2e-progress/.claude-plugin/plugin.json +1 -1
- package/content/rules/nx-layout.md +5 -2
- package/content/rules/subagent-models.md +2 -2
- package/content/skills/agent-sessions/SKILL.md +9 -5
- package/content/skills/agent-sessions/agent-sessions.mjs +283 -34
- package/content/skills/design-exploration/SKILL.md +47 -547
- package/content/skills/design-exploration/reference-check-and-notes.md +92 -0
- package/content/skills/design-exploration/reference-flows-and-wireframes.md +78 -0
- package/content/skills/design-exploration/reference-inbox-and-mcp.md +54 -0
- package/content/skills/design-exploration/reference-order-and-design-system.md +124 -0
- package/content/skills/design-exploration/reference-products-and-sketches.md +211 -0
- package/content/skills/design-exploration/reference-shots.md +25 -0
- package/content/skills/git-commit/SKILL.md +10 -0
- package/content/skills/grilling/SKILL.md +1 -0
- package/content/skills/handoff/SKILL.md +29 -6
- package/package.json +1 -1
- package/src/index.js +2 -1
- package/src/index.js.map +1 -1
- package/src/lib/git-flow-repair.d.ts +1 -1
- package/src/lib/git-flow-start.d.ts +1 -1
- package/src/lib/migrate.js +2 -1
- package/src/lib/migrate.js.map +1 -1
- package/src/lib/output-style-command.d.ts +1 -1
- package/src/lib/prettier.d.ts +21 -0
- package/src/lib/prettier.js +51 -0
- package/src/lib/prettier.js.map +1 -0
- package/src/lib/sync.d.ts +1 -1
- package/src/lib/sync.js +9 -2
- package/src/lib/sync.js.map +1 -1
- package/src/lib/targets/hooks-shared.d.ts +3 -3
- package/src/lib/targets/hooks-shared.js +58 -20
- package/src/lib/targets/hooks-shared.js.map +1 -1
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""PreToolUse hook: git safety in a checkout several agents share.
|
|
3
|
+
|
|
4
|
+
Denies, for a Bash call:
|
|
5
|
+
- `git add -A`, `git add --all` and `git add .` (they stage other sessions' work)
|
|
6
|
+
- every `git stash` that writes (bare, `push`, `save`, `pop`, `apply`, `drop`, `clear`, `branch`,
|
|
7
|
+
`create`, `store`); `git stash list` and `git stash show` stay allowed
|
|
8
|
+
- `git commit --amend`
|
|
9
|
+
- `git commit` without a ` -- <paths>` pathspec, except the commit that concludes a merge: while
|
|
10
|
+
`MERGE_HEAD` exists and every staged path changed between HEAD and MERGE_HEAD or is conflicted
|
|
11
|
+
|
|
12
|
+
`git commit-tree` and `git update-index` stay allowed, so a merge commit built on a temporary
|
|
13
|
+
GIT_INDEX_FILE works. Only real command positions count: commit messages (`-m`, `--message`), quoted text and heredoc
|
|
14
|
+
bodies are ignored, so a grep pattern or message that mentions `git stash` does not trip the hook.
|
|
15
|
+
|
|
16
|
+
Can be disabled per machine via a gitignored ethlete-agents.config.local.json at the repo
|
|
17
|
+
root: {"disableHooks": true} or {"disableHooks": ["git-safety"]}.
|
|
18
|
+
|
|
19
|
+
Fail-safe: any error exits 0 with no output.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import os
|
|
24
|
+
import re
|
|
25
|
+
import subprocess
|
|
26
|
+
import sys
|
|
27
|
+
|
|
28
|
+
HOOK_NAME = "git-safety"
|
|
29
|
+
LOCAL_CONFIG_FILE = "ethlete-agents.config.local.json"
|
|
30
|
+
|
|
31
|
+
MESSAGE_ARG = re.compile(
|
|
32
|
+
r"""(?:^|(?<=\s))(?:-[a-zA-Z]*m|--message)(?:=|\s*)(?:"(?:\\.|[^"\\])*"|'[^']*'|[^\s;&|]*)"""
|
|
33
|
+
)
|
|
34
|
+
SEGMENT_SPLIT = re.compile(r"[;&|\n()`]+")
|
|
35
|
+
HEREDOC = re.compile(r"<<-?[ \t]*(['\"]?)(\w+)\1([^\n]*)\n(?:.*?\n)?[ \t]*\2[ \t]*(?=\n|$)", re.DOTALL)
|
|
36
|
+
QUOTED = re.compile(r"\"(?:\\.|[^\"\\])*\"|'[^']*'")
|
|
37
|
+
GLOBAL_OPTS_WITH_VALUE = ("-C", "-c", "--git-dir", "--work-tree", "--namespace")
|
|
38
|
+
ADD_ALL = ("-A", "--all", ".")
|
|
39
|
+
|
|
40
|
+
def agent_name(argv):
|
|
41
|
+
for index, arg in enumerate(argv):
|
|
42
|
+
if arg == "--agent" and index + 1 < len(argv):
|
|
43
|
+
return argv[index + 1]
|
|
44
|
+
if arg.startswith("--agent="):
|
|
45
|
+
return arg.split("=", 1)[1]
|
|
46
|
+
return "claude"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def repo_root(data):
|
|
50
|
+
for variable in ("CLAUDE_PROJECT_DIR", "CODEX_PROJECT_DIR"):
|
|
51
|
+
value = os.environ.get(variable)
|
|
52
|
+
if value:
|
|
53
|
+
return value
|
|
54
|
+
here = os.path.abspath(__file__)
|
|
55
|
+
derived = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(here))))
|
|
56
|
+
if os.path.isfile(os.path.join(derived, LOCAL_CONFIG_FILE)):
|
|
57
|
+
return derived
|
|
58
|
+
cwd = data.get("cwd")
|
|
59
|
+
return cwd if isinstance(cwd, str) and cwd else derived
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def load_local_config(root):
|
|
63
|
+
if not root:
|
|
64
|
+
return {}
|
|
65
|
+
try:
|
|
66
|
+
with open(os.path.join(root, LOCAL_CONFIG_FILE), encoding="utf-8") as f:
|
|
67
|
+
config = json.load(f)
|
|
68
|
+
except (OSError, ValueError, AttributeError):
|
|
69
|
+
return {}
|
|
70
|
+
return config if isinstance(config, dict) else {}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def disabled_locally(config):
|
|
74
|
+
disabled = config.get("disableHooks")
|
|
75
|
+
return disabled is True or (isinstance(disabled, list) and HOOK_NAME in disabled)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def git_calls_in(command):
|
|
79
|
+
"""Yield (subcommand, args) for every command-position git call in the shell string."""
|
|
80
|
+
stripped = MESSAGE_ARG.sub(" ", HEREDOC.sub(r"<<\3", command))
|
|
81
|
+
unquoted = QUOTED.sub(lambda m: re.sub(r"[\s;&|(`]", "_", m.group(0)[1:-1]), stripped)
|
|
82
|
+
for segment in SEGMENT_SPLIT.split(unquoted):
|
|
83
|
+
words = segment.split()
|
|
84
|
+
index = 0
|
|
85
|
+
while index < len(words) and re.match(r"^[A-Za-z_][A-Za-z0-9_]*=", words[index]):
|
|
86
|
+
index += 1
|
|
87
|
+
if index < len(words) and (words[index] == "git" or words[index].endswith("/git")):
|
|
88
|
+
rest = words[index + 1 :]
|
|
89
|
+
directory = None
|
|
90
|
+
while rest and rest[0].startswith("-"):
|
|
91
|
+
if rest[0] == "-C" and len(rest) > 1:
|
|
92
|
+
directory = rest[1]
|
|
93
|
+
skip = 2 if rest[0] in GLOBAL_OPTS_WITH_VALUE else 1
|
|
94
|
+
rest = rest[skip:]
|
|
95
|
+
if rest:
|
|
96
|
+
yield rest[0], rest[1:], directory
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
STASH_READS = ("list", "show")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def stash_subcommand_of(args):
|
|
103
|
+
"""The stash subcommand `args` run; a bare `git stash` or one starting with an option is `push`."""
|
|
104
|
+
return args[0] if args and not args[0].startswith("-") else "push"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def git_lines(cwd, *args):
|
|
108
|
+
result = subprocess.run(["git", *args], cwd=cwd, capture_output=True, text=True, timeout=10, check=True)
|
|
109
|
+
return {line for line in result.stdout.splitlines() if line}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def concludes_merge(cwd):
|
|
113
|
+
"""True while MERGE_HEAD exists and every staged path belongs to the merge. Any error is False."""
|
|
114
|
+
try:
|
|
115
|
+
merge_head = git_lines(cwd, "rev-parse", "--git-path", "MERGE_HEAD")
|
|
116
|
+
if not merge_head or not os.path.isfile(os.path.join(cwd, next(iter(merge_head)))):
|
|
117
|
+
return False
|
|
118
|
+
staged = git_lines(cwd, "diff", "--cached", "--name-only")
|
|
119
|
+
merge_paths = git_lines(cwd, "diff", "--name-only", "HEAD", "MERGE_HEAD") | git_lines(
|
|
120
|
+
cwd, "diff", "--name-only", "--diff-filter=U"
|
|
121
|
+
)
|
|
122
|
+
return staged <= merge_paths
|
|
123
|
+
except Exception:
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def stages_everything(args):
|
|
128
|
+
return any(arg == "--all" or re.fullmatch(r"-[a-zA-Z]*a[a-zA-Z]*", arg) for arg in args if arg != "--")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def violation_in(command, cwd=None):
|
|
132
|
+
"""(rule, allowed form) for the first forbidden git call, or None."""
|
|
133
|
+
for sub, args, directory in git_calls_in(command):
|
|
134
|
+
if sub == "add":
|
|
135
|
+
if any(arg in ADD_ALL for arg in args):
|
|
136
|
+
return (
|
|
137
|
+
"`git add -A`, `--all` and `.` stage other sessions' work",
|
|
138
|
+
"stage named paths: `git add <path> ...`, or skip staging and use `git commit -m \"...\" -- <paths>`",
|
|
139
|
+
)
|
|
140
|
+
if sub == "stash" and stash_subcommand_of(args) not in STASH_READS:
|
|
141
|
+
return (
|
|
142
|
+
f"`git stash {stash_subcommand_of(args)}` hides or rewrites other sessions' uncommitted work",
|
|
143
|
+
"leave the working tree alone; commit your own paths with `git commit -m \"...\" -- <paths>`",
|
|
144
|
+
)
|
|
145
|
+
if sub == "commit":
|
|
146
|
+
if "--amend" in args:
|
|
147
|
+
return (
|
|
148
|
+
"`git commit --amend` rewrites a commit another session may have built on",
|
|
149
|
+
"make a new commit: `git commit -m \"...\" -- <paths>`",
|
|
150
|
+
)
|
|
151
|
+
if "--" not in args and not (
|
|
152
|
+
not stages_everything(args)
|
|
153
|
+
and concludes_merge(os.path.join(cwd or os.getcwd(), directory) if directory else cwd or os.getcwd())
|
|
154
|
+
):
|
|
155
|
+
return (
|
|
156
|
+
"`git commit` without a pathspec commits everything staged, including other sessions' work",
|
|
157
|
+
"name the paths: `git commit -m \"...\" -- <paths>`",
|
|
158
|
+
)
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def reason_for(rule, allowed):
|
|
163
|
+
return f"Denied: {rule}. Allowed form: {allowed}."
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def main():
|
|
167
|
+
if agent_name(sys.argv[1:]) != "claude":
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
data = json.load(sys.stdin)
|
|
171
|
+
|
|
172
|
+
if data.get("tool_name") != "Bash":
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
tool_input = data.get("tool_input")
|
|
176
|
+
command = tool_input.get("command") if isinstance(tool_input, dict) else None
|
|
177
|
+
|
|
178
|
+
if not isinstance(command, str):
|
|
179
|
+
return
|
|
180
|
+
|
|
181
|
+
if disabled_locally(load_local_config(repo_root(data))):
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
cwd = data.get("cwd")
|
|
185
|
+
found = violation_in(command, cwd if isinstance(cwd, str) and cwd else None)
|
|
186
|
+
|
|
187
|
+
if not found:
|
|
188
|
+
return
|
|
189
|
+
|
|
190
|
+
print(
|
|
191
|
+
json.dumps(
|
|
192
|
+
{
|
|
193
|
+
"hookSpecificOutput": {
|
|
194
|
+
"hookEventName": "PreToolUse",
|
|
195
|
+
"permissionDecision": "deny",
|
|
196
|
+
"permissionDecisionReason": reason_for(*found),
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
if __name__ == "__main__":
|
|
204
|
+
try:
|
|
205
|
+
main()
|
|
206
|
+
except Exception:
|
|
207
|
+
pass
|
|
208
|
+
sys.exit(0)
|
|
@@ -31,8 +31,11 @@ libs/
|
|
|
31
31
|
reason, such as size or build time, and name that reason to the user.
|
|
32
32
|
Never put features, queries, shared UI or theme in the app project.
|
|
33
33
|
- Do not add Nx `feature/ui/data-access/util` folders or `type:*` tags.
|
|
34
|
-
- Every lib has `src/index.ts` and explicit `build`
|
|
35
|
-
and `test`
|
|
34
|
+
- Every lib has `src/index.ts` and an explicit `build` target in `project.json`
|
|
35
|
+
(`@nx/angular:ng-packagr-lite`). `lint` and `test` are inferred by the plugins in `nx.json`
|
|
36
|
+
(`@nx/eslint/plugin`, and `@nx/vite/plugin` or `@nx/jest/plugin` for the test runner), so
|
|
37
|
+
do not write `@nx/eslint:lint` or `@nx/jest:jest` executor targets by hand. Nx 24 has no
|
|
38
|
+
such executors. Run `npx nx show project <name>` to see the inferred targets.
|
|
36
39
|
- Alias = path with slashes: `@acme/domain/admin`, `@acme/queries`, `@acme/types`,
|
|
37
40
|
`@acme/uikit`, `@acme/env`. Project name = path with dashes (`domain-admin`).
|
|
38
41
|
Component `prefix` = the repo abbreviation.
|
|
@@ -24,8 +24,8 @@ agent type carries the model and reasoning effort its own definition sets, and a
|
|
|
24
24
|
inherits the parent's model.
|
|
25
25
|
|
|
26
26
|
A subagent starts cold: it loads the full start context (about 50k tokens) before its first step,
|
|
27
|
-
and measured subagents cost as much as the sessions that spawn them.
|
|
28
|
-
calls
|
|
27
|
+
and measured subagents cost as much as the sessions that spawn them. A lookup or job of under about 10 tool
|
|
28
|
+
calls stays in the session. Give each subagent one large slice, not several small ones.
|
|
29
29
|
|
|
30
30
|
A subagent has the same context budget as the session, measured on its own transcript, and the
|
|
31
31
|
context-warning hook tells it to hand back at the critical tier. Size each prompt to fit: one
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agent-sessions
|
|
3
|
-
description: Run agents in tmux sessions of one checkout, message them, wake idle ones, restart a session with a prompt, and claim paths so two agents do not overwrite each other. Read before you edit a shared file while another agent may be active, and when the user says "mailbox", "coordinate with the other agent", "claim a file", "hand off to the other agent", or "restart the session".
|
|
3
|
+
description: Run agents in tmux sessions of one checkout, message them, wake idle ones, restart a session with a prompt (also your own, to clear yourself and resume after a handoff), and claim paths so two agents do not overwrite each other. Read before you edit a shared file while another agent may be active, and when the user says "mailbox", "coordinate with the other agent", "claim a file", "hand off to the other agent", or "restart the session".
|
|
4
4
|
kind: skill
|
|
5
5
|
scope: both
|
|
6
6
|
---
|
|
@@ -18,9 +18,9 @@ the `ethlete-agents` package is a dependency, `npx ethlete-agents sessions <comm
|
|
|
18
18
|
script, so you do not need `<skill-dir>`. The older name `npx ethlete-agents mailbox` works too.
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
node <skill-dir>/agent-sessions.mjs run <name> <command...>
|
|
21
|
+
node <skill-dir>/agent-sessions.mjs run <name> <command...> [--role builder] [--dry-run]
|
|
22
22
|
node <skill-dir>/agent-sessions.mjs up <name>[=<command>]...
|
|
23
|
-
node <skill-dir>/agent-sessions.mjs restart <name> [--after <seconds>] <prompt...>
|
|
23
|
+
node <skill-dir>/agent-sessions.mjs restart <name> [--after <seconds>] [--notify <agent,...>] [--process] [--role builder|none] <prompt...>
|
|
24
24
|
node <skill-dir>/agent-sessions.mjs down [<name>...]
|
|
25
25
|
node <skill-dir>/agent-sessions.mjs shell-install [<agent>...] [--file <path>] [--remove] [--dry-run]
|
|
26
26
|
node <skill-dir>/agent-sessions.mjs shell-init [<agent>...]
|
|
@@ -45,11 +45,13 @@ Nothing runs until you start it, so a person who does not use this does nothing.
|
|
|
45
45
|
|
|
46
46
|
## Sessions
|
|
47
47
|
|
|
48
|
-
`run <name> <command...>` starts the command in the `tmux` session `<name>@<repo>`, or attaches if it runs already. It also starts the dispatcher when none runs. Session names carry the repo name, so `codex` in two repos never collides. If that session has a terminal attached already, `run` starts `<name>-2`, then `<name>-3`, and so on. A detached session is attached again. So a second `claude` terminal gets its own agent.
|
|
48
|
+
`run <name> <command...>` starts the command in the `tmux` session `<name>@<repo>`, or attaches if it runs already. It also starts the dispatcher when none runs. Session names carry the repo name, so `codex` in two repos never collides. If that session has a terminal attached already, `run` starts `<name>-2`, then `<name>-3`, and so on. A detached session is attached again. `--role builder` sets `ETHLETE_AGENT_ROLE=builder` and, for `claude`, appends a system prompt that keeps the chat silent and sends reports to the grill session. `--dry-run` prints the command instead of starting it. So a second `claude` terminal gets its own agent.
|
|
49
49
|
|
|
50
50
|
`up codex=codex gemini=gemini` starts a detached `tmux` session per agent, named like its sender name, and runs the command in it. It also starts a session named `mailbox-dispatch` that runs `dispatch`. A running session is left alone. Attach to a session with `tmux attach -t <name>`. Detach with `Ctrl-b`, then `d`. The session and its agent keep running. Use `down` to stop the dispatcher and the named sessions.
|
|
51
51
|
|
|
52
|
-
`restart <name> <prompt...>` types the clear command (`/clear`, or `/new` for a `codex` name) and then the prompt into the session `<name>@<repo>`, no sooner than `--after` seconds (default 5). It waits until that session ended its turn and its prompt holds no typed text, and gives up after 30 minutes. After the clear, a new turn does not hold the prompt back: a message can wake the fresh session, and Claude Code queues input typed during a turn. Only typed text in its prompt holds the prompt back. After 60 seconds of typed text, it saves the prompt to `.ethlete/restart.prompt.<session>.txt` and logs why in `.ethlete/restart.log`. The command returns at once, so end your turn after it. An agent uses it to clear itself and resume from its saved work state. It refuses any session other than the caller's own (from `$TMUX_PANE`), and the error names the caller's own sender name. Background commands of the old session still report into the new one.
|
|
52
|
+
`restart <name> <prompt...>` types the clear command (`/clear`, or `/new` for a `codex` name) and then the prompt into the session `<name>@<repo>`, no sooner than `--after` seconds (default 5). It waits until that session ended its turn and its prompt holds no typed text, and gives up after 30 minutes. After the clear, a new turn does not hold the prompt back: a message can wake the fresh session, and Claude Code queues input typed during a turn. Only typed text in its prompt holds the prompt back. The prompt is the text between the two rules of the input box, so the background-agent panel below the footer does not count as typed text. When the focus sits in that panel, it presses Up until the prompt has the focus and clears the line with C-u before it types. After 2 minutes of typed text, and when it gives up, it sends one mailbox line to `<name>` and to each `--notify` agent. After 60 seconds of typed text, it saves the prompt to `.ethlete/restart.prompt.<session>.txt` and logs why in `.ethlete/restart.log`. The command returns at once, so end your turn after it. An agent uses it to clear itself and resume from its saved work state. It refuses any session other than the caller's own (from `$TMUX_PANE`) and the paired session (the builder session, started with `run --role builder`, and any session that is not one), and the error names the caller's own sender name. A restart of the paired session is logged to `restart.log` as `peer restart: <caller> restarted <target>`. Background commands of the old session still report into the new one. A pane counts as busy only when "esc to interrupt" or "Waiting for" shows in the spinner line right above the input box or in the footer below it (a turn that waits on background agents is busy). A chat line that starts with `●` or `⎿` never counts, so an agent that ends its turn with its own "Waiting for ..." text is idle. If the permission system denies your own `restart` (the command never runs), send one mailbox line to the grill/docs session, for example `send <you> --to <grill> 'restart denied, please /clear my pane and type: <prompt>'`. That session drives your pane: it types into it only when the pane is idle by the rule above.
|
|
53
|
+
|
|
54
|
+
`restart --process` ends the agent process instead of clearing it: it types `/exit` (`/quit` for a `codex` name) and starts the agent command again in the same tmux pane, through a login shell, with the prompt as its first message. A `/clear` keeps the old process, so it never picks up a change to an installed plugin, a settings file, an MCP server or `PATH`; after such a change use `--process`. The agent command is the one `run` started, without its prompt, and the pane's start command for an older session. It starts with `command`, so the shell function of the same name does not start a new session. If the exit opens the background work dialog, the restart selects "exit and stop tasks" and writes the stopped jobs to `restart.log` and to a mailbox notice. The new process keeps the role: the session's own role, else `builder` from a prompt that resumes a `builder-*` handoff, else the one `--role` names. A builder process gets `ETHLETE_AGENT_ROLE=builder` and, for `claude`, the builder system prompt once. A session with no role next to another agent session in the repo, none of them a builder, is refused unless the prompt resumes a `builder-*` or `doc-agent-*` handoff or `--role` is passed (`--role none` restarts it without a role).
|
|
53
55
|
|
|
54
56
|
`shell-install` writes one shell function per agent into `~/.zshrc` or `~/.bashrc`, between two marker lines. A second run replaces the block, and `--remove` deletes it. Use `--file` for another startup file and `--dry-run` to preview. After a new terminal opens, typing `claude` or `codex` in a repo with this skill runs the agent through `run`. Elsewhere the function starts the plain command. Run `shell-install` only when the user asks: it changes a file the user owns. `shell-init` prints the same functions without writing a file.
|
|
55
57
|
|
|
@@ -78,4 +80,6 @@ If the prompt of the agent holds typed text, the dispatcher waits until that tex
|
|
|
78
80
|
4. After each larger step, run `inbox` again. Most agents cannot wake on new output, so you must pull messages yourself.
|
|
79
81
|
5. When you finish, run `release`. A claim also expires after 4 hours, or after `--ttl` hours.
|
|
80
82
|
|
|
83
|
+
In this repo commit with `yarn commit -m "..." -- <paths>`: it adds untracked paths and waits for `.git/index.lock`. Specs, lint and typecheck run in CI, not in the hook.
|
|
84
|
+
|
|
81
85
|
A claim is advisory, not a lock. `--force` claims over another agent: use it only after the holder agreed. Silence is not approval. Acknowledge each handoff in words. No agent starts automatically. The user starts each one.
|