@evertonferreira96/agents 0.1.1 → 0.1.2

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
@@ -1,15 +1,437 @@
1
+ <div align="center">
2
+
1
3
  # agents
2
4
 
3
- Personal agent and subagent configuration for Claude Code, portable to Cursor and Codex through `AGENTS.md`.
5
+ **Agents, rules, hooks and skills for Claude Code, installed into any project with one command.**
6
+
7
+ [![npm](https://img.shields.io/npm/v/@evertonferreira96/agents?label=npm&color=cb3837)](https://www.npmjs.com/package/@evertonferreira96/agents)
8
+ [![node](https://img.shields.io/node/v/@evertonferreira96/agents?color=339933)](package.json)
9
+ [![Claude Code](https://img.shields.io/badge/Claude%20Code-agents%20%C2%B7%20rules%20%C2%B7%20hooks%20%C2%B7%20skills-d97757)](docs/README.md)
10
+ [![Cursor and Codex](https://img.shields.io/badge/Cursor%20%26%20Codex-AGENTS.md-555555)](AGENTS.md)
11
+
12
+ </div>
13
+
14
+ ---
15
+
16
+ ## Contents
17
+
18
+ - [What it is](#what-it-is)
19
+ - [Quick start](#quick-start)
20
+ - [How it works](#how-it-works)
21
+ - [The big picture](#the-big-picture)
22
+ - [What `setup:pull` does, step by step](#what-setuppull-does-step-by-step)
23
+ - [How each file is decided](#how-each-file-is-decided)
24
+ - [What lands in your project](#what-lands-in-your-project)
25
+ - [Working in a configured project](#working-in-a-configured-project)
26
+ - [Command reference](#command-reference)
27
+ - [Release pipeline](#release-pipeline)
28
+ - [Maintaining this repo](#maintaining-this-repo)
29
+ - [Troubleshooting](#troubleshooting)
30
+ - [Further reading](#further-reading)
31
+
32
+ ---
33
+
34
+ ## What it is
35
+
36
+ A complete working setup for Claude Code, kept in one repo and shipped as the npm package `@evertonferreira96/agents`. Install it once per machine, run one command in a project, and the project gets the same team of agents, the same guardrails and the same architecture playbooks as every other project.
37
+
38
+ | Piece | What you get |
39
+ |---|---|
40
+ | **Agents** (10) | An orchestrator flow plus specialists: `quick`, `product`, `architect`, `api`, `web`, `app`, `infra`, `ui`, `verifier`, `security` |
41
+ | **Skills** (37) | Workflow (orchestrate, triage, brief, review, grilling, handoff, retro), architecture (`arch-api`, `arch-web`, `arch-app`, `arch-db`), quality (tdd, diagnosing-bugs, ponytail), design and motion |
42
+ | **Rules** (9) | Loaded always or by path glob: git, tickets, api, web, mobile, db, ui, graphify, delivery diagram |
43
+ | **Hooks** (8) | Deterministic guards that block what rules can only ask for: `git push`, `git add -A`, staging `.env`, the orchestrator editing code, lint and tests on Stop |
44
+ | **AGENTS.md** | The shared base that Cursor and Codex read too |
45
+ | **Updates** | A manifest of every installed file, so updates refresh what you did not touch and never overwrite what you changed |
46
+
47
+ ---
48
+
49
+ ## Quick start
50
+
51
+ **Requirements:** Node.js 18 or newer, npm, and Claude Code.
52
+
53
+ ```bash
54
+ # 1. once per machine
55
+ npm install -g @evertonferreira96/agents
56
+
57
+ # 2. in the root of any project, new or already configured
58
+ cd path/to/your-project
59
+ chew agents setup:pull
60
+ ```
61
+
62
+ 3. Fill in the project's purpose and commands in the generated `CLAUDE.md`.
63
+ 4. Open Claude Code in the project and describe the task.
64
+
65
+ To update later, run `chew agents setup:pull` again. Every run downloads the latest published version, so the global install does not need to be current.
66
+
67
+ <details>
68
+ <summary>Without a global install</summary>
69
+
70
+ ```bash
71
+ npx @evertonferreira96/agents init # first time
72
+ npx @evertonferreira96/agents@latest update # later
73
+ npx @evertonferreira96/agents check # drift report
74
+ ```
75
+
76
+ </details>
77
+
78
+ ---
79
+
80
+ ## How it works
81
+
82
+ ### The big picture
83
+
84
+ The config lives in this repo. Every push to `main` that changes a shipped file is checked, versioned and published. On your machine, `chew` pulls the newest published version into the project you are in.
85
+
86
+ ```mermaid
87
+ flowchart LR
88
+ subgraph REPO["This repo on GitHub"]
89
+ SRC[".claude/ agents, rules, skills, hooks<br/>AGENTS.md, templates, scripts"]
90
+ end
91
+
92
+ subgraph CI["GitHub Actions"]
93
+ CHECK["check.yml<br/>parse, catalog, tests"]
94
+ REL["release.yml<br/>version, publish, tag, release"]
95
+ CHECK --> REL
96
+ end
97
+
98
+ subgraph REG["Registries"]
99
+ NPM[("npmjs.com<br/>public")]
100
+ GHP[("GitHub Packages<br/>private")]
101
+ end
102
+
103
+ subgraph DEV["Your machine"]
104
+ CHEW["chew<br/>global CLI"]
105
+ PROJ["your-project/<br/>.claude/ AGENTS.md CLAUDE.md"]
106
+ end
107
+
108
+ SRC -- "git push main" --> CHECK
109
+ REL -- "npm publish" --> NPM
110
+ REL -- "npm publish" --> GHP
111
+ NPM -- "latest tarball" --> CHEW
112
+ CHEW -- "chew agents setup:pull" --> PROJ
113
+ ```
114
+
115
+ ### What `setup:pull` does, step by step
116
+
117
+ ```mermaid
118
+ sequenceDiagram
119
+ autonumber
120
+ actor You
121
+ participant Chew as chew (global)
122
+ participant Npm as npmjs.com
123
+ participant Tmp as Temp folder
124
+ participant Engine as Install engine (downloaded)
125
+ participant Proj as Your project
126
+
127
+ You->>Chew: chew agents setup:pull
128
+ alt registry reachable
129
+ Chew->>Npm: npm pack @evertonferreira96/agents@latest
130
+ Npm-->>Chew: agents-X.Y.Z.tgz
131
+ Chew->>Tmp: extract the package
132
+ Chew->>Engine: load scripts/bootstrap.mjs from version X.Y.Z
133
+ else offline or blocked
134
+ Chew-->>You: warning, using the version installed with chew
135
+ end
136
+ Engine->>Proj: read .claude/config-manifest.json (if any)
137
+ Engine->>Proj: add, update or remove files, one decision per file
138
+ Engine->>Proj: write manifest, config-source.json, CLAUDE.md (once), .gitignore lines
139
+ Chew->>Tmp: delete the temp folder
140
+ Chew-->>You: report: added, updated, removed, kept
141
+ ```
142
+
143
+ 1. **Download.** `chew` asks npm for the latest version, always against the public registry (also for the `@evertonferreira96` scope), so a user `.npmrc` that points to another feed does not interfere.
144
+ 2. **Extract.** The tarball is unpacked into a temporary folder.
145
+ 3. **Use the new engine.** The install runs with the `bootstrap.mjs` of the downloaded version, not the one installed with `chew`. An old global `chew` still applies the newest config and only prints a hint to update itself.
146
+ 4. **Decide file by file.** Each shipped file is compared with the project copy and with the manifest of the last install (next section).
147
+ 5. **Record.** `.claude/config-manifest.json` stores the hash of every installed file; `.claude/config-source.json` stores the package and version.
148
+ 6. **First run only.** `CLAUDE.md` is created from `templates/CLAUDE.project.md`, and the `.gitignore` entries are appended.
149
+ 7. **Clean up and report.** The temp folder is deleted and the result is printed.
150
+
151
+ ### How each file is decided
152
+
153
+ The manifest is what makes updates safe. It records the hash each file had when the config installed it, so the engine can tell a file that is only **outdated** (safe to refresh) from one you **edited** (kept).
154
+
155
+ ```mermaid
156
+ flowchart TD
157
+ A["File shipped by the latest version"] --> B{"Exists in<br/>the project?"}
158
+ B -- no --> ADD["added"]
159
+ B -- yes --> C{"Same content?"}
160
+ C -- yes --> SAME["unchanged"]
161
+ C -- no --> D{"Project copy matches<br/>the manifest hash?"}
162
+ D -- "yes: untouched,<br/>only outdated" --> UPD["updated"]
163
+ D -- "no: edited here" --> F{"run with<br/>force flag?"}
164
+ F -- yes --> UPD
165
+ F -- no --> KEPT["kept<br/>listed as edited"]
166
+
167
+ O["File in the manifest that the<br/>latest version no longer ships"] --> G{"Untouched?"}
168
+ G -- yes --> DEL["removed"]
169
+ G -- no --> OBS["kept<br/>listed as obsolete"]
170
+
171
+ classDef add fill:#dcfce7,stroke:#16a34a,color:#14532d
172
+ classDef upd fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
173
+ classDef keep fill:#fef3c7,stroke:#d97706,color:#78350f
174
+ classDef del fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
175
+ classDef same fill:#f3f4f6,stroke:#9ca3af,color:#374151
176
+ class ADD add
177
+ class UPD upd
178
+ class KEPT,OBS keep
179
+ class DEL del
180
+ class SAME same
181
+ ```
182
+
183
+ **Never touched, not even with `--force`:** `CLAUDE.md`, `.claude/settings.local.json` and `.claude/agent-memory/**`.
184
+
185
+ | Situation | Result |
186
+ |---|---|
187
+ | New project, nothing installed | Everything added, `CLAUDE.md` created, `.gitignore` extended |
188
+ | Configured project, nothing edited | New files added, changed files updated, removed files deleted |
189
+ | You edited a shipped file | Kept and listed; `--force` replaces it with the config version |
190
+ | Project configured before the manifest existed | Differing files count as edited; review them, then run `--force` once |
191
+
192
+ ### What lands in your project
193
+
194
+ ```text
195
+ your-project/
196
+ ├── AGENTS.md shared base (Claude Code, Cursor, Codex)
197
+ ├── CLAUDE.md created once from the template, then yours
198
+ ├── .worktreeinclude files copied into each task worktree
199
+ ├── .gitignore + worktrees, handoffs, local settings, graphify-out
200
+ ├── skills-lock.json origin and hash of every vendored skill
201
+ ├── docs/
202
+ │ ├── plans/README.md plan template, one plan per task
203
+ │ ├── adr/README.md ADR template
204
+ │ └── brief-template.md
205
+ ├── scripts/ bootstrap, check-catalog, skills-update
206
+ └── .claude/
207
+ ├── agents/ 10 agents
208
+ ├── skills/ 37 skills
209
+ ├── rules/ 9 rules
210
+ ├── hooks/ 8 hooks
211
+ ├── settings.json hook wiring, worktree base, context limits
212
+ ├── config-manifest.json hash of every installed file (commit it)
213
+ └── config-source.json installed package and version
214
+ ```
215
+
216
+ ---
217
+
218
+ ## Working in a configured project
219
+
220
+ Open Claude Code and describe the task, paste the ticket or paste the error. The main session acts as the orchestrator: it sizes the work, plans it with you and delegates it. It never edits code itself (a hook enforces this).
221
+
222
+ ```mermaid
223
+ flowchart TD
224
+ REQ["Task, ticket or error"] --> RAW{"Raw report?"}
225
+ RAW -- yes --> TRI["triage<br/>classify, reproduce, brief"]
226
+ RAW -- no --> CLS{"Size"}
227
+ TRI --> CLS
228
+
229
+ CLS -- "S: one file,<br/>one-sentence diff" --> QK["quick agent"]
230
+ QK --> STOP["Stop hook<br/>lint and tests"]
231
+
232
+ CLS -- "M or L" --> WT["Task worktree<br/>.claude/worktrees/slug"]
233
+ WT --> GRL["Grill with you<br/>L always, M on open decisions"]
234
+ GRL --> PRD["product agent<br/>scope, AC, test seams, tickets"]
235
+ PRD -- "L or architecture trigger" --> ARC["architect agent"]
236
+ PRD -- M --> GATE
237
+ ARC --> GATE{"You approve<br/>the tickets"}
238
+ GATE --> IMP["One implementer per ticket<br/>api, web, app, infra, ui<br/>test-first with tdd"]
239
+ IMP --> INT["Integrate"]
240
+ INT --> VER["verifier agent<br/>Spec axis, then Standards axis"]
241
+ VER -- "auth, secrets, PII, payments" --> SEC["security agent"]
242
+ VER --> CMT["Commit and report"]
243
+ SEC --> CMT
244
+ CMT -- L --> RET["retro<br/>lessons become hooks or rules"]
245
+
246
+ classDef you fill:#fef3c7,stroke:#d97706,color:#78350f
247
+ classDef agent fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
248
+ classDef guard fill:#dcfce7,stroke:#16a34a,color:#14532d
249
+ class GRL,GATE you
250
+ class QK,PRD,ARC,IMP,VER,SEC agent
251
+ class STOP guard
252
+ ```
253
+
254
+ <sub>Amber: steps with you. Blue: agents. Green: hooks.</sub>
255
+
256
+ | Agent | Does | Mode |
257
+ |---|---|---|
258
+ | `quick` | S work: one file, one-sentence diff | writes |
259
+ | `product` | Plan: scope, stories, acceptance criteria, test seams, tickets | read-only |
260
+ | `architect` | Boundaries, contracts, failure modes; verdict and ADR proposal | read-only |
261
+ | `api` | Server side: APIs, domain, persistence, workers (`arch-api`, `arch-db`, `tdd`) | writes |
262
+ | `web` | Next.js app (`arch-web`, `tdd`) | writes |
263
+ | `app` | React Native / Expo app (`arch-app`, `tdd`) | writes |
264
+ | `infra` | Terraform, CI, deploy, observability; never applies | writes |
265
+ | `ui` | Design system, tokens, components, motion, accessibility | writes |
266
+ | `verifier` | Fresh-context check of the diff against the plan; runs lint, build, tests | no edits |
267
+ | `security` | Auth, secrets, PII, payments, public routes; severity report | read-only |
268
+
269
+ **Guardrails enforced by hooks**
270
+
271
+ | Hook | Effect |
272
+ |---|---|
273
+ | `guard-bash` | Blocks `git push`, `git add -A` / `.`, staging `.env*`, `--no-verify`, `reset --hard`, `clean -f`, PR and issue writes |
274
+ | `orchestrator-guard` | The main session can edit plans, ADRs and docs, never code |
275
+ | `readonly-guard` | Read-only agents can only write their own memory |
276
+ | `stop-verify` | Lint and tests on Stop when the tree changed |
277
+ | `context-budget` | Warns at 60k, 90k and 110k tokens; compaction at 120k |
278
+ | `post-compact` | Re-injects the active plan after compaction |
279
+ | `graphify-update` | Refreshes the code graph after edits, when one exists |
280
+ | `config-update-check` | One line at session start when a local-clone install has a newer version |
281
+
282
+ The full flow, with every section of the playbook, is in [`.claude/skills/orchestrate/SKILL.md`](.claude/skills/orchestrate/SKILL.md). A five-minute walkthrough is in [docs/quickstart.md](docs/quickstart.md).
283
+
284
+ ---
285
+
286
+ ## Command reference
287
+
288
+ ### `chew` (global)
289
+
290
+ | Command | What it does |
291
+ |---|---|
292
+ | `chew agents setup:pull [dir]` | Download the latest version and install or refresh it in `dir` (default: current folder) |
293
+ | `chew agents setup:check [dir]` | Compare the project with the latest version; exit code 1 on drift |
294
+ | `chew --version` | Version of the installed `chew` |
295
+
296
+ | Flag | Effect |
297
+ |---|---|
298
+ | `--force` | Also overwrite files edited in the project (protected files excepted) |
299
+ | `--dry-run` | Show what would change, write nothing |
300
+ | `--local` | Use the version installed with `chew` instead of downloading |
301
+
302
+ ### `agents` (same package, via `npx` or global)
303
+
304
+ | Command | What it does |
305
+ |---|---|
306
+ | `agents init [dir]` | Copy what is missing; never overwrites |
307
+ | `agents update [dir]` | Same rules as `setup:pull`, using this installed version |
308
+ | `agents check [dir]` | Report missing, outdated, edited and obsolete files; exit 1 on drift |
309
+
310
+ ### Legacy, from a local clone
311
+
312
+ ```bash
313
+ node <agents-repo>/scripts/bootstrap.mjs <project> # init
314
+ node <agents-repo>/scripts/bootstrap.mjs <project> --check # drift report
315
+ node scripts/bootstrap.mjs --sync # inside the project: pull the clone, then update
316
+ ```
317
+
318
+ ---
319
+
320
+ ## Release pipeline
321
+
322
+ Every push to `main` that changes a shipped file (`.claude/**`, `AGENTS.md`, `bin/`, `scripts/`, `templates/`, `package.json`, `VERSION`, the workflow itself) publishes a new version. Pushes that change only files the package does not ship (this README, the CHANGELOG, most of `docs/`) do not.
323
+
324
+ ```mermaid
325
+ flowchart LR
326
+ PUSH["push to main"] --> CHK["check.yml<br/>hooks and scripts parse<br/>JSON, catalog, em-dash scan<br/>npm test, npm pack"]
327
+ CHK --> VER["release-version.mjs<br/>picks X.Y.Z"]
328
+ VER --> PUB["npm publish<br/>npmjs.com"]
329
+ PUB --> TAG["git tag vX.Y.Z"]
330
+ TAG --> GHR["GitHub release<br/>notes from commits"]
331
+ GHR --> GPK["GitHub Packages<br/>same version"]
332
+ ```
333
+
334
+ **Versioning**
335
+
336
+ | You want | Do |
337
+ |---|---|
338
+ | A patch | Nothing. The pipeline publishes the next free patch after the highest published version |
339
+ | A minor or major | Set the new base in `package.json` and `VERSION` (they must match) and push |
340
+
341
+ The bumped version exists only in the published package and the tag; the repo keeps the base.
342
+
343
+ **Credentials**
344
+
345
+ | Target | Auth |
346
+ |---|---|
347
+ | npmjs.com | npm trusted publishing (OIDC, no secret), or the `NPM_TOKEN` repository secret until it is configured |
348
+ | GitHub Packages | The workflow's own `GITHUB_TOKEN`; nothing to set up |
349
+
350
+ `.npmrc` pins the public registry and reads the token only from the `NPM_TOKEN` environment variable. Never write a token into it: the file is committed.
351
+
352
+ ---
353
+
354
+ ## Maintaining this repo
355
+
356
+ ```bash
357
+ npm test # CLI round trip: init, check, update, force, obsolete files, chew from a tarball
358
+ npm run check # skills on disk vs catalog vs lock vs agents vs hooks
359
+ node scripts/skills-update.mjs # vendored skills that changed upstream
360
+ ```
361
+
362
+ - Every change that ships gets an entry in [CHANGELOG.md](CHANGELOG.md) in the same commit.
363
+ - Decisions about the config itself go to [docs/adr/](docs/adr/).
364
+ - Adding a skill: [docs/skills.md](docs/skills.md), section "Adding a skill"; `npm run check` lists what is missing.
365
+ - CI rejects em dashes in our own prose (`AGENTS.md`, docs, agents, rules, own skills).
366
+
367
+ ```text
368
+ agents/
369
+ ├── .claude/ the config that ships (agents, hooks, rules, skills, settings.json)
370
+ ├── .github/workflows/ check.yml (CI) and release.yml (publish)
371
+ ├── bin/ chew.mjs (global CLI), agents.mjs (package CLI)
372
+ ├── scripts/ bootstrap.mjs (install engine), release-version.mjs, tests, catalog and skills checks
373
+ ├── templates/ CLAUDE.project.md for new projects
374
+ ├── docs/ quickstart, catalog, ADRs, plan and brief templates
375
+ ├── AGENTS.md shared base
376
+ └── CHANGELOG.md
377
+ ```
378
+
379
+ ---
380
+
381
+ ## Troubleshooting
382
+
383
+ <details>
384
+ <summary><b><code>chew: command not found</code> after the global install</b></summary>
385
+
386
+ The npm global bin folder is not on your `PATH`. `npm prefix -g` shows the prefix; on Windows the binaries live in `%AppData%\npm`, on macOS and Linux in `<prefix>/bin`. Add it to `PATH` and open a new terminal.
387
+
388
+ </details>
389
+
390
+ <details>
391
+ <summary><b>The global install fails with E401 or asks for another registry</b></summary>
392
+
393
+ Your user `.npmrc` points to a private feed (for example Azure Artifacts). Install from the public registry:
394
+
395
+ ```bash
396
+ npm install -g @evertonferreira96/agents --registry https://registry.npmjs.org/
397
+ # or, once:
398
+ npm config set @evertonferreira96:registry https://registry.npmjs.org/
399
+ ```
400
+
401
+ `chew` itself already pins the public registry when it downloads updates.
402
+
403
+ </details>
404
+
405
+ <details>
406
+ <summary><b>A file I changed was not updated</b></summary>
407
+
408
+ By design: files edited in the project are kept and listed as `edited`. `chew agents setup:check` shows them. To adopt the config version, run `chew agents setup:pull --force` (try `--dry-run` first).
409
+
410
+ </details>
411
+
412
+ <details>
413
+ <summary><b>"no manifest from a previous install"</b></summary>
414
+
415
+ The project was configured by copy before the manifest existed, so every differing file looks edited. Review the list, then run `chew agents setup:pull --force` once. From then on, updates are automatic.
416
+
417
+ </details>
418
+
419
+ <details>
420
+ <summary><b>No internet, or npm is blocked</b></summary>
421
+
422
+ `setup:pull` warns and installs the version that came with `chew`. Use `--local` to do that on purpose.
423
+
424
+ </details>
4
425
 
5
- - Orchestrator playbook: `.claude/skills/orchestrate/SKILL.md`
6
- - Roster: `.claude/agents/` (quick, product, architect, api, web, app, infra, ui, verifier, security)
7
- - Deterministic guards: `.claude/hooks/` wired in `.claude/settings.json`
8
- - `skills-lock.json` records origin, hash and adaptation of every vendored skill; `node scripts/skills-update.mjs` checks them against upstream; catalog in [docs/skills.md](docs/skills.md)
9
- - Decisions about this config: [docs/adr/](docs/adr/); history: [CHANGELOG.md](CHANGELOG.md)
10
- - Put it in a project: `npx @evertonferreira96/agents init` in the project root (or `npx github:EvertonFerreira96/agents init` straight from the repo); keep it current with `npx @evertonferreira96/agents@latest update` and look for drift with `npx @evertonferreira96/agents check`. A local clone still works: `node scripts/bootstrap.mjs <project-root>`. First task: [docs/quickstart.md](docs/quickstart.md)
11
- - Release: every push to `main` that changes what the package ships runs `.github/workflows/release.yml`: the full check, then `npm publish`, the `vX.Y.Z` tag and the GitHub release. Patches are automatic; for a minor or major, bump `version` in `package.json` and `VERSION` together. Add the CHANGELOG entry in the same commit
426
+ ---
12
427
 
13
- ## Docs
428
+ ## Further reading
14
429
 
15
- How to bootstrap a project with this config, and what each piece does: [docs/](docs/README.md)
430
+ | Document | Covers |
431
+ |---|---|
432
+ | [docs/quickstart.md](docs/quickstart.md) | First task in five minutes |
433
+ | [docs/README.md](docs/README.md) | Every file in the config, bootstrap details, common questions |
434
+ | [docs/skills.md](docs/skills.md) | Skill catalog: invocation and who uses each one |
435
+ | [docs/adr/](docs/adr/) | Why the config is the way it is |
436
+ | [AGENTS.md](AGENTS.md) | The shared base every agent reads |
437
+ | [CHANGELOG.md](CHANGELOG.md) | What changed, by version |
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.1.2
package/bin/chew.mjs ADDED
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env node
2
+ // chew: the global CLI. Install once with `npm install -g @evertonferreira96/agents`, then, in any project:
3
+ // chew agents setup:pull [dir] download the latest published config and install it (fresh project: everything;
4
+ // configured project: refresh what was not edited there, add new files, drop obsolete ones)
5
+ // chew agents setup:check [dir] compare the project with the latest published config; exit 1 on drift
6
+ // Flags: --force (also overwrite files edited in the project), --dry-run (show, write nothing),
7
+ // --local (use the version installed with chew instead of downloading the latest).
8
+ // The install runs with the engine of the downloaded version, so an old global chew still applies the newest setup.
9
+ // Test hook: CHEW_TARBALL=<path to a .tgz from `npm pack`> replaces the registry download.
10
+ import { spawnSync } from "node:child_process";
11
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
12
+ import { tmpdir } from "node:os";
13
+ import { dirname, join, resolve, sep } from "node:path";
14
+ import { fileURLToPath, pathToFileURL } from "node:url";
15
+ import { gunzipSync } from "node:zlib";
16
+
17
+ const ROOT = fileURLToPath(new URL("..", import.meta.url));
18
+ const SELF = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8"));
19
+ const REGISTRY = SELF.publishConfig?.registry || "https://registry.npmjs.org/";
20
+ const SCOPE = SELF.name.startsWith("@") ? SELF.name.split("/")[0] : null;
21
+
22
+ const HELP = `chew ${SELF.version}
23
+
24
+ Usage: chew agents <command> [dir] [--force] [--dry-run] [--local]
25
+
26
+ Commands:
27
+ setup:pull download the latest ${SELF.name} and install it into the project
28
+ (fresh project: everything; configured project: refresh what was not edited there)
29
+ setup:check compare the project with the latest published config (exit 1 on drift)
30
+
31
+ Flags:
32
+ --force also overwrite files edited in the project
33
+ (CLAUDE.md, .claude/settings.local.json and .claude/agent-memory/** are never touched)
34
+ --dry-run show what would change without writing
35
+ --local use the version installed with chew (${SELF.version}) instead of downloading
36
+
37
+ dir defaults to the current directory.`;
38
+
39
+ // npm pinned to the public registry, also for the package scope, so a user npmrc pointing elsewhere (Azure feed) does not matter.
40
+ function npm(args, cwd) {
41
+ const full = [...args, "--registry", REGISTRY, ...(SCOPE ? [`--${SCOPE}:registry=${REGISTRY}`] : [])];
42
+ const win = process.platform === "win32";
43
+ const r = spawnSync(win ? "npm.cmd" : "npm", full, { cwd, encoding: "utf8", shell: win });
44
+ if (r.status !== 0) throw new Error((r.stderr || r.stdout || r.error?.message || "npm failed").trim().split("\n").slice(-3).join(" | "));
45
+ return r.stdout;
46
+ }
47
+
48
+ // Minimal reader for an npm tarball (ustar, GNU long names, pax paths); strips the leading "package/" and keeps file modes.
49
+ function untar(gz, dest) {
50
+ const buf = gunzipSync(gz);
51
+ const root = resolve(dest);
52
+ let off = 0, longName = null;
53
+ while (off + 512 <= buf.length) {
54
+ const h = buf.subarray(off, off + 512);
55
+ if (h.every((b) => b === 0)) break;
56
+ const field = (start, len) => h.subarray(start, start + len).toString("utf8").replace(/\0[\s\S]*$/, "");
57
+ let name = field(0, 100);
58
+ const prefix = field(345, 155);
59
+ if (prefix) name = `${prefix}/${name}`;
60
+ const size = parseInt(field(124, 12).trim() || "0", 8);
61
+ const mode = parseInt(field(100, 8).trim() || "644", 8) & 0o777;
62
+ const type = String.fromCharCode(h[156] || 48);
63
+ off += 512;
64
+ const data = buf.subarray(off, off + size);
65
+ off += Math.ceil(size / 512) * 512;
66
+ if (type === "L") { longName = data.toString("utf8").replace(/\0[\s\S]*$/, ""); continue; }
67
+ if (type === "x") { const m = /\d+ path=([^\n]+)\n/.exec(data.toString("utf8")); if (m) longName = m[1]; continue; }
68
+ if (longName) { name = longName; longName = null; }
69
+ if (type !== "0" && type !== "7") continue; // directories are created on demand; npm tarballs carry no links
70
+ const out = resolve(root, name.replace(/^package\//, ""));
71
+ if (!out.startsWith(root + sep)) throw new Error(`unsafe path in tarball: ${name}`);
72
+ mkdirSync(dirname(out), { recursive: true });
73
+ writeFileSync(out, data, { mode: mode || 0o644 });
74
+ }
75
+ }
76
+
77
+ // Directory with the config to install: the latest published version, or the one installed with chew.
78
+ function fetchConfig(local) {
79
+ const installed = { dir: ROOT, version: SELF.version, cleanup() {} };
80
+ if (local) return installed;
81
+ const tmp = mkdtempSync(join(tmpdir(), "chew-"));
82
+ const cleanup = () => rmSync(tmp, { recursive: true, force: true });
83
+ try {
84
+ let tarball = process.env.CHEW_TARBALL;
85
+ if (!tarball) {
86
+ const packed = JSON.parse(npm(["pack", `${SELF.name}@latest`, "--pack-destination", ".", "--json"], tmp));
87
+ tarball = join(tmp, packed[0].filename);
88
+ }
89
+ const dir = join(tmp, "package");
90
+ untar(readFileSync(tarball), dir);
91
+ const version = JSON.parse(readFileSync(join(dir, "package.json"), "utf8")).version;
92
+ return { dir, version, cleanup };
93
+ } catch (e) {
94
+ cleanup();
95
+ console.warn(`could not download the latest ${SELF.name} (${e.message}); using the installed ${SELF.version}`);
96
+ return installed;
97
+ }
98
+ }
99
+
100
+ const newer = (a, b) => { const x = a.split(".").map(Number), y = b.split(".").map(Number); for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i] > y[i]; return false; };
101
+
102
+ async function main(argv) {
103
+ const flags = new Set(argv.filter((a) => a.startsWith("-")));
104
+ const [ns, command, dir] = argv.filter((a) => !a.startsWith("-"));
105
+ if (flags.has("--version") || flags.has("-v")) { console.log(SELF.version); return 0; }
106
+ if (!ns || ns === "help" || flags.has("--help") || flags.has("-h")) { console.log(HELP); return ns ? 0 : 2; }
107
+ if (ns !== "agents" || !["setup:pull", "setup:check"].includes(command)) {
108
+ console.error(`unknown command: chew ${[ns, command].filter(Boolean).join(" ")}\n\n${HELP}`);
109
+ return 2;
110
+ }
111
+
112
+ const target = resolve(dir ?? process.cwd());
113
+ if (!existsSync(target)) throw new Error(`target is not a directory: ${target}`);
114
+ try { if (JSON.parse(readFileSync(join(target, "package.json"), "utf8")).name === SELF.name) throw new Error("this is the config repo itself; run chew inside a project"); }
115
+ catch (e) { if (e.message.startsWith("this is the config repo")) throw e; }
116
+
117
+ const config = fetchConfig(flags.has("--local"));
118
+ try {
119
+ const engine = await import(pathToFileURL(join(config.dir, "scripts", "bootstrap.mjs")).href);
120
+ const mode = command === "setup:check" ? "check" : "update";
121
+ const r = engine.install({ src: config.dir, target, mode, force: flags.has("--force"), dryRun: flags.has("--dry-run") });
122
+ engine.report(r);
123
+ if (mode === "update" && !flags.has("--dry-run") && r.written.some((w) => w.startsWith("CLAUDE.md"))) {
124
+ console.log("\nnext: fill in purpose and commands in CLAUDE.md; optional: `npx impeccable install` (for /ui) and `/graphify .`");
125
+ }
126
+ if (newer(config.version, SELF.version)) console.log(`\nchew itself is ${SELF.version} (latest ${config.version}); update it with: npm install -g ${SELF.name}@latest`);
127
+ return mode === "check" && engine.drift(r) ? 1 : 0;
128
+ } finally {
129
+ config.cleanup();
130
+ }
131
+ }
132
+
133
+ main(process.argv.slice(2)).then((code) => { process.exitCode = code; }, (e) => { console.error(e.message); process.exitCode = 2; });
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@evertonferreira96/agents",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Personal agents, rules, hooks and skills for Claude Code (portable to Cursor and Codex through AGENTS.md), installable into any project.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "agents": "bin/agents.mjs"
7
+ "agents": "bin/agents.mjs",
8
+ "chew": "bin/chew.mjs"
8
9
  },
9
10
  "files": [
10
11
  "bin/",
@@ -168,7 +168,7 @@ function legacy(argv) {
168
168
  const info = readJson(join(process.cwd(), SOURCE_FILE));
169
169
  if (!info) { console.error(`--sync needs ${SOURCE_FILE} (written by a previous install)`); return 2; }
170
170
  if (!info.source || !existsSync(info.source)) {
171
- console.log(`This project was installed from the ${info.package || "config"} package. Update it with: npx ${info.package || "<package>"}@latest update`);
171
+ console.log(`This project was installed from the ${info.package || "config"} package. Update it with: chew agents setup:pull (global CLI) or npx ${info.package || "<package>"}@latest update`);
172
172
  return 0;
173
173
  }
174
174
  src = info.source; target = process.cwd();