docs: record that the session-start hook stores an absolute path (task 0042) #51
@@ -156,16 +156,37 @@ _Avoid_: mock mode, stub mode
|
||||
|
||||
### Distribution
|
||||
|
||||
**Agent Skill**: The markdown file bundled inside the npm package and installed to `~/.claude/skills/` by the `setup` command.
|
||||
**Agent Skill**: The markdown file bundled inside the npm package and installed to `~/.claude/skills/` by the `setup` command, or declared from the package by the [[home-manager module]].
|
||||
_Avoid_: skill file, Claude skill
|
||||
|
||||
**setup**: The explicit subcommand that installs the Agent Skill into `~/.claude/skills/`; gitea-axi's primary fulfillment of AXI Principle 7 (Ambient context).
|
||||
Idempotent: re-running reports already-installed/updated rather than failing.
|
||||
There is no postinstall script — installation of the skill is always an explicit user action.
|
||||
`setup hooks` additionally opts into the [[SessionStart hook]].
|
||||
It is the [[imperative install path]], and works only where the operator owns the target files; against a read-only target it reports the condition rather than writing.
|
||||
_Avoid_: postinstall, installer script
|
||||
|
||||
**SessionStart hook**: An opt-in ambient-context mechanism installed by `setup hooks` via `axi-sdk-js`'s `installSessionStartHooks()` (Claude Code `settings.json`, Codex `hooks.json`, OpenCode plugin).
|
||||
**SessionStart hook**: An opt-in ambient-context mechanism installed by `setup hooks` via `axi-sdk-js`'s `installSessionStartHooks()` (Claude Code `settings.json`, Codex `hooks.json`, OpenCode plugin), or declared by the [[home-manager module]].
|
||||
It runs the bare `gitea-axi` binary (the short [[dashboard]] tier) in the session's working directory at session start and injects the output into the agent's context.
|
||||
The SDK's installer registers the binary with no arguments, so the hook always runs the short tier; outside a Gitea repo it produces the dashboard's `REPO_NOT_FOUND` error, an accepted noise trade-off.
|
||||
The recorded command is currently the entrypoint's absolute path on any wrapper-based install, which rots whenever that path moves; recording the bare binary name and resolving it through `PATH` is agreed and lands with task 0043.
|
||||
_Avoid_: session hook, ambient hook, postinstall hook
|
||||
|
||||
**imperative install path**: Installation of the Agent Skill and the [[SessionStart hook]] by running `setup`, which writes into the operator's agent configuration directory.
|
||||
Requires the operator to own those files; a declaratively generated configuration renders them read-only and the command reports rather than writes.
|
||||
Contrast the [[declarative install path]]. Both are supported and neither supersedes the other.
|
||||
_Avoid_: manual install, imperative setup
|
||||
|
||||
**declarative install path**: Installation of the Agent Skill and the [[SessionStart hook]] by declaring them in a Nix configuration, which generates the agent configuration rather than mutating it.
|
||||
Agreed and specified; the outputs it consumes land with task 0045.
|
||||
Consumes the package's exposed Skill location and [[hook specification]], either directly or through the [[home-manager module]].
|
||||
Chosen where the operator's agent configuration is generated and therefore read-only; contrast the [[imperative install path]].
|
||||
_Avoid_: nix install, declarative setup
|
||||
|
||||
**home-manager module**: The flake output that declares the Agent Skill and the [[SessionStart hook]] from the package, as the [[declarative install path]]'s ergonomic front end (task 0045).
|
||||
Importing it does nothing until enabled; it installs the package by default, with a null package the documented way to declare configuration without installing the binary, and carries a toggle per managed piece.
|
||||
_Avoid_: nix module, HM module
|
||||
|
||||
**hook specification**: The single committed declaration of the [[SessionStart hook]]'s recorded shape — command, timeout, and matcher (task 0045).
|
||||
Read by both the Nix expression and the test suite, so that the [[declarative install path]] and the [[imperative install path]] cannot disagree about what the hook is without failing a test.
|
||||
_Avoid_: hook config, hook schema
|
||||
|
||||
@@ -87,6 +87,12 @@ Leaving a statement the commit itself documents as untrue seemed worse than a on
|
||||
|
||||
### Follow-up
|
||||
|
||||
The successor task is described in the spec's Further Notes but not yet written as a task file.
|
||||
It should carry its own ADR and cover both defects together, since both trace to the same resolution line: the absolute-path recording, and `isManagedHook` recognising its hook by substring.
|
||||
Deleting `package.nix`'s `postUnpack` rename belongs to it.
|
||||
The successor is task 0043, which covers both defects together — the absolute-path recording and `isManagedHook` recognising its hook by substring — since both trace to the same resolution line, and deletes `package.nix`'s `postUnpack` rename with them.
|
||||
|
||||
Grilling the mitigation afterwards found the framing here too narrow.
|
||||
The maintainer's agent configuration is generated declaratively, so `~/.claude/settings.json` and the installed Skill are both read-only symlinks into the Nix store: `setup hooks` cannot write at all, and `setup` crashes outright on an unhandled filesystem error.
|
||||
The recorded path's *shape* is therefore not the whole defect — the deeper one is that `setup` is write-only against a target that some operators cannot let it write.
|
||||
Tasks 0044 and 0045 follow from that: a clean failure on unwritable targets, and a declarative install path that generates the configuration instead of mutating it.
|
||||
|
||||
The help-text mitigation added by this task is deliberately left in place rather than pre-emptively reverted.
|
||||
It is accurate until task 0043 lands, which removes it as an acceptance criterion.
|
||||
|
||||
34
.claude/tasks/0043-hook-records-bare-binary-name.md
Normal file
34
.claude/tasks/0043-hook-records-bare-binary-name.md
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
spec: nix-flake-packaging
|
||||
---
|
||||
|
||||
## What to build
|
||||
|
||||
Make the SessionStart hook survive an upgrade by recording a name that does not move.
|
||||
|
||||
Task 0042 established by observation that the hook records the entrypoint's absolute path on every wrapper-based install, and documented a re-run-after-upgrade mitigation.
|
||||
This task removes the need for that mitigation.
|
||||
|
||||
The SDK returns the bare binary name only when a `PATH` entry realpath-matches the entrypoint it is handed.
|
||||
An npm install satisfies that by symlinking its `bin` entry straight at the entrypoint; a wrapper-based install cannot, because a script that *invokes* a file never resolves *to* that file.
|
||||
Handing the SDK the location where the binary actually resolves on `PATH`, rather than the module-relative entrypoint, makes the match succeed and the bare name get recorded — using the SDK's own resolution rather than bypassing it.
|
||||
When the binary is not on `PATH` there is nothing to hand it, and the existing absolute-path behaviour stands unchanged as the fallback.
|
||||
|
||||
This is not a Nix accommodation.
|
||||
Any wrapper-based install has the same shape — a shim, a launcher, a generated `.cmd` — and the fix is the convention for tools that write into user-owned configuration: prior art records a bare name and lets `PATH` resolve it, reserving absolute paths for configuration that a package manager regenerates.
|
||||
|
||||
A second defect shares the same line and is fixed here.
|
||||
The hook is recognised as its own by testing whether the recorded command string *contains* the marker, so an entrypoint path lacking that substring makes re-running `setup hooks` append a duplicate rather than update in place, contradicting the idempotency its help text promises.
|
||||
Recording the bare name makes the marker match by construction, but the recognition itself should not depend on the recorded command's shape.
|
||||
|
||||
The Nix derivation renames its build tree solely to work around that substring coupling.
|
||||
Once the coupling is gone the rename has no remaining purpose and goes with it.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] The recorded hook command is the bare binary name whenever that name resolves to the running program on `PATH`.
|
||||
- [ ] The recorded hook command remains the absolute entrypoint path when the binary is not resolvable on `PATH`, and that fallback is exercised by a test.
|
||||
- [ ] Re-running `setup hooks` updates the existing entry in place rather than appending a second one, including when the entrypoint path does not contain the marker.
|
||||
- [ ] The `setup` help text no longer instructs the user to re-run hooks after an upgrade, that instruction having become false.
|
||||
- [ ] The derivation no longer renames its build tree, and the build still passes with the tree at a path that does not contain the marker.
|
||||
- [ ] The behaviour is verified against a real wrapper-based install, not only against a source checkout.
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
spec: nix-flake-packaging
|
||||
---
|
||||
|
||||
## What to build
|
||||
|
||||
Report an unwritable target as an error the user can act on, instead of crashing.
|
||||
|
||||
Both halves of `setup` assume the files they manage are writable.
|
||||
When they are not — because a configuration manager owns them, because a file is flagged immutable, because the path is root-owned — the skill install raises a raw filesystem error with no handling at all, and the hook install surfaces the underlying message through its error collector without saying what a reader should do about it.
|
||||
|
||||
Neither failure is exotic.
|
||||
Any tool that manages a user's agent configuration declaratively renders these paths read-only, and gitea-axi's own Nix install method encourages exactly that arrangement.
|
||||
|
||||
The error names the file and the condition, and points at the general remedy: the file appears to be managed elsewhere, so the skill or hook should be declared through that configuration rather than installed by this command.
|
||||
|
||||
It deliberately does not guess at the cause.
|
||||
Read-only is not diagnostic of any particular manager, and naming one would be wrong for most users who hit this.
|
||||
|
||||
The failure follows the CLI's existing error convention rather than inventing a shape, so it carries a code and help lines like every other error the tool reports.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] An unwritable skill target produces a structured CLI error rather than an unhandled filesystem exception.
|
||||
- [ ] An unwritable hook target produces the same class of error, with the same guidance.
|
||||
- [ ] Both errors name the file that could not be written and state that it appears to be managed by another tool.
|
||||
- [ ] Neither error names or infers a specific configuration manager.
|
||||
- [ ] A skill target that is unwritable but already byte-identical to the bundled copy succeeds rather than failing, since nothing needs to be written.
|
||||
- [ ] The errors carry a code and help lines consistent with the rest of the CLI's error surface.
|
||||
45
.claude/tasks/0045-declarative-nix-outputs-and-hm-module.md
Normal file
45
.claude/tasks/0045-declarative-nix-outputs-and-hm-module.md
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
spec: nix-flake-packaging
|
||||
blocked-by: 0043-hook-records-bare-binary-name
|
||||
---
|
||||
|
||||
## What to build
|
||||
|
||||
Let a Nix configuration declare gitea-axi's ambient context, instead of running a command that writes it.
|
||||
|
||||
`setup` and `setup hooks` are write-only.
|
||||
They install the Agent Skill and the SessionStart hook by writing into the user's agent configuration directory, which works only when the user owns those files imperatively.
|
||||
An operator whose agent configuration is generated declaratively cannot use either: the targets are read-only, and the operator is left hand-copying the Skill into their own configuration, where it silently drifts from the package that ships it.
|
||||
|
||||
The spec currently lists a home-manager module under Out of Scope, deferring it until there was usage evidence that the trade-off was worth making.
|
||||
That evidence now exists, and the deferral's stated reasoning does not survive it: the concern was the automatism that ADR 0009 rejected when it chose an explicit `setup` command over a postinstall script, and a module the operator explicitly imports and enables is the opposite of an implicit install.
|
||||
Revising that Out of Scope entry, and recording the decision as an ADR, is part of this task.
|
||||
|
||||
Two layers, the second built on the first.
|
||||
|
||||
The package gains a stable, documented location for the bundled Agent Skill, and exposes both the Skill and the hook's specification as attributes a Nix expression can consume.
|
||||
Today the Skill's only address is a path inside the installed node modules tree, which is an implementation detail no consumer should depend on.
|
||||
|
||||
On top of that, the flake exposes a home-manager module: a thin wiring layer that declares the Skill and the hook from those attributes.
|
||||
It follows the conventions the home-manager module tree overwhelmingly uses — an enable option so that importing the module does nothing until it is switched on, an overridable package option, and installation of that package by default with a null value as the documented opt-out for an operator who supplies the binary another way.
|
||||
Each managed piece has its own toggle, defaulting on, so an operator can take the Skill declaratively while continuing to write the hook by hand.
|
||||
|
||||
The hook's specification is declared once, in a committed file that both the Nix expression and the test suite read.
|
||||
Declaring it in the Nix expression alone would create a second source of truth alongside the behaviour of the imperative install path, with nothing to keep them agreed; a test that hardcoded the same values a third time would verify nothing.
|
||||
The test drives the imperative install against a temporary home directory and asserts that what it writes matches what the file declares, so a divergence — including one introduced by the SDK changing the envelope it writes — fails a test rather than passing silently into a release.
|
||||
|
||||
The two installation paths remain independent and both supported: the command for operators who own their configuration, the module for operators whose configuration owns them.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] The bundled Agent Skill is installed to a stable location in the package output that is not an internal implementation path.
|
||||
- [ ] The package exposes the Skill and the hook specification as attributes consumable from a Nix expression without building or running anything.
|
||||
- [ ] The hook specification is declared in a single committed file, read by both the Nix expression and the test suite.
|
||||
- [ ] A test drives the imperative hook install and asserts that what it writes matches the declared specification, failing if either side drifts.
|
||||
- [ ] The flake exposes a home-manager module that declares the Skill and the hook.
|
||||
- [ ] Importing the module without enabling it changes nothing about the resulting configuration.
|
||||
- [ ] The module installs the package by default, and accepts a null package as the documented way to declare the configuration without installing the binary.
|
||||
- [ ] The Skill and the hook each have their own toggle, both defaulting to on.
|
||||
- [ ] The module composes with an existing configuration that already declares its own SessionStart hooks and skills, rather than conflicting with it.
|
||||
- [ ] The spec's Out of Scope entry excluding a home-manager module is revised, and the decision to reverse it is recorded as an ADR.
|
||||
- [ ] The user-facing documentation describes both installation paths and when each applies.
|
||||
Reference in New Issue
Block a user