Files
Ignacy Łątka bef319134e fix: add project_root arg to flow-start-recording (#114)
* fix: detect project root via .argent dir instead of git

Flow tools previously shelled out to 'git rev-parse --show-toplevel' to
locate the project root, which failed outside git repos and pulled flows
from the wrong place in nested worktrees. Walk up from cwd looking for
an existing .argent/ directory; fall back to cwd so the first
flow-start-recording still creates it in place.

* fix: skip $HOME when walking up for .argent/

The MCP launcher stores its own state in ~/.argent/ (logs, tool-server
pid). Without this guard, running any flow tool from anywhere under
$HOME — but outside a real project with its own .argent/ — would stop
at the home directory and treat it as the project root, mixing personal
MCP state with flow files.

* revert: restore getGitRoot in flow-utils

The walk-up .argent approach is fundamentally broken because the
tool-server is a singleton daemon — its process.cwd() is not the
calling project's cwd. Reverting both 882ed4c and 83e6ae6 so the
next commit can replace the resolution with an AsyncLocalStorage
context populated from an X-Argent-Project-Root header sent by the
argent mcp proxy.

This reverts commit 882ed4c.
This reverts commit 83e6ae6.

* feat: resolve project root via AsyncLocalStorage from mcp proxy header

The tool-server is a singleton daemon shared by every MCP client,
so its process.cwd() is never the calling project's cwd. The git-
root and walk-up heuristics both ran inside the daemon and silently
returned the wrong directory.

Shift the source of truth: the argent mcp proxy is per-client, so
it IS in the client's workspace. Stamp process.cwd() on every POST
to the tool-server as X-Argent-Project-Root, run invokeTool inside
an AsyncLocalStorage scope, and expose requireProjectRoot() to any
tool that needs it. Missing/malformed headers surface a clear error
from the getter instead of silently defaulting to the daemon's cwd.

- packages/mcp/src/mcp-server.ts: callTool sends the header
- packages/tool-server/src/request-context.ts: ALS module
- packages/tool-server/src/http.ts: extract header, wrap invokeTool
- packages/tool-server/src/tools/flows/flow-utils.ts: requireProjectRoot
- packages/tool-server/test/http-project-root.test.ts: integration tests
  covering present/missing/empty/malformed header, unicode roundtrip,
  non-flow tools bypassing the getter, and concurrent-request isolation

* refactor: profiler tools resolve project root via requireProjectRoot

Drop the project_root input parameter from react-profiler-analyze
and react-profiler-component-source. Both now pull the root from
the AsyncLocalStorage context stamped by the argent mcp proxy,
matching flow-utils.

Second caller for requireProjectRoot so the mechanism is exercised
beyond flows. If the ALS approach turns out flaky, these two tools
break in the same way as the flow tools and fail loudly with the
same clear error message.

Update the profiler skill doc to drop the project_root arg since
agents no longer pass it.

* test: stress-test AsyncLocalStorage projectRoot mechanism

Four comprehensive test files generated by a parallel swarm audit of
the new request-context.ts / requireProjectRoot() approach, covering
every realistic way it could break.

- test/als-propagation.test.ts — 15 tests documenting what Node's
  AsyncLocalStorage actually does under every async boundary the
  tool-server uses. Split into SAFE baselines (Promise chains,
  microtasks, timers, streams, synchronous EventEmitter emits,
  concurrent frames) and LATENT TRAPS (listeners fired outside
  an active frame, worker_threads, child_process). Serves as the
  authoritative reference for "can I read projectRoot here?".

- test/als-nested-invocation.test.ts — 10 tests proving propagation
  through registry.invokeTool nesting: two-level, deep (A→B→C→D→E),
  mixed sync/async, parallel fan-out, cross-request isolation,
  flow-execute style loops, error mid-chain, long chains, explicit
  runWithContext re-entry overrides.

- test/als-http-edge-cases.test.ts — HTTP boundary stress tests:
  malformed percent-encoding, null-byte injection, header case
  variants, duplicate headers, unicode round-trip, abort mid-flight,
  relative/Windows/URL-shaped paths.

- test/als-state-contamination.test.ts — adversarial audit that
  surfaced two PRE-EXISTING P0 bugs the ALS fix does NOT solve:
    * activeFlowName module-level singleton in flow-utils.ts
      leaks between concurrent projects on a shared daemon
    * profilerPathsCache in react-profiler-session.ts is keyed
      only by Metro port, not projectRoot — two projects sharing
      port 8081 see each other's cached profiler sessions
  Both are documented via it.fails() so vitest passes today; the
  day a fix lands the it.fails flips to an alert and the test
  becomes a regression guard.

449/449 tool-server tests pass.

* revert: drop AsyncLocalStorage project-root approach

Reverts the ALS-based project root resolution because the approach
isn't working well in practice. Restores the previous getGitRoot
implementation in flow-utils, the project_root input parameter on
the profiler tools, and the skill doc that describes it.

Reverts:
- 3932eb5 test: stress-test AsyncLocalStorage projectRoot mechanism
- 5398cbc refactor: profiler tools resolve project root via requireProjectRoot
- b7ae895 feat: resolve project root via AsyncLocalStorage from mcp proxy header

A follow-up commit adds clearer error handling around the git call
so the agent gets a useful message when the tool-server's cwd isn't
inside a git repo.

* fix: clearer error when getGitRoot fails outside a git repo

Wrap the `git rev-parse --show-toplevel` call in getGitRoot so that
the failure surfaces a useful message to the agent instead of the
raw child_process stderr. The tool-server daemon's cwd is not the
calling project's cwd, so if the daemon happens to be launched
outside any git repository the git command fails with a terse
"fatal: not a git repository" that tells the agent nothing about
what to do next.

The new error reports the daemon's cwd, explains that flows are
anchored to a git root, and points the user at `git init`.

* refactor: require project_root as a flow tool parameter

The tool-server is a singleton daemon — its process.cwd() is never
the calling project's cwd, so flow-utils cannot infer the project
root on its own. Instead of guessing via git or walking the
filesystem, ask the calling agent directly: every flow tool now
takes project_root as a required parameter and the agent supplies
the absolute path it is working in.

This matches the existing pattern used by react-profiler-analyze,
react-profiler-component-source, and gather-workspace-data, all of
which already accept an agent-provided project/workspace path.

Changes:
- flow-utils.ts: drop getGitRoot/execFile; getFlowsDir and
  getFlowPath become pure functions that take projectRoot; throw
  on non-absolute paths at the boundary.
- flow-start-recording, flow-add-step, flow-add-echo,
  flow-finish-recording, flow-read-prerequisite, flow-execute:
  add project_root to each zod schema and plumb it through.
- argent-create-flow SKILL.md: document project_root on every
  example tool call; note that it must be passed every time.
- flow-tools.test.ts: remove the flow-utils vi.mock (no longer
  needed) and pass project_root: tmpDir on every call; add a
  regression test rejecting relative project_root.

* prettier

* cleanup skill

* refactor: store project_root on flow-start-recording, not on every tool

The tool-server cannot infer the calling project's working directory
on its own. The agent now provides it once — as project_root on
flow-start-recording, which stores it alongside activeFlowName in
module state. All other flow tools (flow-add-step, flow-add-echo,
flow-finish-recording, flow-execute, flow-read-prerequisite) read
the stored value and require no project_root parameter.

This matches the existing "active flow" pattern: just as flow name
is tracked automatically after flow-start-recording, so is the
project root.

* feat: require project_root on flow-execute and flow-read-prerequisite

These tools are independent entry points that may be called without a
prior recording session (e.g. after daemon restart), so they cannot
rely on module state set by flow-start-recording. Adding project_root
as an explicit parameter mirrors the pattern already used by
flow-start-recording and react-profiler-analyze.

* fix: make requireActiveProjectRoot error message tool-agnostic

The previous message pointed only at flow-start-recording, but the
check is now reached from flow-execute and flow-read-prerequisite as
well.

* refactor: store flows in .argent/flows/ instead of .argent/

Keeps the .argent/ directory clean for other argent-related artifacts
and groups flow files under a dedicated subdirectory.

* style: prettier --write
2026-04-15 19:10:48 +02:00
..