gemba-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gemba/__init__.py +1 -0
- gemba/assets/harness/guide.md +88 -0
- gemba/assets/init/gemba.yaml +12 -0
- gemba/assets/skills/gemba-archive/SKILL.md +78 -0
- gemba/assets/skills/gemba-config/SKILL.md +176 -0
- gemba/assets/skills/gemba-create/SKILL.md +99 -0
- gemba/assets/skills/gemba-harness/SKILL.md +194 -0
- gemba/assets/skills/gemba-init/SKILL.md +81 -0
- gemba/assets/skills/gemba-list/SKILL.md +137 -0
- gemba/assets/skills/gemba-mark/SKILL.md +88 -0
- gemba/assets/skills/gemba-record/SKILL.md +89 -0
- gemba/assets/skills/gemba-report/SKILL.md +149 -0
- gemba/assets/skills/gemba-set/SKILL.md +104 -0
- gemba/assets/skills/gemba-worktree/SKILL.md +137 -0
- gemba/cli.py +35 -0
- gemba/commands/__init__.py +1 -0
- gemba/commands/archive.py +62 -0
- gemba/commands/config.py +117 -0
- gemba/commands/create.py +104 -0
- gemba/commands/harness.py +534 -0
- gemba/commands/init.py +112 -0
- gemba/commands/list.py +51 -0
- gemba/commands/mark.py +91 -0
- gemba/commands/record.py +113 -0
- gemba/commands/report.py +80 -0
- gemba/commands/selection.py +170 -0
- gemba/commands/set.py +81 -0
- gemba/commands/skills.py +102 -0
- gemba/commands/worktree.py +292 -0
- gemba/config.py +371 -0
- gemba/harness.py +401 -0
- gemba/metadata.py +442 -0
- gemba/repo.py +76 -0
- gemba/report.py +153 -0
- gemba/skills.py +199 -0
- gemba/tasks.py +621 -0
- gemba/values.py +16 -0
- gemba/worktree.py +191 -0
- gemba_cli-0.1.0.dist-info/METADATA +321 -0
- gemba_cli-0.1.0.dist-info/RECORD +43 -0
- gemba_cli-0.1.0.dist-info/WHEEL +4 -0
- gemba_cli-0.1.0.dist-info/entry_points.txt +2 -0
- gemba_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
gemba/worktree.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""Worktree rules: the branch of a run, the repositories folder, the state of a worktree and its problems."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path, PurePosixPath
|
|
6
|
+
from typing import Literal
|
|
7
|
+
|
|
8
|
+
# The folder of a harness that holds its worktrees, excluded from the harness repository.
|
|
9
|
+
WORKTREES_DIR = "worktrees"
|
|
10
|
+
# The first segment of every branch of a run when the gemba config declares no branch_prefix.
|
|
11
|
+
DEFAULT_BRANCH_PREFIX = "gemba"
|
|
12
|
+
# The remote of every operation.
|
|
13
|
+
REMOTE = "origin"
|
|
14
|
+
HEADS = "refs/heads/"
|
|
15
|
+
|
|
16
|
+
# The last line each verb prints in each of its outcomes.
|
|
17
|
+
CREATED = "created"
|
|
18
|
+
MOVED = "moved"
|
|
19
|
+
ALREADY_ATTACHED = "already attached, worktree unchanged"
|
|
20
|
+
NOT_REMOVED = "not removed: pass --yes to delete the worktree"
|
|
21
|
+
REMOVED = "removed"
|
|
22
|
+
NO_WORKTREES = "no worktrees"
|
|
23
|
+
NOT_A_WORKTREE = "not a worktree"
|
|
24
|
+
UNREADABLE = "state not readable with git"
|
|
25
|
+
NOT_ADDED_REFUSAL = "worktree not added:"
|
|
26
|
+
NOT_MOVED_REFUSAL = "worktree not moved:"
|
|
27
|
+
NOT_REMOVED_REFUSAL = "worktree not removed:"
|
|
28
|
+
|
|
29
|
+
Kind = Literal["worktree", "not a worktree", "unreadable"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class Record:
|
|
34
|
+
"""One worktree of a repository, as `git worktree list --porcelain` reports it."""
|
|
35
|
+
|
|
36
|
+
path: str
|
|
37
|
+
head: str | None
|
|
38
|
+
branch: str | None # short name; None when detached or bare
|
|
39
|
+
prunable: bool
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class State:
|
|
44
|
+
"""What a folder under `worktrees/` holds, read from git when a verb runs and never stored."""
|
|
45
|
+
|
|
46
|
+
path: str # relative to the harness root, POSIX separators
|
|
47
|
+
kind: Kind = "worktree"
|
|
48
|
+
branch: str | None = None # None when detached
|
|
49
|
+
upstream: str | None = None
|
|
50
|
+
uncommitted: tuple[str, ...] = () # each path as `git status --porcelain` names it
|
|
51
|
+
unpushed: int = 0 # commits of HEAD that are on no remote branch
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def branch_name(prefix: str, organization: str, ref: str) -> str:
|
|
55
|
+
"""The local branch of every worktree of the run of `ref`."""
|
|
56
|
+
return f"{prefix}/{organization}/{ref}"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def relative_path(repo: str) -> str:
|
|
60
|
+
"""Where the worktree of `repo` lives, relative to the harness root."""
|
|
61
|
+
return PurePosixPath(WORKTREES_DIR, repo).as_posix()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def worktree_path(harness_root: Path, repo: str) -> Path:
|
|
65
|
+
"""Where the worktree of `repo` lives in the harness at `harness_root`."""
|
|
66
|
+
return harness_root / WORKTREES_DIR / repo
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def repos_dir(config_root: Path, declared: str) -> Path:
|
|
70
|
+
"""The repositories folder: `declared` with `~` expanded, relative to the tasks repo root unless absolute."""
|
|
71
|
+
path = Path(declared).expanduser()
|
|
72
|
+
return path if path.is_absolute() else config_root / path
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def repo_problem(folder: Path, repo: str, repositories: Sequence[str] | None) -> str | None:
|
|
76
|
+
"""Why `repo` is not a repository at the root of `folder`, or None when it is one.
|
|
77
|
+
|
|
78
|
+
`repositories` are the names of the folders directly under `folder` that hold a git
|
|
79
|
+
repository, or None when `folder` is not a folder.
|
|
80
|
+
"""
|
|
81
|
+
if repositories is None:
|
|
82
|
+
return f"repos_dir {folder} is not a folder"
|
|
83
|
+
if repo in repositories:
|
|
84
|
+
return None
|
|
85
|
+
found = f"repositories there: {', '.join(sorted(repositories))}" if repositories else "no repository is there"
|
|
86
|
+
return f"no repository named '{repo}' is at the root of {folder} ({found})"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def parse_worktrees(lines: Sequence[str]) -> list[Record]:
|
|
90
|
+
"""The records of `git worktree list --porcelain`, the repository's own working tree first."""
|
|
91
|
+
records = []
|
|
92
|
+
block: list[str] = []
|
|
93
|
+
for line in [*lines, ""]:
|
|
94
|
+
if line:
|
|
95
|
+
block.append(line)
|
|
96
|
+
elif block:
|
|
97
|
+
records.append(_record(block))
|
|
98
|
+
block = []
|
|
99
|
+
return records
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _record(block: Sequence[str]) -> Record:
|
|
103
|
+
path = head = branch = None
|
|
104
|
+
prunable = False
|
|
105
|
+
for line in block:
|
|
106
|
+
key, _separator, value = line.partition(" ")
|
|
107
|
+
if key == "worktree":
|
|
108
|
+
path = value
|
|
109
|
+
elif key == "HEAD":
|
|
110
|
+
head = value
|
|
111
|
+
elif key == "branch":
|
|
112
|
+
branch = value.removeprefix(HEADS)
|
|
113
|
+
elif key == "prunable":
|
|
114
|
+
prunable = True
|
|
115
|
+
return Record(path=path or "", head=head, branch=branch, prunable=prunable)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def render_state(state: State) -> str:
|
|
119
|
+
"""The block `list`, `remove` and the refusals of `add` print for one folder under `worktrees/`."""
|
|
120
|
+
if state.kind == "not a worktree":
|
|
121
|
+
return f"{state.path}\n{NOT_A_WORKTREE}"
|
|
122
|
+
if state.kind == "unreadable":
|
|
123
|
+
return f"{state.path}\n{UNREADABLE}"
|
|
124
|
+
return "\n".join(
|
|
125
|
+
[
|
|
126
|
+
state.path,
|
|
127
|
+
f"branch: {state.branch or 'detached'}",
|
|
128
|
+
f"upstream: {state.upstream or 'none'}",
|
|
129
|
+
f"uncommitted: {len(state.uncommitted)}",
|
|
130
|
+
f"unpushed: {state.unpushed}",
|
|
131
|
+
]
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def pending_problems(state: State) -> list[str]:
|
|
136
|
+
"""One line for the uncommitted paths and one for the unpushed commits of a worktree, when there are any."""
|
|
137
|
+
if state.kind == "not a worktree":
|
|
138
|
+
return [f"{state.path} is not a worktree"]
|
|
139
|
+
if state.kind == "unreadable":
|
|
140
|
+
return [f"the state of {state.path} could not be read with git"]
|
|
141
|
+
problems = []
|
|
142
|
+
if state.uncommitted:
|
|
143
|
+
count = _count(len(state.uncommitted), "uncommitted path")
|
|
144
|
+
problems.append(f"{state.path} has {count}: {', '.join(state.uncommitted)}")
|
|
145
|
+
if state.unpushed:
|
|
146
|
+
problems.append(f"{state.path} has {_count(state.unpushed, 'commit')} not on any remote branch")
|
|
147
|
+
return problems
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def orphan_problem(repo: str, branch: str, unpushed: int) -> str | None:
|
|
151
|
+
"""Why the branch of a run left in `repo` without a worktree may not be deleted, or None when it may."""
|
|
152
|
+
if not unpushed:
|
|
153
|
+
return None
|
|
154
|
+
return f"branch {branch} of {repo} has {_count(unpushed, 'commit')} not on any remote branch and no worktree"
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def existing_branch_problem(repo: str, branch: str) -> str:
|
|
158
|
+
"""The refusal of `add` when the branch of the run is already in `repo` without a worktree."""
|
|
159
|
+
return (
|
|
160
|
+
f"branch {branch} already exists in {repo} without a worktree, left by a harness deleted by hand; "
|
|
161
|
+
"--force deletes and recreates it, discarding its commits"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def folder_problem(path: str, repo: str) -> str:
|
|
166
|
+
"""The refusal of `add` when the folder of the worktree holds something else."""
|
|
167
|
+
return f"{path} exists and is not a worktree of {repo}; gemba did not put it there"
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def base_problem(from_branch: str | None, remote_branches: Sequence[str], default_branch: str | None) -> str | None:
|
|
171
|
+
"""Why the base of a worktree cannot be resolved after the fetch, or None when it can."""
|
|
172
|
+
if from_branch is None and default_branch is None:
|
|
173
|
+
return f"the repository records no default branch for {REMOTE}: --from names the base branch"
|
|
174
|
+
base = from_branch or default_branch
|
|
175
|
+
if base not in remote_branches:
|
|
176
|
+
return f"{REMOTE} has no branch {base}"
|
|
177
|
+
return None
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def remote_base(branch: str) -> str:
|
|
181
|
+
"""The remote-tracking branch a worktree starts from."""
|
|
182
|
+
return f"{REMOTE}/{branch}"
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def render_outcome(path: str, branch: str, base: str, verdict: str) -> str:
|
|
186
|
+
"""What `add` prints when it creates or moves a worktree."""
|
|
187
|
+
return "\n".join([path, f"branch: {branch}", f"from: {remote_base(base)}", verdict])
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _count(number: int, noun: str) -> str:
|
|
191
|
+
return f"{number} {noun}" if number == 1 else f"{number} {noun}s"
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: gemba-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Gemba: a task-based system for working with AI agents
|
|
5
|
+
Project-URL: Repository, https://github.com/web-crafts-org/gemba
|
|
6
|
+
Project-URL: Changelog, https://github.com/web-crafts-org/gemba/blob/main/CHANGELOG.md
|
|
7
|
+
Project-URL: Issues, https://github.com/web-crafts-org/gemba/issues
|
|
8
|
+
Author: Manuel Robles
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agent-harness,agent-skills,agentic-development,ai-agents,claude-code,cli,codex,context-engineering,harness,task-management
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Operating System :: POSIX
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Office/Business
|
|
22
|
+
Classifier: Topic :: Software Development
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Requires-Dist: pyyaml>=6
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# Gemba
|
|
28
|
+
|
|
29
|
+
Gemba is a task-based system for working with AI agents. Every unit of work is one
|
|
30
|
+
Markdown file in a repository of your own, your agent registers, launches, updates and
|
|
31
|
+
closes those tasks through fixed processes, and each task is worked on inside a
|
|
32
|
+
harness: a folder of its own, with its notes, its evidence and the code it touches,
|
|
33
|
+
where nothing leaves without your written approval. Everything that is specific to one
|
|
34
|
+
organization, its kinds of work, its fields, its rules, lives in one config file, so
|
|
35
|
+
you keep one tasks repo per organization and the agent behaves the same in all of them.
|
|
36
|
+
|
|
37
|
+
The name is 現場, *gemba*: "the actual place", where the work happens. In Gemba that
|
|
38
|
+
place is the harness, a folder made for one task, where the agent works on it and on
|
|
39
|
+
nothing else, with everything the task needs inside and none of the noise of every
|
|
40
|
+
other pending thing. The task file is the logbook of that work: what was done, when,
|
|
41
|
+
and what it cost, recorded day by day. The harness is where the work gets done; the
|
|
42
|
+
task file is where you learn from it, the record that over time lets you see how you
|
|
43
|
+
work, measure it and improve it.
|
|
44
|
+
|
|
45
|
+
## The idea
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
your tasks repo the harness of one run
|
|
49
|
+
+-------------------------------------+ +--------------------------------+
|
|
50
|
+
| gemba.yaml your organization: | launch | task.md the task, read-only|
|
|
51
|
+
| workstreams, fields, | -------> | notes/ what the agent |
|
|
52
|
+
| rules per process | | found and decided |
|
|
53
|
+
| tasks/ one file per task, | | assets/ the raw evidence |
|
|
54
|
+
| a ref like SUP-12 | record | worktrees/ the repositories, |
|
|
55
|
+
| .claude/ the skills: the | <------- | one branch per run |
|
|
56
|
+
| skills/ processes the agent | | CLAUDE.md the guide of the |
|
|
57
|
+
| follows | | agent |
|
|
58
|
+
+-------------------------------------+ +--------------------------------+
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The cycle of a task:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
create --> launch --> work in the harness --> record the day --> mark done
|
|
65
|
+
notes, worktrees, | |
|
|
66
|
+
approved actions v v
|
|
67
|
+
report archive,
|
|
68
|
+
of the period remove the harness
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Three things keep it honest:
|
|
72
|
+
|
|
73
|
+
- **Commands own the frontmatter, agents own the body.** The status, the dates and the
|
|
74
|
+
metadata of a task are written only by `gemba` commands; the agent writes prose.
|
|
75
|
+
- **Nothing leaves the harness without an approved note.** A push, a message, a
|
|
76
|
+
deployment: the agent writes what it is about to do, and you approve it by editing
|
|
77
|
+
the note. A yes in the chat does not count.
|
|
78
|
+
- **Every number comes from a command.** Counts and totals in a report are computed
|
|
79
|
+
by `gemba report`, never by the agent.
|
|
80
|
+
|
|
81
|
+
Gemba works with any agent that reads skills from a folder: Claude Code
|
|
82
|
+
(`.claude/skills/`) and the agents that read `.agents/skills/` (Codex, Cursor,
|
|
83
|
+
Gemini CLI and others) are built in.
|
|
84
|
+
|
|
85
|
+
## Install and start
|
|
86
|
+
|
|
87
|
+
The only prerequisite is [uv](https://docs.astral.sh/uv/).
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
uv tool install gemba-cli
|
|
91
|
+
gemba --version
|
|
92
|
+
gemba init ~/nakatomi-tasks # a tasks repo for the organization you work with
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`gemba init` writes a minimal `gemba.yaml`, the `tasks/` folder, a `.gitignore` and
|
|
96
|
+
the skills for every agent it knows (`--providers claude` keeps only one). It runs no
|
|
97
|
+
git: make the folder a repository and commit it when you like.
|
|
98
|
+
|
|
99
|
+
Then open the folder with your agent and ask it to configure Gemba. The `gemba-init`
|
|
100
|
+
skill asks, one question at a time, for your organization, the kinds of work you do,
|
|
101
|
+
which one takes a task that fits no other, what to record per task, your agents, your
|
|
102
|
+
editor and the folder of your repositories, and writes the answers into `gemba.yaml`.
|
|
103
|
+
|
|
104
|
+
From there, talk to the agent:
|
|
105
|
+
|
|
106
|
+
> Register a task: login fails on Safari for Acme's checkout app.
|
|
107
|
+
|
|
108
|
+
> Launch SUP-1.
|
|
109
|
+
|
|
110
|
+
> Record the day on this task.
|
|
111
|
+
|
|
112
|
+
> What did I do this week?
|
|
113
|
+
|
|
114
|
+
## The cycle of a task
|
|
115
|
+
|
|
116
|
+
Each step is a skill the agent follows and a command the skill runs. You can run the
|
|
117
|
+
commands yourself too; every one of them prints what it did or every problem it found,
|
|
118
|
+
at once, and exits 1 without writing anything when it refuses.
|
|
119
|
+
|
|
120
|
+
### Register
|
|
121
|
+
|
|
122
|
+
The `gemba-create` skill picks the workstream from the descriptions in `gemba.yaml`,
|
|
123
|
+
asks for any required field you did not give, and runs:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
gemba create --workstream support --slug login-fails-on-safari \
|
|
127
|
+
--description "Login fails on Safari for the checkout app" --set client=acme
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The task file is `tasks/support/2026-09-30-SUP-1-login-fails-on-safari.md`:
|
|
131
|
+
|
|
132
|
+
```markdown
|
|
133
|
+
---
|
|
134
|
+
status: open
|
|
135
|
+
ref: SUP-1
|
|
136
|
+
updated_at: 2026-09-30
|
|
137
|
+
metadata:
|
|
138
|
+
client: acme
|
|
139
|
+
hours: null
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
Login fails on Safari for the checkout app
|
|
143
|
+
|
|
144
|
+
## Updates
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The ref is allocated from the highest number already used, archives included, so it
|
|
148
|
+
is never reused. The metadata keys are exactly the ones declared for that workstream,
|
|
149
|
+
present even when unset, each read, compared and added by the type its field declares.
|
|
150
|
+
|
|
151
|
+
### Launch
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
gemba harness launch SUP-1
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
creates `harnesses/support/2026-09-30-SUP-1-login-fails-on-safari/` as a git
|
|
158
|
+
repository of its own, with `harness.yaml`, the run's identity; `task.md`, a link to
|
|
159
|
+
the task; the guide of the agent (`CLAUDE.md`, `AGENTS.md`); the harness skills;
|
|
160
|
+
`notes/`, `assets/` and `worktrees/`. It moves an `open` task to `in-progress` and
|
|
161
|
+
opens the folder with the `ide` command of `gemba.yaml` (`--no-open` skips it).
|
|
162
|
+
Launching again only reopens it. `harnesses/` is ignored by the tasks repo: a run is
|
|
163
|
+
not a record, the task is.
|
|
164
|
+
|
|
165
|
+
### Work in the harness
|
|
166
|
+
|
|
167
|
+
Start the agent inside the harness. The guide tells it to read the task and the notes
|
|
168
|
+
first, to write before it acts, and to keep every finding, blocker, decision or
|
|
169
|
+
summary as a small note, drafted as `notes/<slug>.md` and filed with the
|
|
170
|
+
`gemba-harness` skill, which names it after the clock, writes its frontmatter and
|
|
171
|
+
commits:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
gemba harness note notes/login-trace.md --type finding --asset assets/1759350491-console.log
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
An `external-action` note describes, word for word, something to be done outside the
|
|
178
|
+
harness. Nothing happens until you set `approved: true` in its frontmatter from your
|
|
179
|
+
editor. After acting, the agent writes the outcome into the note and runs
|
|
180
|
+
`gemba harness resolve notes/<file>.md`, which records when.
|
|
181
|
+
|
|
182
|
+
When the task touches code, the agent checks out the repositories it needs as git
|
|
183
|
+
worktrees, each on a local branch named after the run:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
gemba worktree add nakatomi-web # from the default branch of origin
|
|
187
|
+
gemba worktree add nakatomi-web --from release/2.4 # from another branch, or move to it
|
|
188
|
+
gemba worktree list # branch, upstream, uncommitted, unpushed
|
|
189
|
+
gemba worktree remove nakatomi-web --yes # the worktree and its branch
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
A repository is a folder under the `repos_dir` of `gemba.yaml`; Gemba never clones.
|
|
193
|
+
The branch, `gemba/nakatomi/SUP-1`, exists only on your machine and is never pushed
|
|
194
|
+
under its own name: a push needs an approved note naming the branch of the remote, and
|
|
195
|
+
always names its target, `git push -u origin HEAD:<that branch>`. `add --from` and
|
|
196
|
+
`remove` refuse while anything is uncommitted or on no remote branch; `--force`
|
|
197
|
+
discards it, and the agent passes it only after you confirm what the command listed.
|
|
198
|
+
|
|
199
|
+
### Record the day
|
|
200
|
+
|
|
201
|
+
At the end of the day, ask the agent to record the day on the task. It distills the
|
|
202
|
+
notes of that date into a few bullets in `update.md` at the harness root and runs:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
gemba record update.md # today
|
|
206
|
+
gemba record update.md --date 2026-09-30 # a past day
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
The bullets land under `### 2026-09-30` in the task's `## Updates` section,
|
|
210
|
+
`updated_at` moves forward, and the draft is deleted. Nothing is committed: the tasks
|
|
211
|
+
repo is yours to review and commit.
|
|
212
|
+
|
|
213
|
+
### Report
|
|
214
|
+
|
|
215
|
+
Ask the agent in the tasks repo for yesterday, for a day, a week or a month. The
|
|
216
|
+
`gemba-report` skill finds the tasks alive in the period with `gemba list`, reads the
|
|
217
|
+
subsections dated in it, groups what it finds by workstream, and lists the tasks
|
|
218
|
+
updated recently with nothing recorded. Every count and total comes from:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
gemba report --done-from 2026-09-01 --done-to 2026-09-30 --by client --sum hours
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
which takes the filters of `gemba list`, groups with `--by` (`workstream`, `status`,
|
|
225
|
+
or a text or boolean field) and adds number fields with `--sum`, exactly.
|
|
226
|
+
|
|
227
|
+
### Close out
|
|
228
|
+
|
|
229
|
+
Three steps, each on its own, each asking before it deletes anything:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
gemba mark SUP-1 done # done today: done_at and updated_at move
|
|
233
|
+
gemba mark SUP-1 open # reopened: done_at is removed
|
|
234
|
+
gemba harness remove SUP-1 # show what the run holds, delete nothing
|
|
235
|
+
gemba harness remove SUP-1 --yes # delete the harness, its worktrees and branches
|
|
236
|
+
gemba archive SUP-1 OPS-2 # move each task file into archive/
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
A task done today can be reopened tomorrow in the same harness; a run can be thrown
|
|
240
|
+
away on a task still in progress; a task is archived only once it went cold, and only
|
|
241
|
+
after its harness is gone. Archived tasks leave `gemba list` unless
|
|
242
|
+
`--include-archived` is given.
|
|
243
|
+
|
|
244
|
+
### Keep the metadata
|
|
245
|
+
|
|
246
|
+
The hours grow, the work gets invoiced. Tell the agent, from the tasks repo or inside
|
|
247
|
+
the harness, and the `gemba-set` skill runs:
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
gemba set SUP-1 hours+=2 # added to the number the task holds
|
|
251
|
+
gemba set SUP-1 invoiced=2026-10-03 # a value, written by the type of its field
|
|
252
|
+
gemba set SUP-1 merge_requests=null # cleared
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
It rewrites only the metadata, never `updated_at`, and refuses an undeclared key or a
|
|
256
|
+
value of the wrong type.
|
|
257
|
+
|
|
258
|
+
## The config
|
|
259
|
+
|
|
260
|
+
`gemba.yaml` is edited by hand, or by the `gemba-config` skill when you ask the agent
|
|
261
|
+
to add a workstream, a field or a rule; `gemba config check` validates it and
|
|
262
|
+
`gemba config show` prints it resolved, as the skills read it. No command rewrites it.
|
|
263
|
+
|
|
264
|
+
```yaml
|
|
265
|
+
default_workstream: operations
|
|
266
|
+
|
|
267
|
+
agent_providers: [claude, agents]
|
|
268
|
+
|
|
269
|
+
ide: code -n
|
|
270
|
+
organization: nakatomi
|
|
271
|
+
repos_dir: ~/projects
|
|
272
|
+
|
|
273
|
+
metadata:
|
|
274
|
+
- { name: client, type: text, required: true, description: Who the work is for }
|
|
275
|
+
- { name: hours, type: number, description: Total hours spent on the task }
|
|
276
|
+
- { name: invoiced, type: date, description: When the work was invoiced }
|
|
277
|
+
|
|
278
|
+
harness:
|
|
279
|
+
guide: |
|
|
280
|
+
Client data never leaves the harness: strip it from every asset you save.
|
|
281
|
+
|
|
282
|
+
workstreams:
|
|
283
|
+
support:
|
|
284
|
+
prefix: SUP
|
|
285
|
+
description: Investigate a client-reported problem and deliver a report or a fix
|
|
286
|
+
worktree:
|
|
287
|
+
add:
|
|
288
|
+
rules:
|
|
289
|
+
- start from the branch deployed to production; ask when the task does not say it
|
|
290
|
+
operations:
|
|
291
|
+
prefix: OPS
|
|
292
|
+
description: Anything that does not fit another workstream
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
What you can customize, all of it here and never in a skill:
|
|
296
|
+
|
|
297
|
+
- **Workstreams**, each with a prefix, a description the agent uses to choose one, and
|
|
298
|
+
its own fields, and the one that takes a task that fits no other.
|
|
299
|
+
- **Metadata fields**, shared or per workstream, each of one type: `text`, `number`,
|
|
300
|
+
`boolean` or `date`. Gemba reads, filters, groups and adds values by their type and
|
|
301
|
+
never interprets their meaning.
|
|
302
|
+
- **Agent providers**, the IDE command, the organization and the repositories folder.
|
|
303
|
+
- **`context` and `rules` for every process**, at the top level and per workstream:
|
|
304
|
+
what the agent should know and do when it creates, lists, launches, notes, records,
|
|
305
|
+
marks, archives or checks out, with the workstream's rule winning a contradiction.
|
|
306
|
+
- **The guide** of every harness, with a paragraph of your organization and one of
|
|
307
|
+
the workstream.
|
|
308
|
+
|
|
309
|
+
Changing any of it is a config edit: no skill and no code changes.
|
|
310
|
+
|
|
311
|
+
## Status
|
|
312
|
+
|
|
313
|
+
Gemba is young, at version 0.x, and built from daily use: the whole cycle above
|
|
314
|
+
works, and the shape of the commands may still change between minor versions, with
|
|
315
|
+
the changelog saying so. Ideas, questions and reports of what got in your way are the
|
|
316
|
+
most useful contribution right now: see [CONTRIBUTING.md](CONTRIBUTING.md), which also
|
|
317
|
+
describes how the product is developed, by specification, one case per scenario.
|
|
318
|
+
|
|
319
|
+
## License
|
|
320
|
+
|
|
321
|
+
[MIT](LICENSE).
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
gemba/__init__.py,sha256=RbNI2zl3aSluYz39uAM5jHQt8BNYbz9uogMktlS3t3Y,61
|
|
2
|
+
gemba/cli.py,sha256=zbPZTEB6OIgzD_n5bNxE66tHakW32NdzzGAfzgANHn8,1432
|
|
3
|
+
gemba/config.py,sha256=QD_y0Sq2HTp_R9QRfgTJbXsXru3JQaZBHu3JBPJh6Ng,15710
|
|
4
|
+
gemba/harness.py,sha256=_WXUm-PfmRgNw8yKo9X4E1AYZe319R_ddBM-kpWyYTc,16531
|
|
5
|
+
gemba/metadata.py,sha256=MOqeXJNF9ebiE_FaVLc-FJEx4LVMXV5e_yJdTQVo7TE,18639
|
|
6
|
+
gemba/repo.py,sha256=UZwiPtQ93IFyb40HsG79ztUpdKekQIH6_iIq1vFq_CA,3246
|
|
7
|
+
gemba/report.py,sha256=VVkmiNs3iXValnIJ_00euTTwaxalLEUCnnEFvc7kkno,5383
|
|
8
|
+
gemba/skills.py,sha256=DYLe70HW8Y30A9KGTOI2rKw95uFTh40P_YlUTpB3aGg,7018
|
|
9
|
+
gemba/tasks.py,sha256=X2MnLIfv6Z_xHFVPaAulx1VtNY62u_J1UzjQqhVTUA4,25647
|
|
10
|
+
gemba/values.py,sha256=fY80Pif69R81B0wGA5XSNEHgCdlYob_NrDJ1qBepXQk,438
|
|
11
|
+
gemba/worktree.py,sha256=IQJhEaH1OMOcKxbxoXBN1lEUaxgiwCqqVqX_bLEym6I,7379
|
|
12
|
+
gemba/assets/harness/guide.md,sha256=-Ze-Vsd3Ldwd2xfCRj30WCBgQaRv0jwPWVR24BS7x-c,4287
|
|
13
|
+
gemba/assets/init/gemba.yaml,sha256=Oq6ZqClnBRx04hk2dBUcquO7DnHJfJnbdGifDHnlmaY,409
|
|
14
|
+
gemba/assets/skills/gemba-archive/SKILL.md,sha256=MaGiAvcaJD19ISyN4oxkAEBWe9TqGn4X0MwmglHiNPo,4111
|
|
15
|
+
gemba/assets/skills/gemba-config/SKILL.md,sha256=UwY70PFahtBwGVN6JIly5qmEfJ8rGAPy-x2caTqGDRM,9394
|
|
16
|
+
gemba/assets/skills/gemba-create/SKILL.md,sha256=1qiHjBPrX9l7bifBdd3cTLXXMcmxn4En4XtZB2IjsiY,5628
|
|
17
|
+
gemba/assets/skills/gemba-harness/SKILL.md,sha256=e86Fb57z2HUSrFxkQipog01ti1zKrINPG_xUeGbbPKk,10817
|
|
18
|
+
gemba/assets/skills/gemba-init/SKILL.md,sha256=mAte9xxflqBY7DAJTcaBjgk3YHTIjU4WIEPrXeg6_QQ,4718
|
|
19
|
+
gemba/assets/skills/gemba-list/SKILL.md,sha256=EhSV5RhRj-JMEF5n9gqjlLd9ptf8yBJ3F4icp_yK3ck,8320
|
|
20
|
+
gemba/assets/skills/gemba-mark/SKILL.md,sha256=VYHG38I7KIGG6p0eSF8sFBoIFK70vqxyhAy68jlreRQ,4894
|
|
21
|
+
gemba/assets/skills/gemba-record/SKILL.md,sha256=93xEfUJGQpARRxeu9QwtAO-ezUPS__-enfvYMfrvRzM,5387
|
|
22
|
+
gemba/assets/skills/gemba-report/SKILL.md,sha256=Lt79TdgSecgLhDpVJiu5ISz3ilz3R1nrGGbomX8_QNQ,8891
|
|
23
|
+
gemba/assets/skills/gemba-set/SKILL.md,sha256=3nLnPvLazMRcGDBNtnaRzx9fl16NsqYFznKdz4JwvYc,6114
|
|
24
|
+
gemba/assets/skills/gemba-worktree/SKILL.md,sha256=8DAiM-9ozaXfwilUJQkt8j6Ov-V3FReSUQAnmpM3nNY,7395
|
|
25
|
+
gemba/commands/__init__.py,sha256=wr8mNC2UTAyTRKdNZqm7Dd6YEPHzvHuCJa-PmpZW_yc,77
|
|
26
|
+
gemba/commands/archive.py,sha256=2lbUvSCYquyzU3jYmR_mgJQgYzASJR7wPsG9fTlCIiQ,2538
|
|
27
|
+
gemba/commands/config.py,sha256=e6m8rTAJFROYnb5jClI94koRasXGe9qzz6dqRQvzeYU,4829
|
|
28
|
+
gemba/commands/create.py,sha256=eVMCqUS1YX6QOTf4-orOhx0EkeQgD7Y02CPIDAmnbhM,4255
|
|
29
|
+
gemba/commands/harness.py,sha256=Wnzhz7iAUUvWe8EKM86N7sd7qwTlpAWL38T6CQWhymg,23877
|
|
30
|
+
gemba/commands/init.py,sha256=3tNIKGlZEHGY6vdukG5zwGDdUsFmNnWf82upni8zL8s,4227
|
|
31
|
+
gemba/commands/list.py,sha256=oiIMKHh34Br3YIgT3lNheB1pSINB1XLvs0gvyrJVyRo,1825
|
|
32
|
+
gemba/commands/mark.py,sha256=t7sLg9Ydiuz1__JffuMObwuEAJYt_10nBOoChkkWmsw,3659
|
|
33
|
+
gemba/commands/record.py,sha256=XHXxa8nBBREmrDcqCRBKMNOX7yepxWlNXHTxcjqvSng,4600
|
|
34
|
+
gemba/commands/report.py,sha256=Ac_qCwJ_KYTBVBsgCRccI9pxJxLYclhYFsVqQzmtD-0,2959
|
|
35
|
+
gemba/commands/selection.py,sha256=wJdwZ95RojvNm7Q5DzPsdpyOZV9a5VrA3qWGUV6wGUA,6624
|
|
36
|
+
gemba/commands/set.py,sha256=Y4Jru2yllSgAVG83P59Roe766ippYbuUIjOUGLvvj3k,3218
|
|
37
|
+
gemba/commands/skills.py,sha256=P8xaY8G9kG1PUsPTvY0uCXlJO6DHYuS9V-caeU_VIVc,4150
|
|
38
|
+
gemba/commands/worktree.py,sha256=aWyfQBy54rNJAlFxRQ78b4Ox2xhwygW9FNvFbC4_v38,14051
|
|
39
|
+
gemba_cli-0.1.0.dist-info/METADATA,sha256=xCxcHZN7QXwpfcsZcYIiMGeKQDwvoRYRGwQgUrOg5G0,13597
|
|
40
|
+
gemba_cli-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
41
|
+
gemba_cli-0.1.0.dist-info/entry_points.txt,sha256=aqxtvtHmslMud8S7z-VhfmV08CGQwUUV-DxJ6ndN46s,41
|
|
42
|
+
gemba_cli-0.1.0.dist-info/licenses/LICENSE,sha256=8cwPq1tG0xQjpUO9ENcmVO0l-i8rUAqryZu1EPK3Oo0,1070
|
|
43
|
+
gemba_cli-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Manuel Robles
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|