Commit Graph

12 Commits

Author SHA1 Message Date
Ignacy Łątka c876fe8dbc ci: add knip anti-dead-code gate (#572)
## What

Adds a CI gate for dead code, using [Knip](https://knip.dev).

We didn't have one. ESLint's `no-unused-vars` only sees unused locals
inside a module, so whole files nobody imports, exports nobody uses, and
dependencies declared but never required all slipped through. Knip
covers that gap in one tool (roughly `ts-prune` + `depcheck` +
`unimported` combined).

The new `.github/workflows/knip.yml` workflow ("Dead Code", job `Knip`)
runs `npm run knip` on push and PR to `main`. `CONTRIBUTING.md`'s
pull-request checklist gains it as a step, with the reason it has to run
against a tree with no build output. Knip reads source directly, mapping
each package's `dist` entry back to `src`, so the job needs no workspace
build. It installs with `--ignore-scripts`: an analysis that only reads
`package.json` and source gains nothing from the seven packages here
that carry a native install script, six of which reach this runner
(`fsevents` is darwin-only). Five try a prebuilt binary before
compiling, and `dtrace-provider` always runs node-gyp but ends in `||
node suppress-error.js` — and off mac/solaris/freebsd its `binding.gyp`
picks a stub target with no sources, so on ubuntu-latest node-gyp builds
nothing for it and `node-pty` is the only package that reaches a
compiler at all — an `optionalDependency`, whose failure npm ignores.
The exposure is a bump that leaves a package with no prebuild for the
runner, which is exactly what `dependabot.yml` pins `tree-sitter`
against (0.25.0 ships no Node 24 prebuild and its compile breaks `npm
ci`).

While adding it I checked whether any other installed checker was
missing a gate. None were: ESLint (`lint.yml`), Prettier (`format.yml`),
`tsc` build + `typecheck:tests` + `typecheck:scripts` + vitest
(`unit-tests.yml`), the workspace-version / focused-test / merge-marker
checks (`repo-hygiene.yml`) and lockfile sync (`lockfile.yml`) are all
already enforced. Dead code was the only gap.

## How it's enforced

`npm run knip` is two invocations:

```
knip --exclude exports,types && knip --include exports,types,classMembers,nsExports,nsTypes --max-issues 206
```

Every issue type knip reports is an error — that is its default
severity, so `knip.jsonc` sets no `rules` block at all. The pre-existing
backlog is bounded by the `--max-issues` ceiling on the second pass
instead of being hidden behind a non-failing severity: it cannot grow
unnoticed, and the number comes down as follow-up passes pay it off.
When a change legitimately adds an export nothing imports yet, the
number moves up in the same commit — that bump is what review should
see, rather than the contributor deleting someone else's backlog to make
room. Two PRs making that identical one-line edit merge without
conflicting, so `main` lands one short of the truth and its
push-triggered run is what catches it. Splitting the passes also keeps a
real failure out from under the backlog — the enforced classes report on
their own, and the second pass only runs once the first is clean.

The second pass names `classMembers`, `nsExports` and `nsTypes`
explicitly because they sit in knip's own `defaultExcludedIssueTypes`:
`--exclude exports,types` never covered them and `--include
exports,types` did not ask for them, so a new unused class member cost
nothing and moving an export behind opaque namespace use took it out of
the ceiling's scope entirely. `classMembers` has 12 findings on the
unbuilt tree the ceiling counts (9 on a built one); the other two have
none today. None of the three is gated at zero — all five parked classes
share the one ceiling, so a new `nsExports` finding passes if something
else in the backlog is retired in the same PR. Naming `classMembers`
also turns knip's `isSkipLibs` off (`create-options.js:122`), which
makes it search declaration files for external references and
**suppresses** 44 export/type findings the same run reports without it —
57/181 becomes 44/150. That, not the 12 findings it adds, is why the
ceiling is lower than the two counts alone would suggest;
`--include-libs` with no `classMembers` reproduces 44/150 exactly.

The ceiling counts what CI counts, against a tree with no build output.
Whether a workspace's `main`/`types` path resolves to an existing file
changes the result, and not by way of anything in that file — a
zero-byte `packages/registry/dist/index.js` moves the count just as a
real build does, while a zero-byte `dist/zzz.js` does nothing. A built
tree reports fewer issues — 43/147/9 = 199 built, against 44/150/12 =
206 unbuilt — so `npm run knip` passing after a local `tsc --build` is
not evidence the job will pass, which is how the Knip job first went red
on 199. The ceiling is 206 and it is exact: 206 passes, 205 fails.
Remove every `dist/` before trusting a number from it.

The ceiling is one total across those classes, so it bounds the backlog
rather than each class: paying off one parked export licences one new
one elsewhere in the same PR. Knip has no per-class ceiling.

`treatConfigHintsAsErrors` is on, so an exemption that stops suppressing
anything fails the job rather than printing a hint below the backlog.

## Config

`knip.jsonc` documents the false positives so the gate is both green and
meaningful.

- **`@swmansion/argent`** is a thin dispatcher whose real payload is an
esbuild bundle of the other workspaces
(`packages/argent/scripts/bundle-tools.cjs`). Knip can't follow imports
across that boundary, so packages consumed only by the bundled-in code
read as unused; 11 are exempted by name here, though not all for that
reason — the http3-quiche transport is pulled in by
`@fails-components/webtransport` itself at runtime, and no first-party
file imports it. They're exempted **by name**, not behind a catch-all:
`ignoreDependencies` also suppresses `unlisted`, and a pattern that
matches everything would take this workspace's own `src/` and `scripts/`
out of that check — where an import with no matching declaration is a
genuine bug rather than a bundler artifact. (It reaches `unresolved`
only nominally: knip matches the list against a package name parsed out
of the specifier, and an unresolvable relative path yields none, so even
a catch-all leaves those reported. `ignoreUnresolved` is the option that
does silence them.)
- **`tool-server`** reaches a few packages in ways static analysis can't
see. The exemptions cover two distinct classes, kept apart in the config
so each stated reason matches the finding it silences:
- *declared but loaded too dynamically to detect* (would be reported as
**unused dependencies**): `tree-sitter` / `tree-sitter-typescript` via
dynamic `_require`, and the http3-quiche transport as a runtime
companion of `@fails-components/webtransport`;
- *imported but deliberately absent from that `package.json`* (would be
reported as **unlisted dependencies**): `sharp` (kept out so the ~30 MB
native binary isn't a cost every consumer pays) and `electron` (an
`optionalDependency` of the published package at one call site, the
Electron main-process API at the other).
- Root `scripts/`: knip resolves those from `package.json` scripts
**and** from `run:` steps in `.github/workflows` via its github-actions
plugin — multi-line `run: |` blocks included. What the plugin doesn't
parse is a PowerShell body, so `scripts/ci/windows-chromium-e2e.mjs` —
invoked at `windows-e2e.yml:176`, in the `windows-latest` job where
PowerShell is the default shell — is the one entry declared by hand.

### Known limits

- `ignoreDependencies` exempts a package **name across its whole
workspace**, in both directions; knip has nothing narrower. While
`sharp` is listed for `tool-server`, an undeclared `import "sharp"`
anywhere in that workspace passes too. The list is held to packages that
genuinely cannot be declared, for exactly that reason.
- Knip analyzes JavaScript and TypeScript only (`DEFAULT_EXTENSIONS`),
so **only 11 of the 30 files under `scripts/` are in the gate at all**.
The other 19 — 15 `.sh` files, `e2e-full/lib/report.py`,
`ci/e2e-chromium-page.html`, and e2e-full's `README.md` and
`results/.gitignore` — are invisible to it however they are reached —
including the whole `scripts/e2e-full/` harness (13 files), invoked from
nothing outside its own directory, its only mention elsewhere being a
`.prettierignore` entry.
- Reachability's widest hole is the workspace entry. Whatever
`src/index.ts` re-exports is reached, so a single `export { x } from
"./dead-module";` takes that module out of `files` altogether — verified
against a module reported as an unused file one run earlier. The same
line applied to parked exports retires them from the count without
deleting anything: re-exporting `equalsCI`, `includesCI` and `Settled`
from `tool-server`'s entry takes 206 to 203 and frees three ceiling
slots. The ceiling bounds what knip can see, not what is dead.
- Test files are entry points, so a production module kept alive only by
its own test still passes `files`. Being reached by a test says nothing
about being reachable in production, which is what the check
approximates, and two modules sit in exactly that gap: `tools/paste` is
imported by two test files and registered by nothing, so it is
unreachable over MCP and HTTP, and `utils/ios-profiler/lifecycle.ts` is
a re-export barrel whose header keeps it "for source compatibility with
existing imports", of which its own test is now the only one — its four
production consumers all import `profiler-shared/lifecycle` directly.
That single test import is exactly what keeps `files` from reporting it:
repoint it and knip returns `Unused files (1)`. `knip --production` is
the lens that reports the class; the repo isn't clean under it — 13
unused files (those two, seven root scripts,
`packages/argent/scripts/{bundle-tools,observe,sync-readme}.cjs` and
`packages/registry/tests/helpers.ts`) with the export/type backlog at
227/206 — measured with `classMembers` off, so comparable to the 57/181
above rather than to the ceiling's 44/150 — so a production-mode pass is
a follow-up of its own.
- The same entry treatment hides the test trees themselves. Root
`package.json`'s `test:scripts` runs `node --test`, which is what makes
knip's node plugin contribute its test entries, and that plugin
registers four entry patterns in **every** workspace: `**/test/**`, plus
`*{.,-,_}test.<ext>`, `test-*.<ext>` and `test.<ext>` matched anywhere.
So none of the **380** JS/TS files under `packages/*/test/` can be
reported unused, and under `packages/*/tests/` only a name none of those
three match can — of today's 15 that is `registry/tests/helpers.ts`
alone, which is why it is the lone `tests/` entry in the `--production`
list above. A `tests/test-utils.ts` or `tests/test.ts` would be just as
invisible. A dead `zz.test.ts` planted in `packages/registry/tests/`
passes; a dead `zz-helper.ts` in the same directory fails.
- `knip@5.88.1` is the last 5.x release ever published — 6.0.0 landed
the next day and `latest` is now 6.x. The pin is deliberate: knip 6
removed the `classMembers` issue type outright, and the shipped script
hard-errors on it (`ERROR: Invalid issue type: classMembers`, exit 2).
Upgrading is therefore not a version bump — it means re-deriving the
ceiling, rewriting the paragraph that explains it, and resolving the
pass-1 findings knip 6 adds (six unlisted binaries: `tail`, `plutil`,
`xctrace`, `netstat`, `pkill`, `pgrep`; two unresolved imports:
`/usr/bin/osascript`, `/usr/libexec/PlistBuddy`). Dependabot's
`npm-minor-patch` group cannot move it, since no 5.x exists above
5.88.1; the `npm-major` group will keep offering the 6.x jump.
- Knip folds `optionalDependencies` into the declared set, so importing
`electron` or `node-pty` is not `unlisted` — they pass because it read
them. What it never reports is an optionalDependency nothing uses, so
only that direction is outside the gate, along with `argent-cli`'s
runtime `req("node-pty")`, resolved through a
`createRequire(import.meta.url)` in `lens-pty.ts`, which knip can't see
either.

## Dead code this found

- `@types/semver` and `@argent/telemetry` from `packages/argent` — both
were being suppressed by exemptions whose stated bundler-boundary reason
could not apply. esbuild strips types and never resolves `@types/*`, and
`packages/argent` names `semver` nowhere; `@argent/telemetry` is aliased
to an absolute source path by `bundle-tools.cjs`, so the declaration
bought nothing. Removing them is proved safe by building: four of the
five bundles still emit and telemetry is inlined in each with no runtime
`require` of it. The fifth, preview-window's main, sits behind a
downloaded native asset absent from a script-free install, so it is
covered by mechanism rather than observation — `bundle-tools.cjs` passes
the same `alias` map to every build, and esbuild's `alias` bypasses node
resolution entirely: deleting `node_modules/@argent/telemetry` outright
leaves all four emitted bundles byte-identical.
Removed alongside, but **not** gate-reported: the `benchmark` npm script
in `packages/argent`, whose `scripts/benchmark.cjs` does not exist. A
dangling script target is a blind spot of this gate — restoring the
script leaves knip at exit 0.

- Four files with no importers: the `render-hook` injected script,
react-profiler's `source-map` + `snippet-extractor`, and
`scripts/setup-in-workspace.cjs` (referenced nowhere, and its own usage
string names a `scripts/setup-project.cjs` that doesn't exist).
- `superagent` in `tool-server`'s `test/http-streaming.test.ts` —
imported for a type but declared in no manifest in the repo, resolving
only through npm hoisting of supertest's own dependency. Reverting that
import to `superagent` reports `Unlisted dependencies (1) … :3:32` and
exits 1; `@types/supertest/index.d.ts:10` re-exports the identical type
(`type Response = superagent.Response`).
- `@clack/prompts` and `@moq/lite` from `tool-server`'s `package.json` —
declared, imported nowhere. (`@moq/net` is the one actually used;
`@moq/lite` isn't a peer of it.)

Also deleted here, but **not** something this gate reported:
`src/tools/simulator/simulator-server.ts`. It sat in the `files` blind
spot above — `interaction-messages.test.ts` imported it, which is enough
to count as reached. `setup-registry.ts` never registers it, so it was
unreachable over MCP and HTTP, and that catalog roll-call was its only
reference; the import and the catalog count (77 → 76) went with it.

## Lockfile

Adding knip moves two transitive packages: `@napi-rs/wasm-runtime` 1.1.5
→ 1.1.6 and `@tybys/wasm-util` 0.10.2 → 0.10.3. Knip pulls in
`@oxc-resolver/binding-wasm32-wasi`, which floors
`@napi-rs/wasm-runtime` at `^1.1.6`, lifting the hoisted entry that had
resolved to 1.1.5 for `@rolldown/binding-wasm32-wasi`'s `^1.1.4`, which
then cascades to `@tybys/wasm-util`. No runtime impact — both are
support packages for the wasm32-wasi fallback bindings, which don't get
installed where a native binding exists; `npm ls` reports both
extraneous on darwin-arm64. Those two are the only resolved versions
that change in the diff; the rest is knip's own tree (49 packages added)
and `@moq/lite` removed.

## Verification

- `npm run knip` exits 0.
- **Coverage of the enforced set.** With no `rules` block, knip's
included set is 15 issue types: ten enforced at zero tolerance by the
first pass, plus the five the second pass names under the ceiling.
Eleven mutations cover all ten, each flipping the gate to exit 1 and
back to 0 on revert:

  | class | mutation |
  | --- | --- |
| `files` | a throwaway `.mjs` in `scripts/`; a throwaway `.ts` in
`packages/argent/src` |
| `dependencies` | a bogus declared dep in
`packages/tool-server/package.json` |
  | `devDependencies` | a bogus declared devDep in the same manifest |
| `optionalPeerDependencies` | `bytes` moved to an optional
`peerDependency` there |
  | `unlisted` | an undeclared import in `packages/argent/src/cli.ts` |
  | `unresolved` | an unresolvable relative specifier in the same file |
| `binaries` | a root `package.json` script invoking a nonexistent
binary |
| `duplicates` | `export default sleep` alongside the existing named
export in `utils/timing.ts` |
| `enumMembers` | an unused member on an enum a second module imports
and uses |
| `catalog` | a `catalog` block in the root `package.json` naming a
package nothing uses |

One nuance worth recording: `duplicates` is narrower than it looks. It
reports a symbol exported both by name and as `default`
(`sleep|default`); a second *named* alias of the same symbol is not a
duplicate, and a freshly added symbol nothing references is not reported
at all — so several naive probes read as a miss.

`enumMembers` needed a shape the repo does not currently contain, not an
unreachable one. The only exported enum here, `ServiceState`, is
re-exported through `@argent/registry`'s entry, where knip counts
exports as used, so an unused member added to it is **not** reported —
but an enum in a non-entry module that another module imports and uses
does report one (`Blue ZzColor …/zz-enum.ts:3:3`, exit 1; deleting the
unused member returns exit 0). That is the ninth of the ten covered.

`catalog` is reachable here despite the repo using npm workspaces:
knip's catalog loader falls back to `manifest.catalog` when there is no
pnpm or yarn workspace file, so a `catalog` block in the root
`package.json` reports `Unused catalog entries (1)` and exits 1.
- The two new mechanisms discriminate as well: one extra unused export
takes the backlog to 207 and fails the ceiling, and a stale
`ignoreDependencies` entry produces `Configuration hints (1) … Remove
from ignoreDependencies` and exit 1.
- `npm ci --ignore-scripts` in a clean worktree, followed by `npx knip`,
is green — the job doesn't need any of the postinstalls it now skips.
- `tsc --build`, `typecheck:tests` (all workspaces),
`typecheck:scripts`, `test:scripts`, `eslint . --max-warnings 0`,
`prettier --check .` and the full workspace vitest suite (4711 passed, 3
skipped across 13 workspaces; 3070 of those are `@argent/tool-server`
alone) are green; `package-lock.json` is untouched by the review pass.
2026-08-10 17:09:16 +02:00
filip131311 9489a220b0 chore(argent): declare the Node version the package actually needs (#653)
Fixes #640.

```json
"engines": { "node": ">=20.12.0" }
```

## Why 20.12.0 and not 20

My first answer was `>=20` — CI's lowest tested line, satisfied by every
runtime dependency, and the
version the issue reporter (and I) installed successfully. **It is
wrong**, and shipping it would
have told npm it is safe to install onto runtimes where the CLI cannot
execute a single line.

The mistake was looking at first-party `src/`. The published artifacts
are four esbuild bundles with
their dependencies **inlined**, and that is where the real floor lives.
Confirmed in
`packages/argent/dist/`:

| blocker | added in | consequence below it |
|---|---|---|
| `import { styleText } from "node:util"` (via inlined `@clack/*`, in
`installer.mjs` and `cli-cmds.mjs`) | **20.12.0** | a **link-time**
`SyntaxError` — `argent init` and every CLI subcommand fail to load |
| `import.meta.dirname` at module scope (`bundled-paths.js`, `cli.js`) |
20.11.0 | `TypeError` during module init |

So 20.12.0 is the lowest version on which the package actually loads. It
is also the lowest line CI
exercises (7 workflows pin Node 20), and it satisfies every runtime
dependency's own constraint
(`@fails-components/webtransport` `>=20`, `@modelcontextprotocol/sdk`
`>=18`).

`electron`'s `>=22.12.0` does **not** apply — it is an
`optionalDependency`, and npm does not
engine-check those. Verified: installing `0.18.0` on Node 20.19.5
produces no `EBADENGINE` at all and
electron is never materialized.

I did not go to `>=22` on EOL grounds: it would refuse a runtime seven
CI workflows still test and
that users demonstrably run.

## Docs corrected alongside

- `README.md` said **20.11** — one minor below where the package can
load.
- `CONTRIBUTING.md` said **Node.js 18+** — predates what the repo's own
lint toolchain needs
  (`eslint@10` requires `^20.19.0 || ^22.13.0 || >=24`).

`packages/argent/README.md` is generated by `scripts/sync-readme.cjs` on
`prepack`, so it follows
automatically and is deliberately untouched.

## What a reviewer should know

**Enforcement is package-manager-dependent**, which matters because
`argent init` installs with
whichever PM the project uses:

| | behaviour on too-old Node |
|---|---|
| npm (default) | `npm warn EBADENGINE` — installs anyway |
| npm + `engine-strict=true` | hard error, install aborts |
| **yarn 1.x** | **hard error by default** |
| **pnpm** | **silently ignores it** |

So this achieves the issue's "warn" but only conditionally its "refuse".
There is no `.npmrc` in the
repo, so contributors are unaffected either way.

**Nothing in the repo validates `engines`** — `repo-hygiene`,
`check-workspace-versions.mjs` and
`pack-mcp.cjs` all ignore unknown manifest keys, and
`next-canary-version.mjs` round-trips the
manifest through `JSON.parse`/`stringify`, which preserves the new field
and stays prettier-clean.

**One inconsistency I did not resolve:**
`packages/argent/scripts/bundle-tools.cjs:383` builds with
`target: "node22"`, which now sits above the declared floor. In practice
the node20→node22 syntax gap
is negligible (tsc targets ES2022 and I found no post-20 *syntax*), so I
left the shipped artifact
alone rather than change build output in a manifest-only PR — but it
should either be lowered to
match or consciously kept. Your call.

## Separate bug found while verifying this

`ios-remote` is broken on the **entire Node 20 line**, independent of
this change: the inlined
`@moq/*` client uses `Promise.withResolvers` (Node 22+) ungated — 6
occurrences in
`dist/tool-server.cjs`, reached from `utils/moq-client.ts` via the
simulator-server blueprint.
Reproduced on Node 20.19.5: `TypeError: Promise.withResolvers is not a
function`.

I deliberately did **not** raise the floor to `>=22` to paper over it —
that would refuse Node 20 for
every user to hide one broken path. `moq-client.ts` already has an
`ensurePolyfill()` that polyfills
`WebSocket`/`WebTransport`; this belongs there. Filed separately.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-08-03 11:01:09 +02:00
Ignacy Łątka c5c3f7af11 feat: feature flags (#260)
## Summary

- New top-level CLI commands `argent enable <flag>` / `argent disable
<flag>` / `argent flags`.
- Storage is a small JSON file at `~/.argent/flags.json` by default;
`--scope project` writes to `<project-root>/.argent/flags.json` instead.
- `enable` writes `true`. `disable` removes the entry at the chosen
scope (and deletes the file + empty `.argent/` when it becomes the last
flag).
- `isFlagEnabled` walks project → global, so a project entry shadows the
same key in the global file. To opt a single project out of a
globally-enabled flag, hand-edit `<project>/.argent/flags.json` to
`{"flags":{"name":false}}` — there is no CLI for an explicit override.
- The dispatcher routes the new commands to `@argent/cli` exports;
bundling needs no config changes (only `node:fs|path|os`).

## Behaviour

- `argent enable foo` → writes `{ "flags": { "foo": true } }` to global
file.
- `argent disable foo` → removes `foo` from the global file (clean round
trip with `enable`).
- `argent enable foo --scope project` → writes to
`<project-root>/.argent/flags.json`.
- `argent flags` → lists effective state with the winning scope;
`--json` for scripting.
- Project root is the nearest ancestor with `.argent`, `.git`, or
`package.json` — falls back to cwd when none found.
- POSIX `--` separator: `argent enable -- weird-name` is supported.
- Help (`--help` / `-h`), invalid `--scope`, invalid flag names, missing
arg, unknown flags all produce a clear stderr error and `exit 2`.
- Flag-name validation: `/^[a-zA-Z][a-zA-Z0-9._-]*$/`.
- Writes are atomic (tmp file + rename) so concurrent invocations cannot
tear the JSON.

## Reader API

`isFlagEnabled(name)` is exported from `@argent/cli` for future
consumers; it walks project → global and returns `false` when unset.

## Files

- `packages/argent-cli/src/flags.ts` (new) — storage + CLI handlers
- `packages/argent-cli/src/index.ts` — re-exports `enable` / `disable` /
`flags` / `isFlagEnabled` / `FlagScope`
- `packages/argent-cli/test/flags.test.ts` (new) — 41 tests
- `packages/argent-cli/vitest.config.ts` (new)
- `packages/argent-cli/package.json` — description bump
- `packages/argent/src/cli.ts` — dispatcher routes + help text
- `README.md`, `CONTRIBUTING.md` — command tables

## Test plan

- [x] `npm test --workspaces --if-present` → 1085 tests pass (41 in
`@argent/cli`).
- [x] `npm run typecheck:tests --workspaces --if-present` → clean.
- [x] `npm run build` (root `tsc --build`) → clean.
- [x] `npm run build -w @swmansion/argent` (dispatcher + esbuild
bundles) → clean.
- [x] `npx prettier --check .` → clean.
- [x] End-to-end against the built dispatcher in a tmpdir:
- `argent enable my-feature-flag` (global) writes to
`~/.argent/flags.json`.
- `argent enable proj-only --scope project` writes to
`<proj>/.argent/flags.json`.
- `argent disable my-feature-flag` removes the entry; round trip leaves
a clean tree (file + empty `.argent/` removed when no other state lives
there).
  - `argent disable foo` preserves sibling flags in the same file.
  - `argent flags` lists each flag with the winning scope.
- `argent enable --` errors cleanly; `argent enable -- name-after`
accepts the literal.
- `argent enable foo --scope project` walks up to `.git` /
`package.json` / `.argent`; stray editor configs (`.claude`, `.cursor`,
…) do NOT count as project roots.
- Error paths (`--scope bogus`, no name, `1bad-name`) exit `2` with a
clear message.
2026-06-01 23:22:10 +02:00
filip131311 6355620c4d refactor(mcp): split mcp package into argent, argent-cli, argent-installer, argent-mcp, argent-tools-client (#162)
## Summary

- Splits the monolithic `packages/mcp` package into five focused
packages:
- `argent` — the user-facing CLI entry (`argent` binary,
init/uninstall/update flows, bundled-paths, scripts).
- `argent-cli` — CLI runner internals (flag-parser, run, server, tools).
- `argent-installer` — `init`/`update`/`uninstall` flows, MCP config
adapters, skills installer.
- `argent-mcp` — MCP server itself (mcp-server, tool-mapping,
auto-screenshot, content adapters).
  - `argent-tools-client` — HTTP client + launcher for the tool-server.
- Adds Gemini agent definitions, VSCode MCP config, and per-package
`tsconfig.json` / `vitest.config.ts`.
- Updates root `tsconfig.json`, `scripts/dev.cjs`, CI workflows, and
`package.json` to wire the new packages together.

## Test plan

- [ ] `npm install` resolves the new workspaces cleanly
- [ ] `npm run build` succeeds across all packages
- [ ] `npm test` passes (renamed test files keep their assertions)
- [ ] `npx argent init` still produces the expected MCP config in a
sample project
- [ ] `npx argent update` / `argent uninstall` exercise the installer
package
- [ ] CI workflows (`publish.yml`, `unit-tests.yml`) run on this branch
2026-04-28 08:36:24 +02:00
Paweł Fornagiel 82418eeb92 feat: AXService daemon, describe tool native-devtools path, and submodule bump (#98)
* feat: first implementation integration of ax-service for enhanced accessibility support

* feat: enhance ax-service with automation enablement and daemon management

* feat: export ensureAutomationEnabled function for improved accessibility automation in simulator server

* feat: add new build scripts for native development and update binary files

* feat: enhance describe tool to support native-devtools fallback and introduce DescribeResult interface

* chore: bump version to 0.4.1

* chore: bump argent-private to main; list ax-service in LICENSE

- Point packages/argent-private at current main and set submodule branch.
- Declare packages/native-devtools-ios/bin/ax-service as proprietary alongside simulator-server and dylibs.

Made-with: Cursor

* docs: document ax-service as proprietary across LICENSE and README

- Extend root README and CONTRIBUTING requirements (mcp README syncs from root at build).
- List bin/ax-service in @swmansion/argent and native-devtools-ios LICENSE.
- Assert ax-service is present and executable in publish workflow.

Made-with: Cursor

---------

Co-authored-by: Filip131311 <Filip.kaminski@swmansion.com>
2026-04-10 11:25:23 +02:00
Paweł Fornagiel 167ccf409d fix: move simulator-server binary to native-devtools-ios and fix esbuild bundling (#82)
* chore: update simulator-server path references across documentation and scripts

* feat: add native-devtools-ios entry to bundle-tools script
2026-04-07 18:25:48 +02:00
filip131311 d9dc17d0d5 fix: move describe to native devtools (#83)
* fix: move describe to native devtools

Replace the AX describe path with an app-scoped native hierarchy adapter so coordinate discovery stays on the supported injection flow.
Also add safer targeting and restart guidance to reduce ambiguous Home and cold-start recovery paths.

Made-with: Cursor

* init

* update sim server

* Delete NATIVE_DEVTOOLS_DESCRIBE_MIGRATION_NOTE.md
2026-04-07 17:36:56 +02:00
Filip131311 4e514c1435 chore: rename package from @software-mansion/argent to @swmansion/argent 2026-04-03 17:16:39 +02:00
Filip131311 8c2a313c76 chore: format codebase with Prettier
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 14:53:58 +02:00
Filip131311 47b66b3eed feat: add native devtools iOS — view hierarchy, network inspection, passive simulator watcher
- Adds native-devtools-ios package with pre-built dylibs (libNativeDevtoolsIos, libKeyboardPatch, libInjectionBootstrap)
- Passive simulator watcher injects dylibs into all booted simulators on tool-server startup, eliminating manual restart requirement
- Lazy network activation: NSURLProtocol interception is opt-in per bundleId via Control socket command, re-activated on reconnect
- New tools: native-find-views, native-full-hierarchy, native-network-logs, native-devtools-status
- dev script: non-fatal argent-private submodule (uses pre-built dylibs when source unavailable)
- Adds Prettier config and format script
- Updates CONTRIBUTING.md with npm run dev workflow and project structure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 14:53:45 +02:00
Paweł Fornagiel 60eaf83979 chore: clean up repository docs and deprecated files (#64)
* docs: update installation guide to streamline setup process and add local tarball installation instructions

* docs: remove INSTALL.md and RELEASING.md, update CONTRIBUTING.md to streamline release process, and adjust README.md to reflect changes

* chore: remove unused agentic files

* chore: remove deprecated configuration and script files, add new setup-in-workspace script for argent installation
2026-04-01 18:37:29 +02:00
Ignacy Łątka 66b5ace73e feat: Add community contributor files (#53)
* docs: add CONTRIBUTING.md, CODE_OF_CONDUCT.md, and CHANGELOG.md

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* docs: fix package name in CONTRIBUTING.md project structure table

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* docs: update CONTRIBUTING.md to reflect UI deprecation and skill renames

Remove @argent/ui references (package deleted), drop start:ui and full-stack
start description, remove ui from build/test examples, update simulator-server
requirement to reflect it is no longer bundled in the repo, and note the
argent-* prefix convention for skill files.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* rm changelog

* docs: replace custom CoC with Contributor Covenant v1.4

Aligns CODE_OF_CONDUCT.md to the standard used across Software Mansion
open source projects (react-native-svg). Direct copy of Contributor
Covenant v1.4 with enforcement email updated for this project.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* docs: replace custom Code of Conduct with Contributor Covenant v1.4

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 21:01:29 +02:00