Skip to content

Commit 089a355

Browse files
Add Intent hook enforcement for supported agents (#174)
* Refactor skill mapping guidance and add `run` command support in intent skills block * Refactor intent skill mappings to use new structure and update related tests * Add hooked-intent condition and related functionality to intent discovery evals - Introduce 'hooked-intent' condition in conditions.ts - Implement live-router-hooked-intent task in live-tasks.ts - Create gate.mjs and hook-core.mts for intent hooks management - Add hook-io.mjs for event handling and observation logging - Implement prepare-copilot-home.ts for managing copilot home directory - Update run-copilot-task.ts to support gate state management - Enhance setup-intent-condition.ts to handle hooked-intent - Add tests for hooked-intent functionality in intent-hooks.eval.ts - Update vitest configuration for live concurrency settings * Refactor install command to use guidance module for intent skills block handling * Add hooks and policy for intent handling with agent-specific outputs * Add support for agent hooks in install command with project/user scope * Enhance intent installation with lifecycle hooks support and update documentation - Added `--hooks` option to `intent install` for lifecycle hook installation. - Updated documentation to reflect new hook options and usage. - Introduced hook adapters for Claude, Codex, and Copilot with project/user scope support. - Implemented tests to verify supported scopes in the adapter registry. * Refactor intent installation and hook handling; enhance command patterns and add skill categorization tests * Add intent hooks command and update documentation for lifecycle hooks installation * Improve folder layout * Refactor intent installation and update file structure; remove deprecated install command and enhance type definitions * ci: apply automated fixes * changeset * Apply fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 559dd06 commit 089a355

96 files changed

Lines changed: 3321 additions & 1083 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/hot-bottles-float.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@tanstack/intent': minor
3+
---
4+
5+
Add `intent hooks install` for supported AI coding agents.
6+
7+
This adds lifecycle-hook installation for supported agents, including project/user scope handling, generated hook runner scripts, and agent-specific enforcement policy. It also documents the hook setup flow and adds eval/test coverage for hooked intent discovery.

docs/cli/intent-hooks.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
title: intent hooks
3+
id: intent-hooks
4+
---
5+
6+
`intent hooks install` installs lifecycle hooks that enforce loading matching guidance before edits in supported agents.
7+
8+
```bash
9+
npx @tanstack/intent@latest hooks install [--scope project|user] [--agents copilot,claude,codex|all]
10+
```
11+
12+
## Options
13+
14+
- `--scope <scope>`: hook install scope, either `project` or `user`; defaults to `project`
15+
- `--agents <agents>`: comma-separated hook agents to configure (`copilot`, `claude`, `codex`) or `all`; defaults to `all`
16+
17+
## Behavior
18+
19+
- Installs hook enforcement without writing an `intent-skills` guidance block.
20+
- `--scope project` writes project-local hook config for agents that support it.
21+
- `--scope user` writes user-level agent config and stores runner scripts under `~/.tanstack/intent/hooks`.
22+
- `--agents all` is the default. In project scope, Copilot is skipped because the supported Copilot CLI hook location is user-scoped.
23+
- Run `intent install` separately when you also want to write project guidance.
24+
25+
## Hook support
26+
27+
| Agent | Project scope | User scope | Enforcement |
28+
| --- | --- | --- | --- |
29+
| Claude Code | `.claude/settings.json` | `~/.claude/settings.json` | Blocks configured edit tools with `PreToolUse` |
30+
| Codex | `.codex/hooks.json` | `~/.codex/hooks.json` | Blocks supported `Bash`, `apply_patch`, and MCP tool calls; Codex hook interception is not a complete security boundary |
31+
| GitHub Copilot CLI | Guidance via `.github/copilot-instructions.md`; blocking hooks are not project-scoped | `$COPILOT_HOME/hooks/hooks.json` or `~/.copilot/hooks/hooks.json` | Blocks supported edit tools with `PreToolUse` |
32+
| Cursor | Guidance only | Guidance only | Use `AGENTS.md` or Cursor rules; no blocking hook is installed |
33+
| Generic `AGENTS.md` agents | Guidance only | Guidance only | Use the `intent-skills` guidance block; no blocking hook is installed |
34+
35+
`.github/copilot-instructions.md` is a supported project guidance target for `intent install`. GitHub Copilot CLI hook enforcement uses the user-scoped Copilot hooks directory because that is the supported hook location.
36+
37+
Codex requires users to review and trust non-managed hooks before they run. If Codex reports hooks awaiting review, open its hook browser and trust the generated Intent hook.
38+
39+
## Status messages
40+
41+
- Hook installed: `Installed Intent hooks for claude (project) in .claude/settings.json.`
42+
- Hook skipped: `Skipped Intent hooks for copilot: project scope is not supported; use --scope user`
43+
44+
## Related
45+
46+
- [intent install](./intent-install)
47+
- [intent list](./intent-list)
48+
- [intent load](./intent-load)

docs/cli/intent-install.md

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,14 @@ npx @tanstack/intent@latest install [--map] [--dry-run] [--print-prompt] [--glob
1111

1212
## Options
1313

14+
### Guidance output
15+
1416
- `--map`: write explicit task-to-skill mappings instead of lightweight loading guidance
1517
- `--dry-run`: print the generated block without writing files
1618
- `--print-prompt`: print the agent setup prompt instead of writing files
19+
20+
### Mapping scan scope
21+
1722
- `--global`: include global packages after project packages when `--map` is passed
1823
- `--global-only`: install mappings from global packages only when `--map` is passed
1924
- `--no-notices`: suppress non-critical notices on stderr
@@ -24,10 +29,10 @@ npx @tanstack/intent@latest install [--map] [--dry-run] [--print-prompt] [--glob
2429
- Creates `AGENTS.md` when no managed block exists.
2530
- Updates an existing managed block in a supported config file.
2631
- Preserves all content outside the managed block.
27-
- Scans packages and writes compact `when` and `use` mappings only when `--map` is passed.
32+
- Scans packages and writes compact `id`, `run`, and `for` mappings only when `--map` is passed.
2833
- Surfaces packages permitted by `package.json#intent.skills` in `--map` mode. See [Configuration](../concepts/configuration).
2934
- Skips reference, meta, maintainer, and maintainer-only skills in `--map` mode.
30-
- Writes compact `when` and `use` entries instead of load paths in `--map` mode.
35+
- Writes compact skill identities and runnable guidance commands instead of local file paths in `--map` mode.
3136
- Verifies the managed block before reporting success.
3237
- Prints `No intent-enabled skills found.` and does not create a config file when `--map` finds no actionable skills.
3338

@@ -41,29 +46,32 @@ The default block tells agents to discover skills and load matching guidance on
4146
<!-- intent-skills:start -->
4247
## Skill Loading
4348

44-
Before substantial work:
45-
- Skill check: run `npx @tanstack/intent@latest list`, or use skills already listed in context.
46-
- Skill guidance: if one local skill clearly matches the task, run `npx @tanstack/intent@latest load <package>#<skill>` and follow the returned `SKILL.md`.
49+
Before editing files for a substantial task:
50+
- Run `npx @tanstack/intent@latest list` from the workspace root to see available local skills.
51+
- If a listed skill matches the task, run `npx @tanstack/intent@latest load <package>#<skill>` before changing files.
52+
- Use the loaded `SKILL.md` guidance while making the change.
4753
- Monorepos: when working across packages, run the skill check from the workspace root and prefer the local skill for the package being changed.
4854
- Multiple matches: prefer the most specific local skill for the package or concern you are changing; load additional skills only when the task spans multiple packages or concerns.
4955
<!-- intent-skills:end -->
5056
```
5157

5258
## Mapping output
5359

54-
`--map` writes compact skill identities:
60+
`--map` writes compact skill identities and commands:
5561

5662
```yaml
5763
<!-- intent-skills:start -->
58-
# Skill mappings - load `use` with `npx @tanstack/intent@latest load <use>`.
59-
skills:
60-
- when: "Query data fetching patterns"
61-
use: "@tanstack/query#fetching"
64+
# TanStack Intent - before editing files, run the matching guidance command.
65+
tanstackIntent:
66+
- id: "@tanstack/query#fetching"
67+
run: "npx @tanstack/intent@latest load @tanstack/query#fetching"
68+
for: "Query data fetching patterns"
6269
<!-- intent-skills:end -->
6370
```
6471

65-
- `when`: task-routing phrase for agents
66-
- `use`: portable skill identity in `<package>#<skill>` format
72+
- `id`: portable skill identity in `<package>#<skill>` format
73+
- `run`: package-manager-aware command agents should run before editing
74+
- `for`: task-routing phrase for agents
6775
- The block does not store `load` paths, absolute paths, or package-manager-internal paths
6876

6977
## Status messages
@@ -82,4 +90,5 @@ To suppress trust and migration notices in automation, pass `--no-notices`.
8290

8391
- [intent list](./intent-list)
8492
- [intent load](./intent-load)
93+
- [intent hooks](./intent-hooks)
8594
- [Quick Start for Consumers](../getting-started/quick-start-consumers)

docs/config.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@
4747
"label": "intent install",
4848
"to": "cli/intent-install"
4949
},
50+
{
51+
"label": "intent hooks",
52+
"to": "cli/intent-hooks"
53+
},
5054
{
5155
"label": "intent exclude",
5256
"to": "cli/intent-exclude"

docs/getting-started/quick-start-consumers.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,31 @@ Intent creates guidance like:
3131
<!-- intent-skills:start -->
3232
## Skill Loading
3333

34-
Before substantial work:
35-
- Skill check: run `pnpm dlx @tanstack/intent@latest list`, or use skills already listed in context.
36-
- Skill guidance: if one local skill clearly matches the task, run `pnpm dlx @tanstack/intent@latest load <package>#<skill>` and follow the returned `SKILL.md`.
34+
Before editing files for a substantial task:
35+
- Run `pnpm dlx @tanstack/intent@latest list` from the workspace root to see available local skills.
36+
- If a listed skill matches the task, run `pnpm dlx @tanstack/intent@latest load <package>#<skill>` before changing files.
37+
- Use the loaded `SKILL.md` guidance while making the change.
3738
- Monorepos: when working across packages, run the skill check from the workspace root and prefer the local skill for the package being changed.
3839
- Multiple matches: prefer the most specific local skill for the package or concern you are changing; load additional skills only when the task spans multiple packages or concerns.
3940
<!-- intent-skills:end -->
4041
```
4142

4243
Intent detects the package manager when generating this block, so the runner may be `npx`, `pnpm dlx`, `yarn dlx`, or `bunx`.
4344

45+
To enforce loading guidance before edits in supported agents, opt in to hooks:
46+
47+
```bash
48+
npx @tanstack/intent@latest hooks install
49+
```
50+
51+
Project-scoped hooks are installed for Claude Code and Codex. `intent install` can write project guidance to `.github/copilot-instructions.md`, but GitHub Copilot CLI hook enforcement is user-scoped, so configure it explicitly:
52+
53+
```bash
54+
npx @tanstack/intent@latest hooks install --scope user --agents copilot
55+
```
56+
57+
Cursor and generic `AGENTS.md` agents use the guidance block only.
58+
4459
## 2. Choose which packages' skills to use
4560

4661
`package.json#intent.skills` is an allowlist of the packages whose skills you want surfaced.
@@ -106,4 +121,3 @@ You can also check if any skills reference outdated source documentation:
106121
```bash
107122
npx @tanstack/intent@latest stale
108123
```
109-

docs/overview.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ Skills are markdown documents that teach AI coding agents how to use your librar
1212
Intent provides tooling for two workflows:
1313

1414
**For consumers:**
15+
1516
- Discover skills from your project and workspace dependencies
1617
- Control which packages' skills are surfaced with an allowlist
1718
- Add lightweight skill loading guidance to your agent config
19+
- Add hook enforcement for agents that support blocking lifecycle hooks
1820
- Keep skills synchronized with library versions
1921

2022
**For maintainers (library teams):**
23+
2124
- Scaffold skills through AI-assisted domain discovery
2225
- Validate SKILL.md format and packaging
2326
- Ship skills in the same release pipeline as code
@@ -50,6 +53,12 @@ npx @tanstack/intent@latest install
5053

5154
Creates or updates lightweight `intent-skills` guidance in your config files (`AGENTS.md`, `CLAUDE.md`, `.cursorrules`, etc.). Existing guidance is updated in place; otherwise `AGENTS.md` is the default target. Pass `--map` to opt in to explicit task-to-skill mappings.
5255

56+
```bash
57+
npx @tanstack/intent@latest hooks install
58+
```
59+
60+
Installs hook enforcement for supported agents. Project-scoped hooks are available for Claude Code and Codex. GitHub Copilot CLI project guidance can live in `.github/copilot-instructions.md`, while blocking hooks are user-scoped. Cursor and generic `AGENTS.md` agents use guidance only.
61+
5362
```bash
5463
npx @tanstack/intent@latest load @tanstack/query#fetching
5564
```

evals/intent-discovery/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ pnpm eval:intent-discovery:report
2222

2323
Set `INTENT_DISCOVERY_RUN_COUNT=3` with the live commands to run each live condition three times and include `pass@k` / `pass^k` in the generated summary.
2424

25+
## Live eval speed
26+
27+
Only the live `copilot -p` subprocess runs are slow; the saved-transcript suite (`pnpm eval:intent-discovery`) is unaffected.
28+
29+
- `INTENT_DISCOVERY_LIVE_CONCURRENCY` bounds how many live runs execute at once (default `1`, clamped to an integer `>= 1`). Values above `1` measured slower here: concurrent `copilot -p` calls on one account contend upstream (a run with its own isolated `COPILOT_HOME` still slowed ~2x), so raise it only with separate accounts or dedicated infrastructure.
30+
- `COPILOT_MODEL` selects the Copilot model end-to-end. The adapter passes the process environment through to `copilot -p`, and the CLI honors `COPILOT_MODEL`. `INTENT_DISCOVERY_COPILOT_MODEL` only sets the model label recorded in report metadata; it does not change the model the CLI runs.
31+
- `INTENT_DISCOVERY_RUN_COUNT` stays `1` by default for iteration. Set it to `3` only when measuring `pass@k` / `pass^k`.
32+
2533
The optional LLM judge is secondary. It can annotate whether final answers appear to apply loaded guidance, but it never changes deterministic scores such as `StrictIntentInvocation`, `CorrectSkillLoaded`, or `AutonomousDiscoverySuccess`.
2634

2735
## Current scope

evals/intent-discovery/condition-setup.eval.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ describe('Intent discovery condition setup', () => {
4747
expect(result.filesWritten).toHaveLength(4)
4848
expect(agents).toContain('Skill Loading')
4949
expect(agents).toContain('npx @tanstack/intent@latest list')
50-
expect(agents).not.toContain('\nskills:\n')
50+
expect(agents).not.toContain('\ntanstackIntent:\n')
5151
expect(packageJson).toContain('"@tanstack/router"')
5252
expect(
5353
existsSync(
@@ -81,8 +81,11 @@ describe('Intent discovery condition setup', () => {
8181
'utf8',
8282
)
8383

84-
expect(agents).toContain('skills:')
85-
expect(agents).toContain('use: "@tanstack/router#routing"')
84+
expect(agents).toContain('tanstackIntent:')
85+
expect(agents).toContain('id: "@tanstack/router#routing"')
86+
expect(agents).toContain(
87+
'run: "npx @tanstack/intent@latest load @tanstack/router#routing"',
88+
)
8689
} finally {
8790
prepared.cleanup()
8891
}

evals/intent-discovery/corpus/conditions.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ const intentDiscoveryConditions = [
1515
id: 'mapped-intent',
1616
countsTowardAutonomousScore: true,
1717
},
18+
{
19+
id: 'hooked-intent',
20+
countsTowardAutonomousScore: true,
21+
},
1822
{
1923
id: 'explicit-intent-control',
2024
countsTowardAutonomousScore: false,

evals/intent-discovery/corpus/live-tasks.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,20 @@ export const liveTasks: Array<IntentDiscoveryTask> = [
4646
failureClass: 'strict-success',
4747
},
4848
},
49+
{
50+
id: 'live-router-hooked-intent',
51+
fixture: 'router-basic',
52+
condition: 'hooked-intent',
53+
explicitnessLevel: 2,
54+
prompt: routerPrompt,
55+
expectedSkillAreas: ['router'],
56+
expected: {
57+
strictInvocation: true,
58+
correctSkillLoaded: true,
59+
referenceOnly: false,
60+
failureClass: 'strict-success',
61+
},
62+
},
4963
{
5064
id: 'live-router-explicit-intent-control',
5165
fixture: 'router-basic',

0 commit comments

Comments
 (0)