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 +2 -2
- package/package.json +5 -2
- package/src/blocks.py +5 -2
- package/src/cli.py +8 -4
- package/src/report.py +43 -0
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
|
|
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
|
|
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.
|
|
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":
|
|
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
|
|
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,
|
|
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 =
|
|
143
|
+
blocks = {HEADER: block(HEADER_START, HEADER_RULES)}
|
|
143
144
|
for source in sources:
|
|
144
145
|
if source.pack.local:
|
|
145
|
-
blocks.
|
|
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.
|
|
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"}']
|