agentsmesh 0.20.0 → 0.22.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 CHANGED
@@ -1,5 +1,75 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.22.0
4
+
5
+ ### Minor Changes
6
+
7
+ - c2a13ad: Add `agentsmesh init --lessons` and a new `agentsmesh/lessons` public API for
8
+ the lessons recall + capture subsystem.
9
+
10
+ The subsystem keeps agents from repeating past mistakes via a procedural rule
11
+ that lives in every target's root file: before any edit or command, scan
12
+ `.agentsmesh/lessons/index.yaml` and read every matched
13
+ `.agentsmesh/lessons/topics/<topic>.md`; after any failure, append to
14
+ `.agentsmesh/lessons/journal.md`. The optional repo-local `pnpm distill` /
15
+ `pnpm distill:apply` scripts can help maintain AgentsMesh's own topic routing,
16
+ but the generated rule does not require package-manager-specific tooling.
17
+
18
+ **Using it:**
19
+ - **Fresh init:** `agentsmesh init --lessons` — creates the canonical scaffold
20
+ AND the lessons subsystem in one command.
21
+ - **Retroactive add (existing project):** the same `agentsmesh init --lessons`
22
+ — when `agentsmesh.yaml` already exists, init only scaffolds the lessons
23
+ artifacts and appends the procedural rule to `_root.md`. Idempotent.
24
+ - After either flow, run `agentsmesh generate` to project the procedural rule
25
+ to every target's root file.
26
+
27
+ **Public API** (importable from `agentsmesh/lessons`):
28
+ - `scaffoldLessons(projectRoot)` — idempotent scaffolder used internally by
29
+ `init --lessons`; reusable from custom tooling.
30
+ - `loadLessonsIndex(projectRoot)`, `readTriggeredLessons(projectRoot, event)`,
31
+ `appendLessonToJournal(projectRoot, input)`, and `formatLessonBullet(input)` —
32
+ one high-level, target-agnostic read/write layer for integrations that should
33
+ not hand-roll filesystem access.
34
+ - `lessonsPaths(projectRoot)`, `LESSONS_PROCEDURAL_RULE`,
35
+ `LESSONS_JOURNAL_TEMPLATE`, `LESSONS_INDEX_TEMPLATE` — paths and templates.
36
+ - `parseIndex`, `LessonsIndexSchema`, `matchTriggers`, `scoreBullet`,
37
+ `loadLedger`, `saveLedger`, `hashBullet`, `parseBullets` — building blocks
38
+ for downstream tooling (custom distillers, recall hooks, IDE plugins).
39
+
40
+ **Universal across every target.** The subsystem uses plain markdown files
41
+ read via standard file I/O — no `Skill` tool, no description-match, no
42
+ per-target projection. Works in Claude Code, Codex CLI, Cline, Roo Code,
43
+ Cursor, Gemini CLI, Aider, Goose, and every other supported harness.
44
+
45
+ **Linter integration:** `agentsmesh lint` now validates the lessons subsystem
46
+ when present — checks that `index.yaml` parses, topic files referenced in the
47
+ index exist, and the journal file is well-formed.
48
+
49
+ **Constraints:**
50
+ - `--lessons` is project-mode only. Combining with `--global` errors out.
51
+ - Removal: `rm -rf .agentsmesh/lessons/` and strip the `## Lessons` paragraph
52
+ from `.agentsmesh/rules/_root.md`.
53
+
54
+ ### Patch Changes
55
+
56
+ - c75a424: Surface marketplace sub-pack install failures instead of swallowing them silently, and route skill-pack sub-packs through the correct install path.
57
+ - c75a424: Fix qwen-code global-mode rule embedding. Rules were silently dropped when generating in global mode; they are now embedded inline using the same pattern as other global-mode targets.
58
+ - c75a424: Restructure the generation contract paragraph to lead with an explicit **NEVER edit generated files** prohibition naming the generated paths (`.claude/`, `.cursor/`, `AGENTS.md`, etc.), followed by **All changes MUST go through `.agentsmesh` first**. Agents were initially understanding the contract but forgetting it over multi-step conversations — front-loading the prohibition makes it stickier. All legacy contract body versions (v1-v10) remain detected for safe in-place upgrade.
59
+
60
+ ## 0.21.0
61
+
62
+ ### Minor Changes
63
+
64
+ - b1efca1: Harden install pipeline against third-party supply-chain attacks.
65
+ - **Strip elevated artifacts from non-local sources by default.** `hooks.yaml`, `permissions.yaml`, and `mcp.json` are now removed from any pack installed from a `github:`, `gitlab:`, or `git+...` source unless you opt in. These three files control your agent's tool settings (shell hooks, granted permissions, MCP launch specs) and a malicious pack shipping any of them could otherwise execute arbitrary local commands the next time the matching event fires. Opt in per-artifact with `--accept-hooks`, `--accept-permissions`, `--accept-mcp`, or all three with `--accept-elevated`. Local sources remain trusted as before.
66
+ - **Skill supporting-file traversal no longer follows symlinks.** A pack containing `skills/foo/keys -> /Users/victim/.ssh` previously pulled external bytes (private keys, etc.) into the canonical skill content. Skill traversal now uses the existing `readDirRecursiveNoSymlinks` helper, mirroring the hardening already applied to install-manifest hashing.
67
+ - **Redact credentials from remote-fetch error output.** `oauth2:<token>@`, `x-access-token:<token>@`, and any other userinfo-bearing URLs are now masked (`https://***@host/...`) in console warnings and thrown error messages so GitHub PATs and GitLab tokens never leak into CI logs, terminal scrollback, or log shippers.
68
+ - **Gate `git+file://` sources behind `AGENTSMESH_ALLOW_LOCAL_GIT=1`.** On shared/multi-tenant hosts a `git+file:///tmp/world-writable-repo` `extends:` clause could silently consume a repo planted by another user; combined with downstream elevated-artifact emission this was a local priv-esc vector. Set `AGENTSMESH_ALLOW_LOCAL_GIT=1` to enable for closed-network development.
69
+ - **Allowlist tar entry types on GitHub tarball extraction.** Previously only `Link` and `SymbolicLink` were rejected (denylist). Now only `File` and `Directory` entries extract; FIFOs, devices, hardlinks, and any future/exotic tar variant are rejected by default.
70
+
71
+ These changes apply to `agentsmesh install`, `agentsmesh refresh`, and any `extends:` resolution against a non-local source. They are behavior changes for users who were silently inheriting hooks/permissions/mcp from a remote pack — re-run with the matching `--accept-*` flag (or `--accept-elevated`) to preserve previous behavior intentionally.
72
+
3
73
  ## 0.20.0
4
74
 
5
75
  ### Minor Changes
package/README.md CHANGED
@@ -87,6 +87,10 @@ AGENTS.md
87
87
  hooks.yaml
88
88
  permissions.yaml
89
89
  ignore
90
+ lessons/
91
+ index.yaml
92
+ journal.md
93
+ topics/
90
94
  ```
91
95
 
92
96
  ```bash
@@ -111,6 +115,12 @@ agentsmesh check # CI-friendly drift gate against .agentsmesh/.lock
111
115
  - **`generate`** — writes `CLAUDE.md`, `AGENTS.md`, `.cursor/`, `.github/copilot-instructions.md`, etc. from canonical sources.
112
116
  - **`check`** — exits non-zero if generated files have drifted from `.agentsmesh/.lock`. Drop into CI.
113
117
 
118
+ Use `agentsmesh init --lessons` when you want the optional lessons recall +
119
+ capture subsystem. Agents read `.agentsmesh/lessons/index.yaml`, load only
120
+ matching topic files before edits/commands, and append failures to
121
+ `.agentsmesh/lessons/journal.md`; the procedural rule is projected through the
122
+ normal root rule, so it stays tool-agnostic.
123
+
114
124
  If you installed via `npm install -D agentsmesh` (also `pnpm add -D` / `yarn add -D`), prefix each command with `npx`. The CLI ships as both `agentsmesh` and the shorter alias `amsh`.
115
125
 
116
126
  ---
@@ -208,6 +218,7 @@ AgentsMesh canonicalizes all of these — rules, commands, agents, skills, MCP s
208
218
  - `hooks.yaml` — pre/post tool hooks.
209
219
  - `permissions.yaml` — allow/deny rules where the target supports them.
210
220
  - `ignore` — paths the assistant should not read or modify.
221
+ - `lessons/` — optional recall/capture memory: trigger index, append-only journal, and small topic rule files read directly by agents.
211
222
 
212
223
  Configuration:
213
224
 
@@ -222,7 +233,7 @@ Detailed contracts: [Canonical Config](https://samplexbro.github.io/agentsmesh/c
222
233
  ## CLI usage
223
234
 
224
235
  ```bash
225
- agentsmesh init [--global] [--yes]
236
+ agentsmesh init [--global] [--yes] [--lessons]
226
237
  agentsmesh generate [--global] [--targets <csv>] [--check] [--dry-run] [--force] [--refresh-cache]
227
238
  agentsmesh import --from <target> [--global]
228
239
  agentsmesh convert --from <target> --to <target> [--global] [--dry-run]
@@ -233,6 +244,7 @@ agentsmesh check [--global]
233
244
  agentsmesh merge [--global]
234
245
  agentsmesh matrix [--global] [--targets <csv>] [--verbose]
235
246
  agentsmesh install <source> [--sync] [--path <dir>] [--target <id>] [--as <kind>] [--name <id>] [--extends] [--all] [--dry-run] [--global] [--force]
247
+ [--accept-hooks|--accept-permissions|--accept-mcp|--accept-elevated]
236
248
  agentsmesh uninstall <name>[,<name>...] [--all] [--keep-pack] [--keep-generated] [--dry-run] [--global] [--force]
237
249
  agentsmesh installs list [--global]
238
250
  agentsmesh refresh [<name>[,<name>...]] [--dry-run] [--force] [--json] [--global]
@@ -383,6 +395,7 @@ Every config file ships with a generated JSON Schema, so VS Code, JetBrains, and
383
395
  | `AGENTSMESH_CACHE` | `~/.agentsmeshcache` | Override the remote-extends / tarball cache directory. |
384
396
  | `AGENTSMESH_MAX_TARBALL_MB` | `500` | Maximum GitHub tarball size in MiB the install command will accept. Allowed range: `1`–`4096`. Increase this when installing from large monorepos. |
385
397
  | `AGENTSMESH_STRICT_PLUGINS` | `0` | When set to `1`, a failed plugin descriptor import fails the build instead of warning-and-skip. Useful in CI where a missing plugin target is a regression. |
398
+ | `AGENTSMESH_ALLOW_LOCAL_GIT` | `0` | When set to `1`, enables `git+file://` sources in `extends` and `install`. Disabled by default because on shared hosts a world-writable repo could be planted by another user and combined with elevated-artifact emission for local privilege escalation. |
386
399
 
387
400
  ---
388
401
 
package/dist/canonical.js CHANGED
@@ -185,6 +185,33 @@ async function readDirRecursive(dir, visited, branchSegments) {
185
185
  );
186
186
  }
187
187
  }
188
+ async function readDirRecursiveNoSymlinks(dir, branchSegments) {
189
+ const currentBranchSegments = branchSegments ?? [basename(dir)];
190
+ try {
191
+ const entries = await readdir(dir, { withFileTypes: true });
192
+ const files = [];
193
+ for (const ent of entries) {
194
+ if (ent.isSymbolicLink()) continue;
195
+ const full = join(dir, ent.name);
196
+ if (ent.isDirectory()) {
197
+ const nextSegments = [...currentBranchSegments, ent.name];
198
+ if (shouldSkipRecursiveBranch(nextSegments)) continue;
199
+ files.push(...await readDirRecursiveNoSymlinks(full, nextSegments));
200
+ } else if (ent.isFile()) {
201
+ files.push(full);
202
+ }
203
+ }
204
+ return files;
205
+ } catch (err) {
206
+ const e = err;
207
+ if (e.code === "ENOENT" || e.code === "ENOTDIR" || e.code === "EACCES") return [];
208
+ throw new FileSystemError(
209
+ dir,
210
+ `Failed to read directory ${dir}: ${e.message}. Check permissions.`,
211
+ { cause: err, errnoCode: e.code }
212
+ );
213
+ }
214
+ }
188
215
  var MAX_RECURSIVE_DEPTH, MAX_SEGMENT_REPETITIONS;
189
216
  var init_fs_traverse = __esm({
190
217
  "src/utils/filesystem/fs-traverse.ts"() {
@@ -1023,7 +1050,7 @@ ${legacy}`, "");
1023
1050
  }
1024
1051
  return result.trim();
1025
1052
  }
1026
- var ROOT_INSTRUCTION_BODY_V1, ROOT_INSTRUCTION_BODY_V2, ROOT_INSTRUCTION_BODY_V3, ROOT_INSTRUCTION_BODY_V4, ROOT_INSTRUCTION_BODY_V5, ROOT_INSTRUCTION_BODY_V6, ROOT_INSTRUCTION_BODY_V7, ROOT_INSTRUCTION_BODY_V8, ROOT_INSTRUCTION_BODY, LEGACY_AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH, LEGACY_AGENTSMESH_ROOT_INSTRUCTION_SECTION, AGENTSMESH_CONTRACT_WITH_V1_BODY, AGENTSMESH_CONTRACT_WITH_V2_BODY, AGENTSMESH_CONTRACT_WITH_V3_BODY, AGENTSMESH_CONTRACT_WITH_V4_BODY, AGENTSMESH_CONTRACT_WITH_V5_BODY, AGENTSMESH_CONTRACT_WITH_V6_BODY, AGENTSMESH_CONTRACT_WITH_V7_BODY, AGENTSMESH_CONTRACT_WITH_V8_BODY, AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH, LEGACY_FORMS;
1053
+ var ROOT_INSTRUCTION_BODY_V1, ROOT_INSTRUCTION_BODY_V2, ROOT_INSTRUCTION_BODY_V3, ROOT_INSTRUCTION_BODY_V4, ROOT_INSTRUCTION_BODY_V5, ROOT_INSTRUCTION_BODY_V6, ROOT_INSTRUCTION_BODY_V7, ROOT_INSTRUCTION_BODY_V8, ROOT_INSTRUCTION_BODY_V9, ROOT_INSTRUCTION_BODY_V10, ROOT_INSTRUCTION_BODY, LEGACY_AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH, LEGACY_AGENTSMESH_ROOT_INSTRUCTION_SECTION, AGENTSMESH_CONTRACT_WITH_V1_BODY, AGENTSMESH_CONTRACT_WITH_V2_BODY, AGENTSMESH_CONTRACT_WITH_V3_BODY, AGENTSMESH_CONTRACT_WITH_V4_BODY, AGENTSMESH_CONTRACT_WITH_V5_BODY, AGENTSMESH_CONTRACT_WITH_V6_BODY, AGENTSMESH_CONTRACT_WITH_V7_BODY, AGENTSMESH_CONTRACT_WITH_V8_BODY, AGENTSMESH_CONTRACT_WITH_V9_BODY, AGENTSMESH_CONTRACT_WITH_V10_BODY, AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH, LEGACY_FORMS;
1027
1054
  var init_root_instruction_paragraph = __esm({
1028
1055
  "src/targets/projection/root-instruction-paragraph.ts"() {
1029
1056
  init_managed_blocks();
@@ -1035,7 +1062,9 @@ var init_root_instruction_paragraph = __esm({
1035
1062
  ROOT_INSTRUCTION_BODY_V6 = "Create canonical files in `.agentsmesh`: `rules/_root.md` and `rules/*.md` are Markdown rules; `commands/*.md`, `agents/*.md`, and `skills/*/SKILL.md` plus supporting files use Claude-style frontmatter Markdown; `mcp.json` is MCP JSON; `hooks.yaml` and `permissions.yaml` are YAML; `ignore` is gitignore-style text. Then run `agentsmesh generate`.";
1036
1063
  ROOT_INSTRUCTION_BODY_V7 = "`.agentsmesh` is the only folder you edit or add these files in: `rules/_root.md` and `rules/*.md` are Markdown rules; `commands/*.md`, `agents/*.md`, and `skills/*/SKILL.md` plus supporting files use Claude-style frontmatter Markdown; `mcp.json` is MCP JSON; `hooks.yaml` and `permissions.yaml` are YAML; `ignore` is gitignore-style text. Do not edit generated tool files; run `agentsmesh generate`.";
1037
1064
  ROOT_INSTRUCTION_BODY_V8 = "`agentsmesh.yaml` selects targets/features (`agentsmesh.local.yaml` overrides locally), and `.agentsmesh` is the only place to add or edit canonical items: `rules/_root.md`, `rules/*.md`, `commands/*.md`, `agents/*.md`, `skills/*/SKILL.md` plus supporting files, `mcp.json`, `hooks.yaml`, `permissions.yaml`, and `ignore`; if missing run `agentsmesh init`, use `agentsmesh import --from <tool>` for native configs, `agentsmesh install <source>` or `install --sync` for reusable packs, then run `agentsmesh generate`. Use `diff`, `lint`, `check`, `watch`, `matrix`, and `merge` as needed; never edit generated tool files.";
1038
- ROOT_INSTRUCTION_BODY = "`agentsmesh.yaml` selects targets/features (`agentsmesh.local.yaml` overrides locally), and `.agentsmesh` is the only place to add or edit canonical items: `rules/_root.md`, `rules/*.md`, `commands/*.md`, `agents/*.md`, `skills/*/SKILL.md` plus supporting files, `mcp.json`, `hooks.yaml`, `permissions.yaml`, and `ignore`; if missing run `agentsmesh init`, use `agentsmesh import --from <tool>` for native configs, `agentsmesh install <source>` or `install --sync` for reusable packs, then run `agentsmesh generate`. Use `diff`, `lint`, `check`, `watch`, `matrix`, `merge`, and `refresh` as needed; never edit generated tool files.";
1065
+ ROOT_INSTRUCTION_BODY_V9 = "`agentsmesh.yaml` selects targets/features (`agentsmesh.local.yaml` overrides locally), and `.agentsmesh` is the only place to add or edit canonical items: `rules/_root.md`, `rules/*.md`, `commands/*.md`, `agents/*.md`, `skills/*/SKILL.md` plus supporting files, `mcp.json`, `hooks.yaml`, `permissions.yaml`, and `ignore`; if missing run `agentsmesh init`, use `agentsmesh import --from <tool>` for native configs, `agentsmesh install <source>` or `install --sync` for reusable packs, then run `agentsmesh generate`. Use `diff`, `lint`, `check`, `watch`, `matrix`, `merge`, and `refresh` as needed; never edit generated tool files.";
1066
+ ROOT_INSTRUCTION_BODY_V10 = "**MUST follow when changing any rule, agent, command, skill, hook, MCP server, permission, or ignore pattern.** `agentsmesh.yaml` selects targets/features (`agentsmesh.local.yaml` overrides locally), and `.agentsmesh` is the only place to add or edit canonical items: `rules/_root.md`, `rules/*.md`, `commands/*.md`, `agents/*.md`, `skills/*/SKILL.md` plus supporting files, `mcp.json`, `hooks.yaml`, `permissions.yaml`, and `ignore`; if missing run `agentsmesh init`, use `agentsmesh import --from <tool>` for native configs, `agentsmesh install <source>` or `install --sync` for reusable packs, then run `agentsmesh generate`. Use `diff`, `lint`, `check`, `watch`, `matrix`, `merge`, and `refresh` as needed; never edit generated tool files.";
1067
+ ROOT_INSTRUCTION_BODY = "**NEVER edit generated files** (`.claude/`, `.cursor/`, `AGENTS.md`, `.github/copilot-instructions.md`, and similar target outputs) \u2014 `agentsmesh generate` overwrites them. **All changes MUST go through `.agentsmesh` first**: edit `rules/_root.md`, `rules/*.md`, `commands/*.md`, `agents/*.md`, `skills/*/SKILL.md` plus supporting files, `mcp.json`, `hooks.yaml`, `permissions.yaml`, and `ignore`; `agentsmesh.yaml` selects targets/features (`agentsmesh.local.yaml` overrides locally); if missing run `agentsmesh init`, use `agentsmesh import --from <tool>` for native configs, `agentsmesh install <source>` or `install --sync` for reusable packs, then run `agentsmesh generate`. Use `diff`, `lint`, `check`, `watch`, `matrix`, `merge`, and `refresh` as needed.";
1039
1068
  LEGACY_AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH = ROOT_INSTRUCTION_BODY_V1;
1040
1069
  LEGACY_AGENTSMESH_ROOT_INSTRUCTION_SECTION = `## Project-Specific Rules
1041
1070
 
@@ -1064,12 +1093,20 @@ ${ROOT_INSTRUCTION_BODY_V7}`;
1064
1093
  AGENTSMESH_CONTRACT_WITH_V8_BODY = `## AgentsMesh Generation Contract
1065
1094
 
1066
1095
  ${ROOT_INSTRUCTION_BODY_V8}`;
1096
+ AGENTSMESH_CONTRACT_WITH_V9_BODY = `## AgentsMesh Generation Contract
1097
+
1098
+ ${ROOT_INSTRUCTION_BODY_V9}`;
1099
+ AGENTSMESH_CONTRACT_WITH_V10_BODY = `## AgentsMesh Generation Contract
1100
+
1101
+ ${ROOT_INSTRUCTION_BODY_V10}`;
1067
1102
  AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH = `${ROOT_CONTRACT_START}
1068
1103
  ## AgentsMesh Generation Contract
1069
1104
 
1070
1105
  ${ROOT_INSTRUCTION_BODY}
1071
1106
  ${ROOT_CONTRACT_END}`;
1072
1107
  LEGACY_FORMS = [
1108
+ AGENTSMESH_CONTRACT_WITH_V10_BODY,
1109
+ AGENTSMESH_CONTRACT_WITH_V9_BODY,
1073
1110
  AGENTSMESH_CONTRACT_WITH_V8_BODY,
1074
1111
  AGENTSMESH_CONTRACT_WITH_V7_BODY,
1075
1112
  AGENTSMESH_CONTRACT_WITH_V6_BODY,
@@ -15756,9 +15793,18 @@ function generateIgnore12(canonical) {
15756
15793
  if (!canonical.ignore || canonical.ignore.length === 0) return [];
15757
15794
  return [{ path: QWEN_IGNORE, content: canonical.ignore.join("\n") }];
15758
15795
  }
15796
+ function renderQwenGlobalInstructions(canonical) {
15797
+ const root = canonical.rules.find((rule) => rule.root);
15798
+ const nonRootRules = canonical.rules.filter((rule) => {
15799
+ if (rule.root) return false;
15800
+ return rule.targets.length === 0 || rule.targets.includes(QWEN_CODE_TARGET);
15801
+ });
15802
+ return appendEmbeddedRulesBlock(root?.body.trim() ?? "", nonRootRules);
15803
+ }
15759
15804
  var init_generator26 = __esm({
15760
15805
  "src/targets/qwen-code/generator.ts"() {
15761
15806
  init_markdown();
15807
+ init_managed_blocks();
15762
15808
  init_constants21();
15763
15809
  }
15764
15810
  });
@@ -15841,6 +15887,7 @@ var init_qwen_code2 = __esm({
15841
15887
  };
15842
15888
  globalLayout22 = {
15843
15889
  rootInstructionPath: QWEN_GLOBAL_ROOT,
15890
+ renderPrimaryRootInstruction: renderQwenGlobalInstructions,
15844
15891
  skillDir: QWEN_GLOBAL_SKILLS_DIR,
15845
15892
  managedOutputs: {
15846
15893
  dirs: [QWEN_GLOBAL_COMMANDS_DIR, QWEN_GLOBAL_AGENTS_DIR, QWEN_GLOBAL_SKILLS_DIR],
@@ -18731,6 +18778,19 @@ init_fs();
18731
18778
 
18732
18779
  // src/config/remote/git-remote.ts
18733
18780
  init_fs();
18781
+
18782
+ // src/utils/output/redact-url-secrets.ts
18783
+ var URL_WITH_CREDENTIALS = /([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)([^/@\s"'<>]+)@([^\s"'<>]+)/g;
18784
+ function redactUrlSecrets(message) {
18785
+ return message.replace(
18786
+ URL_WITH_CREDENTIALS,
18787
+ (_full, scheme, _userinfo, rest) => {
18788
+ return `${scheme}***@${rest}`;
18789
+ }
18790
+ );
18791
+ }
18792
+
18793
+ // src/config/remote/git-remote.ts
18734
18794
  var execFileAsync = promisify(execFile);
18735
18795
  var REPO_DIRNAME = "repo";
18736
18796
  function ensureNotFlag(value, kind) {
@@ -18764,12 +18824,13 @@ async function fetchGitRemoteExtend(parsed, extendName, options, cacheDir, build
18764
18824
  await rm(stagedRoot, { recursive: true, force: true });
18765
18825
  const allowFallback = options.allowOfflineFallback !== false;
18766
18826
  if (allowFallback && await hasCachedRepo(cacheRepoDir)) {
18827
+ const rawMsg = err instanceof Error ? err.message : String(err);
18767
18828
  console.warn(
18768
- `[agentsmesh] Remote fetch failed for ${extendName}; using cached version. Error: ${err instanceof Error ? err.message : String(err)}`
18829
+ `[agentsmesh] Remote fetch failed for ${extendName}; using cached version. Error: ${redactUrlSecrets(rawMsg)}`
18769
18830
  );
18770
18831
  return readCachedRepo(cacheRepoDir);
18771
18832
  }
18772
- throw err;
18833
+ throw err instanceof Error ? Object.assign(new Error(redactUrlSecrets(err.message)), { cause: err.cause }) : err;
18773
18834
  }
18774
18835
  }
18775
18836
  async function readCachedRepo(repoDir) {
@@ -18930,13 +18991,14 @@ async function fetchGithubRemoteExtend(parsed, extendName, options, cacheDir, bu
18930
18991
  if (allowFallback && await exists(extractDir)) {
18931
18992
  const topDir2 = await findExtractTopDir(extractDir);
18932
18993
  if (topDir2) {
18994
+ const rawMsg = err instanceof Error ? err.message : String(err);
18933
18995
  console.warn(
18934
- `[agentsmesh] Network failed for ${extendName}; using cached version. Error: ${err instanceof Error ? err.message : String(err)}`
18996
+ `[agentsmesh] Network failed for ${extendName}; using cached version. Error: ${redactUrlSecrets(rawMsg)}`
18935
18997
  );
18936
18998
  return { resolvedPath: join(extractDir, topDir2), version: tag };
18937
18999
  }
18938
19000
  }
18939
- throw err;
19001
+ throw err instanceof Error ? Object.assign(new Error(redactUrlSecrets(err.message)), { cause: err.cause }) : err;
18940
19002
  }
18941
19003
  await rm(extractDir, { recursive: true, force: true });
18942
19004
  await mkdir(extractDir, { recursive: true });
@@ -18947,12 +19009,14 @@ async function fetchGithubRemoteExtend(parsed, extendName, options, cacheDir, bu
18947
19009
  file: tarPath,
18948
19010
  cwd: extractDir,
18949
19011
  strict: true,
19012
+ // Allowlist entry types instead of denylist: only `File` and `Directory`
19013
+ // can be extracted. Hardlinks (`Link`), symlinks (`SymbolicLink`), FIFOs,
19014
+ // character/block devices, and any future/exotic tar entry type are
19015
+ // rejected. A denylist would silently let an unknown variant through.
18950
19016
  filter: (entryPath, entry) => {
18951
19017
  if (isZipSlipPath(entryPath)) return false;
18952
- if (entry && "type" in entry && (entry.type === "Link" || entry.type === "SymbolicLink")) {
18953
- return false;
18954
- }
18955
- return true;
19018
+ const type = entry && "type" in entry ? entry.type : void 0;
19019
+ return type === "File" || type === "Directory";
18956
19020
  }
18957
19021
  });
18958
19022
  } finally {
@@ -19050,8 +19114,11 @@ function parseGitSource(source) {
19050
19114
  return null;
19051
19115
  }
19052
19116
  const allowInsecure = process.env.AGENTSMESH_ALLOW_INSECURE_GIT === "1" || process.env.AGENTSMESH_ALLOW_INSECURE_GIT === "true";
19053
- const allowedProtocols = allowInsecure ? ["https:", "http:", "ssh:", "file:"] : ["https:", "ssh:", "file:"];
19054
- if (!allowedProtocols.includes(parsedUrl.protocol)) {
19117
+ const allowLocalGit = process.env.AGENTSMESH_ALLOW_LOCAL_GIT === "1" || process.env.AGENTSMESH_ALLOW_LOCAL_GIT === "true";
19118
+ const allowed = ["https:", "ssh:"];
19119
+ if (allowInsecure) allowed.push("http:");
19120
+ if (allowLocalGit) allowed.push("file:");
19121
+ if (!allowed.includes(parsedUrl.protocol)) {
19055
19122
  return null;
19056
19123
  }
19057
19124
  return { url, ref };
@@ -19576,6 +19643,7 @@ async function parseAgents(agentsDir, opts = {}) {
19576
19643
 
19577
19644
  // src/canonical/features/skills.ts
19578
19645
  init_fs();
19646
+ init_fs_traverse();
19579
19647
  init_markdown();
19580
19648
  init_boilerplate_filter();
19581
19649
  async function readContent(path) {
@@ -19594,7 +19662,7 @@ function sanitizeSkillName(raw) {
19594
19662
  return raw.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
19595
19663
  }
19596
19664
  async function listSupportingFiles(skillDir) {
19597
- const files = await readDirRecursive(skillDir);
19665
+ const files = await readDirRecursiveNoSymlinks(skillDir);
19598
19666
  const result = [];
19599
19667
  for (const absPath of files) {
19600
19668
  const raw = absPath.slice(skillDir.length + 1);