charter-cp 0.1.0__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 (113) hide show
  1. charter_cp-0.1.0/.claude-plugin/marketplace.json +17 -0
  2. charter_cp-0.1.0/.claude-plugin/plugin.json +12 -0
  3. charter_cp-0.1.0/.github/workflows/release.yml +85 -0
  4. charter_cp-0.1.0/.github/workflows/test.yml +18 -0
  5. charter_cp-0.1.0/.gitignore +17 -0
  6. charter_cp-0.1.0/LICENSE +21 -0
  7. charter_cp-0.1.0/PKG-INFO +216 -0
  8. charter_cp-0.1.0/README.md +189 -0
  9. charter_cp-0.1.0/charter/__init__.py +10 -0
  10. charter_cp-0.1.0/charter/__main__.py +7 -0
  11. charter_cp-0.1.0/charter/cli.py +603 -0
  12. charter_cp-0.1.0/charter/commands.py +1012 -0
  13. charter_cp-0.1.0/charter/commands_persona.py +984 -0
  14. charter_cp-0.1.0/charter/commands_secrets.py +448 -0
  15. charter_cp-0.1.0/charter/commands_workspace.py +654 -0
  16. charter_cp-0.1.0/charter/commands_worktree.py +191 -0
  17. charter_cp-0.1.0/charter/config.py +103 -0
  18. charter_cp-0.1.0/charter/curate.py +126 -0
  19. charter_cp-0.1.0/charter/dispatch.py +235 -0
  20. charter_cp-0.1.0/charter/doctor.py +334 -0
  21. charter_cp-0.1.0/charter/forge/__init__.py +5 -0
  22. charter_cp-0.1.0/charter/forge/base.py +76 -0
  23. charter_cp-0.1.0/charter/forge/github.py +205 -0
  24. charter_cp-0.1.0/charter/forge/gitlab.py +161 -0
  25. charter_cp-0.1.0/charter/forge/registry.py +217 -0
  26. charter_cp-0.1.0/charter/freshness.py +66 -0
  27. charter_cp-0.1.0/charter/gitpolicy.py +209 -0
  28. charter_cp-0.1.0/charter/glstate.py +209 -0
  29. charter_cp-0.1.0/charter/hooks.py +1165 -0
  30. charter_cp-0.1.0/charter/instance.py +153 -0
  31. charter_cp-0.1.0/charter/inventory.py +178 -0
  32. charter_cp-0.1.0/charter/memstore.py +226 -0
  33. charter_cp-0.1.0/charter/persona.py +651 -0
  34. charter_cp-0.1.0/charter/recall.py +76 -0
  35. charter_cp-0.1.0/charter/render.py +156 -0
  36. charter_cp-0.1.0/charter/root.py +90 -0
  37. charter_cp-0.1.0/charter/secrets/__init__.py +24 -0
  38. charter_cp-0.1.0/charter/secrets/base.py +76 -0
  39. charter_cp-0.1.0/charter/secrets/plain_file.py +144 -0
  40. charter_cp-0.1.0/charter/secrets/registry.py +88 -0
  41. charter_cp-0.1.0/charter/statusline.py +663 -0
  42. charter_cp-0.1.0/charter/toolgate.py +104 -0
  43. charter_cp-0.1.0/charter/trace.py +66 -0
  44. charter_cp-0.1.0/charter/tui.py +296 -0
  45. charter_cp-0.1.0/charter/util.py +66 -0
  46. charter_cp-0.1.0/charter/workspace.py +680 -0
  47. charter_cp-0.1.0/charter/worktree.py +145 -0
  48. charter_cp-0.1.0/docs/control-plane.md +159 -0
  49. charter_cp-0.1.0/docs/forges.md +63 -0
  50. charter_cp-0.1.0/docs/personas.md +151 -0
  51. charter_cp-0.1.0/docs/secrets.md +87 -0
  52. charter_cp-0.1.0/docs/superpowers/plans/2026-08-07-secret-exec-dotenv.md +688 -0
  53. charter_cp-0.1.0/hooks/hooks.json +92 -0
  54. charter_cp-0.1.0/pyproject.toml +41 -0
  55. charter_cp-0.1.0/tests/__init__.py +0 -0
  56. charter_cp-0.1.0/tests/_isolation.py +106 -0
  57. charter_cp-0.1.0/tests/fixtures/dotenv_golden.json +231 -0
  58. charter_cp-0.1.0/tests/fixtures/generate_golden.cjs +62 -0
  59. charter_cp-0.1.0/tests/test_cli_hoist.py +49 -0
  60. charter_cp-0.1.0/tests/test_cli_smoke.py +126 -0
  61. charter_cp-0.1.0/tests/test_commands_discover.py +332 -0
  62. charter_cp-0.1.0/tests/test_config.py +102 -0
  63. charter_cp-0.1.0/tests/test_control_plane_schema.py +172 -0
  64. charter_cp-0.1.0/tests/test_curate.py +124 -0
  65. charter_cp-0.1.0/tests/test_dispatch.py +160 -0
  66. charter_cp-0.1.0/tests/test_docs.py +67 -0
  67. charter_cp-0.1.0/tests/test_doctor_forge_visibility.py +197 -0
  68. charter_cp-0.1.0/tests/test_doctor_git_identity.py +55 -0
  69. charter_cp-0.1.0/tests/test_forge_base.py +26 -0
  70. charter_cp-0.1.0/tests/test_forge_github.py +310 -0
  71. charter_cp-0.1.0/tests/test_forge_gitlab.py +284 -0
  72. charter_cp-0.1.0/tests/test_forge_registry.py +258 -0
  73. charter_cp-0.1.0/tests/test_freshness.py +120 -0
  74. charter_cp-0.1.0/tests/test_git_policy.py +519 -0
  75. charter_cp-0.1.0/tests/test_glstate.py +164 -0
  76. charter_cp-0.1.0/tests/test_hooks.py +338 -0
  77. charter_cp-0.1.0/tests/test_init.py +180 -0
  78. charter_cp-0.1.0/tests/test_instance.py +105 -0
  79. charter_cp-0.1.0/tests/test_isolation_harness.py +90 -0
  80. charter_cp-0.1.0/tests/test_memory_cadence.py +87 -0
  81. charter_cp-0.1.0/tests/test_memory_injection.py +75 -0
  82. charter_cp-0.1.0/tests/test_memory_share.py +65 -0
  83. charter_cp-0.1.0/tests/test_memstore.py +86 -0
  84. charter_cp-0.1.0/tests/test_mixed_inventory.py +108 -0
  85. charter_cp-0.1.0/tests/test_packaging.py +44 -0
  86. charter_cp-0.1.0/tests/test_persona_agent_forge_wording.py +88 -0
  87. charter_cp-0.1.0/tests/test_persona_dir.py +86 -0
  88. charter_cp-0.1.0/tests/test_persona_inheritance.py +82 -0
  89. charter_cp-0.1.0/tests/test_persona_memory.py +66 -0
  90. charter_cp-0.1.0/tests/test_persona_memory_sync.py +69 -0
  91. charter_cp-0.1.0/tests/test_persona_search.py +62 -0
  92. charter_cp-0.1.0/tests/test_persona_skill_lint.py +56 -0
  93. charter_cp-0.1.0/tests/test_persona_stats.py +101 -0
  94. charter_cp-0.1.0/tests/test_plugin.py +193 -0
  95. charter_cp-0.1.0/tests/test_reactive_memory.py +136 -0
  96. charter_cp-0.1.0/tests/test_readme_personas.py +88 -0
  97. charter_cp-0.1.0/tests/test_recall.py +78 -0
  98. charter_cp-0.1.0/tests/test_render_forge_label.py +49 -0
  99. charter_cp-0.1.0/tests/test_root.py +104 -0
  100. charter_cp-0.1.0/tests/test_secret_audit.py +44 -0
  101. charter_cp-0.1.0/tests/test_secret_dotenv.py +532 -0
  102. charter_cp-0.1.0/tests/test_secret_exec.py +104 -0
  103. charter_cp-0.1.0/tests/test_statusline_crash_guard.py +59 -0
  104. charter_cp-0.1.0/tests/test_statusline_gauge.py +238 -0
  105. charter_cp-0.1.0/tests/test_trace.py +61 -0
  106. charter_cp-0.1.0/tests/test_workspace_charter.py +130 -0
  107. charter_cp-0.1.0/tests/test_workspace_enforcement.py +140 -0
  108. charter_cp-0.1.0/tests/test_workspace_lock.py +184 -0
  109. charter_cp-0.1.0/tests/test_workspace_manifest.py +140 -0
  110. charter_cp-0.1.0/tests/test_workspace_memory.py +82 -0
  111. charter_cp-0.1.0/tests/test_workspace_rename.py +96 -0
  112. charter_cp-0.1.0/tests/test_workspace_structure.py +101 -0
  113. charter_cp-0.1.0/tests/test_worktree.py +651 -0
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "charter",
3
+ "owner": {
4
+ "name": "Aaron Yordanyan",
5
+ "url": "https://github.com/diazoxide"
6
+ },
7
+ "description": "charter — a control plane for Claude Code agents working across many repos on GitHub or GitLab, as an installable Claude Code plugin.",
8
+ "plugins": [
9
+ {
10
+ "name": "charter",
11
+ "source": "./",
12
+ "description": "Personas, workspaces and memory for Claude Code agents across many repos",
13
+ "category": "developer-tools",
14
+ "keywords": ["claude-code", "agents", "monorepo", "polyrepo", "developer-tools"]
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "charter",
3
+ "description": "Personas, workspaces and memory for Claude Code agents across many repos",
4
+ "version": "0.1.0",
5
+ "author": {
6
+ "name": "Aaron Yordanyan"
7
+ },
8
+ "homepage": "https://github.com/diazoxide/charter",
9
+ "repository": "https://github.com/diazoxide/charter",
10
+ "license": "MIT",
11
+ "keywords": ["claude-code", "agents", "monorepo", "polyrepo", "developer-tools"]
12
+ }
@@ -0,0 +1,85 @@
1
+ name: release
2
+ # Publishes to PyPI via Trusted Publishing (OIDC) — there is no API token
3
+ # anywhere: not in the repo, not in Actions secrets, not on a laptop. PyPI
4
+ # verifies GitHub's signed identity claim for this exact repo + workflow +
5
+ # environment, so a leaked secret cannot be used to publish charter.
6
+ #
7
+ # Trigger: push a version tag.
8
+ # git tag v0.1.0 && git push origin v0.1.0
9
+ #
10
+ # The tag must match the version in pyproject.toml — the `guard` job below
11
+ # fails the release if they disagree, because PyPI will not let you re-upload
12
+ # a version after a mistake.
13
+ on:
14
+ push:
15
+ tags: ["v*"]
16
+ workflow_dispatch: # lets a failed release be retried without a new tag
17
+
18
+ permissions:
19
+ contents: read
20
+
21
+ jobs:
22
+ guard:
23
+ name: Verify tag matches the packaged version
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ - name: Compare the git tag to pyproject.toml
28
+ if: startsWith(github.ref, 'refs/tags/v')
29
+ run: |
30
+ tag="${GITHUB_REF_NAME#v}"
31
+ pkg=$(python -c 'import tomllib;print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')
32
+ echo "tag=$tag pyproject=$pkg"
33
+ if [ "$tag" != "$pkg" ]; then
34
+ echo "::error::tag v$tag does not match pyproject.toml version $pkg — refusing to publish"
35
+ exit 1
36
+ fi
37
+
38
+ test:
39
+ name: Suite must be green before release
40
+ needs: guard
41
+ runs-on: ubuntu-latest
42
+ strategy:
43
+ matrix:
44
+ python-version: ["3.11", "3.12", "3.13", "3.14"]
45
+ steps:
46
+ - uses: actions/checkout@v4
47
+ - uses: actions/setup-python@v5
48
+ with:
49
+ python-version: ${{ matrix.python-version }}
50
+ - name: Run the suite
51
+ run: python -m unittest discover -s tests -v
52
+
53
+ build:
54
+ name: Build sdist + wheel
55
+ needs: test
56
+ runs-on: ubuntu-latest
57
+ steps:
58
+ - uses: actions/checkout@v4
59
+ - uses: actions/setup-python@v5
60
+ with:
61
+ python-version: "3.12"
62
+ - name: Build
63
+ run: pipx run build
64
+ - name: Validate metadata renders on PyPI
65
+ run: pipx run twine check dist/*
66
+ - uses: actions/upload-artifact@v4
67
+ with:
68
+ name: dist
69
+ path: dist/
70
+
71
+ publish:
72
+ name: Publish to PyPI
73
+ needs: build
74
+ runs-on: ubuntu-latest
75
+ environment:
76
+ name: pypi
77
+ url: https://pypi.org/p/charter-cp
78
+ permissions:
79
+ id-token: write # REQUIRED: mints the OIDC token PyPI verifies
80
+ steps:
81
+ - uses: actions/download-artifact@v4
82
+ with:
83
+ name: dist
84
+ path: dist/
85
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,18 @@
1
+ name: tests
2
+ on:
3
+ push:
4
+ pull_request:
5
+ workflow_dispatch: # lets CI be re-triggered without a noise commit
6
+ jobs:
7
+ test:
8
+ runs-on: ubuntu-latest
9
+ strategy:
10
+ matrix:
11
+ python-version: ["3.11", "3.12", "3.13", "3.14"]
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-python@v5
15
+ with:
16
+ python-version: ${{ matrix.python-version }}
17
+ - name: Run the suite
18
+ run: python -m unittest discover -s tests -v
@@ -0,0 +1,17 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ build/
4
+ dist/
5
+ *.egg-info/
6
+ .venv/
7
+ .pytest_cache/
8
+ .mypy_cache/
9
+ .DS_Store
10
+
11
+ # Per-developer secret vaults + registry (plaintext secrets, tokens, file paths).
12
+ # NEVER commit this — it holds credentials. Also where local session/usage state
13
+ # (e.g. sessions/<id>.usage) lands when the CLI or its tests run against ROOT.
14
+ /.edm/
15
+
16
+ # SDD scratch (review packages, task briefs) — never publish
17
+ .superpowers/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aaron Yordanyan
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.
@@ -0,0 +1,216 @@
1
+ Metadata-Version: 2.4
2
+ Name: charter-cp
3
+ Version: 0.1.0
4
+ Summary: Personas, workspaces and memory for Claude Code agents across many repos
5
+ Project-URL: Homepage, https://github.com/diazoxide/charter
6
+ Project-URL: Repository, https://github.com/diazoxide/charter
7
+ Project-URL: Issues, https://github.com/diazoxide/charter/issues
8
+ Project-URL: Changelog, https://github.com/diazoxide/charter/commits/main
9
+ Author-email: Aaron Yordanyan <aaron.yor@gmail.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: agents,claude-code,developer-tools,monorepo,polyrepo
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Software Development :: Version Control :: Git
24
+ Classifier: Topic :: Utilities
25
+ Requires-Python: >=3.11
26
+ Description-Content-Type: text/markdown
27
+
28
+ # charter
29
+
30
+ **charter** is a control plane for Claude Code agents working across many repos on
31
+ GitHub or GitLab. It gives an agent durable **personas** (specialist role identities,
32
+ each with its own committed memory and a scoped credential vault), isolated per-task
33
+ **workspaces** for cloning and working several repos in parallel without mixing them up,
34
+ and a **vault** that keeps credentials out of the model's context — so an agent (or a
35
+ whole team of them) can move between repos and tasks without losing what it has learned
36
+ or leaking a secret into a transcript.
37
+
38
+ If you've never seen it before, you can go from `uv tool install charter` to a working
39
+ control plane in about a minute — see [60 seconds](#60-seconds-from-nothing-to-a-working-control-plane) below.
40
+
41
+ ## Install
42
+
43
+ charter ships as **two artifacts** — install both. A CLI-only install leaves the plugin's
44
+ hooks inert (no session context injection, no golden-rule guard, no auto-save), since the
45
+ plugin is what actually wires them into Claude Code.
46
+
47
+ ### 1. The CLI
48
+
49
+ ```
50
+ uv tool install charter
51
+ ```
52
+
53
+ Lead with [`uv`](https://docs.astral.sh/uv/) for a concrete reason, not a preference:
54
+ charter requires **Python ≥ 3.11** (it leans on stdlib `tomllib`, which is 3.11+ only),
55
+ and stock macOS ships 3.9. `uv tool install` can fetch and manage a suitable Python for
56
+ you; `pipx` and `pip` both require one to already be on your `PATH`.
57
+
58
+ Alternatives, once you have a 3.11+ Python:
59
+
60
+ ```
61
+ pipx install charter
62
+ pip install charter
63
+ ```
64
+
65
+ ### 2. The Claude Code plugin
66
+
67
+ This repo also ships as a Claude Code plugin — `.claude-plugin/plugin.json` +
68
+ `hooks/hooks.json` — installed the way you install any Claude Code plugin from a git
69
+ repo (inside a session: `/plugin marketplace add diazoxide/charter`, then `/plugin
70
+ install charter@charter`; consult Claude Code's own `/plugin` help if that flow has
71
+ moved on since this was written).
72
+
73
+ The plugin supplies the pieces that only make sense running *inside* a Claude Code
74
+ session: injecting the active persona's memory at session start, the `PreToolUse` guard
75
+ that enforces the one-credential rule below, the record-memory nudges, and the
76
+ Stop-hook auto-save. **The plugin ships no Python of its own** — every hook it declares
77
+ just shells out to the `charter` CLI you installed in step 1, so the CLI must be on
78
+ `PATH` first. The CLI works standalone for everything else (`charter clone`, `charter
79
+ persona show`, …) with the plugin absent; install the plugin too if you want charter
80
+ actively driving a live session, not just scripted from a terminal.
81
+
82
+ ## 60 seconds: from nothing to a working control plane
83
+
84
+ ```
85
+ mkdir my-control-plane && cd my-control-plane
86
+ charter init --forge github --owner my-org
87
+ charter doctor
88
+ charter discover
89
+ charter clone some-repo
90
+ ```
91
+
92
+ - **`charter init`** scaffolds `charter.toml`, the baseline directories
93
+ (`personas/`, `inventory/`, `workspaces/`), a `.gitignore` tuned for the layout, and a
94
+ Claude Code status line — additive and idempotent, so re-running it is always safe.
95
+ `--forge` is `gitlab` (the default) or `github`; `--owner` is the GitLab group or
96
+ GitHub org/user whose repos this control plane tracks.
97
+ - **`charter doctor`** preflights the environment (python, git, git identity, the
98
+ forge's CLI and its auth) and tells you exactly what's missing before anything else
99
+ tries to use it.
100
+ - **`charter discover`** queries the forge and writes `inventory/repos.json` — the
101
+ durable, git-tracked map of every repo in the group, complete even when nothing is
102
+ cloned yet.
103
+ - **`charter clone <repo>`** clones a repo on demand into the active workspace
104
+ (`workspaces/default/<repo>/`), already configured with the one-credential git policy
105
+ below.
106
+
107
+ ## Concepts
108
+
109
+ - **Control plane** — any directory marked by `charter.toml`. Not a fixed location: `cd`
110
+ anywhere beneath one and commands resolve it by walking up, the way git resolves
111
+ `.git`. See `docs/control-plane.md` for the file in full.
112
+ - **Workspace** — an isolated, per-task directory of repo clones
113
+ (`workspaces/<name>/<repo>`), so several tasks can each hold their own repos on their
114
+ own branches without stepping on each other. `default` always exists; `charter
115
+ workspace create <name> --use` starts a new one.
116
+ - **Worktree** — a further split *within* one workspace's clone of a repo: several git
117
+ worktrees over one clone (`workspaces/<ws>/.worktrees/<repo>/<piece>`), so parallel
118
+ sub-agents can each work their own branch of the *same* repo without re-cloning it.
119
+ - **Persona** — a specialist role identity (`devops`, `qa`, …) with a committed charter,
120
+ its own persistent memory, and a named credential vault — dispatchable as an isolated
121
+ Claude Code sub-agent. This is charter's differentiator; see the worked example below
122
+ and `docs/personas.md`.
123
+ - **Memory** — durable notes a persona or workspace records as it works
124
+ (`charter persona remember` / `charter workspace remember`). How far a note travels —
125
+ disk only, committed locally, or pushed to the team — is one setting,
126
+ `[memory].share`, and it **defaults to `local`**: see `docs/control-plane.md`.
127
+ - **Vault** — where a persona's credentials live: plaintext JSON at file mode 0600, with
128
+ **no encryption at rest**. What it protects against is different and real — keeping a
129
+ secret value out of an agent's context and transcript. Read `docs/secrets.md` before
130
+ storing anything real in one; the vault is **not a password manager**.
131
+
132
+ ## Worked example: a persona, end to end
133
+
134
+ ```
135
+ charter persona create devops --role "DevOps Engineer" --with-vault
136
+ charter persona use devops
137
+ charter persona secret set API_TOKEN --stdin # value never touches argv/history
138
+ charter persona remember "prod kubeconfig lives in the devops vault, key KUBECONFIG"
139
+ charter persona sync-agents
140
+ ```
141
+
142
+ The last step writes `.claude/agents/devops.md` — a generated Claude Code sub-agent
143
+ carrying devops's charter, its memory instructions, and a reminder to use the vault
144
+ (`exec`/`cp`, never `--reveal`). From here on, any session can hand work to it in an
145
+ isolated context instead of guessing with borrowed credentials:
146
+
147
+ ```
148
+ Agent(subagent_type: "devops", prompt: "Check whether the prod deployment rolled out cleanly.")
149
+ ```
150
+
151
+ The devops sub-agent runs with *its own* vault and *its own* memory — it can read the
152
+ `prod kubeconfig` note it (or a teammate) recorded earlier, pull `API_TOKEN` via
153
+ `charter persona secret exec`, and never expose the raw value back to the caller. Every
154
+ dispatch like this is tallied (agent name + date, never the prompt) so `charter persona
155
+ stats` can show whether devops is actually being used, or whether that work is quietly
156
+ routing to a generic agent instead. Full format, inheritance, and the memory model:
157
+ `docs/personas.md`.
158
+
159
+ ### Feeding a tool that wants a dotenv secrets file
160
+
161
+ Some tools take a *file* of secrets rather than env vars. `--dotenv` writes one
162
+ 0600 temp file containing every entry you name, points an env var at its path,
163
+ and deletes it when the command exits — so no value is ever printed, stored, or
164
+ placed in argv.
165
+
166
+ ```bash
167
+ charter secret exec qa \
168
+ --dotenv PLAYWRIGHT_MCP_SECRETS_FILE=EASYDMARC_USER:platform-user \
169
+ --dotenv PLAYWRIGHT_MCP_SECRETS_FILE=EASYDMARC_PASS:platform-pass \
170
+ -- npx @playwright/cli@0.1.18 -s=login fill e3 EASYDMARC_PASS
171
+ ```
172
+
173
+ Repeats sharing an env-var name merge into a single file, in flag order.
174
+ Different names produce separate files. Defining the same NAME twice under one
175
+ ENVVAR is an error (exit code 2).
176
+
177
+ The value is never typed by the caller: the tool refers to the secret by the
178
+ **name** you gave it (`EASYDMARC_PASS`), and resolves it from the file. Any
179
+ value that does appear in captured output is redacted.
180
+
181
+ `--dotenv` cannot be combined with `--exec` — exec replaces this process, so
182
+ the temp file would never be cleaned up. Use `--env` for an exec'd command.
183
+
184
+ ## The one-credential rule
185
+
186
+ Every git operation charter performs — from any persona, any sub-agent, any repo clone —
187
+ authenticates with **that repo's own forge's CLI token, over HTTPS**: `glab` for GitLab,
188
+ `gh` for GitHub. Never an SSH key, never commit/tag signing. `charter git-policy --apply`
189
+ writes this into a repo's *local* git config (a credential helper, `commit.gpgsign =
190
+ false`, and SSH→HTTPS URL rewrites so even a repo whose remote is an SSH URL still
191
+ transports over HTTPS); `charter clone` applies it automatically to everything it clones.
192
+
193
+ This is deliberate, not incidental: an **SSH key prompt or a GPG signer prompt hangs an
194
+ autonomous agent** mid-run — there's no human at the keyboard to answer it. One
195
+ credential, held by the forge's own CLI, over HTTPS, is the only shape that can never
196
+ block on a question nobody is there to answer.
197
+
198
+ The Claude Code plugin's `PreToolUse` guard **denies** a command that would bypass this
199
+ — a raw SSH GitLab/GitHub URL handed to git, `GIT_SSH_COMMAND=`, `-S`/`--gpg-sign`, `ssh
200
+ -T git@github.com`. **If you hit one of these denials, that is the rule working, not a
201
+ bug** — the message names the fix (usually: nothing, since `charter git-policy --apply`
202
+ already configured the repo correctly). Check the credential with `glab auth status` /
203
+ `gh auth status`, never `ssh -T`.
204
+
205
+ ## Learn more
206
+
207
+ - `docs/control-plane.md` — `charter.toml` in full: every key, a self-hosted example, a
208
+ mixed-forge example, and the memory posture (`local`/`commit`/`push`) in detail.
209
+ - `docs/forges.md` — what GitLab and GitHub each need, self-hosted hosts, and the rule
210
+ for a repo name that collides across forges.
211
+ - `docs/personas.md` — the charter format, the memory model, and dispatching a persona
212
+ as a sub-agent.
213
+ - `docs/secrets.md` — exactly what the vault does and does not protect against.
214
+
215
+ Development: the test suite is stdlib `unittest` — `python3 -m unittest discover -s
216
+ tests`. Report issues at [github.com/diazoxide/charter](https://github.com/diazoxide/charter).
@@ -0,0 +1,189 @@
1
+ # charter
2
+
3
+ **charter** is a control plane for Claude Code agents working across many repos on
4
+ GitHub or GitLab. It gives an agent durable **personas** (specialist role identities,
5
+ each with its own committed memory and a scoped credential vault), isolated per-task
6
+ **workspaces** for cloning and working several repos in parallel without mixing them up,
7
+ and a **vault** that keeps credentials out of the model's context — so an agent (or a
8
+ whole team of them) can move between repos and tasks without losing what it has learned
9
+ or leaking a secret into a transcript.
10
+
11
+ If you've never seen it before, you can go from `uv tool install charter` to a working
12
+ control plane in about a minute — see [60 seconds](#60-seconds-from-nothing-to-a-working-control-plane) below.
13
+
14
+ ## Install
15
+
16
+ charter ships as **two artifacts** — install both. A CLI-only install leaves the plugin's
17
+ hooks inert (no session context injection, no golden-rule guard, no auto-save), since the
18
+ plugin is what actually wires them into Claude Code.
19
+
20
+ ### 1. The CLI
21
+
22
+ ```
23
+ uv tool install charter
24
+ ```
25
+
26
+ Lead with [`uv`](https://docs.astral.sh/uv/) for a concrete reason, not a preference:
27
+ charter requires **Python ≥ 3.11** (it leans on stdlib `tomllib`, which is 3.11+ only),
28
+ and stock macOS ships 3.9. `uv tool install` can fetch and manage a suitable Python for
29
+ you; `pipx` and `pip` both require one to already be on your `PATH`.
30
+
31
+ Alternatives, once you have a 3.11+ Python:
32
+
33
+ ```
34
+ pipx install charter
35
+ pip install charter
36
+ ```
37
+
38
+ ### 2. The Claude Code plugin
39
+
40
+ This repo also ships as a Claude Code plugin — `.claude-plugin/plugin.json` +
41
+ `hooks/hooks.json` — installed the way you install any Claude Code plugin from a git
42
+ repo (inside a session: `/plugin marketplace add diazoxide/charter`, then `/plugin
43
+ install charter@charter`; consult Claude Code's own `/plugin` help if that flow has
44
+ moved on since this was written).
45
+
46
+ The plugin supplies the pieces that only make sense running *inside* a Claude Code
47
+ session: injecting the active persona's memory at session start, the `PreToolUse` guard
48
+ that enforces the one-credential rule below, the record-memory nudges, and the
49
+ Stop-hook auto-save. **The plugin ships no Python of its own** — every hook it declares
50
+ just shells out to the `charter` CLI you installed in step 1, so the CLI must be on
51
+ `PATH` first. The CLI works standalone for everything else (`charter clone`, `charter
52
+ persona show`, …) with the plugin absent; install the plugin too if you want charter
53
+ actively driving a live session, not just scripted from a terminal.
54
+
55
+ ## 60 seconds: from nothing to a working control plane
56
+
57
+ ```
58
+ mkdir my-control-plane && cd my-control-plane
59
+ charter init --forge github --owner my-org
60
+ charter doctor
61
+ charter discover
62
+ charter clone some-repo
63
+ ```
64
+
65
+ - **`charter init`** scaffolds `charter.toml`, the baseline directories
66
+ (`personas/`, `inventory/`, `workspaces/`), a `.gitignore` tuned for the layout, and a
67
+ Claude Code status line — additive and idempotent, so re-running it is always safe.
68
+ `--forge` is `gitlab` (the default) or `github`; `--owner` is the GitLab group or
69
+ GitHub org/user whose repos this control plane tracks.
70
+ - **`charter doctor`** preflights the environment (python, git, git identity, the
71
+ forge's CLI and its auth) and tells you exactly what's missing before anything else
72
+ tries to use it.
73
+ - **`charter discover`** queries the forge and writes `inventory/repos.json` — the
74
+ durable, git-tracked map of every repo in the group, complete even when nothing is
75
+ cloned yet.
76
+ - **`charter clone <repo>`** clones a repo on demand into the active workspace
77
+ (`workspaces/default/<repo>/`), already configured with the one-credential git policy
78
+ below.
79
+
80
+ ## Concepts
81
+
82
+ - **Control plane** — any directory marked by `charter.toml`. Not a fixed location: `cd`
83
+ anywhere beneath one and commands resolve it by walking up, the way git resolves
84
+ `.git`. See `docs/control-plane.md` for the file in full.
85
+ - **Workspace** — an isolated, per-task directory of repo clones
86
+ (`workspaces/<name>/<repo>`), so several tasks can each hold their own repos on their
87
+ own branches without stepping on each other. `default` always exists; `charter
88
+ workspace create <name> --use` starts a new one.
89
+ - **Worktree** — a further split *within* one workspace's clone of a repo: several git
90
+ worktrees over one clone (`workspaces/<ws>/.worktrees/<repo>/<piece>`), so parallel
91
+ sub-agents can each work their own branch of the *same* repo without re-cloning it.
92
+ - **Persona** — a specialist role identity (`devops`, `qa`, …) with a committed charter,
93
+ its own persistent memory, and a named credential vault — dispatchable as an isolated
94
+ Claude Code sub-agent. This is charter's differentiator; see the worked example below
95
+ and `docs/personas.md`.
96
+ - **Memory** — durable notes a persona or workspace records as it works
97
+ (`charter persona remember` / `charter workspace remember`). How far a note travels —
98
+ disk only, committed locally, or pushed to the team — is one setting,
99
+ `[memory].share`, and it **defaults to `local`**: see `docs/control-plane.md`.
100
+ - **Vault** — where a persona's credentials live: plaintext JSON at file mode 0600, with
101
+ **no encryption at rest**. What it protects against is different and real — keeping a
102
+ secret value out of an agent's context and transcript. Read `docs/secrets.md` before
103
+ storing anything real in one; the vault is **not a password manager**.
104
+
105
+ ## Worked example: a persona, end to end
106
+
107
+ ```
108
+ charter persona create devops --role "DevOps Engineer" --with-vault
109
+ charter persona use devops
110
+ charter persona secret set API_TOKEN --stdin # value never touches argv/history
111
+ charter persona remember "prod kubeconfig lives in the devops vault, key KUBECONFIG"
112
+ charter persona sync-agents
113
+ ```
114
+
115
+ The last step writes `.claude/agents/devops.md` — a generated Claude Code sub-agent
116
+ carrying devops's charter, its memory instructions, and a reminder to use the vault
117
+ (`exec`/`cp`, never `--reveal`). From here on, any session can hand work to it in an
118
+ isolated context instead of guessing with borrowed credentials:
119
+
120
+ ```
121
+ Agent(subagent_type: "devops", prompt: "Check whether the prod deployment rolled out cleanly.")
122
+ ```
123
+
124
+ The devops sub-agent runs with *its own* vault and *its own* memory — it can read the
125
+ `prod kubeconfig` note it (or a teammate) recorded earlier, pull `API_TOKEN` via
126
+ `charter persona secret exec`, and never expose the raw value back to the caller. Every
127
+ dispatch like this is tallied (agent name + date, never the prompt) so `charter persona
128
+ stats` can show whether devops is actually being used, or whether that work is quietly
129
+ routing to a generic agent instead. Full format, inheritance, and the memory model:
130
+ `docs/personas.md`.
131
+
132
+ ### Feeding a tool that wants a dotenv secrets file
133
+
134
+ Some tools take a *file* of secrets rather than env vars. `--dotenv` writes one
135
+ 0600 temp file containing every entry you name, points an env var at its path,
136
+ and deletes it when the command exits — so no value is ever printed, stored, or
137
+ placed in argv.
138
+
139
+ ```bash
140
+ charter secret exec qa \
141
+ --dotenv PLAYWRIGHT_MCP_SECRETS_FILE=EASYDMARC_USER:platform-user \
142
+ --dotenv PLAYWRIGHT_MCP_SECRETS_FILE=EASYDMARC_PASS:platform-pass \
143
+ -- npx @playwright/cli@0.1.18 -s=login fill e3 EASYDMARC_PASS
144
+ ```
145
+
146
+ Repeats sharing an env-var name merge into a single file, in flag order.
147
+ Different names produce separate files. Defining the same NAME twice under one
148
+ ENVVAR is an error (exit code 2).
149
+
150
+ The value is never typed by the caller: the tool refers to the secret by the
151
+ **name** you gave it (`EASYDMARC_PASS`), and resolves it from the file. Any
152
+ value that does appear in captured output is redacted.
153
+
154
+ `--dotenv` cannot be combined with `--exec` — exec replaces this process, so
155
+ the temp file would never be cleaned up. Use `--env` for an exec'd command.
156
+
157
+ ## The one-credential rule
158
+
159
+ Every git operation charter performs — from any persona, any sub-agent, any repo clone —
160
+ authenticates with **that repo's own forge's CLI token, over HTTPS**: `glab` for GitLab,
161
+ `gh` for GitHub. Never an SSH key, never commit/tag signing. `charter git-policy --apply`
162
+ writes this into a repo's *local* git config (a credential helper, `commit.gpgsign =
163
+ false`, and SSH→HTTPS URL rewrites so even a repo whose remote is an SSH URL still
164
+ transports over HTTPS); `charter clone` applies it automatically to everything it clones.
165
+
166
+ This is deliberate, not incidental: an **SSH key prompt or a GPG signer prompt hangs an
167
+ autonomous agent** mid-run — there's no human at the keyboard to answer it. One
168
+ credential, held by the forge's own CLI, over HTTPS, is the only shape that can never
169
+ block on a question nobody is there to answer.
170
+
171
+ The Claude Code plugin's `PreToolUse` guard **denies** a command that would bypass this
172
+ — a raw SSH GitLab/GitHub URL handed to git, `GIT_SSH_COMMAND=`, `-S`/`--gpg-sign`, `ssh
173
+ -T git@github.com`. **If you hit one of these denials, that is the rule working, not a
174
+ bug** — the message names the fix (usually: nothing, since `charter git-policy --apply`
175
+ already configured the repo correctly). Check the credential with `glab auth status` /
176
+ `gh auth status`, never `ssh -T`.
177
+
178
+ ## Learn more
179
+
180
+ - `docs/control-plane.md` — `charter.toml` in full: every key, a self-hosted example, a
181
+ mixed-forge example, and the memory posture (`local`/`commit`/`push`) in detail.
182
+ - `docs/forges.md` — what GitLab and GitHub each need, self-hosted hosts, and the rule
183
+ for a repo name that collides across forges.
184
+ - `docs/personas.md` — the charter format, the memory model, and dispatching a persona
185
+ as a sub-agent.
186
+ - `docs/secrets.md` — exactly what the vault does and does not protect against.
187
+
188
+ Development: the test suite is stdlib `unittest` — `python3 -m unittest discover -s
189
+ tests`. Report issues at [github.com/diazoxide/charter](https://github.com/diazoxide/charter).
@@ -0,0 +1,10 @@
1
+ """charter: an engine for control-plane repo tooling.
2
+
3
+ A tiny, stdlib-only CLI that keeps an inventory of every repo in a configured
4
+ GitLab group and clones them on demand into ``workspaces/``.
5
+
6
+ Run it as ``python3 -m charter`` (from the control plane root) or via the
7
+ installed ``charter`` console script.
8
+ """
9
+
10
+ __version__ = "0.1.0"
@@ -0,0 +1,7 @@
1
+ """Enable ``python3 -m charter``."""
2
+
3
+ import sys
4
+
5
+ from .cli import main
6
+
7
+ sys.exit(main())