context-packs 0.1.1 → 0.2.0

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 (3) hide show
  1. package/README.md +88 -60
  2. package/package.json +1 -1
  3. package/src/packs.py +8 -4
package/README.md CHANGED
@@ -1,97 +1,125 @@
1
1
  # context-packs
2
2
 
3
- Sync shared agent rules and docs into each repo's `AGENTS.md`.
3
+ Keep your coding agents' rules in one git repo, and sync them into the `AGENTS.md` of every repo that should follow them.
4
4
 
5
- A context pack is a folder in a git repo holding rules every agent session should see, and docs that agents read only for some kinds of work. `context-packs` copies the packs a repo installs into that repo, pinned to a tag or commit, so Claude Code, Codex and any other agent that reads `AGENTS.md` get the same rules. That includes cloud sessions and fresh clones, which see only what's committed.
5
+ Agent rules copied from repo to repo drift apart. context-packs syncs them instead:
6
6
 
7
- ## Install
7
+ - **One source.** Every repo that installs a pack gets the same text, pinned to a tag or commit, and moves on with `update`.
8
+ - **Committed, so every session sees it.** Packs are copied in, not linked or loaded by a hook, so clones, CI and cloud agent sessions get them too.
9
+ - **Read only when relevant.** Long docs don't load into every session: `AGENTS.md` gets a table saying when to read each one.
10
+ - **Any agent.** Anything that reads `AGENTS.md` works, and `CLAUDE.md` is linked to it for Claude Code.
8
11
 
9
- Run it with npx:
12
+ A synced pack in `AGENTS.md`:
10
13
 
11
- ```bash
12
- npx context-packs <command>
13
- ```
14
-
15
- It syncs the current folder. Pass `--dir <folder>` before the command to sync another.
16
-
17
- It needs `git`, Node 18 or later, and Python 3.9 or later. On macOS, the Xcode command line tools that provide `git` provide Python too.
18
-
19
- This repo is also a Claude Code plugin with a `/context-packs` skill, which runs the commands for you. Add it to a plugin marketplace, or install just the skill for Claude Code and Codex with `npx skills add mjewell/context-packs -g -a claude-code -a codex`.
14
+ ```markdown
15
+ <!-- context-packs:start team-rules from=https://github.com/acme/agent-rules.git path=context ref=v1.2.0 -->
16
+ Prefer small PRs. Never commit secrets.
20
17
 
21
- ## Use it
18
+ ## team-rules context
22
19
 
23
- ```bash
24
- npx context-packs add team-rules https://github.com/acme/agent-rules.git --path context
20
+ | When | Read |
21
+ |---|---|
22
+ | Writing or changing tests | `docs/agents/packs/team-rules/testing.md` |
23
+ <!-- context-packs:end sha256:3f2c1aa0b9d4 -->
25
24
  ```
26
25
 
27
- That writes two blocks into `AGENTS.md`: a header block explaining context packs, and one for the pack. It also copies the pack's doc sets into `docs/agents/packs/team-rules/`, and creates `CLAUDE.md` as a symlink to `AGENTS.md`, so Claude Code reads the same file. Commit the result.
28
-
29
- If `AGENTS.md` already exists, the first run prints two marker lines instead. Paste them where the shared rules should go, after the intro and before the project-specific sections, and run it again.
26
+ Run it with `npx context-packs`, or [let your agent run it](#let-your-agent-run-it). It needs `git`, Node 18+ and Python 3.9+; on macOS, the command line tools provide both `git` and Python.
30
27
 
31
- | Command | Does |
32
- |---|---|
33
- | *(none)* | Resyncs every pack at its pinned ref |
34
- | `add <name> <origin> [--path P] [--ref R] [--docs a,b]` | Installs a pack, pinned to the origin's latest commit or to `--ref` |
35
- | `remove <name>` | Uninstalls a pack |
36
- | `update [name…]` | Moves packs to their latest version |
37
- | `update <name> --ref R` | Pins one pack to a tag or commit |
38
- | `docs <name> a,b` | Replaces a pack's optional doc sets (`''` for none) |
39
- | `list` | Lists each pack and its doc sets, marking the installed ones |
28
+ ## Write a pack
40
29
 
41
- A folder that holds several repos without being one can be synced too, so sessions started there get the rules. A Claude Code session started inside one of those repos then loads both copies, since Claude Code reads `CLAUDE.md` from the folders above it as well.
30
+ A pack is a folder in any git repo. `pack.md` holds the rules every session sees, and each doc set, one `.md` file or a folder with an `index.md`, covers a topic agents read about only when it's relevant:
42
31
 
43
- ### Pinning and updates
32
+ ```text
33
+ context/
34
+ ├── pack.md # rules pasted into every repo's AGENTS.md
35
+ ├── testing.md # a doc set in one file
36
+ └── typescript/
37
+ ├── index.md # a doc set's entry point
38
+ └── react.md # linked from index.md
39
+ ```
44
40
 
45
- Each pack's start marker records where it comes from and where it's pinned:
41
+ Each doc set starts with front matter. `when` becomes its row in the table, and `tier: required` installs it in every repo; without it, the set is optional and each repo chooses it with `--docs`.
46
42
 
47
43
  ```markdown
48
- <!-- context-packs:start team-rules from=https://github.com/acme/agent-rules.git path=context ref=v1.2.0 -->
44
+ ---
45
+ when: Writing or changing tests
46
+ tier: required
47
+ ---
49
48
  ```
50
49
 
51
- `--ref` takes a tag or a commit, never a branch, since a branch moves. `update` moves a pack along what it's pinned to:
50
+ Keep `pack.md` short, since every session loads it.
52
51
 
53
- - **A commit** moves to the tip of the origin's default branch.
54
- - **A release tag**, meaning a prefix followed by dot-separated numbers such as `v1.2.0` or `acme-v3`, moves to the newest release with the same prefix. Pre-releases like `v2.0.0-rc1` are skipped.
55
- - **Any other tag** stays put.
52
+ Tags are optional. A repo pins to a commit by default, and `update` moves it to the tip of your default branch. Tag releases like `v1.2.0` once you want repos to pin to versions and `update` from one release to the next.
56
53
 
57
- The sync fetches origins into `~/.cache/context-packs/` using your own git credentials, so private repos work.
54
+ ## Sync a pack from another repo
58
55
 
59
- A pack can also live in the repo it's synced into: `add <name> . --path <folder>` reads it from the working tree, with no ref. Its table points at the docs where they are instead of copying them, and it installs every doc set it has.
56
+ ```bash
57
+ npx context-packs add <name> <git-url> --path <folder>
58
+ ```
60
59
 
61
- That's the way to keep a repo's own context docs. Put each one in `docs/agents/project/` with a `when:` line, run `add project . --path docs/agents/project` once, and resync after adding a doc or changing its `when:`. The table in `AGENTS.md` stays generated from the docs.
60
+ - `<name>` is what this repo calls the pack, such as `team-rules`.
61
+ - `<git-url>` is the repo holding the pack, as you'd pass it to `git clone`.
62
+ - `<folder>` is the pack's folder in that repo, such as `context`. Leave out `--path` if the pack is the whole repo.
62
63
 
63
- ### Edits stay upstream
64
+ That adds the pack's block to `AGENTS.md`, creating it if needed, and copies the doc sets into `docs/agents/packs/<name>/`, pinned to the origin's latest commit or to `--ref <tag or commit>`. Commit the result. If `AGENTS.md` already exists, the first run prints two marker lines to paste where the shared rules should go; then run it again.
64
65
 
65
- Each block ends with a hash covering its text and its docs folder. If either was edited in the repo, the sync stops without changing anything and says what to fix. Change a pack in its origin, then update.
66
+ Later, `npx context-packs update` moves packs to their latest version, and `npx context-packs` resyncs at the pinned ones. Private repos work, since origins are fetched with your own git credentials.
66
67
 
67
- ## Write a pack
68
+ ## Sync your project's own docs
68
69
 
69
- A pack is a folder holding an optional `agents.md` and any number of doc sets. A doc set is a single `.md` file, or a folder whose `index.md` links to its other files:
70
+ The same table works for a repo's own docs. Put each in a folder with a `when:` line, and install the folder as a pack read from the working tree:
70
71
 
71
- ```text
72
- context/
73
- ├── agents.md # rules pasted into the pack's block
74
- ├── testing.md # a doc set in one file
75
- └── typescript/
76
- ├── index.md # a doc set's entry point
77
- └── react.md # other files, linked from index.md
72
+ ```bash
73
+ npx context-packs add <name> . --path <folder>
78
74
  ```
79
75
 
80
- Each doc set's entry point starts with front matter saying when to read it:
76
+ - `<name>` is what this repo calls the pack.
77
+ - `.` is the origin: the pack is in this repo, so it has no ref and syncs from your working tree.
78
+ - `<folder>` holds the docs.
81
79
 
82
- ```markdown
83
- ---
84
- when: Writing or changing tests, or fixing a bug
85
- tier: required
86
- ---
80
+ Any name and folder work. We suggest `project` and `docs/agents/project`:
81
+
82
+ ```bash
83
+ npx context-packs add project . --path docs/agents/project
87
84
  ```
88
85
 
89
- The pack's block lists its installed doc sets in a table that agents check before starting work. `tier: required` installs the set in every repo that installs the pack. Leave `tier` out for an optional set, which each repo chooses with `--docs` or `docs`.
86
+ Its table points at the docs where they are. Resync after adding a doc or changing a `when:` line.
90
87
 
91
- Keep `agents.md` small, since every session loads it, and put anything needed only for some work in a doc set. agy stops reading `AGENTS.md` after 24 KB.
88
+ ## Let your agent run it
92
89
 
93
- ## Tests
90
+ The `context-packs` skill lets you skip the commands: ask Claude Code or Codex in plain words, and it runs them, shows you what changed, and leaves the result uncommitted for you to review. Install it for every project:
94
91
 
95
92
  ```bash
96
- python3 -m unittest discover tests
93
+ npx skills add mjewell/context-packs -g
97
94
  ```
95
+
96
+ Leave out `-g` to install it into the current project instead.
97
+
98
+ Then ask things like:
99
+
100
+ - "Add the context pack in the `context` folder of github.com/acme/agent-rules"
101
+ - "Update our context packs"
102
+ - "Install the typescript docs from team-rules"
103
+ - "Sync our project docs"
104
+
105
+ In Claude Code, `/context-packs` invokes it directly.
106
+
107
+ ## Reference
108
+
109
+ Each command syncs the current folder, or `--dir <folder>`, and prints what changed. The folder can also hold several repos without being one itself, so sessions started there get the rules too.
110
+
111
+ | Command | Does |
112
+ |---|---|
113
+ | *(none)* | Resyncs every pack at its pinned ref |
114
+ | `add <name> <origin> [--path P] [--ref R] [--docs a,b]` | Installs a pack, pinned to the origin's latest commit or to `--ref` |
115
+ | `remove <name>` | Uninstalls a pack |
116
+ | `update [name…]` | Moves packs to their latest version |
117
+ | `update <name> --ref R` | Pins one pack to a tag or commit |
118
+ | `docs <name> a,b` | Replaces a pack's optional doc sets (`''` for none) |
119
+ | `list` | Lists each pack and its doc sets, marking the installed ones |
120
+
121
+ **Pinning.** `--ref` takes a tag or a commit, never a branch, since a branch moves. `update` moves a pack pinned to a commit to the tip of the default branch, and one pinned to a release tag (a prefix and dot-separated numbers, such as `v1.2.0` or `acme-v3`) to the newest release with the same prefix, skipping pre-releases. Other tags stay put.
122
+
123
+ **Edits stay upstream.** Each block ends with a hash of its text and synced docs. If either was edited in the repo, the sync stops and says what to fix: change the pack in its origin, then `update`.
124
+
125
+ **Contributing.** [design.md](docs/agents/project/design.md) explains the choices behind the format. Run the tests with `python3 -m unittest discover tests`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-packs",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Sync shared agent rules and docs into each repo's AGENTS.md",
5
5
  "license": "MIT",
6
6
  "bin": {
package/src/packs.py CHANGED
@@ -42,14 +42,18 @@ class Pack(NamedTuple):
42
42
 
43
43
 
44
44
  class Source(NamedTuple):
45
- """A pack's content, fetched into a folder holding its agents.md and doc sets."""
45
+ """A pack's content, fetched into a folder holding its pack.md and doc sets."""
46
46
  pack: Pack
47
47
  folder: Path
48
48
 
49
49
  def available(self):
50
- """The pack's doc sets: each a folder holding an index.md, or a single .md file other than agents.md."""
50
+ """The pack's doc sets: each a folder holding an index.md, or a single .md file other than pack.md."""
51
+ if (self.folder / 'agents.md').is_file():
52
+ sys.exit(f'context-packs: {(Path(self.pack.path) / "agents.md").as_posix()} in {self.pack.origin} holds '
53
+ 'rules under the name packs used before 0.2.0. Rename it to pack.md in the pack, or run update '
54
+ 'to move to a version of the pack that has one.')
51
55
  folders = {p.name for p in self.folder.iterdir() if p.is_dir() and not p.name.startswith('.')}
52
- files = {p.stem for p in self.folder.glob('*.md') if p.name != 'agents.md'}
56
+ files = {p.stem for p in self.folder.glob('*.md') if p.name != 'pack.md'}
53
57
  for name in sorted(folders & files):
54
58
  sys.exit(f'context-packs: {self.pack.origin} has both {name}.md and {name}/ in '
55
59
  f'{self.pack.path or "its root"}, so the doc set {name} is ambiguous. Keep one.')
@@ -85,7 +89,7 @@ class Source(NamedTuple):
85
89
  shutil.copy2(self.folder / f'{name}.md', synced / f'{name}.md')
86
90
 
87
91
  def body(self, names):
88
- rules_file = self.folder / 'agents.md'
92
+ rules_file = self.folder / 'pack.md'
89
93
  rules = rules_file.read_text(encoding='utf-8') if rules_file.is_file() else ''
90
94
  # The end marker has to start its own line, or the next sync can't find the block.
91
95
  if rules and not rules.endswith('\n'):