agentsmesh 0.19.0 → 0.20.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,138 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.20.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 6739c63: feat(refresh): add `agentsmesh refresh` to re-fetch and re-apply installed packs
8
+
9
+ A new top-level CLI command and MCP tool for keeping installed packs in
10
+ sync with their declared sources. Branch pins re-resolve to the current
11
+ tip; tag pins re-resolve in case the tag moved; SHA pins stay put. Per-pack
12
+ atomic via the existing `materializePack` swap — a failure or interruption
13
+ leaves the affected pack at its pre-refresh state.
14
+
15
+ ```
16
+ agentsmesh refresh # refresh every installed pack
17
+ agentsmesh refresh my-pack,other-pack # refresh just these
18
+ agentsmesh refresh --dry-run # preview without writing
19
+ agentsmesh refresh --force # skip the drift consent prompt
20
+ agentsmesh refresh --json # JSON output (implies --force)
21
+ agentsmesh refresh --global # global scope
22
+ ```
23
+
24
+ **MCP parity:** new `mcp__agentsmesh__refresh` tool with the same
25
+ `{ names?, dry_run?, global? }` input shape MCP install/uninstall use.
26
+ `force: true` is implicit (no TTY). Errors map to two new codes —
27
+ `REFRESH_RESOLVE_FAILED` and `REFRESH_APPLY_FAILED` — plus the existing
28
+ `LOCK_HELD` and `IO_ERROR`.
29
+
30
+ **Drift handling:** modified pack files trigger a consolidated consent
31
+ prompt with a 5-minute timeout (default no). `--force` bypasses the prompt
32
+ and overwrites local edits. The prompt is collapsed across packs so a bulk
33
+ refresh asks once, not N times.
34
+
35
+ **Schema additions** (`installs.yaml`, all optional, backwards-compatible):
36
+ - `original_ref?: string` — the user's original ref expression (e.g.
37
+ `main`, `v1.2.3`) captured at install time. Used by refresh to
38
+ re-resolve branch pins. Absent on installs predating this release;
39
+ refresh becomes a deterministic no-op for those rows.
40
+ - `refreshed_at?: string` — ISO-8601 timestamp of the last successful
41
+ refresh. Surfaces in `installs list` under the "LAST TOUCHED" column
42
+ (falls back to `installed_at` when absent).
43
+
44
+ **Behavior changes that could affect existing consumers:**
45
+ - `installs.yaml` rows written by this release include `original_ref`.
46
+ Pre-existing rows continue to parse and behave identically.
47
+ - `--json` on `agentsmesh refresh` implies `--force` (CI/MCP can't
48
+ answer the consent prompt). Documented on the website CLI reference.
49
+ - `installs list` column header was "INSTALLED AT", now "LAST TOUCHED",
50
+ showing `refreshed_at` when present and `installed_at` otherwise.
51
+
52
+ **Architecture notes:**
53
+ - `installAsPack` gains an optional `forceFreshMaterialize` flag,
54
+ threaded through five layers (`install-flags → run-install →
55
+ run-install-locked → single-pack-install → run-install-execute →
56
+ installAsPack`). Default is false; install's existing flow is
57
+ untouched. Refresh sets the flag to bypass the
58
+ merge-into-existing-pack branch and force atomic replacement via
59
+ `materializePack`.
60
+ - Source-URL parsing is now shared between install and refresh via the
61
+ new pure `parseSourceUrl` helper (`src/install/source/parse-source-url.ts`).
62
+
63
+ **Refresh does NOT switch refs.** To move a pack to a different ref,
64
+ re-run `agentsmesh install <source>@<new-ref>` — install silently
65
+ overwrites an existing pack of the same name.
66
+
67
+ **Refresh vs `install --sync`** are orthogonal. `--sync` replays missing
68
+ installs from `installs.yaml` (fresh clone). `refresh` updates existing
69
+ installs against their declared sources.
70
+
71
+ Verified end-to-end against 64 community packs from the install
72
+ compatibility log spanning Anthropic skill-packs, marketplaces (`--all`),
73
+ canonical mixed packs, flat collections, root SKILL.md, root CLAUDE.md,
74
+ manual `--path`/`--as`/`--target` combinations, and packs using `pick`
75
+ selectors. Full unit/integration/e2e suite green (7700+ tests).
76
+
77
+ ## 0.19.1
78
+
79
+ ### Patch Changes
80
+
81
+ - 041b9c5: fix(security): plug input/path/proto-pollution holes in plugin, install, MCP, and config
82
+
83
+ Closes a batch of security audit findings (2 HIGH + 5 MEDIUM):
84
+ - **Plugin source containment** (`src/plugins/load-plugin.ts`) — local plugin
85
+ sources are now resolved with `realpath` and rejected when they escape
86
+ `projectRoot`. A hostile `agentsmesh.yaml` with
87
+ `plugins[].source: "../../tmp/evil.js"` no longer reaches dynamic
88
+ `import()`. Bare npm specifiers continue to resolve through
89
+ `node_modules/<source>`. Both sides are canonicalized so macOS
90
+ `/tmp -> /private/tmp` (and other platform-level symlinks) do not
91
+ produce false positives.
92
+ - **Prototype pollution denylist** (`src/config/core/loader.ts`) —
93
+ `deepMergeObjects` over `agentsmesh.local.yaml` now skips `__proto__`,
94
+ `constructor`, and `prototype` keys. Defense-in-depth: the `yaml` v2
95
+ parser already strips `__proto__`, but this pins the invariant against
96
+ future parser swaps.
97
+ - **Install manifest name validation** (`src/install/core/install-manifest.ts`) —
98
+ `installManifestEntrySchema.name` now refuses path separators, NUL,
99
+ and `.`/`..` segments. A poisoned `installs.yaml` entry can no longer
100
+ drive `rm -rf` outside `.agentsmesh/packs/` at uninstall time.
101
+ - **`git+http://` allowlist** (`src/config/remote/remote-source.ts`) —
102
+ rejected by default; opt-in via `AGENTSMESH_ALLOW_INSECURE_GIT=1` for
103
+ closed-network development. `https://`, `ssh://`, and `file://` are
104
+ unchanged. Closes a MITM window before SHA pinning resolves.
105
+ - **MCP `cwd` / `description` refinement** (`src/mcp/schemas.ts`) — `cwd`
106
+ rejects `..` segments (POSIX + Windows separators), NUL, and newlines;
107
+ `description` rejects NUL and newlines. MCP clients can no longer
108
+ plant a structurally-escaping working directory that downstream agents
109
+ consume via `spawn(command, args, { cwd })`.
110
+ - **Global path redaction in MCP errors** (`src/mcp/errors.ts`) —
111
+ `redactAbsolutePaths` now strips paths anywhere in the string, catching
112
+ embedded paths in stack frames (`at Foo (/Users/...)`) and quoted
113
+ paths in Node errors (`ENOENT, open '/Users/...'`) the prior
114
+ whitespace-anchored regex missed.
115
+ - **`copyDir` symlink hardening** (`src/utils/filesystem/fs-traverse.ts`) —
116
+ `copyDir` now uses `lstat` and skips symlinks. A symlink in the source
117
+ tree pointing outside its root can no longer have its target's bytes
118
+ exfiltrated into the destination (and into any redistributed pack
119
+ built on top of it).
120
+
121
+ Behavioral changes that could affect existing consumers:
122
+ - `git+http://...` extends/installs require `AGENTSMESH_ALLOW_INSECURE_GIT=1`.
123
+ - MCP server entries with `cwd: "../foo"` no longer parse — rewrite as a
124
+ POSIX-relative path without `..` segments.
125
+ - Plugin `source:` entries pointing outside the project tree no longer
126
+ load. The standard `node_modules/<plugin>` and project-local layouts
127
+ are unaffected.
128
+ - A poisoned `installs.yaml` entry whose `name` contains separators or
129
+ `..` is now dropped at parse time (the rest of the manifest survives).
130
+ - A `agentsmesh.local.yaml` payload at `__proto__`, `constructor`, or
131
+ `prototype` keys is silently dropped instead of merged.
132
+
133
+ Branch coverage > 95% on every touched file; full unit/integration suite
134
+ (7596 tests) and plugin e2e suite (57 tests) green.
135
+
3
136
  ## 0.19.0
4
137
 
5
138
  ### Minor Changes
package/README.md CHANGED
@@ -235,6 +235,7 @@ agentsmesh matrix [--global] [--targets <csv>] [--verbose]
235
235
  agentsmesh install <source> [--sync] [--path <dir>] [--target <id>] [--as <kind>] [--name <id>] [--extends] [--all] [--dry-run] [--global] [--force]
236
236
  agentsmesh uninstall <name>[,<name>...] [--all] [--keep-pack] [--keep-generated] [--dry-run] [--global] [--force]
237
237
  agentsmesh installs list [--global]
238
+ agentsmesh refresh [<name>[,<name>...]] [--dry-run] [--force] [--json] [--global]
238
239
  agentsmesh plugin add|list|remove|info [--version <v>] [--id <id>]
239
240
  agentsmesh target scaffold <id> [--name <displayName>] [--force]
240
241
  ```
@@ -314,6 +315,34 @@ agentsmesh uninstall <name> --dry-run # preview; no writes
314
315
 
315
316
  Each install writes `.agentsmesh-install-manifest.json` next to the pack with per-file sha256 hashes; uninstall compares current contents against that manifest and prompts before deleting locally-modified files. `--force` accepts the documented defaults (bulk = accept all, broken-link = leave-with-warnings, modified = delete-anyway). `.agentsmesh/.install.lock` serialises install/uninstall so concurrent runs on the same project fail fast rather than racing on disk.
316
317
 
318
+ ### Refreshing packs
319
+
320
+ `agentsmesh refresh` re-fetches every installed pack from its recorded source/ref
321
+ and re-applies it. Branch pins (`@main`) advance to the current tip; tag pins
322
+ re-resolve in case the tag moved; SHA pins stay put (re-fetch with the same content).
323
+
324
+ ```bash
325
+ agentsmesh refresh # refresh every installed pack
326
+ agentsmesh refresh my-pack,other-pack # refresh just these
327
+ agentsmesh refresh --dry-run # preview without writing
328
+ agentsmesh refresh --force # skip the drift prompt
329
+ ```
330
+
331
+ Each pack is refreshed atomically — a failure or interruption leaves the
332
+ affected pack at its pre-refresh state. Local edits to pack files trigger
333
+ a consolidated consent prompt (5-minute timeout) unless `--force` is set.
334
+
335
+ **refresh does NOT switch refs.** To move a pack to a different ref, just install
336
+ with the new ref — install silently overwrites an existing pack of the same name:
337
+
338
+ ```bash
339
+ agentsmesh install github:org/repo@v2.0.0
340
+ ```
341
+
342
+ **refresh vs `install --sync`.** `--sync` replays missing installs from
343
+ `installs.yaml` (e.g. after a fresh clone). `refresh` updates existing
344
+ installs against their declared sources. They are orthogonal.
345
+
317
346
  ### What to commit and what to gitignore
318
347
 
319
348
  `agentsmesh init` writes a `.gitignore` that follows the recommended convention. The defaults are deliberate:
@@ -457,7 +486,7 @@ See the [full feature matrix docs](https://samplexbro.github.io/agentsmesh/refer
457
486
 
458
487
  - **[Getting Started](https://samplexbro.github.io/agentsmesh/getting-started/installation/)** — installation, quick start
459
488
  - **[Canonical Config](https://samplexbro.github.io/agentsmesh/canonical-config/)** — rules, commands, agents, skills, MCP, hooks, ignore, permissions
460
- - **[CLI Reference](https://samplexbro.github.io/agentsmesh/cli/)** — `init`, `generate`, `import`, `convert`, `install`, `uninstall`, `installs`, `diff`, `lint`, `watch`, `check`, `merge`, `matrix`, `plugin`, `target`
489
+ - **[CLI Reference](https://samplexbro.github.io/agentsmesh/cli/)** — `init`, `generate`, `import`, `convert`, `install`, `uninstall`, `installs`, `refresh`, `diff`, `lint`, `watch`, `check`, `merge`, `matrix`, `plugin`, `target`
461
490
  - **[Configuration](https://samplexbro.github.io/agentsmesh/configuration/agentsmesh-yaml/)** — `agentsmesh.yaml`, local overrides, extends, collaboration, conversions
462
491
  - **[Guides](https://samplexbro.github.io/agentsmesh/guides/existing-project/)** — adopting in existing projects · multi-tool teams · sharing config · CI drift detection · community packs · **building plugins**
463
492
  - **[Reference](https://samplexbro.github.io/agentsmesh/reference/generation-pipeline/)** — supported tools matrix · generation pipeline · managed embedding
package/dist/canonical.js CHANGED
@@ -1023,7 +1023,7 @@ ${legacy}`, "");
1023
1023
  }
1024
1024
  return result.trim();
1025
1025
  }
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, 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_ROOT_INSTRUCTION_PARAGRAPH, LEGACY_FORMS;
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;
1027
1027
  var init_root_instruction_paragraph = __esm({
1028
1028
  "src/targets/projection/root-instruction-paragraph.ts"() {
1029
1029
  init_managed_blocks();
@@ -1034,7 +1034,8 @@ var init_root_instruction_paragraph = __esm({
1034
1034
  ROOT_INSTRUCTION_BODY_V5 = "Use Claude-style Markdown in `.agentsmesh`: `agents/*.md`, `commands/*.md`, and `skills/*/SKILL.md`; keep rules in `rules/*.md`, hooks in `hooks.yaml`, MCP in `mcp.json`, permissions in `permissions.yaml`, and ignore patterns in `ignore`, then run `agentsmesh generate`.";
1035
1035
  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
1036
  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
- 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`, and `merge` as needed; never edit generated tool files.";
1037
+ 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.";
1038
1039
  LEGACY_AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH = ROOT_INSTRUCTION_BODY_V1;
1039
1040
  LEGACY_AGENTSMESH_ROOT_INSTRUCTION_SECTION = `## Project-Specific Rules
1040
1041
 
@@ -1060,12 +1061,16 @@ ${ROOT_INSTRUCTION_BODY_V6}`;
1060
1061
  AGENTSMESH_CONTRACT_WITH_V7_BODY = `## AgentsMesh Generation Contract
1061
1062
 
1062
1063
  ${ROOT_INSTRUCTION_BODY_V7}`;
1064
+ AGENTSMESH_CONTRACT_WITH_V8_BODY = `## AgentsMesh Generation Contract
1065
+
1066
+ ${ROOT_INSTRUCTION_BODY_V8}`;
1063
1067
  AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH = `${ROOT_CONTRACT_START}
1064
1068
  ## AgentsMesh Generation Contract
1065
1069
 
1066
1070
  ${ROOT_INSTRUCTION_BODY}
1067
1071
  ${ROOT_CONTRACT_END}`;
1068
1072
  LEGACY_FORMS = [
1073
+ AGENTSMESH_CONTRACT_WITH_V8_BODY,
1069
1074
  AGENTSMESH_CONTRACT_WITH_V7_BODY,
1070
1075
  AGENTSMESH_CONTRACT_WITH_V6_BODY,
1071
1076
  AGENTSMESH_CONTRACT_WITH_V5_BODY,
@@ -19044,7 +19049,9 @@ function parseGitSource(source) {
19044
19049
  } catch {
19045
19050
  return null;
19046
19051
  }
19047
- if (!["https:", "http:", "ssh:", "file:"].includes(parsedUrl.protocol)) {
19052
+ 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)) {
19048
19055
  return null;
19049
19056
  }
19050
19057
  return { url, ref };
@@ -20984,10 +20991,12 @@ async function loadConfig(configPath) {
20984
20991
  }
20985
20992
  return result.data;
20986
20993
  }
20994
+ var PROTOTYPE_POLLUTION_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
20987
20995
  function deepMergeObjects(base, overrides2) {
20988
20996
  const result = { ...base };
20989
20997
  for (const [k, v] of Object.entries(overrides2)) {
20990
20998
  if (v === null || v === void 0) continue;
20999
+ if (PROTOTYPE_POLLUTION_KEYS.has(k)) continue;
20991
21000
  const baseVal = result[k];
20992
21001
  if (typeof v === "object" && !Array.isArray(v) && v !== null && typeof baseVal === "object" && baseVal !== null && !Array.isArray(baseVal)) {
20993
21002
  result[k] = deepMergeObjects(