@skillsmith/core 0.9.0 → 0.10.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 +12 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/api/client.d.ts.map +1 -1
- package/dist/src/api/client.js +6 -0
- package/dist/src/api/client.js.map +1 -1
- package/dist/src/api/client.test.d.ts +9 -0
- package/dist/src/api/client.test.d.ts.map +1 -0
- package/dist/src/api/client.test.js +81 -0
- package/dist/src/api/client.test.js.map +1 -0
- package/dist/src/exports/services.d.ts +3 -1
- package/dist/src/exports/services.d.ts.map +1 -1
- package/dist/src/exports/services.js +7 -1
- package/dist/src/exports/services.js.map +1 -1
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +5 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/install/agent-config-merge.json-array.d.ts +29 -0
- package/dist/src/install/agent-config-merge.json-array.d.ts.map +1 -0
- package/dist/src/install/agent-config-merge.json-array.js +100 -0
- package/dist/src/install/agent-config-merge.json-array.js.map +1 -0
- package/dist/src/install/agent-config-merge.json.d.ts +37 -0
- package/dist/src/install/agent-config-merge.json.d.ts.map +1 -0
- package/dist/src/install/agent-config-merge.json.js +134 -0
- package/dist/src/install/agent-config-merge.json.js.map +1 -0
- package/dist/src/install/agent-config-merge.toml-block.d.ts +48 -0
- package/dist/src/install/agent-config-merge.toml-block.d.ts.map +1 -0
- package/dist/src/install/agent-config-merge.toml-block.js +107 -0
- package/dist/src/install/agent-config-merge.toml-block.js.map +1 -0
- package/dist/src/install/agent-config-merge.types.d.ts +87 -0
- package/dist/src/install/agent-config-merge.types.d.ts.map +1 -0
- package/dist/src/install/agent-config-merge.types.js +83 -0
- package/dist/src/install/agent-config-merge.types.js.map +1 -0
- package/dist/src/install/agent-config-merge.yaml.d.ts +46 -0
- package/dist/src/install/agent-config-merge.yaml.d.ts.map +1 -0
- package/dist/src/install/agent-config-merge.yaml.js +133 -0
- package/dist/src/install/agent-config-merge.yaml.js.map +1 -0
- package/dist/src/install/agent-config-merge.yaml.test.d.ts +2 -0
- package/dist/src/install/agent-config-merge.yaml.test.d.ts.map +1 -0
- package/dist/src/install/agent-config-merge.yaml.test.js +210 -0
- package/dist/src/install/agent-config-merge.yaml.test.js.map +1 -0
- package/dist/src/install/agent-harness-targets.d.ts +98 -0
- package/dist/src/install/agent-harness-targets.d.ts.map +1 -0
- package/dist/src/install/agent-harness-targets.js +154 -0
- package/dist/src/install/agent-harness-targets.js.map +1 -0
- package/dist/src/install/agent-home-relocate.d.ts +29 -0
- package/dist/src/install/agent-home-relocate.d.ts.map +1 -0
- package/dist/src/install/agent-home-relocate.js +38 -0
- package/dist/src/install/agent-home-relocate.js.map +1 -0
- package/dist/src/install/agent-manifest-path-guard.d.ts +55 -0
- package/dist/src/install/agent-manifest-path-guard.d.ts.map +1 -0
- package/dist/src/install/agent-manifest-path-guard.js +109 -0
- package/dist/src/install/agent-manifest-path-guard.js.map +1 -0
- package/dist/src/install/agent-manifest-path-guard.test.d.ts +2 -0
- package/dist/src/install/agent-manifest-path-guard.test.d.ts.map +1 -0
- package/dist/src/install/agent-manifest-path-guard.test.js +72 -0
- package/dist/src/install/agent-manifest-path-guard.test.js.map +1 -0
- package/dist/src/install/agent-manifest.d.ts +58 -0
- package/dist/src/install/agent-manifest.d.ts.map +1 -0
- package/dist/src/install/agent-manifest.js +103 -0
- package/dist/src/install/agent-manifest.js.map +1 -0
- package/dist/src/install/agent-pack-installer.d.ts +39 -0
- package/dist/src/install/agent-pack-installer.d.ts.map +1 -0
- package/dist/src/install/agent-pack-installer.entry.d.ts +55 -0
- package/dist/src/install/agent-pack-installer.entry.d.ts.map +1 -0
- package/dist/src/install/agent-pack-installer.entry.js +90 -0
- package/dist/src/install/agent-pack-installer.entry.js.map +1 -0
- package/dist/src/install/agent-pack-installer.fs-helpers.d.ts +33 -0
- package/dist/src/install/agent-pack-installer.fs-helpers.d.ts.map +1 -0
- package/dist/src/install/agent-pack-installer.fs-helpers.js +59 -0
- package/dist/src/install/agent-pack-installer.fs-helpers.js.map +1 -0
- package/dist/src/install/agent-pack-installer.harness.d.ts +66 -0
- package/dist/src/install/agent-pack-installer.harness.d.ts.map +1 -0
- package/dist/src/install/agent-pack-installer.harness.js +310 -0
- package/dist/src/install/agent-pack-installer.harness.js.map +1 -0
- package/dist/src/install/agent-pack-installer.js +221 -0
- package/dist/src/install/agent-pack-installer.js.map +1 -0
- package/dist/src/install/agent-pack-installer.preserve.test.d.ts +11 -0
- package/dist/src/install/agent-pack-installer.preserve.test.d.ts.map +1 -0
- package/dist/src/install/agent-pack-installer.preserve.test.js +72 -0
- package/dist/src/install/agent-pack-installer.preserve.test.js.map +1 -0
- package/dist/src/install/agent-pack-installer.test.d.ts +2 -0
- package/dist/src/install/agent-pack-installer.test.d.ts.map +1 -0
- package/dist/src/install/agent-pack-installer.test.js +350 -0
- package/dist/src/install/agent-pack-installer.test.js.map +1 -0
- package/dist/src/install/agent-pack-installer.types.d.ts +51 -0
- package/dist/src/install/agent-pack-installer.types.d.ts.map +1 -0
- package/dist/src/install/agent-pack-installer.types.js +16 -0
- package/dist/src/install/agent-pack-installer.types.js.map +1 -0
- package/dist/src/install/agent-pack-uninstaller.d.ts +43 -0
- package/dist/src/install/agent-pack-uninstaller.d.ts.map +1 -0
- package/dist/src/install/agent-pack-uninstaller.js +113 -0
- package/dist/src/install/agent-pack-uninstaller.js.map +1 -0
- package/dist/src/install/agent-pack-uninstaller.test.d.ts +2 -0
- package/dist/src/install/agent-pack-uninstaller.test.d.ts.map +1 -0
- package/dist/src/install/agent-pack-uninstaller.test.js +212 -0
- package/dist/src/install/agent-pack-uninstaller.test.js.map +1 -0
- package/dist/src/install/index.d.ts +7 -0
- package/dist/src/install/index.d.ts.map +1 -1
- package/dist/src/install/index.js +7 -0
- package/dist/src/install/index.js.map +1 -1
- package/dist/src/install/paths.d.ts +1 -1
- package/dist/src/install/paths.d.ts.map +1 -1
- package/dist/src/install/paths.js +4 -0
- package/dist/src/install/paths.js.map +1 -1
- package/dist/src/install/paths.test.js +15 -0
- package/dist/src/install/paths.test.js.map +1 -1
- package/dist/src/journal/hash.d.ts +13 -0
- package/dist/src/journal/hash.d.ts.map +1 -0
- package/dist/src/journal/hash.js +16 -0
- package/dist/src/journal/hash.js.map +1 -0
- package/dist/src/journal/index.d.ts +15 -0
- package/dist/src/journal/index.d.ts.map +1 -0
- package/dist/src/journal/index.js +15 -0
- package/dist/src/journal/index.js.map +1 -0
- package/dist/src/journal/journal.test.d.ts +16 -0
- package/dist/src/journal/journal.test.d.ts.map +1 -0
- package/dist/src/journal/journal.test.js +171 -0
- package/dist/src/journal/journal.test.js.map +1 -0
- package/dist/src/journal/path.d.ts +44 -0
- package/dist/src/journal/path.d.ts.map +1 -0
- package/dist/src/journal/path.js +61 -0
- package/dist/src/journal/path.js.map +1 -0
- package/dist/src/journal/reader.d.ts +38 -0
- package/dist/src/journal/reader.d.ts.map +1 -0
- package/dist/src/journal/reader.js +93 -0
- package/dist/src/journal/reader.js.map +1 -0
- package/dist/src/journal/types.d.ts +87 -0
- package/dist/src/journal/types.d.ts.map +1 -0
- package/dist/src/journal/types.js +21 -0
- package/dist/src/journal/types.js.map +1 -0
- package/dist/src/journal/writer.d.ts +52 -0
- package/dist/src/journal/writer.d.ts.map +1 -0
- package/dist/src/journal/writer.js +125 -0
- package/dist/src/journal/writer.js.map +1 -0
- package/dist/src/paywall-triggers/index.d.ts +13 -0
- package/dist/src/paywall-triggers/index.d.ts.map +1 -0
- package/dist/src/paywall-triggers/index.js +13 -0
- package/dist/src/paywall-triggers/index.js.map +1 -0
- package/dist/src/paywall-triggers/path.d.ts +47 -0
- package/dist/src/paywall-triggers/path.d.ts.map +1 -0
- package/dist/src/paywall-triggers/path.js +73 -0
- package/dist/src/paywall-triggers/path.js.map +1 -0
- package/dist/src/paywall-triggers/store.d.ts +75 -0
- package/dist/src/paywall-triggers/store.d.ts.map +1 -0
- package/dist/src/paywall-triggers/store.js +122 -0
- package/dist/src/paywall-triggers/store.js.map +1 -0
- package/dist/src/paywall-triggers/store.test.d.ts +2 -0
- package/dist/src/paywall-triggers/store.test.d.ts.map +1 -0
- package/dist/src/paywall-triggers/store.test.js +121 -0
- package/dist/src/paywall-triggers/store.test.js.map +1 -0
- package/dist/src/paywall-triggers/types.d.ts +24 -0
- package/dist/src/paywall-triggers/types.d.ts.map +1 -0
- package/dist/src/paywall-triggers/types.js +13 -0
- package/dist/src/paywall-triggers/types.js.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.compound.d.ts +13 -0
- package/dist/src/security/scanner/SecurityScanner.compound.d.ts.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.compound.js +127 -0
- package/dist/src/security/scanner/SecurityScanner.compound.js.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.js +10 -1
- package/dist/src/security/scanner/SecurityScanner.js.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.pii.d.ts +23 -0
- package/dist/src/security/scanner/SecurityScanner.pii.d.ts.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.pii.js +152 -0
- package/dist/src/security/scanner/SecurityScanner.pii.js.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.scanners.d.ts +5 -12
- package/dist/src/security/scanner/SecurityScanner.scanners.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.scanners.js +63 -137
- package/dist/src/security/scanner/SecurityScanner.scanners.js.map +1 -1
- package/dist/src/security/scanner/patterns.d.ts +2 -0
- package/dist/src/security/scanner/patterns.d.ts.map +1 -1
- package/dist/src/security/scanner/patterns.js +79 -11
- package/dist/src/security/scanner/patterns.js.map +1 -1
- package/dist/src/security/scanner/types.d.ts +2 -0
- package/dist/src/security/scanner/types.d.ts.map +1 -1
- package/dist/src/services/agent-pack/agent-pack.test.d.ts +18 -0
- package/dist/src/services/agent-pack/agent-pack.test.d.ts.map +1 -0
- package/dist/src/services/agent-pack/agent-pack.test.js +344 -0
- package/dist/src/services/agent-pack/agent-pack.test.js.map +1 -0
- package/dist/src/services/agent-pack/hooks.d.ts +29 -0
- package/dist/src/services/agent-pack/hooks.d.ts.map +1 -0
- package/dist/src/services/agent-pack/hooks.js +139 -0
- package/dist/src/services/agent-pack/hooks.js.map +1 -0
- package/dist/src/services/agent-pack/index.d.ts +26 -0
- package/dist/src/services/agent-pack/index.d.ts.map +1 -0
- package/dist/src/services/agent-pack/index.js +109 -0
- package/dist/src/services/agent-pack/index.js.map +1 -0
- package/dist/src/services/agent-pack/prompt-source.d.ts +47 -0
- package/dist/src/services/agent-pack/prompt-source.d.ts.map +1 -0
- package/dist/src/services/agent-pack/prompt-source.js +182 -0
- package/dist/src/services/agent-pack/prompt-source.js.map +1 -0
- package/dist/src/services/agent-pack/shims.d.ts +40 -0
- package/dist/src/services/agent-pack/shims.d.ts.map +1 -0
- package/dist/src/services/agent-pack/shims.js +96 -0
- package/dist/src/services/agent-pack/shims.js.map +1 -0
- package/dist/src/services/agent-pack/skill-md.d.ts +35 -0
- package/dist/src/services/agent-pack/skill-md.d.ts.map +1 -0
- package/dist/src/services/agent-pack/skill-md.js +89 -0
- package/dist/src/services/agent-pack/skill-md.js.map +1 -0
- package/dist/src/services/agent-pack/types.d.ts +118 -0
- package/dist/src/services/agent-pack/types.d.ts.map +1 -0
- package/dist/src/services/agent-pack/types.js +61 -0
- package/dist/src/services/agent-pack/types.js.map +1 -0
- package/dist/src/services/agent-tool-profile.d.ts +59 -0
- package/dist/src/services/agent-tool-profile.d.ts.map +1 -0
- package/dist/src/services/agent-tool-profile.js +76 -0
- package/dist/src/services/agent-tool-profile.js.map +1 -0
- package/dist/src/services/agent-tool-profile.test.d.ts +2 -0
- package/dist/src/services/agent-tool-profile.test.d.ts.map +1 -0
- package/dist/src/services/agent-tool-profile.test.js +28 -0
- package/dist/src/services/agent-tool-profile.test.js.map +1 -0
- package/dist/src/services/bundled-sibling-scan.d.ts +111 -0
- package/dist/src/services/bundled-sibling-scan.d.ts.map +1 -0
- package/dist/src/services/bundled-sibling-scan.js +170 -0
- package/dist/src/services/bundled-sibling-scan.js.map +1 -0
- package/dist/src/services/skill-installation.io.d.ts +16 -5
- package/dist/src/services/skill-installation.io.d.ts.map +1 -1
- package/dist/src/services/skill-installation.io.js +44 -20
- package/dist/src/services/skill-installation.io.js.map +1 -1
- package/dist/src/services/skill-installation.policy.d.ts +96 -0
- package/dist/src/services/skill-installation.policy.d.ts.map +1 -0
- package/dist/src/services/skill-installation.policy.js +144 -0
- package/dist/src/services/skill-installation.policy.js.map +1 -0
- package/dist/src/services/skill-installation.service.d.ts.map +1 -1
- package/dist/src/services/skill-installation.service.js +9 -3
- package/dist/src/services/skill-installation.service.js.map +1 -1
- package/dist/src/sources/index.d.ts +1 -0
- package/dist/src/sources/index.d.ts.map +1 -1
- package/dist/src/sources/index.js +4 -0
- package/dist/src/sources/index.js.map +1 -1
- package/dist/src/sync/index.d.ts +1 -1
- package/dist/src/sync/index.d.ts.map +1 -1
- package/dist/src/sync/index.js +1 -1
- package/dist/src/sync/index.js.map +1 -1
- package/dist/src/sync/inventory-client.d.ts +16 -0
- package/dist/src/sync/inventory-client.d.ts.map +1 -1
- package/dist/src/sync/inventory-client.js +54 -0
- package/dist/src/sync/inventory-client.js.map +1 -1
- package/dist/src/sync/inventory-client.test.js +67 -1
- package/dist/src/sync/inventory-client.test.js.map +1 -1
- package/dist/src/sync/inventory-collector.d.ts.map +1 -1
- package/dist/src/sync/inventory-collector.js +32 -5
- package/dist/src/sync/inventory-collector.js.map +1 -1
- package/dist/src/sync/inventory-collector.test.js +50 -1
- package/dist/src/sync/inventory-collector.test.js.map +1 -1
- package/dist/src/sync/inventory-types.d.ts +9 -0
- package/dist/src/sync/inventory-types.d.ts.map +1 -1
- package/dist/src/sync/inventory-types.js +3 -0
- package/dist/src/sync/inventory-types.js.map +1 -1
- package/dist/src/telemetry/agent-marker.d.ts +137 -0
- package/dist/src/telemetry/agent-marker.d.ts.map +1 -0
- package/dist/src/telemetry/agent-marker.js +242 -0
- package/dist/src/telemetry/agent-marker.js.map +1 -0
- package/dist/src/telemetry/agent-marker.test.d.ts +13 -0
- package/dist/src/telemetry/agent-marker.test.d.ts.map +1 -0
- package/dist/src/telemetry/agent-marker.test.js +202 -0
- package/dist/src/telemetry/agent-marker.test.js.map +1 -0
- package/dist/src/telemetry/index.d.ts +2 -1
- package/dist/src/telemetry/index.d.ts.map +1 -1
- package/dist/src/telemetry/index.js +6 -1
- package/dist/src/telemetry/index.js.map +1 -1
- package/dist/src/telemetry/posthog.d.ts +10 -0
- package/dist/src/telemetry/posthog.d.ts.map +1 -1
- package/dist/src/telemetry/posthog.js +5 -1
- package/dist/src/telemetry/posthog.js.map +1 -1
- package/dist/src/telemetry/wrap.d.ts +28 -7
- package/dist/src/telemetry/wrap.d.ts.map +1 -1
- package/dist/src/telemetry/wrap.gate.test.d.ts +22 -0
- package/dist/src/telemetry/wrap.gate.test.d.ts.map +1 -0
- package/dist/src/telemetry/wrap.gate.test.js +197 -0
- package/dist/src/telemetry/wrap.gate.test.js.map +1 -0
- package/dist/src/telemetry/wrap.js +111 -26
- package/dist/src/telemetry/wrap.js.map +1 -1
- package/dist/src/telemetry/wrap.marker.test.d.ts +16 -0
- package/dist/src/telemetry/wrap.marker.test.d.ts.map +1 -0
- package/dist/src/telemetry/wrap.marker.test.js +185 -0
- package/dist/src/telemetry/wrap.marker.test.js.map +1 -0
- package/dist/src/telemetry/wrap.test.js +4 -0
- package/dist/src/telemetry/wrap.test.js.map +1 -1
- package/dist/tests/SecurityScanner.exec.test.js +60 -0
- package/dist/tests/SecurityScanner.exec.test.js.map +1 -1
- package/dist/tests/security/ContinuousSecurity.test.js +10 -4
- package/dist/tests/security/ContinuousSecurity.test.js.map +1 -1
- package/dist/tests/security/SecurityScanner.chmod-evasion.test.d.ts +2 -0
- package/dist/tests/security/SecurityScanner.chmod-evasion.test.d.ts.map +1 -0
- package/dist/tests/security/SecurityScanner.chmod-evasion.test.js +126 -0
- package/dist/tests/security/SecurityScanner.chmod-evasion.test.js.map +1 -0
- package/dist/tests/security/chmod-compound.test.d.ts +2 -0
- package/dist/tests/security/chmod-compound.test.d.ts.map +1 -0
- package/dist/tests/security/chmod-compound.test.js +188 -0
- package/dist/tests/security/chmod-compound.test.js.map +1 -0
- package/dist/tests/security/pii-detection.test.js +24 -0
- package/dist/tests/security/pii-detection.test.js.map +1 -1
- package/dist/tests/security/scanner-regression-guard.test.d.ts +23 -7
- package/dist/tests/security/scanner-regression-guard.test.d.ts.map +1 -1
- package/dist/tests/security/scanner-regression-guard.test.js +27 -11
- package/dist/tests/security/scanner-regression-guard.test.js.map +1 -1
- package/dist/tests/security/sensitive-path-fp.test.d.ts +2 -0
- package/dist/tests/security/sensitive-path-fp.test.d.ts.map +1 -0
- package/dist/tests/security/sensitive-path-fp.test.js +142 -0
- package/dist/tests/security/sensitive-path-fp.test.js.map +1 -0
- package/dist/tests/services/bundled-sibling-scan.test.d.ts +2 -0
- package/dist/tests/services/bundled-sibling-scan.test.d.ts.map +1 -0
- package/dist/tests/services/bundled-sibling-scan.test.js +139 -0
- package/dist/tests/services/bundled-sibling-scan.test.js.map +1 -0
- package/dist/tests/unit/services/skill-installation.io.test.js +166 -0
- package/dist/tests/unit/services/skill-installation.io.test.js.map +1 -1
- package/dist/tests/unit/services/skill-installation.policy.test.d.ts +8 -0
- package/dist/tests/unit/services/skill-installation.policy.test.d.ts.map +1 -0
- package/dist/tests/unit/services/skill-installation.policy.test.js +151 -0
- package/dist/tests/unit/services/skill-installation.policy.test.js.map +1 -0
- package/package.json +23 -3
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON config-merge helper for `sklx agent install` (SMI-5456 Wave 1 Step 5).
|
|
3
|
+
*
|
|
4
|
+
* Targets the `mcpServers`/`mcp`-shaped config files four of the seven
|
|
5
|
+
* harnesses use (claude-code, cursor, copilot, windsurf; also opencode's
|
|
6
|
+
* best-effort `mcp` key — see `agent-harness-targets.ts`). Backup-first,
|
|
7
|
+
* idempotent, preserve-and-prompt semantics per the plan's P-5 "Harness MCP
|
|
8
|
+
* config files" invariant: "Installer merges keys it owns, never whole-file
|
|
9
|
+
* writes; detects foreign `skillsmith` entries and prompts."
|
|
10
|
+
*
|
|
11
|
+
* Survey of existing config-write patterns (plan-mandated, "convention check
|
|
12
|
+
* before novelty"): `grep -rn "mcp.json\|mcp_config\|claude.json"
|
|
13
|
+
* packages/cli/src/ packages/core/src/` turned up
|
|
14
|
+
* `mcp-server.template.snippets.ts` (SMI-4580, docs-only — renders snippet
|
|
15
|
+
* TEXT for a human to paste, never writes a file) and
|
|
16
|
+
* `skill-installation.policy.ts` (classifies `.mcp.json` as a scan target,
|
|
17
|
+
* never writes one). Neither is a merge-writer to follow structurally, so
|
|
18
|
+
* this module's shape instead mirrors the nearest write-path precedent in
|
|
19
|
+
* the codebase: `config/index.ts`'s `saveConfig()` (mkdir 0700 + writeFile
|
|
20
|
+
* 0600, read-merge-write, JSON.stringify(..., null, 2)).
|
|
21
|
+
*
|
|
22
|
+
* @module @skillsmith/core/install/agent-config-merge.json
|
|
23
|
+
*/
|
|
24
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
25
|
+
import { dirname, join } from 'node:path';
|
|
26
|
+
import { deepEqualJson, looksLikeOurMcpEntry, markBackedUp, shouldBackup, } from './agent-config-merge.types.js';
|
|
27
|
+
/** Read + set a nested key path on a JSON object, creating intermediate objects. */
|
|
28
|
+
function getAtPath(root, keyPath) {
|
|
29
|
+
let cur = root;
|
|
30
|
+
for (const key of keyPath) {
|
|
31
|
+
if (!cur || typeof cur !== 'object' || Array.isArray(cur))
|
|
32
|
+
return undefined;
|
|
33
|
+
cur = cur[key];
|
|
34
|
+
}
|
|
35
|
+
return cur;
|
|
36
|
+
}
|
|
37
|
+
function setAtPath(root, keyPath, value) {
|
|
38
|
+
let cur = root;
|
|
39
|
+
for (let i = 0; i < keyPath.length - 1; i++) {
|
|
40
|
+
const key = keyPath[i];
|
|
41
|
+
const next = cur[key];
|
|
42
|
+
if (!next || typeof next !== 'object' || Array.isArray(next)) {
|
|
43
|
+
cur[key] = {};
|
|
44
|
+
}
|
|
45
|
+
cur = cur[key];
|
|
46
|
+
}
|
|
47
|
+
const lastKey = keyPath[keyPath.length - 1];
|
|
48
|
+
if (lastKey !== undefined)
|
|
49
|
+
cur[lastKey] = value;
|
|
50
|
+
}
|
|
51
|
+
function writeBackup(sourcePath, backupDir) {
|
|
52
|
+
mkdirSync(backupDir, { recursive: true, mode: 0o700 });
|
|
53
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
54
|
+
const baseName = sourcePath.split('/').pop() ?? 'config';
|
|
55
|
+
const backupPath = join(backupDir, `${stamp}-${baseName}.bak`);
|
|
56
|
+
writeFileSync(backupPath, readFileSync(sourcePath, 'utf-8'), { mode: 0o600 });
|
|
57
|
+
return backupPath;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Merge a `skillsmith` MCP server entry into a JSON config file at
|
|
61
|
+
* `opts.path`, under `opts.keyPath` (e.g. `['mcpServers']`).
|
|
62
|
+
*
|
|
63
|
+
* Never performs a whole-file write of unrelated keys — the file is parsed,
|
|
64
|
+
* exactly one nested key (`skillsmith`) is set at `[...keyPath, 'skillsmith']`,
|
|
65
|
+
* and the WHOLE document (all other keys untouched) is re-serialized. A
|
|
66
|
+
* missing file is treated as `{}` (created fresh).
|
|
67
|
+
*/
|
|
68
|
+
export function mergeJsonMcpEntry(opts) {
|
|
69
|
+
const { path, keyPath, entryValue, backupDir, force = false, alreadyBackedUpPaths } = opts;
|
|
70
|
+
let doc = {};
|
|
71
|
+
let existed = false;
|
|
72
|
+
if (existsSync(path)) {
|
|
73
|
+
existed = true;
|
|
74
|
+
let raw;
|
|
75
|
+
try {
|
|
76
|
+
raw = readFileSync(path, 'utf-8');
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
return { status: 'error', path, backupPath: null, errorMessage: e.message };
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const parsed = raw.trim().length === 0 ? {} : JSON.parse(raw);
|
|
83
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
84
|
+
return { status: 'error', path, backupPath: null, errorMessage: 'not a JSON object' };
|
|
85
|
+
}
|
|
86
|
+
doc = parsed;
|
|
87
|
+
}
|
|
88
|
+
catch (e) {
|
|
89
|
+
return { status: 'error', path, backupPath: null, errorMessage: e.message };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const container = getAtPath(doc, keyPath);
|
|
93
|
+
const existingEntry = container && typeof container === 'object' && !Array.isArray(container)
|
|
94
|
+
? container.skillsmith
|
|
95
|
+
: undefined;
|
|
96
|
+
if (existingEntry !== undefined) {
|
|
97
|
+
if (deepEqualJson(existingEntry, entryValue)) {
|
|
98
|
+
return { status: 'unchanged', path, backupPath: null };
|
|
99
|
+
}
|
|
100
|
+
if (!looksLikeOurMcpEntry(existingEntry) && !force) {
|
|
101
|
+
// Non-interactive refusal (P-5 preserve-existing): never clobber a
|
|
102
|
+
// foreign entry silently, never block on a prompt in a scripted
|
|
103
|
+
// install — surface a 'conflict' the CLI report + --force flag address.
|
|
104
|
+
return { status: 'conflict', path, backupPath: null };
|
|
105
|
+
}
|
|
106
|
+
// Recognizable as ours (or force=true): back up (once per install run),
|
|
107
|
+
// then overwrite. Marking `alreadyBackedUpPaths` UNCONDITIONALLY (not
|
|
108
|
+
// only when a backup was actually written) is what prevents a LATER
|
|
109
|
+
// merge into this same path this run from treating content THIS merge
|
|
110
|
+
// just wrote as pre-install state needing its own backup — see
|
|
111
|
+
// `agent-pack-installer.test.ts` "double-install idempotency" /
|
|
112
|
+
// "preserve-existing" suites.
|
|
113
|
+
const backupPath = existed && shouldBackup(path, alreadyBackedUpPaths) ? writeBackup(path, backupDir) : null;
|
|
114
|
+
markBackedUp(path, alreadyBackedUpPaths);
|
|
115
|
+
setAtPath(doc, keyPath, { ...container, skillsmith: entryValue });
|
|
116
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
117
|
+
writeFileSync(path, JSON.stringify(doc, null, 2) + '\n', { mode: 0o600 });
|
|
118
|
+
return { status: 'updated', path, backupPath };
|
|
119
|
+
}
|
|
120
|
+
// No existing entry — safe create. Still back up the FILE (not the
|
|
121
|
+
// skillsmith key, which didn't exist) so a foreign file with unrelated
|
|
122
|
+
// content the user cares about is always one restore away — unless an
|
|
123
|
+
// earlier merge THIS RUN already captured the pre-install state.
|
|
124
|
+
const backupPath = existed && shouldBackup(path, alreadyBackedUpPaths) ? writeBackup(path, backupDir) : null;
|
|
125
|
+
markBackedUp(path, alreadyBackedUpPaths);
|
|
126
|
+
const currentContainer = container && typeof container === 'object' && !Array.isArray(container)
|
|
127
|
+
? container
|
|
128
|
+
: {};
|
|
129
|
+
setAtPath(doc, keyPath, { ...currentContainer, skillsmith: entryValue });
|
|
130
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
131
|
+
writeFileSync(path, JSON.stringify(doc, null, 2) + '\n', { mode: 0o600 });
|
|
132
|
+
return { status: 'created', path, backupPath };
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=agent-config-merge.json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-config-merge.json.js","sourceRoot":"","sources":["../../../src/install/agent-config-merge.json.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marker-delimited TOML block merge for Codex (SMI-5456 Wave 1 Step 5).
|
|
3
|
+
*
|
|
4
|
+
* Codex's config lives entirely in `~/.codex/config.toml` — MCP server
|
|
5
|
+
* registration (`[mcp_servers.skillsmith]`), the named-agent shim
|
|
6
|
+
* (`[agents.skillsmith-agent]`, already rendered as TOML text by
|
|
7
|
+
* `renderCodexToml` in the — locked for this step — agent-pack generator),
|
|
8
|
+
* and hook wiring all share this one file. The repo has no confirmed real
|
|
9
|
+
* TOML dependency (`smol-toml` appears only under root `overrides`, pinning
|
|
10
|
+
* a transitive tool dependency, not a direct `dependencies`/`devDependencies`
|
|
11
|
+
* entry of `@skillsmith/core` or `@skillsmith/cli`) — adding one is a
|
|
12
|
+
* lockfile-risk change out of scope for this step. Per the task brief's
|
|
13
|
+
* explicit fallback for "no [format] dep": generate the block textually with
|
|
14
|
+
* a clearly-delimited managed section, exactly mirroring how
|
|
15
|
+
* `renderCodexToml` (shims.ts, locked) already emits raw TOML text via
|
|
16
|
+
* `JSON.stringify`-quoted strings rather than a library. This keeps every
|
|
17
|
+
* OTHER line of the user's `config.toml` — their own tables, comments,
|
|
18
|
+
* formatting — completely untouched; only the text between our markers is
|
|
19
|
+
* ever rewritten.
|
|
20
|
+
*
|
|
21
|
+
* Ownership model: text between `# >>> skillsmith:<markerId> >>>` and
|
|
22
|
+
* `# <<< skillsmith:<markerId> <<<` is unambiguously ours (idempotent
|
|
23
|
+
* update-in-place). A bare occurrence of the target table header OUTSIDE any
|
|
24
|
+
* marker block (`foreignHeaderPattern`) is a hand-written foreign entry —
|
|
25
|
+
* `force` does NOT override this case: appending a second `[table]` header
|
|
26
|
+
* with the same name risks producing invalid/ambiguous TOML without a real
|
|
27
|
+
* parser to detect and merge it, so this always returns `'conflict'` for a
|
|
28
|
+
* genuinely foreign, non-delimited entry regardless of `force`.
|
|
29
|
+
*
|
|
30
|
+
* @module @skillsmith/core/install/agent-config-merge.toml-block
|
|
31
|
+
*/
|
|
32
|
+
import { type MergeResult } from './agent-config-merge.types.js';
|
|
33
|
+
export interface TomlBlockMergeOptions {
|
|
34
|
+
path: string;
|
|
35
|
+
/** Stable id for the marker comment, e.g. `'mcp_servers.skillsmith'`. */
|
|
36
|
+
markerId: string;
|
|
37
|
+
/** Raw TOML text to install between the markers (no marker lines, no leading/trailing blank lines required). */
|
|
38
|
+
blockContent: string;
|
|
39
|
+
/** Matches a bare (non-delimited) occurrence of the same table header, e.g. `/^\[mcp_servers\.skillsmith\]/m`. */
|
|
40
|
+
foreignHeaderPattern: RegExp;
|
|
41
|
+
backupDir: string;
|
|
42
|
+
force?: boolean;
|
|
43
|
+
/** See `MergeOptions.alreadyBackedUpPaths` — Codex merges TWO blocks (MCP + agents shim) into the same `config.toml` per install run. */
|
|
44
|
+
alreadyBackedUpPaths?: Set<string>;
|
|
45
|
+
}
|
|
46
|
+
/** Merge a marker-delimited TOML block into `opts.path`. See module header for the full ownership contract. */
|
|
47
|
+
export declare function mergeTomlBlock(opts: TomlBlockMergeOptions): MergeResult;
|
|
48
|
+
//# sourceMappingURL=agent-config-merge.toml-block.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-config-merge.toml-block.d.ts","sourceRoot":"","sources":["../../../src/install/agent-config-merge.toml-block.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAKH,OAAO,EAA8B,KAAK,WAAW,EAAE,MAAM,+BAA+B,CAAA;AAE5F,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAA;IACZ,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAA;IAChB,gHAAgH;IAChH,YAAY,EAAE,MAAM,CAAA;IACpB,kHAAkH;IAClH,oBAAoB,EAAE,MAAM,CAAA;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,yIAAyI;IACzI,oBAAoB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CACnC;AAkBD,+GAA+G;AAC/G,wBAAgB,cAAc,CAAC,IAAI,EAAE,qBAAqB,GAAG,WAAW,CAmEvE"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marker-delimited TOML block merge for Codex (SMI-5456 Wave 1 Step 5).
|
|
3
|
+
*
|
|
4
|
+
* Codex's config lives entirely in `~/.codex/config.toml` — MCP server
|
|
5
|
+
* registration (`[mcp_servers.skillsmith]`), the named-agent shim
|
|
6
|
+
* (`[agents.skillsmith-agent]`, already rendered as TOML text by
|
|
7
|
+
* `renderCodexToml` in the — locked for this step — agent-pack generator),
|
|
8
|
+
* and hook wiring all share this one file. The repo has no confirmed real
|
|
9
|
+
* TOML dependency (`smol-toml` appears only under root `overrides`, pinning
|
|
10
|
+
* a transitive tool dependency, not a direct `dependencies`/`devDependencies`
|
|
11
|
+
* entry of `@skillsmith/core` or `@skillsmith/cli`) — adding one is a
|
|
12
|
+
* lockfile-risk change out of scope for this step. Per the task brief's
|
|
13
|
+
* explicit fallback for "no [format] dep": generate the block textually with
|
|
14
|
+
* a clearly-delimited managed section, exactly mirroring how
|
|
15
|
+
* `renderCodexToml` (shims.ts, locked) already emits raw TOML text via
|
|
16
|
+
* `JSON.stringify`-quoted strings rather than a library. This keeps every
|
|
17
|
+
* OTHER line of the user's `config.toml` — their own tables, comments,
|
|
18
|
+
* formatting — completely untouched; only the text between our markers is
|
|
19
|
+
* ever rewritten.
|
|
20
|
+
*
|
|
21
|
+
* Ownership model: text between `# >>> skillsmith:<markerId> >>>` and
|
|
22
|
+
* `# <<< skillsmith:<markerId> <<<` is unambiguously ours (idempotent
|
|
23
|
+
* update-in-place). A bare occurrence of the target table header OUTSIDE any
|
|
24
|
+
* marker block (`foreignHeaderPattern`) is a hand-written foreign entry —
|
|
25
|
+
* `force` does NOT override this case: appending a second `[table]` header
|
|
26
|
+
* with the same name risks producing invalid/ambiguous TOML without a real
|
|
27
|
+
* parser to detect and merge it, so this always returns `'conflict'` for a
|
|
28
|
+
* genuinely foreign, non-delimited entry regardless of `force`.
|
|
29
|
+
*
|
|
30
|
+
* @module @skillsmith/core/install/agent-config-merge.toml-block
|
|
31
|
+
*/
|
|
32
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
33
|
+
import { dirname, join } from 'node:path';
|
|
34
|
+
import { markBackedUp, shouldBackup } from './agent-config-merge.types.js';
|
|
35
|
+
function markerStart(markerId) {
|
|
36
|
+
return `# >>> skillsmith:${markerId} >>>`;
|
|
37
|
+
}
|
|
38
|
+
function markerEnd(markerId) {
|
|
39
|
+
return `# <<< skillsmith:${markerId} <<<`;
|
|
40
|
+
}
|
|
41
|
+
function writeBackup(sourcePath, backupDir) {
|
|
42
|
+
mkdirSync(backupDir, { recursive: true, mode: 0o700 });
|
|
43
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
44
|
+
const baseName = sourcePath.split('/').pop() ?? 'config';
|
|
45
|
+
const backupPath = join(backupDir, `${stamp}-${baseName}.bak`);
|
|
46
|
+
writeFileSync(backupPath, readFileSync(sourcePath, 'utf-8'), { mode: 0o600 });
|
|
47
|
+
return backupPath;
|
|
48
|
+
}
|
|
49
|
+
/** Merge a marker-delimited TOML block into `opts.path`. See module header for the full ownership contract. */
|
|
50
|
+
export function mergeTomlBlock(opts) {
|
|
51
|
+
const { path, markerId, blockContent, foreignHeaderPattern, backupDir, force = false, alreadyBackedUpPaths, } = opts;
|
|
52
|
+
const start = markerStart(markerId);
|
|
53
|
+
const end = markerEnd(markerId);
|
|
54
|
+
const trimmedBlock = blockContent.trim();
|
|
55
|
+
const existed = existsSync(path);
|
|
56
|
+
let raw = '';
|
|
57
|
+
if (existed) {
|
|
58
|
+
try {
|
|
59
|
+
raw = readFileSync(path, 'utf-8');
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
return { status: 'error', path, backupPath: null, errorMessage: e.message };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const blockRegex = new RegExp(`${escapeRegExp(start)}\\n([\\s\\S]*?)\\n${escapeRegExp(end)}`);
|
|
66
|
+
const match = raw.match(blockRegex);
|
|
67
|
+
if (match) {
|
|
68
|
+
const existingBlock = (match[1] ?? '').trim();
|
|
69
|
+
if (existingBlock === trimmedBlock) {
|
|
70
|
+
return { status: 'unchanged', path, backupPath: null };
|
|
71
|
+
}
|
|
72
|
+
const backupPath = shouldBackup(path, alreadyBackedUpPaths)
|
|
73
|
+
? writeBackup(path, backupDir)
|
|
74
|
+
: null;
|
|
75
|
+
markBackedUp(path, alreadyBackedUpPaths);
|
|
76
|
+
const replacement = `${start}\n${trimmedBlock}\n${end}`;
|
|
77
|
+
const updated = raw.replace(blockRegex, replacement);
|
|
78
|
+
writeFileSync(path, updated, { mode: 0o600 });
|
|
79
|
+
return { status: 'updated', path, backupPath };
|
|
80
|
+
}
|
|
81
|
+
// No marker block found. A bare same-name table header outside any marker
|
|
82
|
+
// is a foreign hand-written entry — never safely mergeable without a real
|
|
83
|
+
// TOML parser (see module header). `force` does not override this case.
|
|
84
|
+
if (foreignHeaderPattern.test(raw)) {
|
|
85
|
+
void force;
|
|
86
|
+
return { status: 'conflict', path, backupPath: null };
|
|
87
|
+
}
|
|
88
|
+
const backupPath = existed && shouldBackup(path, alreadyBackedUpPaths) ? writeBackup(path, backupDir) : null;
|
|
89
|
+
markBackedUp(path, alreadyBackedUpPaths);
|
|
90
|
+
const separator = raw.length > 0 && !raw.endsWith('\n\n') ? (raw.endsWith('\n') ? '\n' : '\n\n') : '';
|
|
91
|
+
const appended = `${raw}${separator}${start}\n${trimmedBlock}\n${end}\n`;
|
|
92
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
93
|
+
writeFileSync(path, appended, { mode: 0o600 });
|
|
94
|
+
// Always 'created', never `existed ? 'updated' : 'created'`: OUR block
|
|
95
|
+
// didn't exist before this write regardless of whether the surrounding
|
|
96
|
+
// FILE pre-existed (a foreign file with unrelated content, or — within
|
|
97
|
+
// one install run — a file another block already created moments ago,
|
|
98
|
+
// e.g. Codex's agents-shim block creating config.toml before the
|
|
99
|
+
// mcp_servers block merges into it). 'updated' is reserved for replacing
|
|
100
|
+
// OUR OWN previously-installed block with different content (the `match`
|
|
101
|
+
// branch above).
|
|
102
|
+
return { status: 'created', path, backupPath };
|
|
103
|
+
}
|
|
104
|
+
function escapeRegExp(s) {
|
|
105
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=agent-config-merge.toml-block.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-config-merge.toml-block.js","sourceRoot":"","sources":["../../../src/install/agent-config-merge.toml-block.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared result/option types for the agent-install config-merge helpers
|
|
3
|
+
* (SMI-5456 Wave 1 Step 5). One shape reused by the JSON, YAML, and
|
|
4
|
+
* TOML-block merge implementations so the installer orchestrator branches on
|
|
5
|
+
* a single result contract regardless of which harness's config format it
|
|
6
|
+
* just merged into.
|
|
7
|
+
*
|
|
8
|
+
* @module @skillsmith/core/install/agent-config-merge.types
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Outcome of a single merge attempt.
|
|
12
|
+
*
|
|
13
|
+
* created - the file (or the whole key path) did not exist; created fresh.
|
|
14
|
+
* updated - an entry recognizable as OURS existed with different content
|
|
15
|
+
* (e.g. a version bump); backed up the file, then overwrote.
|
|
16
|
+
* unchanged - an entry recognizable as OURS already matched exactly —
|
|
17
|
+
* idempotent no-op, no backup written (P-5 double-install test).
|
|
18
|
+
* conflict - an entry existed that does NOT look like ours; left
|
|
19
|
+
* untouched unless `force` was passed.
|
|
20
|
+
* error - the file could not be read/parsed/written; left untouched.
|
|
21
|
+
*/
|
|
22
|
+
export type MergeStatus = 'created' | 'updated' | 'unchanged' | 'conflict' | 'error';
|
|
23
|
+
export interface MergeResult {
|
|
24
|
+
status: MergeStatus;
|
|
25
|
+
path: string;
|
|
26
|
+
/** Absolute path to the pre-write backup, or null when no backup was needed. */
|
|
27
|
+
backupPath: string | null;
|
|
28
|
+
/** Present only when status is 'error'. */
|
|
29
|
+
errorMessage?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface MergeOptions {
|
|
32
|
+
path: string;
|
|
33
|
+
/** The value to install under the `skillsmith` key. */
|
|
34
|
+
entryValue: Record<string, unknown>;
|
|
35
|
+
/** Directory backups are written into. Caller ensures it exists. */
|
|
36
|
+
backupDir: string;
|
|
37
|
+
/**
|
|
38
|
+
* When an existing entry does not look like ours, overwrite it anyway.
|
|
39
|
+
* Defaults to false — the non-interactive refusal path (P-5 preserve-
|
|
40
|
+
* existing test): no prompt, no silent clobber, just a `'conflict'` result
|
|
41
|
+
* the caller surfaces in the per-harness report.
|
|
42
|
+
*/
|
|
43
|
+
force?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Paths already backed up during THIS `installAgentPack` run (shared
|
|
46
|
+
* across every merge call in the run — `HarnessInstallCtx.backedUpPaths`).
|
|
47
|
+
* Some harnesses (claude-code) merge into the SAME file more than once per
|
|
48
|
+
* run (SessionStart hook, SessionEnd hook, then MCP registration, all in
|
|
49
|
+
* `~/.claude/settings.json`) — without this, the second and third merges
|
|
50
|
+
* would each "back up" a state that WE ourselves just wrote earlier in the
|
|
51
|
+
* same run, producing redundant, meaningless backups. A path is only ever
|
|
52
|
+
* backed up once per run, capturing genuine pre-install content.
|
|
53
|
+
*/
|
|
54
|
+
alreadyBackedUpPaths?: Set<string>;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Decide whether a merge should take a backup of `path` right now: only if
|
|
58
|
+
* it hasn't already been backed up earlier in this same install run. Callers
|
|
59
|
+
* that DO write a backup must call {@link markBackedUp} immediately after.
|
|
60
|
+
*/
|
|
61
|
+
export declare function shouldBackup(path: string, alreadyBackedUpPaths: Set<string> | undefined): boolean;
|
|
62
|
+
/** Record that `path` has now been backed up for the remainder of this install run. */
|
|
63
|
+
export declare function markBackedUp(path: string, alreadyBackedUpPaths: Set<string> | undefined): void;
|
|
64
|
+
/**
|
|
65
|
+
* Heuristic ownership check shared by every format-specific merge helper:
|
|
66
|
+
* does an existing entry look like a Skillsmith MCP server registration we
|
|
67
|
+
* (a prior `sklx agent install` run) wrote, as opposed to a user's own
|
|
68
|
+
* hand-written `skillsmith`-named entry pointing somewhere else entirely?
|
|
69
|
+
*
|
|
70
|
+
* There is no injected marker field in the entry value itself (that would
|
|
71
|
+
* pollute a config another tool's strict-schema client might read) — instead
|
|
72
|
+
* this checks for the structural fingerprint our own entries always carry,
|
|
73
|
+
* across BOTH entry conventions the installer writes:
|
|
74
|
+
* - mcpServers convention (claude-code/cursor/copilot/windsurf/hermes):
|
|
75
|
+
* an `args` array mentioning `@skillsmith/mcp-server`, or an
|
|
76
|
+
* `env.SKILLSMITH_TOOL_PROFILE` key.
|
|
77
|
+
* - OpenCode convention (`buildOpenCodeMcpEntryValue`): a `command` ARRAY
|
|
78
|
+
* mentioning `@skillsmith/mcp-server`, or an
|
|
79
|
+
* `environment.SKILLSMITH_TOOL_PROFILE` key. Without these two checks, a
|
|
80
|
+
* re-install after any entry-value change would misclassify our own
|
|
81
|
+
* prior OpenCode entry as foreign and refuse to update it.
|
|
82
|
+
* Any one is a strong, low-false-positive signal.
|
|
83
|
+
*/
|
|
84
|
+
export declare function looksLikeOurMcpEntry(value: unknown): boolean;
|
|
85
|
+
/** Deep structural equality for plain JSON-shaped values (objects/arrays/primitives). */
|
|
86
|
+
export declare function deepEqualJson(a: unknown, b: unknown): boolean;
|
|
87
|
+
//# sourceMappingURL=agent-config-merge.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-config-merge.types.d.ts","sourceRoot":"","sources":["../../../src/install/agent-config-merge.types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,OAAO,CAAA;AAEpF,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,WAAW,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,gFAAgF;IAChF,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,2CAA2C;IAC3C,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACnC,oEAAoE;IACpE,SAAS,EAAE,MAAM,CAAA;IACjB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;;;;;;;OASG;IACH,oBAAoB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CACnC;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,oBAAoB,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS,GAAG,OAAO,CAEjG;AAED,uFAAuF;AACvF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,oBAAoB,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS,GAAG,IAAI,CAE9F;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAa5D;AAED,yFAAyF;AACzF,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CAkB7D"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared result/option types for the agent-install config-merge helpers
|
|
3
|
+
* (SMI-5456 Wave 1 Step 5). One shape reused by the JSON, YAML, and
|
|
4
|
+
* TOML-block merge implementations so the installer orchestrator branches on
|
|
5
|
+
* a single result contract regardless of which harness's config format it
|
|
6
|
+
* just merged into.
|
|
7
|
+
*
|
|
8
|
+
* @module @skillsmith/core/install/agent-config-merge.types
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Decide whether a merge should take a backup of `path` right now: only if
|
|
12
|
+
* it hasn't already been backed up earlier in this same install run. Callers
|
|
13
|
+
* that DO write a backup must call {@link markBackedUp} immediately after.
|
|
14
|
+
*/
|
|
15
|
+
export function shouldBackup(path, alreadyBackedUpPaths) {
|
|
16
|
+
return !alreadyBackedUpPaths?.has(path);
|
|
17
|
+
}
|
|
18
|
+
/** Record that `path` has now been backed up for the remainder of this install run. */
|
|
19
|
+
export function markBackedUp(path, alreadyBackedUpPaths) {
|
|
20
|
+
alreadyBackedUpPaths?.add(path);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Heuristic ownership check shared by every format-specific merge helper:
|
|
24
|
+
* does an existing entry look like a Skillsmith MCP server registration we
|
|
25
|
+
* (a prior `sklx agent install` run) wrote, as opposed to a user's own
|
|
26
|
+
* hand-written `skillsmith`-named entry pointing somewhere else entirely?
|
|
27
|
+
*
|
|
28
|
+
* There is no injected marker field in the entry value itself (that would
|
|
29
|
+
* pollute a config another tool's strict-schema client might read) — instead
|
|
30
|
+
* this checks for the structural fingerprint our own entries always carry,
|
|
31
|
+
* across BOTH entry conventions the installer writes:
|
|
32
|
+
* - mcpServers convention (claude-code/cursor/copilot/windsurf/hermes):
|
|
33
|
+
* an `args` array mentioning `@skillsmith/mcp-server`, or an
|
|
34
|
+
* `env.SKILLSMITH_TOOL_PROFILE` key.
|
|
35
|
+
* - OpenCode convention (`buildOpenCodeMcpEntryValue`): a `command` ARRAY
|
|
36
|
+
* mentioning `@skillsmith/mcp-server`, or an
|
|
37
|
+
* `environment.SKILLSMITH_TOOL_PROFILE` key. Without these two checks, a
|
|
38
|
+
* re-install after any entry-value change would misclassify our own
|
|
39
|
+
* prior OpenCode entry as foreign and refuse to update it.
|
|
40
|
+
* Any one is a strong, low-false-positive signal.
|
|
41
|
+
*/
|
|
42
|
+
export function looksLikeOurMcpEntry(value) {
|
|
43
|
+
if (!value || typeof value !== 'object')
|
|
44
|
+
return false;
|
|
45
|
+
const v = value;
|
|
46
|
+
const mentionsOurPackage = (arr) => Array.isArray(arr) &&
|
|
47
|
+
arr.some((a) => typeof a === 'string' && a.includes('@skillsmith/mcp-server'));
|
|
48
|
+
if (mentionsOurPackage(v.args))
|
|
49
|
+
return true;
|
|
50
|
+
if (mentionsOurPackage(v.command))
|
|
51
|
+
return true;
|
|
52
|
+
const hasProfileKey = (obj) => !!obj && typeof obj === 'object' && 'SKILLSMITH_TOOL_PROFILE' in obj;
|
|
53
|
+
if (hasProfileKey(v.env))
|
|
54
|
+
return true;
|
|
55
|
+
if (hasProfileKey(v.environment))
|
|
56
|
+
return true;
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
/** Deep structural equality for plain JSON-shaped values (objects/arrays/primitives). */
|
|
60
|
+
export function deepEqualJson(a, b) {
|
|
61
|
+
if (a === b)
|
|
62
|
+
return true;
|
|
63
|
+
if (typeof a !== typeof b)
|
|
64
|
+
return false;
|
|
65
|
+
if (a === null || b === null)
|
|
66
|
+
return a === b;
|
|
67
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
68
|
+
if (!Array.isArray(a) || !Array.isArray(b))
|
|
69
|
+
return false;
|
|
70
|
+
if (a.length !== b.length)
|
|
71
|
+
return false;
|
|
72
|
+
return a.every((v, i) => deepEqualJson(v, b[i]));
|
|
73
|
+
}
|
|
74
|
+
if (typeof a === 'object' && typeof b === 'object') {
|
|
75
|
+
const aKeys = Object.keys(a).sort();
|
|
76
|
+
const bKeys = Object.keys(b).sort();
|
|
77
|
+
if (aKeys.length !== bKeys.length || aKeys.some((k, i) => k !== bKeys[i]))
|
|
78
|
+
return false;
|
|
79
|
+
return aKeys.every((k) => deepEqualJson(a[k], b[k]));
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=agent-config-merge.types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-config-merge.types.js","sourceRoot":"","sources":["../../../src/install/agent-config-merge.types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YAML config-merge helper for Hermes (SMI-5456 Wave 1 Step 5).
|
|
3
|
+
*
|
|
4
|
+
* Hermes is the one target harness whose MCP config is YAML
|
|
5
|
+
* (`~/.hermes/config.yaml`, key `mcp_servers:` — spike report §(b),
|
|
6
|
+
* well-verified against three independent official doc pages). The repo
|
|
7
|
+
* already carries a real `yaml` dependency (`packages/core/package.json` /
|
|
8
|
+
* root `package.json`, `"yaml": "^2.8.3"` — also already used by
|
|
9
|
+
* `packages/core/src/services/agent-pack/agent-pack.test.ts`), so this uses
|
|
10
|
+
* the library's structured Document API (`parseDocument`/`Document#setIn`)
|
|
11
|
+
* rather than the textual delimited-block scheme the task brief allowed as a
|
|
12
|
+
* fallback for "no YAML dep" — that fallback does not apply here since the
|
|
13
|
+
* dependency exists.
|
|
14
|
+
*
|
|
15
|
+
* GOVERNANCE FIX (SMI-5456 code review, 2026-07-01): this module originally
|
|
16
|
+
* used the library's plain `parse`/`stringify` functions. Those DISCARD
|
|
17
|
+
* every comment in the source file on round-trip (verified: `# top comment`
|
|
18
|
+
* + `# a server` + a trailing inline `# inline comment` all vanish after a
|
|
19
|
+
* `parse` → mutate → `stringify` cycle) — a straightforward
|
|
20
|
+
* preserve-user-content violation for anyone who annotates their own
|
|
21
|
+
* `~/.hermes/config.yaml`. `yaml`'s `Document`/`parseDocument`/`setIn` API
|
|
22
|
+
* operates on the CST and round-trips comments, anchors, and key ordering
|
|
23
|
+
* untouched, only rewriting the one path this module actually sets — see the
|
|
24
|
+
* in-container verification in the SMI-5456 code review report. One
|
|
25
|
+
* documented gap remains: a source file whose root is YAML-null (`~`/`null`,
|
|
26
|
+
* or a fully empty document) with a comment attached to that null node loses
|
|
27
|
+
* that specific comment when the null root is replaced with a fresh mapping
|
|
28
|
+
* (there is no collection node for `setIn` to attach into) — an edge case for
|
|
29
|
+
* a config file that carries no actual config, accepted rather than adding a
|
|
30
|
+
* bespoke CST-splicing path for it.
|
|
31
|
+
*
|
|
32
|
+
* @module @skillsmith/core/install/agent-config-merge.yaml
|
|
33
|
+
*/
|
|
34
|
+
import { type MergeOptions, type MergeResult } from './agent-config-merge.types.js';
|
|
35
|
+
/**
|
|
36
|
+
* Merge a `skillsmith` entry into `mcp_servers:` of a YAML config file.
|
|
37
|
+
* Same created/updated/unchanged/conflict/error contract as
|
|
38
|
+
* {@link import('./agent-config-merge.json.js').mergeJsonMcpEntry}. Uses the
|
|
39
|
+
* `yaml` package's `Document` (CST-backed) API so every comment, anchor, and
|
|
40
|
+
* unrelated key in the user's file survives the merge byte-for-byte except
|
|
41
|
+
* for the one path this function actually sets — see module header.
|
|
42
|
+
*/
|
|
43
|
+
export declare function mergeYamlMcpEntry(opts: MergeOptions & {
|
|
44
|
+
mcpServersKey: string;
|
|
45
|
+
}): MergeResult;
|
|
46
|
+
//# sourceMappingURL=agent-config-merge.yaml.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-config-merge.yaml.d.ts","sourceRoot":"","sources":["../../../src/install/agent-config-merge.yaml.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAOH,OAAO,EAKL,KAAK,YAAY,EACjB,KAAK,WAAW,EACjB,MAAM,+BAA+B,CAAA;AAwBtC;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG;IAAE,aAAa,EAAE,MAAM,CAAA;CAAE,GAAG,WAAW,CAqE7F"}
|