context-packs 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -9,7 +9,7 @@ A context pack is a folder in a git repo holding rules every agent session shoul
9
9
  Run it with npx:
10
10
 
11
11
  ```bash
12
- npx github:mjewell/context-packs <command>
12
+ npx context-packs <command>
13
13
  ```
14
14
 
15
15
  It syncs the current folder. Pass `--dir <folder>` before the command to sync another.
@@ -21,7 +21,7 @@ This repo is also a Claude Code plugin with a `/context-packs` skill, which runs
21
21
  ## Use it
22
22
 
23
23
  ```bash
24
- npx github:mjewell/context-packs add team-rules https://github.com/acme/agent-rules.git --path context
24
+ npx context-packs add team-rules https://github.com/acme/agent-rules.git --path context
25
25
  ```
26
26
 
27
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-packs",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Sync shared agent rules and docs into each repo's AGENTS.md",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -13,5 +13,8 @@
13
13
  "engines": {
14
14
  "node": ">=18"
15
15
  },
16
- "repository": "github:mjewell/context-packs"
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/mjewell/context-packs.git"
19
+ }
17
20
  }
package/src/blocks.py CHANGED
@@ -19,10 +19,12 @@ BLOCK = re.compile(r'^<!-- context-packs:start (\S+)((?: \S+)*) -->\n(.*?)'
19
19
 
20
20
 
21
21
  class Region(NamedTuple):
22
- """Where AGENTS.md's context-packs blocks sit, the packs they hold, and the doc sets each installed."""
22
+ """Where AGENTS.md's context-packs blocks sit, each block's text by name, the packs they hold, and the doc sets
23
+ each installed."""
23
24
  text: str
24
25
  start: int
25
26
  end: int
27
+ blocks: Dict[str, str]
26
28
  packs: List[Pack]
27
29
  installed: Dict[str, List[str]]
28
30
 
@@ -110,4 +112,5 @@ def region(repo):
110
112
  sys.exit(f'context-packs: {agents_md}: the {name} block or a doc in {PACKS / name} was edited since '
111
113
  'the last sync. Move the change into its context pack or outside them, revert it, '
112
114
  'then sync again.')
113
- return Region(text, blocks[0].start(), blocks[-1].end(), packs, current)
115
+ return Region(text, blocks[0].start(), blocks[-1].end(), {m.group(1): m.group(0) for m in blocks}, packs,
116
+ current)
package/src/cli.py CHANGED
@@ -11,6 +11,7 @@ import shutil
11
11
  import sys
12
12
 
13
13
  import origins
14
+ import report
14
15
  from blocks import HEADER, HEADER_RULES, HEADER_START, block, region, snapshot
15
16
  from packs import LOCAL, PACKS, Pack, Source, fetch
16
17
 
@@ -139,23 +140,26 @@ def main():
139
140
  for gone in {p.name for p in found.packs} - {p.name for p in packs}:
140
141
  if (repo / PACKS / gone).exists():
141
142
  shutil.rmtree(repo / PACKS / gone)
142
- blocks = [block(HEADER_START, HEADER_RULES)]
143
+ blocks = {HEADER: block(HEADER_START, HEADER_RULES)}
143
144
  for source in sources:
144
145
  if source.pack.local:
145
- blocks.append(block(starts[source.pack.name], bodies[source.pack.name]))
146
+ blocks[source.pack.name] = block(starts[source.pack.name], bodies[source.pack.name])
146
147
  continue
147
148
  synced = repo / source.pack.synced
148
149
  if synced.exists():
149
150
  shutil.rmtree(synced)
150
151
  for name in chosen[source.pack.name]:
151
152
  source.copy(name, synced)
152
- blocks.append(block(starts[source.pack.name], bodies[source.pack.name], snapshot(synced)))
153
+ blocks[source.pack.name] = block(starts[source.pack.name], bodies[source.pack.name], snapshot(synced))
153
154
  if (repo / PACKS).is_dir() and not any((repo / PACKS).iterdir()):
154
155
  (repo / PACKS).rmdir()
155
- text = found.text[:found.start] + '\n'.join(blocks) + found.text[found.end:]
156
+ text = found.text[:found.start] + '\n'.join(blocks.values()) + found.text[found.end:]
157
+ created = not (repo / 'AGENTS.md').exists()
156
158
  (repo / 'AGENTS.md').write_bytes(text.encode('utf-8'))
157
159
  if not linked:
158
160
  claude_md.symlink_to('AGENTS.md')
161
+ for line in report.changes(found, packs, chosen, blocks, created, linked):
162
+ print(line)
159
163
 
160
164
 
161
165
  if __name__ == '__main__':
package/src/report.py ADDED
@@ -0,0 +1,43 @@
1
+ """What a sync changed, in lines for the person who ran it."""
2
+ from blocks import HEADER
3
+ from origins import COMMIT
4
+
5
+
6
+ def short(ref):
7
+ """ref as git shows it: a commit abbreviated, a tag in full."""
8
+ return ref[:7] if COMMIT.fullmatch(ref) else ref
9
+
10
+
11
+ def at(pack):
12
+ """Where the pack is pinned, or nothing for one that syncs from the repo's working tree."""
13
+ return f' @ {short(pack.ref)}' if pack.ref else ''
14
+
15
+
16
+ def sets(names):
17
+ return ', '.join(names) or 'none'
18
+
19
+
20
+ def changes(found, after, chosen, blocks, created, linked):
21
+ """Lines describing what the sync did: whether it created AGENTS.md or found CLAUDE.md already linked to it, and
22
+ how the packs found in AGENTS.md became the packs after it, with chosen holding the doc sets each pack installs
23
+ and blocks the text the sync wrote for each."""
24
+ before = found.packs
25
+ old = {p.name: p for p in before}
26
+ new = {p.name for p in after}
27
+ lines = (['Created AGENTS.md'] * created + ['Linked CLAUDE.md to AGENTS.md'] * (not linked)
28
+ + ['Updated the header block'] * (not created and found.blocks[HEADER] != blocks[HEADER]))
29
+ for pack in after:
30
+ was = old.get(pack.name)
31
+ if not was:
32
+ docs = chosen[pack.name]
33
+ lines.append(f'{pack.name}: added{at(pack)}' + f' (docs: {sets(docs)})' * bool(docs))
34
+ elif was.ref != pack.ref:
35
+ lines.append(f'{pack.name}: {short(was.ref)} → {short(pack.ref)}')
36
+ elif not pack.local and found.installed[pack.name] != chosen[pack.name]:
37
+ lines.append(f'{pack.name}: doc sets {sets(found.installed[pack.name])} → {sets(chosen[pack.name])}')
38
+ elif found.blocks[pack.name] != blocks[pack.name]:
39
+ lines.append(f'{pack.name}: changed')
40
+ lines += [f'{p.name}: removed' for p in before if p.name not in new]
41
+ if lines:
42
+ return lines
43
+ return [f'Nothing changed: {", ".join(p.name + at(p) for p in after) or "no packs installed"}']