agentsmesh 0.36.0 → 0.37.0
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/CHANGELOG.md +38 -0
- package/README.md +2 -0
- package/dist/canonical.js +242 -123
- package/dist/canonical.js.map +1 -1
- package/dist/cli.js +259 -252
- package/dist/engine.js +398 -157
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +409 -163
- package/dist/index.js.map +1 -1
- package/dist/{init-Dtp4V1_v.d.ts → init-BtgoRmIs.d.ts} +1 -6
- package/dist/lessons.d.ts +2 -2
- package/dist/lessons.js +127 -32
- package/dist/lessons.js.map +1 -1
- package/dist/targets.js +231 -103
- package/dist/targets.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.37.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f5ab3d1: **Changed — writes are atomic and stay inside the project.** Every generated file is written through an exclusively created temporary file and renamed into place, so a concurrent run or a crash never leaves a half-written file, and a symlink at the destination is replaced rather than followed. `generate` and `convert` reject an output directory that resolves outside the project (or the home directory with `--global`) through a symlink; symlinks that stay inside the boundary keep working. A stale `.generate.lock` left by a dead process is evicted, and one that cannot be removed fails fast with the error instead of retrying forever.
|
|
8
|
+
|
|
9
|
+
**Changed — Claude Code.** An explicitly empty canonical file now clears the entries agentsmesh generated: `allow: []`, `deny: []` and `ask: []` remove permission rules, `hooks.yaml` containing `{}` removes hooks, and `mcp.json` containing `{"mcpServers": {}}` removes servers, while every other key in `.claude/settings.json` and `.mcp.json` stays as it was. A missing canonical file still leaves the tool's settings alone.
|
|
10
|
+
|
|
11
|
+
**Fixed — MCP server and installs.** `add_mcp_server`, `update_mcp_server` and `remove_mcp_server` keep unrelated servers, unknown server fields and top-level keys of `.agentsmesh/mcp.json`, and refuse to overwrite a malformed file. `create_skill` and `update_skill` validate every supporting path and file size before writing anything, and `SKILL.md` can no longer be replaced through `supportingFiles`. Files written by the MCP server get the same line-ending normalization and executable bit as generated files. `install` stages a pack in a private transaction directory, so a sibling `<pack>.tmp` or `<pack>.old` directory is never touched and a failed swap restores the previous pack. `watch` ignores the new temporary files and no longer re-triggers on its own writes.
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- 87486d2: **Fixed — output boundary check runs before any write, and under `--dry-run`.** `generate` and `convert` now check every output path and every managed directory of the active targets against the project (or home) boundary before writing anything. Previously a managed directory that produced no output this run — `.cursor/agents` with agents disabled, say — was first checked during stale cleanup, after every file had been written and before the lock was saved, so a symlink escaping the boundary left a half-generated project with no lock and `check` reporting "not initialized" on every rerun. `--dry-run` now runs the same check, so a preview no longer reports OK for a layout the real run rejects. The error names the path, where it resolves to, and the boundary it escapes.
|
|
16
|
+
- f0668ac: Bumped the `smol-toml` production dependency to 1.8.0, clearing GHSA-7w5x-hrqm-74c2 (high) which affects every version up to 1.7.0. `smol-toml` parses `.codex/config.toml`, so the advisory shipped to anyone installing agentsmesh.
|
|
17
|
+
- ce9647d: **Fixed — a token in an install source is no longer written to files you commit.** Installing from `git+https://user:token@host/org/repo.git` recorded the URL verbatim in `installs.yaml`, `pack.yaml` and the install manifest, and used it to name a directory in the remote cache. The credential is now stripped from everything persisted and from cache keys; only the fetch itself sees it, and the same repository resolves to one cache entry whether or not a token was supplied. Authentication for a later `refresh` comes from git's own credential mechanism (a helper, or SSH) rather than from a secret committed alongside the manifest.
|
|
18
|
+
|
|
19
|
+
**Fixed — `agentsmesh.yaml` keeps its comments.** Adding an extends entry with `install --extends`, or removing one with `uninstall`, rewrote the file by re-serializing a parsed object, which silently discarded every comment and any key this version does not model. Both paths now edit the document in place.
|
|
20
|
+
|
|
21
|
+
**Fixed — `install --name` will not adopt an extends entry you wrote by hand.** A name already used by an entry pointing at a different source is refused with a message naming that source, because `uninstall` removes the row by name — so adopting it meant your entry disappeared along with the pack. Reusing the name of an earlier install of the same source still works.
|
|
22
|
+
|
|
23
|
+
**Changed — more skill file types are carried as bytes.** The binary allowlist added Office and design documents (`.xlsx`, `.docx`, `.pptx`, `.psd`, `.ai`, `.sketch`, `.fig`), uncompressed and modern archives (`.tar`, `.zst`, `.br`, `.lz4`), columnar data and model formats (`.parquet`, `.avro`, `.onnx`, `.safetensors`, `.gguf`), and further media and database extensions. A skill shipping a spreadsheet template is no longer corrupted on generate.
|
|
24
|
+
|
|
25
|
+
- 0ea6be3: **Fixed — `--json` output is complete when you pipe it.** Node writes to a pipe asynchronously, and `process.exit` discards whatever is still queued, so any envelope larger than the pipe buffer reached the consumer as an unparseable fragment: `agentsmesh diff --json | jq` received 64 KiB of a 7 MB document, while the same command redirected to a file was whole. Redirect and pipe now agree at any payload size. Human-readable output on a failing command is covered by the same change.
|
|
26
|
+
|
|
27
|
+
**Fixed — seeding the self-serve MCP entry no longer costs you the rest of `mcp.json`.** `init` and `import` add an `agentsmesh` server entry to `.agentsmesh/mcp.json`; that write went through the canonical parser, which models only the fields AgentsMesh itself uses. Every other top-level key and every per-server field it does not model — `cwd`, `disabled`, `timeout` — was dropped on the way back out, and a file that failed to parse was replaced by a document containing nothing but the new entry. The write now patches the raw document, so unknown keys survive verbatim. A file that cannot be rewritten safely (invalid JSON, or JSON with comments a rewrite would discard) is left untouched with a warning naming the reason, matching how the generated-output mergers already treat a file they cannot parse.
|
|
28
|
+
|
|
29
|
+
- 0b91ded: **Fixed — a rule scoped to one target is no longer dropped when targets share `AGENTS.md`.** Several tools read the same root instruction file, so two rules each scoped with `targets:` to a different one of them produced two different bodies for one path. The resolver kept whichever body was byte-longer and discarded the other silently, so one target's rule simply never appeared. Bodies that differ only inside the embedded-rules block — which is exactly what scoped rules produce — are now combined, keeping every rule and warning that the scoping was widened, because one file cannot differ per target. Where bodies differ outside that block, the run fails with the usual conflict error instead of guessing. The related Codex preference now compares substance rather than byte length: it applies only when one body carries every line of the other.
|
|
30
|
+
|
|
31
|
+
**Fixed — ordinary Markdown no longer fails generation.** Three shapes were read as broken local links and aborted the run: link syntax quoted inside inline code (`` `[label](path/to/file.md)` ``, which is how a rule documents the syntax), GFM footnote definitions (`[^1]: prose`), and — for URLs — a path containing parentheses. A Next.js route group such as `apps/(marketing)/package.json` was also truncated to `apps/(marketing)package.json` when the tail matched a real project file, because the protected span stopped at the first parenthesis. URLs now keep balanced parenthesised segments, and the broken-link check skips footnotes and inline code. A path written inside backticks is still rebased as before; only the check treats it as prose.
|
|
32
|
+
|
|
33
|
+
- 5374565: **Fixed — binary files a skill ships are no longer corrupted.** Supporting files under `.agentsmesh/skills/<name>/` were read as UTF-8 text, so an image, font, archive or PDF had every invalid byte replaced before any generator saw it, and the broken copy was written to every target. Binary payloads now travel as bytes through read, write and checksum, so a skill's diagram or icon arrives intact and `agentsmesh check` no longer reports drift on it the moment it is generated. Text files are unchanged: line-ending normalization and BOM handling still apply to them, and only a fixed list of binary extensions takes the byte path.
|
|
34
|
+
|
|
35
|
+
**Fixed — `agentsmesh init` no longer replaces canonical files.** When `agentsmesh.yaml` was missing but `.agentsmesh/` already held real content — deleted by hand, lost in a merge, or never committed — `init` overwrote `rules/_root.md`, `mcp.json`, `hooks.yaml`, `permissions.yaml` and `ignore` with templates and exited 0. The already-initialized error also told users to remove the config file and re-run, which was exactly the sequence that destroyed their work. The scaffold now only fills gaps, and the message explains that re-running `init` is not a reset.
|
|
36
|
+
|
|
37
|
+
**Fixed — the lessons merge driver no longer discards the other branch's lessons.** A failing driver does not make git re-merge the file with conflict markers: git leaves your side exactly as it was and marks the path unmerged, so `git add` silently threw away every rule the other branch captured. Three ordinary situations hit that path — both branches creating the graph for the first time (git supplies an empty ancestor), the same rule captured under two ids, and any validation error that already existed in the ancestor. The driver now treats an empty ancestor as an empty graph, ignores errors that predate the merge, and writes the union of both branches whenever it can build one, reserving a non-zero exit for "a human should look at this" instead of "throw work away".
|
|
38
|
+
|
|
39
|
+
**Fixed — `agentsmesh install --extends` no longer copies private extends into the shared config.** The entry was merged into the config as loaded, which already included the gitignored `agentsmesh.local.yaml`, so every local-only `extends` entry was written into the committed `agentsmesh.yaml`. The write is now scoped to what the shared file itself declares.
|
|
40
|
+
|
|
3
41
|
## 0.36.0
|
|
4
42
|
|
|
5
43
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -142,6 +142,8 @@ Built to be depended on: 12,000+ tests on Linux, macOS, and Windows CI, JSON-Sch
|
|
|
142
142
|
- **Lossless two-way sync.** When a tool has no native slot for a feature, it is embedded with round-trip metadata instead of dropped. [Managed embedding](https://samplexbro.github.io/agentsmesh/reference/managed-embedding/)
|
|
143
143
|
- **Automatic link rebasing.** `.agentsmesh/skills/api-gen/template.hbs` becomes `.claude/skills/...` or `.cursor/skills/...` in each generated file. [Generation pipeline](https://samplexbro.github.io/agentsmesh/reference/generation-pipeline/)
|
|
144
144
|
- **Safe adoption.** `import`, then `diff`, then `generate`, then `check`; nothing is overwritten blind. [Existing-project guide](https://samplexbro.github.io/agentsmesh/guides/existing-project/)
|
|
145
|
+
- **Output directory safety.** `generate` and `convert` reject output directories that resolve outside the project (or home directory with `--global`), checked before any write and under `--dry-run` too. Stale-file cleanup enforces the same boundary.
|
|
146
|
+
- **Safe updates.** Explicitly empty Claude permission, hook, and MCP configurations clear their generated entries. MCP edits preserve untouched server fields and reject malformed files; skill updates validate supporting files before writing.
|
|
145
147
|
- **Migrate between tools.** `convert --from <a> --to <b>` rewrites one tool's config directly into another's. [convert](https://samplexbro.github.io/agentsmesh/cli/convert/)
|
|
146
148
|
- **Global mode.** `~/.agentsmesh/` syncs personal config to `~/.claude/`, `~/.cursor/`, `~/.codex/`, and more; config commands accept `--global`. [Global paths](https://samplexbro.github.io/agentsmesh/reference/supported-tools/#global-mode)
|
|
147
149
|
- **CI-ready.** `check` gates drift, `diff` previews, `merge` rebuilds the lock after a Git conflict, and `lint` warns about content a tool would mishandle. [check](https://samplexbro.github.io/agentsmesh/cli/check/) · [lint](https://samplexbro.github.io/agentsmesh/cli/lint/)
|
package/dist/canonical.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { join, basename, dirname, relative, extname, resolve, posix, win32 } from 'path';
|
|
2
|
-
import { access, readdir, readFile, rm, mkdir, lstat,
|
|
2
|
+
import { access, readdir, readFile, rm, mkdir, lstat, open, stat, realpath, rename, writeFile, mkdtemp, cp } from 'fs/promises';
|
|
3
3
|
import { setTimeout } from 'timers/promises';
|
|
4
|
+
import { randomUUID, createHash } from 'crypto';
|
|
4
5
|
import { constants, existsSync, readFileSync, realpathSync, statSync } from 'fs';
|
|
5
6
|
import { parse, stringify, parseDocument, isMap, Document, isSeq, isScalar, Scalar, Pair, YAMLSeq } from 'yaml';
|
|
6
7
|
import { z } from 'zod';
|
|
7
8
|
import { parse as parse$1, stringify as stringify$1 } from 'smol-toml';
|
|
8
9
|
import { Buffer } from 'buffer';
|
|
9
10
|
import { homedir, tmpdir } from 'os';
|
|
10
|
-
import { createHash } from 'crypto';
|
|
11
11
|
import { execFile } from 'child_process';
|
|
12
12
|
import { URL as URL$1 } from 'url';
|
|
13
13
|
import { promisify } from 'util';
|
|
@@ -92,13 +92,19 @@ function shouldNormalizeLineEndings(path) {
|
|
|
92
92
|
const base = basename(path).toLowerCase();
|
|
93
93
|
return TEXT_DOTFILES.has(base);
|
|
94
94
|
}
|
|
95
|
+
function isBinaryPayloadPath(path) {
|
|
96
|
+
return BINARY_EXTENSIONS.has(extname(path).toLowerCase());
|
|
97
|
+
}
|
|
98
|
+
function payloadEncodingFor(path) {
|
|
99
|
+
return isBinaryPayloadPath(path) ? "latin1" : "utf-8";
|
|
100
|
+
}
|
|
95
101
|
function normalizeLineEndings(content) {
|
|
96
102
|
return content.replace(/\r\n?/g, "\n");
|
|
97
103
|
}
|
|
98
104
|
function executableModeFor(path) {
|
|
99
105
|
return EXECUTABLE_SCRIPT_EXTENSIONS.has(extname(path).toLowerCase()) ? 493 : void 0;
|
|
100
106
|
}
|
|
101
|
-
var UTF8_BOM, TEXT_EXTENSIONS, TEXT_DOTFILES, EXECUTABLE_SCRIPT_EXTENSIONS;
|
|
107
|
+
var UTF8_BOM, TEXT_EXTENSIONS, TEXT_DOTFILES, BINARY_EXTENSIONS, EXECUTABLE_SCRIPT_EXTENSIONS;
|
|
102
108
|
var init_fs_text_encoding = __esm({
|
|
103
109
|
"src/utils/filesystem/fs-text-encoding.ts"() {
|
|
104
110
|
UTF8_BOM = "\uFEFF";
|
|
@@ -143,9 +149,109 @@ var init_fs_text_encoding = __esm({
|
|
|
143
149
|
".rooignore",
|
|
144
150
|
".antigravityignore"
|
|
145
151
|
]);
|
|
152
|
+
BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
153
|
+
".png",
|
|
154
|
+
".jpg",
|
|
155
|
+
".jpeg",
|
|
156
|
+
".gif",
|
|
157
|
+
".bmp",
|
|
158
|
+
".ico",
|
|
159
|
+
".webp",
|
|
160
|
+
".avif",
|
|
161
|
+
".tiff",
|
|
162
|
+
".pdf",
|
|
163
|
+
".zip",
|
|
164
|
+
".gz",
|
|
165
|
+
".tgz",
|
|
166
|
+
".bz2",
|
|
167
|
+
".xz",
|
|
168
|
+
".7z",
|
|
169
|
+
".rar",
|
|
170
|
+
".jar",
|
|
171
|
+
".woff",
|
|
172
|
+
".woff2",
|
|
173
|
+
".ttf",
|
|
174
|
+
".otf",
|
|
175
|
+
".eot",
|
|
176
|
+
".mp3",
|
|
177
|
+
".mp4",
|
|
178
|
+
".wav",
|
|
179
|
+
".ogg",
|
|
180
|
+
".webm",
|
|
181
|
+
".mov",
|
|
182
|
+
".wasm",
|
|
183
|
+
".bin",
|
|
184
|
+
".dat",
|
|
185
|
+
".db",
|
|
186
|
+
".sqlite",
|
|
187
|
+
".so",
|
|
188
|
+
".dylib",
|
|
189
|
+
".dll",
|
|
190
|
+
".exe",
|
|
191
|
+
".class",
|
|
192
|
+
".pyc",
|
|
193
|
+
// Office and design documents are zip or proprietary containers.
|
|
194
|
+
".xlsx",
|
|
195
|
+
".xls",
|
|
196
|
+
".docx",
|
|
197
|
+
".doc",
|
|
198
|
+
".pptx",
|
|
199
|
+
".ppt",
|
|
200
|
+
".odt",
|
|
201
|
+
".ods",
|
|
202
|
+
".psd",
|
|
203
|
+
".ai",
|
|
204
|
+
".sketch",
|
|
205
|
+
".fig",
|
|
206
|
+
".heic",
|
|
207
|
+
".heif",
|
|
208
|
+
// Archives, columnar data, models and compressed payloads.
|
|
209
|
+
".tar",
|
|
210
|
+
".zst",
|
|
211
|
+
".br",
|
|
212
|
+
".lz4",
|
|
213
|
+
".parquet",
|
|
214
|
+
".avro",
|
|
215
|
+
".orc",
|
|
216
|
+
".pkl",
|
|
217
|
+
".npy",
|
|
218
|
+
".npz",
|
|
219
|
+
".onnx",
|
|
220
|
+
".pt",
|
|
221
|
+
".safetensors",
|
|
222
|
+
".gguf",
|
|
223
|
+
".sqlite3",
|
|
224
|
+
".avi",
|
|
225
|
+
".mkv",
|
|
226
|
+
".flac",
|
|
227
|
+
".aac",
|
|
228
|
+
".m4a",
|
|
229
|
+
".ttc"
|
|
230
|
+
]);
|
|
146
231
|
EXECUTABLE_SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([".sh", ".bash", ".zsh"]);
|
|
147
232
|
}
|
|
148
233
|
});
|
|
234
|
+
async function renameWithRetry(from, to, options = {}) {
|
|
235
|
+
const attempts = options.attempts ?? 5;
|
|
236
|
+
const delayMs = options.delayMs ?? 50;
|
|
237
|
+
for (let attempt = 0; ; attempt++) {
|
|
238
|
+
try {
|
|
239
|
+
await rename(from, to);
|
|
240
|
+
return;
|
|
241
|
+
} catch (err) {
|
|
242
|
+
const code = err.code;
|
|
243
|
+
const transient = code !== void 0 && TRANSIENT_RENAME_CODES.has(code);
|
|
244
|
+
if (!transient || attempt >= attempts - 1) throw err;
|
|
245
|
+
await setTimeout(delayMs * 2 ** attempt);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
var TRANSIENT_RENAME_CODES;
|
|
250
|
+
var init_rename_retry = __esm({
|
|
251
|
+
"src/utils/filesystem/rename-retry.ts"() {
|
|
252
|
+
TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY", "ENOTEMPTY", "EEXIST"]);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
149
255
|
function shouldSkipRecursiveBranch(segments) {
|
|
150
256
|
if (segments.length > MAX_RECURSIVE_DEPTH) return true;
|
|
151
257
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -236,30 +342,10 @@ var init_fs_traverse = __esm({
|
|
|
236
342
|
MAX_SEGMENT_REPETITIONS = 3;
|
|
237
343
|
}
|
|
238
344
|
});
|
|
239
|
-
async function renameWithRetry(from, to, options = {}) {
|
|
240
|
-
const attempts = options.attempts ?? 5;
|
|
241
|
-
const delayMs = options.delayMs ?? 50;
|
|
242
|
-
for (let attempt = 0; ; attempt++) {
|
|
243
|
-
try {
|
|
244
|
-
await rename(from, to);
|
|
245
|
-
return;
|
|
246
|
-
} catch (err) {
|
|
247
|
-
const code = err.code;
|
|
248
|
-
const transient = code !== void 0 && TRANSIENT_RENAME_CODES.has(code);
|
|
249
|
-
if (!transient || attempt >= attempts - 1) throw err;
|
|
250
|
-
await setTimeout(delayMs * 2 ** attempt);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
var TRANSIENT_RENAME_CODES;
|
|
255
|
-
var init_rename_retry = __esm({
|
|
256
|
-
"src/utils/filesystem/rename-retry.ts"() {
|
|
257
|
-
TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY", "ENOTEMPTY", "EEXIST"]);
|
|
258
|
-
}
|
|
259
|
-
});
|
|
260
345
|
async function readFileSafe(path) {
|
|
261
346
|
try {
|
|
262
|
-
const data = await readFile(path,
|
|
347
|
+
const data = await readFile(path, payloadEncodingFor(path));
|
|
348
|
+
if (isBinaryPayloadPath(path)) return data;
|
|
263
349
|
return data.startsWith(UTF8_BOM) ? data.slice(UTF8_BOM.length) : data;
|
|
264
350
|
} catch (err) {
|
|
265
351
|
const e = err;
|
|
@@ -283,40 +369,28 @@ async function writeFileAtomic(path, content, options) {
|
|
|
283
369
|
{ errnoCode: "EISDIR" }
|
|
284
370
|
);
|
|
285
371
|
}
|
|
286
|
-
if (info.isSymbolicLink()) {
|
|
287
|
-
await unlink(path).catch((e) => {
|
|
288
|
-
if (e.code !== "ENOENT") throw e;
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
372
|
} catch (err) {
|
|
292
373
|
if (err instanceof FileSystemError) throw err;
|
|
293
374
|
const e = err;
|
|
294
375
|
if (e.code !== "ENOENT") throw err;
|
|
295
376
|
}
|
|
296
|
-
const tmpPath = `${path}.tmp`;
|
|
377
|
+
const tmpPath = `${path}.tmp-${randomUUID()}`;
|
|
297
378
|
const payload = shouldNormalizeLineEndings(path) ? normalizeLineEndings(content) : content;
|
|
298
379
|
const mode = executableModeFor(path);
|
|
380
|
+
let handle;
|
|
381
|
+
let ownsTemporaryFile = false;
|
|
299
382
|
try {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
const writeOpts = {
|
|
309
|
-
encoding: "utf-8",
|
|
310
|
-
flag: "w"
|
|
311
|
-
};
|
|
312
|
-
if (mode !== void 0) writeOpts.mode = mode;
|
|
313
|
-
await writeFile(tmpPath, payload, writeOpts);
|
|
314
|
-
await rename(tmpPath, path);
|
|
315
|
-
if (mode !== void 0) {
|
|
316
|
-
await chmod(path, mode);
|
|
317
|
-
}
|
|
383
|
+
handle = await open(tmpPath, "wx", mode);
|
|
384
|
+
ownsTemporaryFile = true;
|
|
385
|
+
await handle.writeFile(payload, payloadEncodingFor(path));
|
|
386
|
+
if (mode !== void 0) await handle.chmod(mode);
|
|
387
|
+
await handle.close();
|
|
388
|
+
handle = void 0;
|
|
389
|
+
await renameWithRetry(tmpPath, path);
|
|
318
390
|
} catch (err) {
|
|
319
|
-
await
|
|
391
|
+
await handle?.close().catch(() => {
|
|
392
|
+
});
|
|
393
|
+
if (ownsTemporaryFile) await rm(tmpPath, { force: true }).catch(() => {
|
|
320
394
|
});
|
|
321
395
|
const e = err;
|
|
322
396
|
throw new FileSystemError(
|
|
@@ -341,6 +415,7 @@ var init_fs = __esm({
|
|
|
341
415
|
"src/utils/filesystem/fs.ts"() {
|
|
342
416
|
init_errors();
|
|
343
417
|
init_fs_text_encoding();
|
|
418
|
+
init_rename_retry();
|
|
344
419
|
init_fs_traverse();
|
|
345
420
|
init_fs_text_encoding();
|
|
346
421
|
init_rename_retry();
|
|
@@ -487,6 +562,60 @@ var init_boilerplate_filter = __esm({
|
|
|
487
562
|
}
|
|
488
563
|
});
|
|
489
564
|
|
|
565
|
+
// src/utils/text/json-comments.ts
|
|
566
|
+
function stripJsonComments(text) {
|
|
567
|
+
let result2 = "";
|
|
568
|
+
let i = 0;
|
|
569
|
+
const len = text.length;
|
|
570
|
+
while (i < len) {
|
|
571
|
+
const ch = text[i];
|
|
572
|
+
if (ch === '"') {
|
|
573
|
+
result2 += ch;
|
|
574
|
+
i++;
|
|
575
|
+
while (i < len) {
|
|
576
|
+
const sc = text[i];
|
|
577
|
+
result2 += sc;
|
|
578
|
+
if (sc === "\\") {
|
|
579
|
+
i++;
|
|
580
|
+
if (i < len) {
|
|
581
|
+
result2 += text[i];
|
|
582
|
+
}
|
|
583
|
+
} else if (sc === '"') {
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
i++;
|
|
587
|
+
}
|
|
588
|
+
i++;
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
if (ch === "/" && text[i + 1] === "*") {
|
|
592
|
+
i += 2;
|
|
593
|
+
while (i < len) {
|
|
594
|
+
if (text[i] === "*" && text[i + 1] === "/") {
|
|
595
|
+
i += 2;
|
|
596
|
+
break;
|
|
597
|
+
}
|
|
598
|
+
i++;
|
|
599
|
+
}
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
if (ch === "/" && text[i + 1] === "/") {
|
|
603
|
+
i += 2;
|
|
604
|
+
while (i < len && text[i] !== "\n") {
|
|
605
|
+
i++;
|
|
606
|
+
}
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
result2 += ch;
|
|
610
|
+
i++;
|
|
611
|
+
}
|
|
612
|
+
return result2;
|
|
613
|
+
}
|
|
614
|
+
var init_json_comments = __esm({
|
|
615
|
+
"src/utils/text/json-comments.ts"() {
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
|
|
490
619
|
// src/canonical/features/syntax-error.ts
|
|
491
620
|
function failSyntax(filePath2, cause, onParseError) {
|
|
492
621
|
const error = new CanonicalParseError(filePath2, cause);
|
|
@@ -536,54 +665,6 @@ function parseServer(raw) {
|
|
|
536
665
|
env
|
|
537
666
|
};
|
|
538
667
|
}
|
|
539
|
-
function stripJsonComments(text) {
|
|
540
|
-
let result2 = "";
|
|
541
|
-
let i = 0;
|
|
542
|
-
const len = text.length;
|
|
543
|
-
while (i < len) {
|
|
544
|
-
const ch = text[i];
|
|
545
|
-
if (ch === '"') {
|
|
546
|
-
result2 += ch;
|
|
547
|
-
i++;
|
|
548
|
-
while (i < len) {
|
|
549
|
-
const sc = text[i];
|
|
550
|
-
result2 += sc;
|
|
551
|
-
if (sc === "\\") {
|
|
552
|
-
i++;
|
|
553
|
-
if (i < len) {
|
|
554
|
-
result2 += text[i];
|
|
555
|
-
}
|
|
556
|
-
} else if (sc === '"') {
|
|
557
|
-
break;
|
|
558
|
-
}
|
|
559
|
-
i++;
|
|
560
|
-
}
|
|
561
|
-
i++;
|
|
562
|
-
continue;
|
|
563
|
-
}
|
|
564
|
-
if (ch === "/" && text[i + 1] === "*") {
|
|
565
|
-
i += 2;
|
|
566
|
-
while (i < len) {
|
|
567
|
-
if (text[i] === "*" && text[i + 1] === "/") {
|
|
568
|
-
i += 2;
|
|
569
|
-
break;
|
|
570
|
-
}
|
|
571
|
-
i++;
|
|
572
|
-
}
|
|
573
|
-
continue;
|
|
574
|
-
}
|
|
575
|
-
if (ch === "/" && text[i + 1] === "/") {
|
|
576
|
-
i += 2;
|
|
577
|
-
while (i < len && text[i] !== "\n") {
|
|
578
|
-
i++;
|
|
579
|
-
}
|
|
580
|
-
continue;
|
|
581
|
-
}
|
|
582
|
-
result2 += ch;
|
|
583
|
-
i++;
|
|
584
|
-
}
|
|
585
|
-
return result2;
|
|
586
|
-
}
|
|
587
668
|
async function parseMcp(mcpPath, onParseError) {
|
|
588
669
|
const content = await readFileSafe(mcpPath);
|
|
589
670
|
if (!content) return null;
|
|
@@ -606,6 +687,7 @@ async function parseMcp(mcpPath, onParseError) {
|
|
|
606
687
|
}
|
|
607
688
|
var init_mcp = __esm({
|
|
608
689
|
"src/canonical/features/mcp.ts"() {
|
|
690
|
+
init_json_comments();
|
|
609
691
|
init_syntax_error();
|
|
610
692
|
init_fs();
|
|
611
693
|
}
|
|
@@ -2310,43 +2392,72 @@ function isGlobAdjacent(content, start, end) {
|
|
|
2310
2392
|
const next = end < content.length ? content.at(end) : "";
|
|
2311
2393
|
return prev === "*" || next === "*";
|
|
2312
2394
|
}
|
|
2395
|
+
var NON_REWRITABLE_BARE_FILES, PATH_TOKEN, LINE_NUMBER_SUFFIX;
|
|
2396
|
+
var init_link_rebaser_helpers = __esm({
|
|
2397
|
+
"src/core/reference/link-rebaser-helpers.ts"() {
|
|
2398
|
+
init_path_helpers();
|
|
2399
|
+
init_link_format_registry();
|
|
2400
|
+
NON_REWRITABLE_BARE_FILES = /* @__PURE__ */ new Set([
|
|
2401
|
+
"AGENTS.md",
|
|
2402
|
+
"CLAUDE.md",
|
|
2403
|
+
"GEMINI.md",
|
|
2404
|
+
"codex.md",
|
|
2405
|
+
".windsurfrules",
|
|
2406
|
+
".cursorrules"
|
|
2407
|
+
]);
|
|
2408
|
+
PATH_TOKEN = /(?:\.\.[\\/]|\.\/|\.\\|\/[A-Za-z0-9._-]|[A-Za-z]:[\\/][A-Za-z0-9._-]|\.agentsmesh[\\/]|\.claude[\\/]|\.cursor[\\/]|\.github[\\/]|\.continue[\\/]|\.junie[\\/]|\.kiro[\\/]|\.gemini[\\/]|\.clinerules[\\/]|\.cline[\\/]|\.codex[\\/]|\.agents[\\/]|\.windsurf[\\/]|\.roo[\\/]|(?:[A-Za-z0-9._-]+[\\/])+|[A-Za-z0-9._-]+\.[A-Za-z0-9._-]+)[A-Za-z0-9._@%+~:\\/-]*/g;
|
|
2409
|
+
LINE_NUMBER_SUFFIX = /(?::(\d+)){1,2}$/;
|
|
2410
|
+
}
|
|
2411
|
+
});
|
|
2412
|
+
|
|
2413
|
+
// src/core/reference/protected-ranges.ts
|
|
2414
|
+
function extendAcrossBalancedParens(content, end) {
|
|
2415
|
+
let cursor = end;
|
|
2416
|
+
while (content[cursor] === "(") {
|
|
2417
|
+
let depth = 0;
|
|
2418
|
+
let scan = cursor;
|
|
2419
|
+
while (scan < content.length) {
|
|
2420
|
+
const ch = content[scan];
|
|
2421
|
+
if (ch === "(") depth += 1;
|
|
2422
|
+
else if (ch === ")") {
|
|
2423
|
+
depth -= 1;
|
|
2424
|
+
if (depth === 0) break;
|
|
2425
|
+
} else if (ch === void 0 || /\s/.test(ch)) return cursor;
|
|
2426
|
+
scan += 1;
|
|
2427
|
+
}
|
|
2428
|
+
if (depth !== 0) return cursor;
|
|
2429
|
+
cursor = scan + 1;
|
|
2430
|
+
while (cursor < content.length && !/[\s<>()\]]/.test(content.charAt(cursor))) cursor += 1;
|
|
2431
|
+
}
|
|
2432
|
+
return cursor;
|
|
2433
|
+
}
|
|
2313
2434
|
function protectedRanges(content) {
|
|
2314
2435
|
const ranges = [];
|
|
2315
2436
|
for (const pattern of getLinkFormatRegistry().protectedSchemes) {
|
|
2316
2437
|
const globalPattern = pattern.flags.includes("g") ? pattern : new RegExp(pattern.source, `${pattern.flags}g`);
|
|
2317
2438
|
for (const match of content.matchAll(globalPattern)) {
|
|
2318
|
-
|
|
2439
|
+
const start = match.index;
|
|
2440
|
+
ranges.push([start, extendAcrossBalancedParens(content, start + match[0].length)]);
|
|
2319
2441
|
}
|
|
2320
2442
|
}
|
|
2321
2443
|
for (const match of content.matchAll(FENCED_CODE_BLOCK)) {
|
|
2322
|
-
ranges.push([match.index
|
|
2444
|
+
ranges.push([match.index, match.index + match[0].length]);
|
|
2323
2445
|
}
|
|
2324
2446
|
for (const match of content.matchAll(ROOT_GENERATION_CONTRACT_BLOCK)) {
|
|
2325
|
-
ranges.push([match.index
|
|
2447
|
+
ranges.push([match.index, match.index + match[0].length]);
|
|
2326
2448
|
}
|
|
2327
2449
|
for (const match of content.matchAll(EMBEDDED_RULES_BLOCK)) {
|
|
2328
|
-
ranges.push([match.index
|
|
2450
|
+
ranges.push([match.index, match.index + match[0].length]);
|
|
2329
2451
|
}
|
|
2330
2452
|
return ranges;
|
|
2331
2453
|
}
|
|
2332
|
-
var
|
|
2333
|
-
var
|
|
2334
|
-
"src/core/reference/
|
|
2335
|
-
init_path_helpers();
|
|
2454
|
+
var FENCED_CODE_BLOCK, ROOT_GENERATION_CONTRACT_BLOCK, EMBEDDED_RULES_BLOCK;
|
|
2455
|
+
var init_protected_ranges = __esm({
|
|
2456
|
+
"src/core/reference/protected-ranges.ts"() {
|
|
2336
2457
|
init_link_format_registry();
|
|
2337
|
-
NON_REWRITABLE_BARE_FILES = /* @__PURE__ */ new Set([
|
|
2338
|
-
"AGENTS.md",
|
|
2339
|
-
"CLAUDE.md",
|
|
2340
|
-
"GEMINI.md",
|
|
2341
|
-
"codex.md",
|
|
2342
|
-
".windsurfrules",
|
|
2343
|
-
".cursorrules"
|
|
2344
|
-
]);
|
|
2345
2458
|
FENCED_CODE_BLOCK = /^(?:```|~~~)[^\n]*\n[\s\S]*?^(?:```|~~~)/gm;
|
|
2346
2459
|
ROOT_GENERATION_CONTRACT_BLOCK = /<!-- agentsmesh:root-generation-contract:start -->[\s\S]*?<!-- agentsmesh:root-generation-contract:end -->/g;
|
|
2347
2460
|
EMBEDDED_RULES_BLOCK = /<!-- agentsmesh:embedded-rules:start -->[\s\S]*?<!-- agentsmesh:embedded-rules:end -->/g;
|
|
2348
|
-
PATH_TOKEN = /(?:\.\.[\\/]|\.\/|\.\\|\/[A-Za-z0-9._-]|[A-Za-z]:[\\/][A-Za-z0-9._-]|\.agentsmesh[\\/]|\.claude[\\/]|\.cursor[\\/]|\.github[\\/]|\.continue[\\/]|\.junie[\\/]|\.kiro[\\/]|\.gemini[\\/]|\.clinerules[\\/]|\.cline[\\/]|\.codex[\\/]|\.agents[\\/]|\.windsurf[\\/]|\.roo[\\/]|(?:[A-Za-z0-9._-]+[\\/])+|[A-Za-z0-9._-]+\.[A-Za-z0-9._-]+)[A-Za-z0-9._@%+~:\\/-]*/g;
|
|
2349
|
-
LINE_NUMBER_SUFFIX = /(?::(\d+)){1,2}$/;
|
|
2350
2461
|
}
|
|
2351
2462
|
});
|
|
2352
2463
|
|
|
@@ -2839,6 +2950,7 @@ var init_link_rebaser = __esm({
|
|
|
2839
2950
|
"src/core/reference/link-rebaser.ts"() {
|
|
2840
2951
|
init_path_helpers();
|
|
2841
2952
|
init_link_rebaser_helpers();
|
|
2953
|
+
init_protected_ranges();
|
|
2842
2954
|
init_link_rebaser_output();
|
|
2843
2955
|
init_link_rebaser_resolution();
|
|
2844
2956
|
init_link_uri_encoding();
|
|
@@ -8509,7 +8621,7 @@ ${rawBody}`;
|
|
|
8509
8621
|
});
|
|
8510
8622
|
}
|
|
8511
8623
|
function generateMcp3(canonical) {
|
|
8512
|
-
if (!canonical.mcp
|
|
8624
|
+
if (!canonical.mcp) return [];
|
|
8513
8625
|
const content = JSON.stringify({ mcpServers: canonical.mcp.mcpServers }, null, 2);
|
|
8514
8626
|
return [{ path: CLAUDE_MCP_JSON, content }];
|
|
8515
8627
|
}
|
|
@@ -8544,14 +8656,12 @@ function generatePermissions4(canonical) {
|
|
|
8544
8656
|
if (!canonical.permissions) return [];
|
|
8545
8657
|
const { allow, deny } = canonical.permissions;
|
|
8546
8658
|
const ask = canonical.permissions.ask ?? [];
|
|
8547
|
-
if (allow.length === 0 && deny.length === 0 && ask.length === 0) return [];
|
|
8548
8659
|
const content = JSON.stringify({ permissions: { allow, deny, ask } }, null, 2);
|
|
8549
8660
|
return [{ path: CLAUDE_SETTINGS, content }];
|
|
8550
8661
|
}
|
|
8551
8662
|
function generateHooks3(canonical) {
|
|
8552
|
-
if (!canonical.hooks
|
|
8663
|
+
if (!canonical.hooks) return [];
|
|
8553
8664
|
const claudeHooks = buildClaudeHooksObjectFromCanonical(canonical);
|
|
8554
|
-
if (Object.keys(claudeHooks).length === 0) return [];
|
|
8555
8665
|
const content = JSON.stringify({ hooks: claudeHooks }, null, 2);
|
|
8556
8666
|
return [{ path: CLAUDE_SETTINGS, content }];
|
|
8557
8667
|
}
|
|
@@ -20369,9 +20479,9 @@ var init_config_toml = __esm({
|
|
|
20369
20479
|
function isValidKimiPermissionPattern(pattern) {
|
|
20370
20480
|
const trimmed = pattern.trim();
|
|
20371
20481
|
if (trimmed.length === 0) return false;
|
|
20372
|
-
const
|
|
20373
|
-
if (
|
|
20374
|
-
return trimmed.endsWith(")") &&
|
|
20482
|
+
const open2 = trimmed.indexOf("(");
|
|
20483
|
+
if (open2 === -1) return true;
|
|
20484
|
+
return trimmed.endsWith(")") && open2 > 0;
|
|
20375
20485
|
}
|
|
20376
20486
|
function listOf(permissions, key) {
|
|
20377
20487
|
return permissions[key] ?? [];
|
|
@@ -29130,6 +29240,14 @@ function redactUrlSecrets(message) {
|
|
|
29130
29240
|
}
|
|
29131
29241
|
);
|
|
29132
29242
|
}
|
|
29243
|
+
function stripUrlCredentials(url) {
|
|
29244
|
+
return url.replace(
|
|
29245
|
+
URL_WITH_CREDENTIALS,
|
|
29246
|
+
(_full, scheme, _userinfo, rest) => {
|
|
29247
|
+
return `${scheme}${rest}`;
|
|
29248
|
+
}
|
|
29249
|
+
);
|
|
29250
|
+
}
|
|
29133
29251
|
function gitProtocolOptIns() {
|
|
29134
29252
|
const on = (v) => v === "1" || v === "true";
|
|
29135
29253
|
return {
|
|
@@ -29528,6 +29646,7 @@ async function sweepStaleCache(cacheDir, maxAgeMs) {
|
|
|
29528
29646
|
var MAX_CACHE_KEY_LENGTH = 80;
|
|
29529
29647
|
var CACHE_KEY_HASH_LENGTH = 12;
|
|
29530
29648
|
function buildCacheKey(provider, identifier, ref) {
|
|
29649
|
+
identifier = stripUrlCredentials(identifier);
|
|
29531
29650
|
const safe = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_").replace(/^\.+/, "_");
|
|
29532
29651
|
const [org, repo] = provider === "github" ? identifier.split("/", 2) : [];
|
|
29533
29652
|
const readable = org && repo ? `${safe(org)}--${safe(repo)}--${safe(ref)}` : `${safe(provider)}__${safe(identifier)}__${safe(ref)}`;
|