@open-agent-toolkit/cli 0.0.31 → 0.0.32
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 +5 -0
- package/assets/docs/cli-utilities/config-and-local-state.md +12 -4
- package/assets/docs/provider-sync/commands.md +13 -6
- package/assets/docs/provider-sync/index.md +2 -0
- package/assets/docs/provider-sync/instruction-sync.md +146 -0
- package/assets/docs/provider-sync/scope-and-surface.md +2 -2
- package/assets/docs/reference/troubleshooting.md +10 -5
- package/assets/public-package-versions.json +4 -4
- package/dist/commands/instructions/instructions.types.d.ts +17 -4
- package/dist/commands/instructions/instructions.types.d.ts.map +1 -1
- package/dist/commands/instructions/instructions.types.js +5 -1
- package/dist/commands/instructions/instructions.utils.d.ts +4 -2
- package/dist/commands/instructions/instructions.utils.d.ts.map +1 -1
- package/dist/commands/instructions/instructions.utils.js +238 -25
- package/dist/commands/instructions/sync/sync.d.ts +3 -1
- package/dist/commands/instructions/sync/sync.d.ts.map +1 -1
- package/dist/commands/instructions/sync/sync.js +176 -16
- package/dist/commands/instructions/validate/validate.d.ts +1 -1
- package/dist/commands/instructions/validate/validate.d.ts.map +1 -1
- package/dist/commands/instructions/validate/validate.js +16 -6
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Open Agent Toolkit command-line interface for provider sync, docs tooling, workflow utilities, and diagnostics.
|
|
4
4
|
|
|
5
|
+
The CLI also supports project-scoped instruction sync for nested `AGENTS.md` / `CLAUDE.md` files, including pointer, symlink, and hard-copy repair strategies plus Claude-only adoption.
|
|
6
|
+
|
|
5
7
|
## Install
|
|
6
8
|
|
|
7
9
|
```bash
|
|
@@ -26,6 +28,8 @@ oat config describe
|
|
|
26
28
|
Additional useful entry points:
|
|
27
29
|
|
|
28
30
|
- `oat tools install`
|
|
31
|
+
- `oat instructions validate --strategy pointer`
|
|
32
|
+
- `oat instructions sync --dry-run --strategy symlink`
|
|
29
33
|
- `oat docs init --app-name my-docs`
|
|
30
34
|
- `oat config dump --json`
|
|
31
35
|
- `oat project status --json`
|
|
@@ -50,4 +54,5 @@ Use these commands when you want structured runtime/project state out of the CLI
|
|
|
50
54
|
- [Docs Home](https://voxmedia.github.io/open-agent-toolkit/)
|
|
51
55
|
- [CLI Utilities](https://voxmedia.github.io/open-agent-toolkit/cli-utilities)
|
|
52
56
|
- [Provider Sync](https://voxmedia.github.io/open-agent-toolkit/provider-sync)
|
|
57
|
+
- [Instruction Sync](https://voxmedia.github.io/open-agent-toolkit/provider-sync/instruction-sync)
|
|
53
58
|
- [Reference](https://voxmedia.github.io/open-agent-toolkit/reference)
|
|
@@ -80,12 +80,20 @@ Use these reference pages for file ownership and schema details:
|
|
|
80
80
|
|
|
81
81
|
## `oat instructions ...`
|
|
82
82
|
|
|
83
|
-
These commands validate and repair
|
|
83
|
+
These commands validate and repair project-scoped instruction integrity between `AGENTS.md` and sibling `CLAUDE.md` files.
|
|
84
84
|
|
|
85
|
-
- `oat instructions validate` - read-only integrity check
|
|
86
|
-
- `oat instructions sync` - preview or apply pointer repairs
|
|
85
|
+
- `oat instructions validate` - read-only integrity check with `--strategy pointer|symlink|copy`
|
|
86
|
+
- `oat instructions sync` - preview or apply pointer, symlink, or hard-copy repairs
|
|
87
87
|
|
|
88
|
-
Use this command group when instruction files drift after manual edits or generated updates.
|
|
88
|
+
Use this command group when instruction files drift after manual edits or generated updates, or when nested project directories contain Claude-only stray files that should be adopted into canonical `AGENTS.md`.
|
|
89
|
+
|
|
90
|
+
Operational notes:
|
|
91
|
+
|
|
92
|
+
- Validation and sync use the same recursive scan model, so `--dry-run` previews the same states that `validate` reports.
|
|
93
|
+
- `pointer` is the default strategy; `symlink` and `copy` make file shape part of correctness.
|
|
94
|
+
- Unreadable canonical `AGENTS.md` files and unreadable Claude-only sources are surfaced as drift, but sync leaves them in manual-repair mode instead of guessing at recovery.
|
|
95
|
+
|
|
96
|
+
For the full state model, repair semantics, and examples, see [Instruction Sync](../provider-sync/instruction-sync.md).
|
|
89
97
|
|
|
90
98
|
## Repo state helpers
|
|
91
99
|
|
|
@@ -84,23 +84,30 @@ These commands are documented here because they are commonly used during interop
|
|
|
84
84
|
|
|
85
85
|
Purpose:
|
|
86
86
|
|
|
87
|
-
- Validate AGENTS.md to CLAUDE.md
|
|
87
|
+
- Validate project-scoped `AGENTS.md` to `CLAUDE.md` integrity
|
|
88
88
|
|
|
89
89
|
Key behavior:
|
|
90
90
|
|
|
91
|
-
- Read-only validation of
|
|
92
|
-
-
|
|
91
|
+
- Read-only validation of nested project-scoped instruction directories
|
|
92
|
+
- Supports `--strategy pointer|symlink|copy` to validate the expected file shape
|
|
93
|
+
- Reports `ok`, `missing`, `content_mismatch`, and `stray` states
|
|
94
|
+
- Detects Claude-only adoptable directories and unreadable/broken instruction paths as drift
|
|
93
95
|
- Exit code `0` when all entries are valid, `1` when drift is detected
|
|
96
|
+
- Detailed behavior: [`Instruction Sync`](instruction-sync.md)
|
|
94
97
|
|
|
95
98
|
## `oat instructions sync`
|
|
96
99
|
|
|
97
100
|
Purpose:
|
|
98
101
|
|
|
99
|
-
- Repair AGENTS.md to CLAUDE.md
|
|
102
|
+
- Repair project-scoped `AGENTS.md` to `CLAUDE.md` drift
|
|
100
103
|
|
|
101
104
|
Key behavior:
|
|
102
105
|
|
|
103
106
|
- Mutates by default; use `--dry-run` to preview changes
|
|
104
|
-
-
|
|
107
|
+
- Supports `--strategy pointer|symlink|copy`
|
|
108
|
+
- Creates missing `CLAUDE.md` files using the selected strategy
|
|
109
|
+
- Adopts Claude-only stray files by writing canonical `AGENTS.md` content first, then regenerating `CLAUDE.md`
|
|
105
110
|
- Skips mismatched files unless `--force` is provided
|
|
106
|
-
-
|
|
111
|
+
- Skips unreadable canonical or Claude-only sources and reports manual-repair guidance instead of forcing recovery
|
|
112
|
+
- Uses pointer content `@AGENTS.md\n`, file symlinks, or hard copies depending on the selected strategy
|
|
113
|
+
- Detailed behavior and examples: [`Instruction Sync`](instruction-sync.md)
|
|
@@ -28,6 +28,7 @@ This section explains how OAT treats `.agents/` and `.oat/` as the source of tru
|
|
|
28
28
|
## Common Tasks
|
|
29
29
|
|
|
30
30
|
- Understand the canonical/provider-view model in [Scope and Surface](scope-and-surface.md).
|
|
31
|
+
- Manage nested `AGENTS.md` / `CLAUDE.md` integrity in [Instruction Sync](instruction-sync.md).
|
|
31
32
|
- Learn provider-specific mappings in [Providers](providers.md).
|
|
32
33
|
- Diagnose drift and adoption behavior in [Manifest and Drift](manifest-and-drift.md).
|
|
33
34
|
- Adjust provider enablement and scope behavior in [Sync Config](config.md).
|
|
@@ -38,6 +39,7 @@ This section explains how OAT treats `.agents/` and `.oat/` as the source of tru
|
|
|
38
39
|
- [CLI Bootstrap](../cli-utilities/bootstrap.md) - Foundational setup before first sync.
|
|
39
40
|
- [Scope and Surface](scope-and-surface.md) - Canonical assets, provider views, scopes, and the sync surface area.
|
|
40
41
|
- [Commands](commands.md) - `oat status`, `oat sync`, and `oat providers ...` behavior.
|
|
42
|
+
- [Instruction Sync](instruction-sync.md) - Project-scoped `AGENTS.md` / `CLAUDE.md` validation, repair, and Claude-only adoption.
|
|
41
43
|
- [Providers](providers.md) - Provider-specific mappings, capabilities, and path conventions.
|
|
42
44
|
- [Manifest and Drift](manifest-and-drift.md) - How OAT tracks synced state, stray files, and adoption decisions.
|
|
43
45
|
- [Sync Config](config.md) - Provider config model, enablement, and scope semantics.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Instruction Sync
|
|
3
|
+
description: Project-scoped AGENTS.md and CLAUDE.md validation, repair strategies, and Claude-only adoption.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Instruction Sync
|
|
7
|
+
|
|
8
|
+
`oat instructions ...` is the project-scoped lane for keeping canonical `AGENTS.md` files aligned with sibling `CLAUDE.md` files throughout a repository tree.
|
|
9
|
+
|
|
10
|
+
Use it when you want OAT to:
|
|
11
|
+
|
|
12
|
+
- validate nested `AGENTS.md` / `CLAUDE.md` pairs
|
|
13
|
+
- repair missing or drifted `CLAUDE.md` files with a chosen strategy
|
|
14
|
+
- adopt Claude-only directories back into canonical `AGENTS.md`
|
|
15
|
+
|
|
16
|
+
This command group is intentionally separate from manifest-backed provider sync. It operates on repo-local instruction files, not provider view manifests.
|
|
17
|
+
|
|
18
|
+
## Scope
|
|
19
|
+
|
|
20
|
+
Instruction sync is currently project-only.
|
|
21
|
+
|
|
22
|
+
- It scans the current repository recursively.
|
|
23
|
+
- It supports nested directories all the way down the tree.
|
|
24
|
+
- It skips provider-irrelevant or local-only roots such as `.git`, `.oat`, `.worktrees`, and `node_modules`.
|
|
25
|
+
- It does not scan user-level provider roots such as `~/.claude` in this release.
|
|
26
|
+
|
|
27
|
+
## Canonical Model
|
|
28
|
+
|
|
29
|
+
- `AGENTS.md` is canonical.
|
|
30
|
+
- `CLAUDE.md` is derived from the sibling `AGENTS.md`.
|
|
31
|
+
- If a directory contains only `CLAUDE.md`, OAT can adopt that file by writing canonical `AGENTS.md` content first and then regenerating `CLAUDE.md`.
|
|
32
|
+
|
|
33
|
+
## Commands
|
|
34
|
+
|
|
35
|
+
### `oat instructions validate`
|
|
36
|
+
|
|
37
|
+
Read-only validation for instruction integrity.
|
|
38
|
+
|
|
39
|
+
- Resolves the project root automatically.
|
|
40
|
+
- Scans nested directories for instruction pairs and Claude-only strays.
|
|
41
|
+
- Returns exit code `0` when everything is valid and `1` when drift is detected.
|
|
42
|
+
- Suggests the matching repair command, including `--strategy` when you validated with a non-default mode.
|
|
43
|
+
|
|
44
|
+
### `oat instructions sync`
|
|
45
|
+
|
|
46
|
+
Repair and adoption command for instruction drift.
|
|
47
|
+
|
|
48
|
+
- Mutates by default.
|
|
49
|
+
- Use `--dry-run` to preview planned actions.
|
|
50
|
+
- Use `--force` to overwrite mismatched `CLAUDE.md` files.
|
|
51
|
+
- Reuses the selected strategy when creating or repairing `CLAUDE.md`.
|
|
52
|
+
|
|
53
|
+
## Supported Strategies
|
|
54
|
+
|
|
55
|
+
`CLAUDE.md` can be generated or validated in one of three modes:
|
|
56
|
+
|
|
57
|
+
| Strategy | Expected `CLAUDE.md` shape | Notes |
|
|
58
|
+
| --------- | -------------------------------- | --------------------------------------- |
|
|
59
|
+
| `pointer` | file content `@AGENTS.md` | Default mode. Lightweight and explicit. |
|
|
60
|
+
| `symlink` | file symlink to `AGENTS.md` | Uses a same-directory relative symlink. |
|
|
61
|
+
| `copy` | hard copy of `AGENTS.md` content | Useful when symlinks are undesirable. |
|
|
62
|
+
|
|
63
|
+
Validation treats the selected file shape as part of correctness. For example, `copy` mode rejects a symlink even if the symlink resolves to identical content.
|
|
64
|
+
|
|
65
|
+
## Reported States
|
|
66
|
+
|
|
67
|
+
`oat instructions validate` and `oat instructions sync --dry-run` work from the same scan model.
|
|
68
|
+
|
|
69
|
+
| State | Meaning |
|
|
70
|
+
| ------------------ | ------------------------------------------------------------------------------------------------ |
|
|
71
|
+
| `ok` | The discovered instruction pair matches the selected strategy. |
|
|
72
|
+
| `missing` | `AGENTS.md` exists but sibling `CLAUDE.md` is missing. |
|
|
73
|
+
| `content_mismatch` | `CLAUDE.md` exists but has the wrong shape/content, or an instruction file is unreadable/broken. |
|
|
74
|
+
| `stray` | `CLAUDE.md` exists without sibling `AGENTS.md` and the Claude file is readable enough to adopt. |
|
|
75
|
+
|
|
76
|
+
## Claude-Only Adoption
|
|
77
|
+
|
|
78
|
+
When a directory contains `CLAUDE.md` but no `AGENTS.md`, sync can adopt it:
|
|
79
|
+
|
|
80
|
+
1. Read the existing `CLAUDE.md`
|
|
81
|
+
2. Write canonical `AGENTS.md` with that content
|
|
82
|
+
3. Regenerate `CLAUDE.md` using the selected strategy
|
|
83
|
+
|
|
84
|
+
This means the original Claude instructions become canonical before the derived file is rewritten.
|
|
85
|
+
|
|
86
|
+
Readable Claude-only files are adoptable. Unreadable Claude-only files are not.
|
|
87
|
+
|
|
88
|
+
## When `--force` Is Required
|
|
89
|
+
|
|
90
|
+
`oat instructions sync` does not overwrite a mismatched `CLAUDE.md` unless you pass `--force`.
|
|
91
|
+
|
|
92
|
+
Typical pattern:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
oat instructions validate --strategy symlink
|
|
96
|
+
oat instructions sync --dry-run --strategy symlink
|
|
97
|
+
oat instructions sync --force --strategy symlink
|
|
98
|
+
oat instructions validate --strategy symlink
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
This keeps validation, preview, and repair aligned to the same expected file shape.
|
|
102
|
+
|
|
103
|
+
## Manual-Repair Cases
|
|
104
|
+
|
|
105
|
+
Some states are intentionally surfaced as drift but not auto-repaired.
|
|
106
|
+
|
|
107
|
+
Examples:
|
|
108
|
+
|
|
109
|
+
- unreadable canonical `AGENTS.md`
|
|
110
|
+
- broken or unreadable `AGENTS.md` symlink targets
|
|
111
|
+
- unreadable Claude-only sources
|
|
112
|
+
- broken or unreadable paired `CLAUDE.md` symlink targets
|
|
113
|
+
|
|
114
|
+
In these cases, sync reports a manual-repair skip instead of guessing at destructive recovery.
|
|
115
|
+
|
|
116
|
+
## Example Workflow
|
|
117
|
+
|
|
118
|
+
Preview and adopt nested Claude-only strays as pointer files:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
oat instructions sync --dry-run
|
|
122
|
+
oat instructions sync
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Validate and repair everything as symlinks:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
oat instructions validate --strategy symlink
|
|
129
|
+
oat instructions sync --dry-run --strategy symlink
|
|
130
|
+
oat instructions sync --force --strategy symlink
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Validate and repair everything as hard copies:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
oat instructions validate --strategy copy
|
|
137
|
+
oat instructions sync --dry-run --strategy copy
|
|
138
|
+
oat instructions sync --force --strategy copy
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Related Pages
|
|
142
|
+
|
|
143
|
+
- [Provider Interop Commands](commands.md)
|
|
144
|
+
- [Provider Interop CLI Scope and Surface](scope-and-surface.md)
|
|
145
|
+
- [Config and Local State](../cli-utilities/config-and-local-state.md)
|
|
146
|
+
- [Troubleshooting](../reference/troubleshooting.md)
|
|
@@ -7,7 +7,7 @@ description: 'Scope boundaries and design principles for canonical-to-provider a
|
|
|
7
7
|
|
|
8
8
|
The provider interop CLI in `packages/cli` manages canonical agent assets under `.agents/` and reconciles provider-specific views.
|
|
9
9
|
|
|
10
|
-
This capability is intentionally independent from OAT workflow artifacts. Teams can adopt provider interoperability usage (`status`, `sync`, `providers ...`) plus optional
|
|
10
|
+
This capability is intentionally independent from OAT workflow artifacts. Teams can adopt provider interoperability usage (`status`, `sync`, `providers ...`) plus optional project-scoped instruction sync integrity checks (`instructions validate/sync`) without using discovery/spec/design/plan/implement project workflows.
|
|
11
11
|
|
|
12
12
|
## Scope
|
|
13
13
|
|
|
@@ -53,7 +53,7 @@ Rules are currently project-scoped canonical content. Unlike skills and agents,
|
|
|
53
53
|
## Non-interop namespaces in the same CLI
|
|
54
54
|
|
|
55
55
|
- `oat project new <name>` (workflow/project scaffolding)
|
|
56
|
-
- `oat instructions validate` / `oat instructions sync` (AGENTS.md
|
|
56
|
+
- `oat instructions validate` / `oat instructions sync` (AGENTS.md/CLAUDE.md pointer, symlink, or copy integrity plus Claude-only adoption)
|
|
57
57
|
- `oat internal validate-oat-skills` (internal maintenance)
|
|
58
58
|
|
|
59
59
|
## Reference artifacts
|
|
@@ -27,13 +27,18 @@ Expected for native-read skill mappings. Codex can read canonical skills without
|
|
|
27
27
|
- `oat providers set --scope project --enabled <providers> --disabled <providers>`
|
|
28
28
|
- Re-run `oat sync --scope project` after updating config.
|
|
29
29
|
|
|
30
|
-
## `instructions validate` reports `missing` or `
|
|
31
|
-
|
|
32
|
-
- Run `oat instructions sync` to preview changes.
|
|
33
|
-
- Run `oat instructions sync` to
|
|
34
|
-
- If mismatched `CLAUDE.md` files should be overwritten, run `oat instructions sync --force
|
|
30
|
+
## `instructions validate` reports `missing`, `content_mismatch`, or `stray`
|
|
31
|
+
|
|
32
|
+
- Run `oat instructions sync --dry-run` to preview changes.
|
|
33
|
+
- Run `oat instructions sync --strategy pointer|symlink|copy` to apply the expected `CLAUDE.md` shape.
|
|
34
|
+
- If mismatched `CLAUDE.md` files should be overwritten, run `oat instructions sync --force` (or combine it with `--strategy` if needed).
|
|
35
|
+
- If `stray` is reported, `oat instructions sync` will adopt the Claude-only file into `AGENTS.md` and then regenerate `CLAUDE.md`.
|
|
36
|
+
- If a broken or unreadable instruction path is reported, fix the underlying file or symlink target first; sync will intentionally skip manual-repair cases instead of forcing recovery.
|
|
37
|
+
- If a directory you expected to see is missing from the scan, confirm it is not under `.git`, `.oat`, `.worktrees`, or `node_modules`.
|
|
35
38
|
- Re-run `oat instructions validate` and confirm status is `ok`.
|
|
36
39
|
|
|
40
|
+
Use [Instruction Sync](../provider-sync/instruction-sync.md) for the full strategy matrix and state model.
|
|
41
|
+
|
|
37
42
|
## `doctor` warns about canonical directories
|
|
38
43
|
|
|
39
44
|
- Run `oat init` for the relevant scope.
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { Dirent, Stats } from 'node:fs';
|
|
2
2
|
import type { CommandContext, GlobalOptions } from '../../app/command-context.js';
|
|
3
|
-
export
|
|
3
|
+
export declare const INSTRUCTION_SYNC_STRATEGIES: readonly ["pointer", "symlink", "copy"];
|
|
4
|
+
export type InstructionSyncStrategy = (typeof INSTRUCTION_SYNC_STRATEGIES)[number];
|
|
5
|
+
export type InstructionStatus = 'ok' | 'missing' | 'content_mismatch' | 'stray';
|
|
4
6
|
export type InstructionsStatus = 'ok' | 'drift';
|
|
5
7
|
export interface InstructionEntry {
|
|
6
|
-
agentsPath: string;
|
|
8
|
+
agentsPath: string | null;
|
|
7
9
|
claudePath: string;
|
|
8
10
|
status: InstructionStatus;
|
|
9
11
|
detail: string;
|
|
@@ -22,6 +24,7 @@ export interface InstructionsSummary {
|
|
|
22
24
|
ok: number;
|
|
23
25
|
missing: number;
|
|
24
26
|
contentMismatch: number;
|
|
27
|
+
stray: number;
|
|
25
28
|
created: number;
|
|
26
29
|
updated: number;
|
|
27
30
|
skipped: number;
|
|
@@ -33,20 +36,30 @@ export interface InstructionsJsonPayload {
|
|
|
33
36
|
entries: InstructionEntry[];
|
|
34
37
|
actions: InstructionActionRecord[];
|
|
35
38
|
}
|
|
39
|
+
export interface InstructionsScanOptions {
|
|
40
|
+
strategy?: InstructionSyncStrategy;
|
|
41
|
+
debug?: (message: string) => void;
|
|
42
|
+
}
|
|
36
43
|
export interface InstructionsScanDependencies {
|
|
37
44
|
readdir: (path: string, options: {
|
|
38
45
|
withFileTypes: true;
|
|
39
46
|
}) => Promise<Dirent[]>;
|
|
47
|
+
lstat: (path: string) => Promise<Stats>;
|
|
48
|
+
realpath: (path: string) => Promise<string>;
|
|
40
49
|
readFile: (path: string, encoding: 'utf8') => Promise<string>;
|
|
50
|
+
readlink: (path: string) => Promise<string>;
|
|
41
51
|
stat: (path: string) => Promise<Stats>;
|
|
42
|
-
debug?: (message: string) => void;
|
|
43
52
|
}
|
|
44
53
|
export interface InstructionsValidateCommandDependencies {
|
|
45
54
|
buildCommandContext: (options: GlobalOptions) => CommandContext;
|
|
46
55
|
resolveProjectRoot: (cwd: string) => Promise<string>;
|
|
47
|
-
scanInstructionFiles: (repoRoot: string, overrides?: Partial<InstructionsScanDependencies>) => Promise<InstructionEntry[]>;
|
|
56
|
+
scanInstructionFiles: (repoRoot: string, options?: InstructionsScanOptions, overrides?: Partial<InstructionsScanDependencies>) => Promise<InstructionEntry[]>;
|
|
48
57
|
}
|
|
49
58
|
export interface InstructionsSyncCommandDependencies extends InstructionsValidateCommandDependencies {
|
|
59
|
+
lstat: (path: string) => Promise<Stats>;
|
|
60
|
+
readFile: (path: string, encoding: 'utf8') => Promise<string>;
|
|
61
|
+
removeFile: (path: string) => Promise<void>;
|
|
62
|
+
symlinkFile: (target: string, path: string) => Promise<void>;
|
|
50
63
|
writeFile: (path: string, content: string, encoding: 'utf8') => Promise<void>;
|
|
51
64
|
}
|
|
52
65
|
//# sourceMappingURL=instructions.types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instructions.types.d.ts","sourceRoot":"","sources":["../../../src/commands/instructions/instructions.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAE7C,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1E,MAAM,MAAM,iBAAiB,GAAG,IAAI,GAAG,SAAS,GAAG,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"instructions.types.d.ts","sourceRoot":"","sources":["../../../src/commands/instructions/instructions.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAE7C,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1E,eAAO,MAAM,2BAA2B,yCAI9B,CAAC;AAEX,MAAM,MAAM,uBAAuB,GACjC,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/C,MAAM,MAAM,iBAAiB,GAAG,IAAI,GAAG,SAAS,GAAG,kBAAkB,GAAG,OAAO,CAAC;AAEhF,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,OAAO,CAAC;AAEhD,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,qBAAqB,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEjE,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAExE,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,qBAAqB,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,uBAAuB,CAAC;CACjC;AAED,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,SAAS,GAAG,OAAO,CAAC;AAEhE,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,gBAAgB,CAAC;IACvB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,OAAO,EAAE,mBAAmB,CAAC;IAC7B,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,OAAO,EAAE,uBAAuB,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,EAAE,uBAAuB,CAAC;IACnC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,4BAA4B;IAC3C,OAAO,EAAE,CACP,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;QAAE,aAAa,EAAE,IAAI,CAAA;KAAE,KAC7B,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACvB,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;IACxC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9D,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,uCAAuC;IACtD,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,oBAAoB,EAAE,CACpB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,uBAAuB,EACjC,SAAS,CAAC,EAAE,OAAO,CAAC,4BAA4B,CAAC,KAC9C,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,mCAAoC,SAAQ,uCAAuC;IAClG,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;IACxC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9D,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/E"}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import type { InstructionActionRecord, InstructionEntry, InstructionsJsonPayload, InstructionsMode, InstructionsScanDependencies, InstructionsSummary } from './instructions.types.js';
|
|
1
|
+
import type { InstructionSyncStrategy, InstructionActionRecord, InstructionEntry, InstructionsJsonPayload, InstructionsMode, InstructionsScanOptions, InstructionsScanDependencies, InstructionsSummary } from './instructions.types.js';
|
|
2
2
|
export declare const EXPECTED_CLAUDE_CONTENT = "@AGENTS.md\n";
|
|
3
|
+
export declare const DEFAULT_INSTRUCTION_SYNC_STRATEGY: InstructionSyncStrategy;
|
|
3
4
|
interface BuildInstructionsPayloadArgs {
|
|
4
5
|
mode: InstructionsMode;
|
|
5
6
|
entries: InstructionEntry[];
|
|
6
7
|
actions: InstructionActionRecord[];
|
|
7
8
|
}
|
|
8
|
-
export declare function
|
|
9
|
+
export declare function resolveInstructionSyncStrategy(strategy?: InstructionSyncStrategy): InstructionSyncStrategy;
|
|
10
|
+
export declare function scanInstructionFiles(repoRoot: string, options?: InstructionsScanOptions, overrides?: Partial<InstructionsScanDependencies>): Promise<InstructionEntry[]>;
|
|
9
11
|
export declare function buildInstructionsSummary(entries: InstructionEntry[], actions: InstructionActionRecord[]): InstructionsSummary;
|
|
10
12
|
export declare function buildInstructionsPayload({ mode, entries, actions, }: BuildInstructionsPayloadArgs): InstructionsJsonPayload;
|
|
11
13
|
export declare function formatInstructionsReport(payload: InstructionsJsonPayload, repoRoot?: string): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instructions.utils.d.ts","sourceRoot":"","sources":["../../../src/commands/instructions/instructions.utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"instructions.utils.d.ts","sourceRoot":"","sources":["../../../src/commands/instructions/instructions.utils.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,uBAAuB,EACvB,uBAAuB,EACvB,gBAAgB,EAChB,uBAAuB,EACvB,gBAAgB,EAChB,uBAAuB,EACvB,4BAA4B,EAE5B,mBAAmB,EACpB,MAAM,sBAAsB,CAAC;AAE9B,eAAO,MAAM,uBAAuB,iBAAiB,CAAC;AACtD,eAAO,MAAM,iCAAiC,EAAE,uBACrC,CAAC;AAKZ,UAAU,4BAA4B;IACpC,IAAI,EAAE,gBAAgB,CAAC;IACvB,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,OAAO,EAAE,uBAAuB,EAAE,CAAC;CACpC;AAqBD,wBAAgB,8BAA8B,CAC5C,QAAQ,CAAC,EAAE,uBAAuB,GACjC,uBAAuB,CAEzB;AAoLD,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,uBAA4B,EACrC,SAAS,GAAE,OAAO,CAAC,4BAA4B,CAAM,GACpD,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAiO7B;AAED,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,gBAAgB,EAAE,EAC3B,OAAO,EAAE,uBAAuB,EAAE,GACjC,mBAAmB,CAoBrB;AAgBD,wBAAgB,wBAAwB,CAAC,EACvC,IAAI,EACJ,OAAO,EACP,OAAO,GACR,EAAE,4BAA4B,GAAG,uBAAuB,CAWxD;AAED,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,uBAAuB,EAChC,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,CAmCR"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
2
|
-
import { dirname, join, relative } from 'node:path';
|
|
1
|
+
import { lstat, readdir, readFile, readlink, realpath, stat, } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join, relative, resolve } from 'node:path';
|
|
3
3
|
export const EXPECTED_CLAUDE_CONTENT = '@AGENTS.md\n';
|
|
4
|
+
export const DEFAULT_INSTRUCTION_SYNC_STRATEGY = 'pointer';
|
|
4
5
|
const ROOT_EXCLUDED_DIRECTORIES = new Set(['.git', '.oat', '.worktrees']);
|
|
5
6
|
const GLOBAL_EXCLUDED_DIRECTORIES = new Set(['node_modules']);
|
|
6
7
|
function getErrorCode(error) {
|
|
@@ -11,12 +12,37 @@ function getErrorCode(error) {
|
|
|
11
12
|
function normalizeLineEndings(content) {
|
|
12
13
|
return content.replaceAll('\r\n', '\n');
|
|
13
14
|
}
|
|
15
|
+
export function resolveInstructionSyncStrategy(strategy) {
|
|
16
|
+
return strategy ?? DEFAULT_INSTRUCTION_SYNC_STRATEGY;
|
|
17
|
+
}
|
|
18
|
+
function getValidInstructionDetail(strategy) {
|
|
19
|
+
switch (strategy) {
|
|
20
|
+
case 'symlink':
|
|
21
|
+
return 'symlink valid';
|
|
22
|
+
case 'copy':
|
|
23
|
+
return 'copy valid';
|
|
24
|
+
default:
|
|
25
|
+
return 'pointer valid';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function getInvalidInstructionDetail(strategy) {
|
|
29
|
+
switch (strategy) {
|
|
30
|
+
case 'symlink':
|
|
31
|
+
return 'expected symlink to AGENTS.md';
|
|
32
|
+
case 'copy':
|
|
33
|
+
return 'expected hard copy of AGENTS.md content';
|
|
34
|
+
default:
|
|
35
|
+
return `expected ${JSON.stringify(EXPECTED_CLAUDE_CONTENT)}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
14
38
|
function toPosixPath(pathValue) {
|
|
15
39
|
return pathValue.replaceAll('\\', '/');
|
|
16
40
|
}
|
|
17
41
|
function normalizeEntries(entries) {
|
|
18
42
|
return [...entries].sort((left, right) => {
|
|
19
|
-
|
|
43
|
+
const leftSortPath = left.agentsPath ?? left.claudePath;
|
|
44
|
+
const rightSortPath = right.agentsPath ?? right.claudePath;
|
|
45
|
+
return (leftSortPath.localeCompare(rightSortPath) ||
|
|
20
46
|
left.claudePath.localeCompare(right.claudePath) ||
|
|
21
47
|
left.status.localeCompare(right.status) ||
|
|
22
48
|
left.detail.localeCompare(right.detail));
|
|
@@ -30,9 +56,23 @@ function normalizeActions(actions) {
|
|
|
30
56
|
left.reason.localeCompare(right.reason));
|
|
31
57
|
});
|
|
32
58
|
}
|
|
33
|
-
|
|
59
|
+
function recordInstructionFile(directoryEntries, directoryPath, entryName, entryPath) {
|
|
60
|
+
const current = directoryEntries.get(directoryPath) ?? {};
|
|
61
|
+
if (entryName === 'AGENTS.md') {
|
|
62
|
+
current.agentsPath = entryPath;
|
|
63
|
+
current.brokenAgentsPath = undefined;
|
|
64
|
+
current.brokenAgentsErrorCode = undefined;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
current.claudePath = entryPath;
|
|
68
|
+
current.brokenClaudePath = undefined;
|
|
69
|
+
current.brokenClaudeErrorCode = undefined;
|
|
70
|
+
}
|
|
71
|
+
directoryEntries.set(directoryPath, current);
|
|
72
|
+
}
|
|
73
|
+
async function scanInstructionDirectories(repoRoot, dependencies, debug) {
|
|
34
74
|
const queue = [repoRoot];
|
|
35
|
-
const
|
|
75
|
+
const directoryEntries = new Map();
|
|
36
76
|
while (queue.length > 0) {
|
|
37
77
|
const currentDirectory = queue.shift();
|
|
38
78
|
if (!currentDirectory) {
|
|
@@ -46,7 +86,7 @@ async function scanAgentsFiles(repoRoot, dependencies) {
|
|
|
46
86
|
}
|
|
47
87
|
catch (error) {
|
|
48
88
|
const errorCode = getErrorCode(error);
|
|
49
|
-
|
|
89
|
+
debug?.(`Skipping directory scan for ${toPosixPath(currentDirectory)} (${errorCode ?? 'unknown error'})`);
|
|
50
90
|
continue;
|
|
51
91
|
}
|
|
52
92
|
for (const entry of entries) {
|
|
@@ -63,8 +103,8 @@ async function scanAgentsFiles(repoRoot, dependencies) {
|
|
|
63
103
|
continue;
|
|
64
104
|
}
|
|
65
105
|
if (entry.isFile()) {
|
|
66
|
-
if (entry.name === 'AGENTS.md') {
|
|
67
|
-
|
|
106
|
+
if (entry.name === 'AGENTS.md' || entry.name === 'CLAUDE.md') {
|
|
107
|
+
recordInstructionFile(directoryEntries, currentDirectory, entry.name, entryPath);
|
|
68
108
|
}
|
|
69
109
|
continue;
|
|
70
110
|
}
|
|
@@ -77,38 +117,157 @@ async function scanAgentsFiles(repoRoot, dependencies) {
|
|
|
77
117
|
}
|
|
78
118
|
catch (error) {
|
|
79
119
|
const errorCode = getErrorCode(error);
|
|
80
|
-
|
|
120
|
+
if (entry.name === 'CLAUDE.md') {
|
|
121
|
+
const current = directoryEntries.get(currentDirectory) ?? {};
|
|
122
|
+
current.claudePath = entryPath;
|
|
123
|
+
current.brokenClaudePath = entryPath;
|
|
124
|
+
current.brokenClaudeErrorCode = errorCode ?? 'unknown error';
|
|
125
|
+
directoryEntries.set(currentDirectory, current);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (entry.name === 'AGENTS.md') {
|
|
129
|
+
const current = directoryEntries.get(currentDirectory) ?? {};
|
|
130
|
+
current.brokenAgentsPath = entryPath;
|
|
131
|
+
current.brokenAgentsErrorCode = errorCode ?? 'unknown error';
|
|
132
|
+
directoryEntries.set(currentDirectory, current);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
debug?.(`Skipping symlink target stat for ${toPosixPath(entryPath)} (${errorCode ?? 'unknown error'})`);
|
|
81
136
|
continue;
|
|
82
137
|
}
|
|
83
138
|
if (entryStats.isDirectory()) {
|
|
84
139
|
continue;
|
|
85
140
|
}
|
|
86
|
-
if (entryStats.isFile() &&
|
|
87
|
-
|
|
141
|
+
if (entryStats.isFile() &&
|
|
142
|
+
(entry.name === 'AGENTS.md' || entry.name === 'CLAUDE.md')) {
|
|
143
|
+
recordInstructionFile(directoryEntries, currentDirectory, entry.name, entryPath);
|
|
88
144
|
}
|
|
89
145
|
}
|
|
90
146
|
}
|
|
91
|
-
return
|
|
147
|
+
return directoryEntries;
|
|
92
148
|
}
|
|
93
|
-
export async function scanInstructionFiles(repoRoot, overrides = {}) {
|
|
149
|
+
export async function scanInstructionFiles(repoRoot, options = {}, overrides = {}) {
|
|
150
|
+
const strategy = resolveInstructionSyncStrategy(options.strategy);
|
|
94
151
|
const dependencies = {
|
|
152
|
+
lstat,
|
|
153
|
+
realpath,
|
|
95
154
|
readdir,
|
|
96
155
|
readFile,
|
|
156
|
+
readlink,
|
|
97
157
|
stat,
|
|
98
158
|
...overrides,
|
|
99
159
|
};
|
|
100
|
-
const
|
|
160
|
+
const instructionDirectories = await scanInstructionDirectories(repoRoot, dependencies, options.debug);
|
|
101
161
|
const entries = [];
|
|
102
|
-
for (const
|
|
103
|
-
const
|
|
162
|
+
for (const [directoryPath, directoryEntry] of instructionDirectories) {
|
|
163
|
+
const agentsPath = directoryEntry.agentsPath ?? null;
|
|
164
|
+
const brokenAgentsPath = directoryEntry.brokenAgentsPath ?? null;
|
|
165
|
+
const brokenAgentsErrorCode = directoryEntry.brokenAgentsErrorCode ?? 'unknown error';
|
|
166
|
+
const brokenClaudePath = directoryEntry.brokenClaudePath ?? null;
|
|
167
|
+
const brokenClaudeErrorCode = directoryEntry.brokenClaudeErrorCode ?? 'unknown error';
|
|
168
|
+
const claudePath = directoryEntry.claudePath ?? join(directoryPath, 'CLAUDE.md');
|
|
169
|
+
if (!agentsPath && brokenAgentsPath) {
|
|
170
|
+
entries.push({
|
|
171
|
+
agentsPath: brokenAgentsPath,
|
|
172
|
+
claudePath,
|
|
173
|
+
status: 'content_mismatch',
|
|
174
|
+
detail: brokenAgentsErrorCode === 'ENOENT'
|
|
175
|
+
? 'broken AGENTS.md symlink'
|
|
176
|
+
: `unreadable AGENTS.md symlink target (${brokenAgentsErrorCode})`,
|
|
177
|
+
});
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (brokenClaudePath) {
|
|
181
|
+
entries.push({
|
|
182
|
+
agentsPath,
|
|
183
|
+
claudePath,
|
|
184
|
+
status: 'content_mismatch',
|
|
185
|
+
detail: brokenClaudeErrorCode === 'ENOENT'
|
|
186
|
+
? 'broken CLAUDE.md symlink'
|
|
187
|
+
: `unreadable CLAUDE.md symlink target (${brokenClaudeErrorCode})`,
|
|
188
|
+
});
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (!agentsPath) {
|
|
192
|
+
try {
|
|
193
|
+
await dependencies.readFile(claudePath, 'utf8');
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
entries.push({
|
|
197
|
+
agentsPath: null,
|
|
198
|
+
claudePath,
|
|
199
|
+
status: 'content_mismatch',
|
|
200
|
+
detail: `unable to read CLAUDE.md (${getErrorCode(error) ?? 'unknown'})`,
|
|
201
|
+
});
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
entries.push({
|
|
205
|
+
agentsPath: null,
|
|
206
|
+
claudePath,
|
|
207
|
+
status: 'stray',
|
|
208
|
+
detail: 'CLAUDE.md found without AGENTS.md',
|
|
209
|
+
});
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
let claudeStats;
|
|
104
213
|
try {
|
|
105
|
-
|
|
106
|
-
|
|
214
|
+
claudeStats = await dependencies.lstat(claudePath);
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
const errorCode = getErrorCode(error);
|
|
218
|
+
if (errorCode === 'ENOENT') {
|
|
219
|
+
entries.push({
|
|
220
|
+
agentsPath,
|
|
221
|
+
claudePath,
|
|
222
|
+
status: 'missing',
|
|
223
|
+
detail: 'CLAUDE.md missing',
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
entries.push({
|
|
228
|
+
agentsPath,
|
|
229
|
+
claudePath,
|
|
230
|
+
status: 'content_mismatch',
|
|
231
|
+
detail: `unable to read CLAUDE.md (${errorCode ?? 'unknown error'})`,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (strategy === 'symlink') {
|
|
237
|
+
if (!claudeStats.isSymbolicLink()) {
|
|
238
|
+
entries.push({
|
|
239
|
+
agentsPath,
|
|
240
|
+
claudePath,
|
|
241
|
+
status: 'content_mismatch',
|
|
242
|
+
detail: getInvalidInstructionDetail(strategy),
|
|
243
|
+
});
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
let claudeTarget;
|
|
247
|
+
try {
|
|
248
|
+
claudeTarget = await dependencies.readlink(claudePath);
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
const errorCode = getErrorCode(error);
|
|
252
|
+
entries.push({
|
|
253
|
+
agentsPath,
|
|
254
|
+
claudePath,
|
|
255
|
+
status: 'content_mismatch',
|
|
256
|
+
detail: `unable to read CLAUDE.md symlink target (${errorCode ?? 'unknown error'})`,
|
|
257
|
+
});
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
const resolvedTarget = resolve(dirname(claudePath), claudeTarget);
|
|
261
|
+
const [canonicalTarget, canonicalAgentsPath] = await Promise.all([
|
|
262
|
+
dependencies.realpath(resolvedTarget).catch(() => resolvedTarget),
|
|
263
|
+
dependencies.realpath(agentsPath).catch(() => agentsPath),
|
|
264
|
+
]);
|
|
265
|
+
if (canonicalTarget === canonicalAgentsPath) {
|
|
107
266
|
entries.push({
|
|
108
267
|
agentsPath,
|
|
109
268
|
claudePath,
|
|
110
269
|
status: 'ok',
|
|
111
|
-
detail:
|
|
270
|
+
detail: getValidInstructionDetail(strategy),
|
|
112
271
|
});
|
|
113
272
|
}
|
|
114
273
|
else {
|
|
@@ -116,9 +275,23 @@ export async function scanInstructionFiles(repoRoot, overrides = {}) {
|
|
|
116
275
|
agentsPath,
|
|
117
276
|
claudePath,
|
|
118
277
|
status: 'content_mismatch',
|
|
119
|
-
detail:
|
|
278
|
+
detail: getInvalidInstructionDetail(strategy),
|
|
120
279
|
});
|
|
121
280
|
}
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (claudeStats.isSymbolicLink()) {
|
|
284
|
+
entries.push({
|
|
285
|
+
agentsPath,
|
|
286
|
+
claudePath,
|
|
287
|
+
status: 'content_mismatch',
|
|
288
|
+
detail: getInvalidInstructionDetail(strategy),
|
|
289
|
+
});
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
let claudeContent;
|
|
293
|
+
try {
|
|
294
|
+
claudeContent = await dependencies.readFile(claudePath, 'utf8');
|
|
122
295
|
}
|
|
123
296
|
catch (error) {
|
|
124
297
|
const errorCode = getErrorCode(error);
|
|
@@ -138,6 +311,44 @@ export async function scanInstructionFiles(repoRoot, overrides = {}) {
|
|
|
138
311
|
detail: `unable to read CLAUDE.md (${errorCode ?? 'unknown error'})`,
|
|
139
312
|
});
|
|
140
313
|
}
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
const expectedContent = strategy === 'copy'
|
|
317
|
+
? await (async () => {
|
|
318
|
+
try {
|
|
319
|
+
return await dependencies.readFile(agentsPath, 'utf8');
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
const errorCode = getErrorCode(error);
|
|
323
|
+
entries.push({
|
|
324
|
+
agentsPath,
|
|
325
|
+
claudePath,
|
|
326
|
+
status: 'content_mismatch',
|
|
327
|
+
detail: `unable to read AGENTS.md (${errorCode ?? 'unknown error'})`,
|
|
328
|
+
});
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
})()
|
|
332
|
+
: EXPECTED_CLAUDE_CONTENT;
|
|
333
|
+
if (expectedContent === null) {
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
if (normalizeLineEndings(claudeContent) ===
|
|
337
|
+
normalizeLineEndings(expectedContent)) {
|
|
338
|
+
entries.push({
|
|
339
|
+
agentsPath,
|
|
340
|
+
claudePath,
|
|
341
|
+
status: 'ok',
|
|
342
|
+
detail: getValidInstructionDetail(strategy),
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
entries.push({
|
|
347
|
+
agentsPath,
|
|
348
|
+
claudePath,
|
|
349
|
+
status: 'content_mismatch',
|
|
350
|
+
detail: getInvalidInstructionDetail(strategy),
|
|
351
|
+
});
|
|
141
352
|
}
|
|
142
353
|
}
|
|
143
354
|
return normalizeEntries(entries);
|
|
@@ -151,6 +362,7 @@ export function buildInstructionsSummary(entries, actions) {
|
|
|
151
362
|
missing: normalizedEntries.filter((entry) => entry.status === 'missing')
|
|
152
363
|
.length,
|
|
153
364
|
contentMismatch: normalizedEntries.filter((entry) => entry.status === 'content_mismatch').length,
|
|
365
|
+
stray: normalizedEntries.filter((entry) => entry.status === 'stray').length,
|
|
154
366
|
created: normalizedActions.filter((action) => action.type === 'create')
|
|
155
367
|
.length,
|
|
156
368
|
updated: normalizedActions.filter((action) => action.type === 'update')
|
|
@@ -181,7 +393,7 @@ export function formatInstructionsReport(payload, repoRoot) {
|
|
|
181
393
|
const lines = [
|
|
182
394
|
`instructions ${payload.mode}`,
|
|
183
395
|
`status: ${payload.status}`,
|
|
184
|
-
`summary: scanned=${payload.summary.scanned}, ok=${payload.summary.ok}, missing=${payload.summary.missing}, content_mismatch=${payload.summary.contentMismatch}, created=${payload.summary.created}, updated=${payload.summary.updated}, skipped=${payload.summary.skipped}`,
|
|
396
|
+
`summary: scanned=${payload.summary.scanned}, ok=${payload.summary.ok}, missing=${payload.summary.missing}, content_mismatch=${payload.summary.contentMismatch}, stray=${payload.summary.stray}, created=${payload.summary.created}, updated=${payload.summary.updated}, skipped=${payload.summary.skipped}`,
|
|
185
397
|
];
|
|
186
398
|
if (payload.entries.length === 0) {
|
|
187
399
|
lines.push('entries: none');
|
|
@@ -189,10 +401,11 @@ export function formatInstructionsReport(payload, repoRoot) {
|
|
|
189
401
|
else {
|
|
190
402
|
lines.push('entries:');
|
|
191
403
|
for (const entry of payload.entries) {
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
404
|
+
const displayPath = entry.agentsPath ?? entry.claudePath;
|
|
405
|
+
const relativePath = repoRoot
|
|
406
|
+
? toPosixPath(relative(repoRoot, displayPath)) || '.'
|
|
407
|
+
: toPosixPath(displayPath);
|
|
408
|
+
lines.push(`- ${relativePath} -> ${entry.status} (${entry.detail})`);
|
|
196
409
|
}
|
|
197
410
|
}
|
|
198
411
|
if (payload.actions.length === 0) {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { rm } from 'node:fs/promises';
|
|
2
|
+
import { type InstructionsSyncCommandDependencies } from '../../instructions/instructions.types.js';
|
|
2
3
|
import { Command } from 'commander';
|
|
4
|
+
export declare function removeInstructionFile(path: string, remove?: typeof rm): Promise<void>;
|
|
3
5
|
export declare function createInstructionsSyncCommand(overrides?: Partial<InstructionsSyncCommandDependencies>): Command;
|
|
4
6
|
//# sourceMappingURL=sync.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../../src/commands/instructions/sync/sync.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../../src/commands/instructions/sync/sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,EAAE,EAAsB,MAAM,kBAAkB,CAAC;AAI3E,OAAO,EAKL,KAAK,mCAAmC,EACzC,MAAM,2CAA2C,CAAC;AAYnD,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAQ5C,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,MAAM,EACZ,MAAM,GAAE,OAAO,EAAO,GACrB,OAAO,CAAC,IAAI,CAAC,CAEf;AA+SD,wBAAgB,6BAA6B,CAC3C,SAAS,GAAE,OAAO,CAAC,mCAAmC,CAAM,GAC3D,OAAO,CA6FT"}
|
|
@@ -1,26 +1,96 @@
|
|
|
1
|
-
import { writeFile } from 'node:fs/promises';
|
|
1
|
+
import { lstat, readFile, rm, symlink, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join, relative } from 'node:path';
|
|
2
3
|
import { buildCommandContext } from '../../../app/command-context.js';
|
|
3
|
-
import {
|
|
4
|
+
import { INSTRUCTION_SYNC_STRATEGIES, } from '../../instructions/instructions.types.js';
|
|
5
|
+
import { buildInstructionsPayload, DEFAULT_INSTRUCTION_SYNC_STRATEGY, EXPECTED_CLAUDE_CONTENT, formatInstructionsReport, resolveInstructionSyncStrategy, scanInstructionFiles, } from '../../instructions/instructions.utils.js';
|
|
4
6
|
import { readGlobalOptions } from '../../shared/shared.utils.js';
|
|
5
7
|
import { CliError } from '../../../errors/cli-error.js';
|
|
6
8
|
import { resolveProjectRoot } from '../../../fs/paths.js';
|
|
7
|
-
import { Command } from 'commander';
|
|
9
|
+
import { Command, Option } from 'commander';
|
|
10
|
+
export async function removeInstructionFile(path, remove = rm) {
|
|
11
|
+
await remove(path, { force: true });
|
|
12
|
+
}
|
|
8
13
|
function defaultDependencies() {
|
|
9
14
|
return {
|
|
10
15
|
buildCommandContext,
|
|
16
|
+
lstat,
|
|
17
|
+
readFile,
|
|
18
|
+
removeFile: removeInstructionFile,
|
|
11
19
|
resolveProjectRoot,
|
|
12
20
|
scanInstructionFiles,
|
|
21
|
+
symlinkFile: async (target, path) => {
|
|
22
|
+
await symlink(target, path, 'file');
|
|
23
|
+
},
|
|
13
24
|
writeFile,
|
|
14
25
|
};
|
|
15
26
|
}
|
|
16
|
-
function
|
|
27
|
+
function getSyncReason(actionType, strategy) {
|
|
28
|
+
const label = strategy === 'symlink'
|
|
29
|
+
? 'symlink'
|
|
30
|
+
: strategy === 'copy'
|
|
31
|
+
? 'hard copy'
|
|
32
|
+
: 'pointer file';
|
|
33
|
+
return actionType === 'create'
|
|
34
|
+
? `missing CLAUDE.md ${label}`
|
|
35
|
+
: `overwrite CLAUDE.md with canonical ${label}`;
|
|
36
|
+
}
|
|
37
|
+
function getSyncedDetail(strategy) {
|
|
38
|
+
switch (strategy) {
|
|
39
|
+
case 'symlink':
|
|
40
|
+
return 'symlink synced';
|
|
41
|
+
case 'copy':
|
|
42
|
+
return 'copy synced';
|
|
43
|
+
default:
|
|
44
|
+
return 'pointer synced';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function getAgentsPath(entry) {
|
|
48
|
+
return entry.agentsPath ?? join(dirname(entry.claudePath), 'AGENTS.md');
|
|
49
|
+
}
|
|
50
|
+
function getErrorCode(error) {
|
|
51
|
+
return error && typeof error === 'object' && 'code' in error
|
|
52
|
+
? String(error.code)
|
|
53
|
+
: null;
|
|
54
|
+
}
|
|
55
|
+
function hasUnreadableCanonicalAgents(entry) {
|
|
56
|
+
return (entry.agentsPath !== null &&
|
|
57
|
+
(entry.detail === 'broken AGENTS.md symlink' ||
|
|
58
|
+
entry.detail.startsWith('unreadable AGENTS.md symlink target') ||
|
|
59
|
+
entry.detail.startsWith('unable to read AGENTS.md')));
|
|
60
|
+
}
|
|
61
|
+
function hasUnreadableClaudeSource(entry) {
|
|
62
|
+
return (entry.agentsPath === null &&
|
|
63
|
+
(entry.detail === 'broken CLAUDE.md symlink' ||
|
|
64
|
+
entry.detail.startsWith('unreadable CLAUDE.md symlink target') ||
|
|
65
|
+
entry.detail.startsWith('unable to read CLAUDE.md')));
|
|
66
|
+
}
|
|
67
|
+
function wrapStrayResyncError(error, agentsPath, claudePath) {
|
|
68
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
69
|
+
return new CliError(`Adopted stray instructions into ${agentsPath}, but failed to regenerate ${claudePath}: ${message}`, error instanceof CliError ? error.exitCode : 2);
|
|
70
|
+
}
|
|
71
|
+
function planSyncActions({ entries, force, strategy, }) {
|
|
17
72
|
const actions = [];
|
|
18
73
|
for (const entry of entries) {
|
|
74
|
+
if (entry.status === 'stray') {
|
|
75
|
+
actions.push({
|
|
76
|
+
type: 'create',
|
|
77
|
+
target: getAgentsPath(entry),
|
|
78
|
+
reason: 'adopt stray CLAUDE.md into canonical AGENTS.md',
|
|
79
|
+
result: 'planned',
|
|
80
|
+
});
|
|
81
|
+
actions.push({
|
|
82
|
+
type: 'update',
|
|
83
|
+
target: entry.claudePath,
|
|
84
|
+
reason: getSyncReason('update', strategy),
|
|
85
|
+
result: 'planned',
|
|
86
|
+
});
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
19
89
|
if (entry.status === 'missing') {
|
|
20
90
|
actions.push({
|
|
21
91
|
type: 'create',
|
|
22
92
|
target: entry.claudePath,
|
|
23
|
-
reason: '
|
|
93
|
+
reason: getSyncReason('create', strategy),
|
|
24
94
|
result: 'planned',
|
|
25
95
|
});
|
|
26
96
|
continue;
|
|
@@ -28,6 +98,28 @@ function planSyncActions({ entries, force, }) {
|
|
|
28
98
|
if (entry.status !== 'content_mismatch') {
|
|
29
99
|
continue;
|
|
30
100
|
}
|
|
101
|
+
if (hasUnreadableClaudeSource(entry)) {
|
|
102
|
+
actions.push({
|
|
103
|
+
type: 'skip',
|
|
104
|
+
target: entry.claudePath,
|
|
105
|
+
reason: 'CLAUDE.md unreadable; repair manually',
|
|
106
|
+
result: 'skipped',
|
|
107
|
+
});
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (hasUnreadableCanonicalAgents(entry)) {
|
|
111
|
+
const unreadableAgentsPath = entry.agentsPath;
|
|
112
|
+
if (!unreadableAgentsPath) {
|
|
113
|
+
throw new CliError(`Unable to resolve unreadable AGENTS.md path for ${entry.claudePath}`, 2);
|
|
114
|
+
}
|
|
115
|
+
actions.push({
|
|
116
|
+
type: 'skip',
|
|
117
|
+
target: unreadableAgentsPath,
|
|
118
|
+
reason: 'canonical AGENTS.md unreadable; repair manually',
|
|
119
|
+
result: 'skipped',
|
|
120
|
+
});
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
31
123
|
if (!force) {
|
|
32
124
|
actions.push({
|
|
33
125
|
type: 'skip',
|
|
@@ -40,20 +132,76 @@ function planSyncActions({ entries, force, }) {
|
|
|
40
132
|
actions.push({
|
|
41
133
|
type: 'update',
|
|
42
134
|
target: entry.claudePath,
|
|
43
|
-
reason: '
|
|
135
|
+
reason: getSyncReason('update', strategy),
|
|
44
136
|
result: 'planned',
|
|
45
137
|
});
|
|
46
138
|
}
|
|
47
139
|
return actions;
|
|
48
140
|
}
|
|
49
|
-
async function applySyncActions(actions, dependencies) {
|
|
141
|
+
async function applySyncActions(actions, entries, dependencies, strategy) {
|
|
50
142
|
const appliedActions = [];
|
|
143
|
+
const entriesByTarget = new Map();
|
|
144
|
+
for (const entry of entries) {
|
|
145
|
+
entriesByTarget.set(entry.claudePath, entry);
|
|
146
|
+
entriesByTarget.set(getAgentsPath(entry), entry);
|
|
147
|
+
}
|
|
51
148
|
for (const action of actions) {
|
|
52
149
|
if (action.result !== 'planned') {
|
|
53
150
|
appliedActions.push(action);
|
|
54
151
|
continue;
|
|
55
152
|
}
|
|
56
|
-
|
|
153
|
+
const entry = entriesByTarget.get(action.target);
|
|
154
|
+
if (!entry) {
|
|
155
|
+
throw new CliError(`Unable to resolve instruction entry for ${action.target}`, 2);
|
|
156
|
+
}
|
|
157
|
+
const agentsPath = getAgentsPath(entry);
|
|
158
|
+
const isAgentsAction = action.target === agentsPath;
|
|
159
|
+
if (isAgentsAction) {
|
|
160
|
+
try {
|
|
161
|
+
await dependencies.lstat(agentsPath);
|
|
162
|
+
throw new CliError(`Canonical AGENTS.md appeared during sync at ${agentsPath}; re-run to reclassify before adopting stray CLAUDE.md`, 2);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
if (error instanceof CliError) {
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
if (getErrorCode(error) !== 'ENOENT') {
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const adoptedContent = await dependencies.readFile(entry.claudePath, 'utf8');
|
|
173
|
+
await dependencies.writeFile(agentsPath, adoptedContent, 'utf8');
|
|
174
|
+
appliedActions.push({
|
|
175
|
+
...action,
|
|
176
|
+
result: 'applied',
|
|
177
|
+
});
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (!entry.agentsPath && action.type !== 'update') {
|
|
181
|
+
throw new CliError(`Unable to resolve AGENTS.md for ${action.target}`, 2);
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
if (action.type === 'update') {
|
|
185
|
+
await dependencies.removeFile(action.target);
|
|
186
|
+
}
|
|
187
|
+
if (strategy === 'symlink') {
|
|
188
|
+
const symlinkTarget = relative(dirname(action.target), agentsPath);
|
|
189
|
+
await dependencies.symlinkFile(symlinkTarget, action.target);
|
|
190
|
+
}
|
|
191
|
+
else if (strategy === 'copy') {
|
|
192
|
+
const agentsContent = await dependencies.readFile(agentsPath, 'utf8');
|
|
193
|
+
await dependencies.writeFile(action.target, agentsContent, 'utf8');
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
await dependencies.writeFile(action.target, EXPECTED_CLAUDE_CONTENT, 'utf8');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
if (entry.status === 'stray') {
|
|
201
|
+
throw wrapStrayResyncError(error, agentsPath, action.target);
|
|
202
|
+
}
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
57
205
|
appliedActions.push({
|
|
58
206
|
...action,
|
|
59
207
|
result: 'applied',
|
|
@@ -61,18 +209,21 @@ async function applySyncActions(actions, dependencies) {
|
|
|
61
209
|
}
|
|
62
210
|
return appliedActions;
|
|
63
211
|
}
|
|
64
|
-
function getPostSyncEntries(entries, actions) {
|
|
212
|
+
function getPostSyncEntries(entries, actions, strategy) {
|
|
65
213
|
const actionByTarget = new Map(actions.map((action) => [action.target, action]));
|
|
66
214
|
return entries.map((entry) => {
|
|
67
215
|
const action = actionByTarget.get(entry.claudePath);
|
|
68
|
-
|
|
216
|
+
const adoptedAction = actionByTarget.get(getAgentsPath(entry));
|
|
217
|
+
if (!action && !adoptedAction) {
|
|
69
218
|
return entry;
|
|
70
219
|
}
|
|
71
|
-
if (action
|
|
220
|
+
if (action?.result === 'applied' &&
|
|
221
|
+
(entry.status !== 'stray' || adoptedAction?.result === 'applied')) {
|
|
72
222
|
return {
|
|
73
223
|
...entry,
|
|
224
|
+
agentsPath: getAgentsPath(entry),
|
|
74
225
|
status: 'ok',
|
|
75
|
-
detail:
|
|
226
|
+
detail: getSyncedDetail(strategy),
|
|
76
227
|
};
|
|
77
228
|
}
|
|
78
229
|
return entry;
|
|
@@ -87,25 +238,34 @@ export function createInstructionsSyncCommand(overrides = {}) {
|
|
|
87
238
|
...overrides,
|
|
88
239
|
};
|
|
89
240
|
return new Command('sync')
|
|
90
|
-
.description('Repair AGENTS.md
|
|
241
|
+
.description('Repair AGENTS.md/CLAUDE.md sync drift using the selected strategy')
|
|
91
242
|
.option('--dry-run', 'Preview sync changes without applying')
|
|
92
243
|
.option('--force', 'Overwrite mismatched CLAUDE.md files')
|
|
244
|
+
.addOption(new Option('--strategy <strategy>', 'Sync strategy')
|
|
245
|
+
.choices([...INSTRUCTION_SYNC_STRATEGIES])
|
|
246
|
+
.default(DEFAULT_INSTRUCTION_SYNC_STRATEGY))
|
|
93
247
|
.action(async (options, command) => {
|
|
94
248
|
const context = dependencies.buildCommandContext(readGlobalOptions(command));
|
|
95
249
|
try {
|
|
96
250
|
const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
|
|
97
|
-
const
|
|
251
|
+
const strategy = resolveInstructionSyncStrategy(options.strategy);
|
|
252
|
+
const entries = await dependencies.scanInstructionFiles(repoRoot, {
|
|
253
|
+
strategy,
|
|
254
|
+
});
|
|
98
255
|
const plannedActions = planSyncActions({
|
|
99
256
|
entries,
|
|
100
257
|
force: options.force ?? false,
|
|
258
|
+
strategy,
|
|
101
259
|
});
|
|
102
260
|
const dryRun = options.dryRun ?? false;
|
|
103
261
|
const actions = dryRun
|
|
104
262
|
? plannedActions
|
|
105
|
-
: await applySyncActions(plannedActions, dependencies);
|
|
263
|
+
: await applySyncActions(plannedActions, entries, dependencies, strategy);
|
|
106
264
|
const payload = buildInstructionsPayload({
|
|
107
265
|
mode: dryRun ? 'dry-run' : 'apply',
|
|
108
|
-
entries: dryRun
|
|
266
|
+
entries: dryRun
|
|
267
|
+
? entries
|
|
268
|
+
: getPostSyncEntries(entries, actions, strategy),
|
|
109
269
|
actions,
|
|
110
270
|
});
|
|
111
271
|
if (context.json) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type InstructionsValidateCommandDependencies } from '../../instructions/instructions.types.js';
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
export declare function createInstructionsValidateCommand(overrides?: Partial<InstructionsValidateCommandDependencies>): Command;
|
|
4
4
|
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../../src/commands/instructions/validate/validate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../../src/commands/instructions/validate/validate.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,uCAAuC,EAC7C,MAAM,2CAA2C,CAAC;AAWnD,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAU5C,wBAAgB,iCAAiC,CAC/C,SAAS,GAAE,OAAO,CAAC,uCAAuC,CAAM,GAC/D,OAAO,CA2DT"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { buildCommandContext } from '../../../app/command-context.js';
|
|
2
|
-
import {
|
|
2
|
+
import { INSTRUCTION_SYNC_STRATEGIES, } from '../../instructions/instructions.types.js';
|
|
3
|
+
import { buildInstructionsPayload, DEFAULT_INSTRUCTION_SYNC_STRATEGY, formatInstructionsReport, resolveInstructionSyncStrategy, scanInstructionFiles, } from '../../instructions/instructions.utils.js';
|
|
3
4
|
import { readGlobalOptions } from '../../shared/shared.utils.js';
|
|
4
5
|
import { CliError } from '../../../errors/cli-error.js';
|
|
5
6
|
import { resolveProjectRoot } from '../../../fs/paths.js';
|
|
6
|
-
import { Command } from 'commander';
|
|
7
|
+
import { Command, Option } from 'commander';
|
|
7
8
|
function defaultDependencies() {
|
|
8
9
|
return {
|
|
9
10
|
buildCommandContext,
|
|
@@ -17,12 +18,18 @@ export function createInstructionsValidateCommand(overrides = {}) {
|
|
|
17
18
|
...overrides,
|
|
18
19
|
};
|
|
19
20
|
return new Command('validate')
|
|
20
|
-
.description('Validate AGENTS.md
|
|
21
|
-
.
|
|
21
|
+
.description('Validate AGENTS.md/CLAUDE.md sync integrity for the selected strategy')
|
|
22
|
+
.addOption(new Option('--strategy <strategy>', 'Sync strategy')
|
|
23
|
+
.choices([...INSTRUCTION_SYNC_STRATEGIES])
|
|
24
|
+
.default(DEFAULT_INSTRUCTION_SYNC_STRATEGY))
|
|
25
|
+
.action(async (options, command) => {
|
|
22
26
|
const context = dependencies.buildCommandContext(readGlobalOptions(command));
|
|
27
|
+
const strategy = resolveInstructionSyncStrategy(options.strategy);
|
|
23
28
|
try {
|
|
24
29
|
const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
|
|
25
|
-
const entries = await dependencies.scanInstructionFiles(repoRoot
|
|
30
|
+
const entries = await dependencies.scanInstructionFiles(repoRoot, {
|
|
31
|
+
strategy,
|
|
32
|
+
});
|
|
26
33
|
const payload = buildInstructionsPayload({
|
|
27
34
|
mode: 'validate',
|
|
28
35
|
entries,
|
|
@@ -34,7 +41,10 @@ export function createInstructionsValidateCommand(overrides = {}) {
|
|
|
34
41
|
else {
|
|
35
42
|
context.logger.info(formatInstructionsReport(payload, repoRoot));
|
|
36
43
|
if (payload.status === 'drift') {
|
|
37
|
-
|
|
44
|
+
const fixCommand = strategy === DEFAULT_INSTRUCTION_SYNC_STRATEGY
|
|
45
|
+
? 'Fix with: oat instructions sync'
|
|
46
|
+
: `Fix with: oat instructions sync --strategy ${strategy}`;
|
|
47
|
+
context.logger.info(fixCommand);
|
|
38
48
|
}
|
|
39
49
|
}
|
|
40
50
|
process.exitCode = payload.status === 'ok' ? 0 : 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-agent-toolkit/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.32",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Open Agent Toolkit CLI",
|
|
6
6
|
"homepage": "https://github.com/voxmedia/open-agent-toolkit/tree/main/packages/cli",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"ora": "^9.0.0",
|
|
34
34
|
"yaml": "2.8.2",
|
|
35
35
|
"zod": "^3.25.76",
|
|
36
|
-
"@open-agent-toolkit/control-plane": "0.0.
|
|
36
|
+
"@open-agent-toolkit/control-plane": "0.0.32"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^22.10.0",
|