@skillsmith/core 0.12.1 → 0.12.2
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 +155 -0
- package/README.md +2 -2
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/api/client.d.ts +5 -3
- package/dist/src/api/client.d.ts.map +1 -1
- package/dist/src/api/client.js +23 -33
- package/dist/src/api/client.js.map +1 -1
- package/dist/src/api/client.registry-sync.d.ts +28 -0
- package/dist/src/api/client.registry-sync.d.ts.map +1 -0
- package/dist/src/api/client.registry-sync.js +36 -0
- package/dist/src/api/client.registry-sync.js.map +1 -0
- package/dist/src/api/client.test.js +185 -0
- package/dist/src/api/client.test.js.map +1 -1
- package/dist/src/api/client.toSkill.d.ts +16 -0
- package/dist/src/api/client.toSkill.d.ts.map +1 -0
- package/dist/src/api/client.toSkill.js +46 -0
- package/dist/src/api/client.toSkill.js.map +1 -0
- package/dist/src/api/client.types.d.ts +22 -0
- package/dist/src/api/client.types.d.ts.map +1 -1
- package/dist/src/api/index.d.ts +1 -1
- package/dist/src/api/index.d.ts.map +1 -1
- package/dist/src/api/index.js.map +1 -1
- package/dist/src/api/schemas.d.ts +28 -0
- package/dist/src/api/schemas.d.ts.map +1 -1
- package/dist/src/api/schemas.js +13 -0
- package/dist/src/api/schemas.js.map +1 -1
- package/dist/src/audit/remote-audit.d.ts +85 -0
- package/dist/src/audit/remote-audit.d.ts.map +1 -1
- package/dist/src/audit/remote-audit.js +126 -3
- package/dist/src/audit/remote-audit.js.map +1 -1
- package/dist/src/audit/remote-audit.test.js +217 -1
- package/dist/src/audit/remote-audit.test.js.map +1 -1
- package/dist/src/config/index.d.ts +1 -0
- package/dist/src/config/index.d.ts.map +1 -1
- package/dist/src/config/index.js.map +1 -1
- package/dist/src/db/migration-runner.d.ts.map +1 -1
- package/dist/src/db/migration-runner.js +6 -0
- package/dist/src/db/migration-runner.js.map +1 -1
- package/dist/src/db/migrations/v18-skill-versions-real-content-hash.d.ts +36 -0
- package/dist/src/db/migrations/v18-skill-versions-real-content-hash.d.ts.map +1 -0
- package/dist/src/db/migrations/v18-skill-versions-real-content-hash.js +38 -0
- package/dist/src/db/migrations/v18-skill-versions-real-content-hash.js.map +1 -0
- package/dist/src/db/schema.d.ts +1 -1
- package/dist/src/db/schema.d.ts.map +1 -1
- package/dist/src/db/schema.js +3 -1
- package/dist/src/db/schema.js.map +1 -1
- package/dist/src/exports/services.d.ts +3 -7
- package/dist/src/exports/services.d.ts.map +1 -1
- package/dist/src/exports/services.install.d.ts +18 -0
- package/dist/src/exports/services.install.d.ts.map +1 -0
- package/dist/src/exports/services.install.js +62 -0
- package/dist/src/exports/services.install.js.map +1 -0
- package/dist/src/exports/services.js +12 -32
- package/dist/src/exports/services.js.map +1 -1
- package/dist/src/exports/types.d.ts +1 -1
- package/dist/src/exports/types.d.ts.map +1 -1
- package/dist/src/index.d.ts +5 -5
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/install/agent-config-merge.json.d.ts +5 -4
- package/dist/src/install/agent-config-merge.json.d.ts.map +1 -1
- package/dist/src/install/agent-config-merge.json.js +10 -9
- package/dist/src/install/agent-config-merge.json.js.map +1 -1
- package/dist/src/install/agent-config-merge.types.d.ts +11 -1
- package/dist/src/install/agent-config-merge.types.d.ts.map +1 -1
- package/dist/src/install/agent-config-merge.types.js.map +1 -1
- package/dist/src/install/agent-harness-targets.d.ts +30 -2
- package/dist/src/install/agent-harness-targets.d.ts.map +1 -1
- package/dist/src/install/agent-harness-targets.js +17 -0
- package/dist/src/install/agent-harness-targets.js.map +1 -1
- package/dist/src/install/agent-manifest-path-guard.d.ts +28 -3
- package/dist/src/install/agent-manifest-path-guard.d.ts.map +1 -1
- package/dist/src/install/agent-manifest-path-guard.js +59 -3
- package/dist/src/install/agent-manifest-path-guard.js.map +1 -1
- package/dist/src/install/agent-manifest-path-guard.test.js +27 -1
- package/dist/src/install/agent-manifest-path-guard.test.js.map +1 -1
- package/dist/src/install/agent-pack-installer.antigravity-mcp.d.ts +74 -0
- package/dist/src/install/agent-pack-installer.antigravity-mcp.d.ts.map +1 -0
- package/dist/src/install/agent-pack-installer.antigravity-mcp.js +106 -0
- package/dist/src/install/agent-pack-installer.antigravity-mcp.js.map +1 -0
- package/dist/src/install/agent-pack-installer.antigravity-mcp.test.d.ts +2 -0
- package/dist/src/install/agent-pack-installer.antigravity-mcp.test.d.ts.map +1 -0
- package/dist/src/install/agent-pack-installer.antigravity-mcp.test.js +157 -0
- package/dist/src/install/agent-pack-installer.antigravity-mcp.test.js.map +1 -0
- package/dist/src/install/agent-pack-installer.cursor-mcp.d.ts +53 -0
- package/dist/src/install/agent-pack-installer.cursor-mcp.d.ts.map +1 -0
- package/dist/src/install/agent-pack-installer.cursor-mcp.js +207 -0
- package/dist/src/install/agent-pack-installer.cursor-mcp.js.map +1 -0
- package/dist/src/install/agent-pack-installer.cursor-mcp.test.d.ts +2 -0
- package/dist/src/install/agent-pack-installer.cursor-mcp.test.d.ts.map +1 -0
- package/dist/src/install/agent-pack-installer.cursor-mcp.test.js +285 -0
- package/dist/src/install/agent-pack-installer.cursor-mcp.test.js.map +1 -0
- package/dist/src/install/agent-pack-installer.d.ts +8 -0
- package/dist/src/install/agent-pack-installer.d.ts.map +1 -1
- package/dist/src/install/agent-pack-installer.entry.d.ts +120 -3
- package/dist/src/install/agent-pack-installer.entry.d.ts.map +1 -1
- package/dist/src/install/agent-pack-installer.entry.js +166 -3
- package/dist/src/install/agent-pack-installer.entry.js.map +1 -1
- package/dist/src/install/agent-pack-installer.js +97 -3
- package/dist/src/install/agent-pack-installer.js.map +1 -1
- package/dist/src/install/agent-pack-installer.types.d.ts +27 -0
- package/dist/src/install/agent-pack-installer.types.d.ts.map +1 -1
- package/dist/src/install/agent-pack-installer.types.js +6 -0
- package/dist/src/install/agent-pack-installer.types.js.map +1 -1
- package/dist/src/install/agent-pack-installer.workspace-scope.test.d.ts +2 -0
- package/dist/src/install/agent-pack-installer.workspace-scope.test.d.ts.map +1 -0
- package/dist/src/install/agent-pack-installer.workspace-scope.test.js +148 -0
- package/dist/src/install/agent-pack-installer.workspace-scope.test.js.map +1 -0
- package/dist/src/install/agent-pack-uninstaller.d.ts +18 -7
- package/dist/src/install/agent-pack-uninstaller.d.ts.map +1 -1
- package/dist/src/install/agent-pack-uninstaller.js +59 -9
- package/dist/src/install/agent-pack-uninstaller.js.map +1 -1
- package/dist/src/install/agent-pack-uninstaller.test.js +74 -1
- package/dist/src/install/agent-pack-uninstaller.test.js.map +1 -1
- package/dist/src/install/fan-out.d.ts.map +1 -1
- package/dist/src/install/fan-out.js +6 -0
- package/dist/src/install/fan-out.js.map +1 -1
- package/dist/src/install/index.d.ts +2 -0
- package/dist/src/install/index.d.ts.map +1 -1
- package/dist/src/install/index.js +2 -0
- package/dist/src/install/index.js.map +1 -1
- package/dist/src/install/workspace-scope.d.ts +207 -0
- package/dist/src/install/workspace-scope.d.ts.map +1 -0
- package/dist/src/install/workspace-scope.js +275 -0
- package/dist/src/install/workspace-scope.js.map +1 -0
- package/dist/src/install/workspace-scope.test.d.ts +2 -0
- package/dist/src/install/workspace-scope.test.d.ts.map +1 -0
- package/dist/src/install/workspace-scope.test.js +378 -0
- package/dist/src/install/workspace-scope.test.js.map +1 -0
- package/dist/src/security/__tests__/pathValidation.test.js +30 -1
- package/dist/src/security/__tests__/pathValidation.test.js.map +1 -1
- package/dist/src/services/SubagentGenerator.client-profiles.d.ts +258 -0
- package/dist/src/services/SubagentGenerator.client-profiles.d.ts.map +1 -0
- package/dist/src/services/SubagentGenerator.client-profiles.js +260 -0
- package/dist/src/services/SubagentGenerator.client-profiles.js.map +1 -0
- package/dist/src/services/SubagentGenerator.d.ts +19 -41
- package/dist/src/services/SubagentGenerator.d.ts.map +1 -1
- package/dist/src/services/SubagentGenerator.helpers.d.ts +64 -0
- package/dist/src/services/SubagentGenerator.helpers.d.ts.map +1 -0
- package/dist/src/services/SubagentGenerator.helpers.js +228 -0
- package/dist/src/services/SubagentGenerator.helpers.js.map +1 -0
- package/dist/src/services/SubagentGenerator.js +66 -208
- package/dist/src/services/SubagentGenerator.js.map +1 -1
- package/dist/src/services/SubagentGenerator.types.d.ts +45 -0
- package/dist/src/services/SubagentGenerator.types.d.ts.map +1 -0
- package/dist/src/services/SubagentGenerator.types.js +14 -0
- package/dist/src/services/SubagentGenerator.types.js.map +1 -0
- package/dist/src/services/TransformationService.d.ts +18 -61
- package/dist/src/services/TransformationService.d.ts.map +1 -1
- package/dist/src/services/TransformationService.js +29 -17
- package/dist/src/services/TransformationService.js.map +1 -1
- package/dist/src/services/TransformationService.types.d.ts +74 -0
- package/dist/src/services/TransformationService.types.d.ts.map +1 -0
- package/dist/src/services/TransformationService.types.js +8 -0
- package/dist/src/services/TransformationService.types.js.map +1 -0
- package/dist/src/services/__tests__/SubagentGenerator.client-profiles.test.d.ts +5 -0
- package/dist/src/services/__tests__/SubagentGenerator.client-profiles.test.d.ts.map +1 -0
- package/dist/src/services/__tests__/SubagentGenerator.client-profiles.test.js +72 -0
- package/dist/src/services/__tests__/SubagentGenerator.client-profiles.test.js.map +1 -0
- package/dist/src/services/__tests__/SubagentGenerator.helpers.test.d.ts +2 -0
- package/dist/src/services/__tests__/SubagentGenerator.helpers.test.d.ts.map +1 -0
- package/dist/src/services/__tests__/SubagentGenerator.helpers.test.js +61 -0
- package/dist/src/services/__tests__/SubagentGenerator.helpers.test.js.map +1 -0
- package/dist/src/services/__tests__/SubagentGenerator.test.js +155 -0
- package/dist/src/services/__tests__/SubagentGenerator.test.js.map +1 -1
- package/dist/src/services/__tests__/TransformationService.test.js +48 -0
- package/dist/src/services/__tests__/TransformationService.test.js.map +1 -1
- package/dist/src/services/agent-pack/agent-pack.test.js +1 -0
- package/dist/src/services/agent-pack/agent-pack.test.js.map +1 -1
- package/dist/src/services/agent-pack/prompt-source.d.ts +1 -1
- package/dist/src/services/agent-pack/prompt-source.d.ts.map +1 -1
- package/dist/src/services/agent-pack/prompt-source.js +10 -3
- package/dist/src/services/agent-pack/prompt-source.js.map +1 -1
- package/dist/src/services/agent-pack/types.d.ts +26 -2
- package/dist/src/services/agent-pack/types.d.ts.map +1 -1
- package/dist/src/services/agent-pack/types.js +13 -0
- package/dist/src/services/agent-pack/types.js.map +1 -1
- package/dist/src/services/agent-tool-profile.d.ts.map +1 -1
- package/dist/src/services/agent-tool-profile.js +7 -0
- package/dist/src/services/agent-tool-profile.js.map +1 -1
- package/dist/src/services/agent-tool-profile.test.js +2 -2
- package/dist/src/services/skill-content-comparison.d.ts +71 -0
- package/dist/src/services/skill-content-comparison.d.ts.map +1 -0
- package/dist/src/services/skill-content-comparison.js +90 -0
- package/dist/src/services/skill-content-comparison.js.map +1 -0
- package/dist/src/services/skill-content-comparison.test.d.ts +6 -0
- package/dist/src/services/skill-content-comparison.test.d.ts.map +1 -0
- package/dist/src/services/skill-content-comparison.test.js +83 -0
- package/dist/src/services/skill-content-comparison.test.js.map +1 -0
- package/dist/src/services/skill-identity-classification.d.ts +202 -0
- package/dist/src/services/skill-identity-classification.d.ts.map +1 -0
- package/dist/src/services/skill-identity-classification.js +295 -0
- package/dist/src/services/skill-identity-classification.js.map +1 -0
- package/dist/src/services/skill-identity-classification.test.d.ts +6 -0
- package/dist/src/services/skill-identity-classification.test.d.ts.map +1 -0
- package/dist/src/services/skill-identity-classification.test.js +314 -0
- package/dist/src/services/skill-identity-classification.test.js.map +1 -0
- package/dist/src/services/skill-installation.content.d.ts.map +1 -1
- package/dist/src/services/skill-installation.content.js +6 -1
- package/dist/src/services/skill-installation.content.js.map +1 -1
- package/dist/src/services/skill-installation.helpers.d.ts +8 -15
- package/dist/src/services/skill-installation.helpers.d.ts.map +1 -1
- package/dist/src/services/skill-installation.helpers.js +11 -105
- package/dist/src/services/skill-installation.helpers.js.map +1 -1
- package/dist/src/services/skill-installation.helpers.test.js +106 -1
- package/dist/src/services/skill-installation.helpers.test.js.map +1 -1
- package/dist/src/services/skill-installation.service.js +1 -1
- package/dist/src/services/skill-installation.service.js.map +1 -1
- package/dist/src/services/skill-installation.types.d.ts +33 -0
- package/dist/src/services/skill-installation.types.d.ts.map +1 -1
- package/dist/src/services/skill-installation.types.js.map +1 -1
- package/dist/src/services/skill-installation.uninstall.d.ts +84 -0
- package/dist/src/services/skill-installation.uninstall.d.ts.map +1 -0
- package/dist/src/services/skill-installation.uninstall.js +252 -0
- package/dist/src/services/skill-installation.uninstall.js.map +1 -0
- package/dist/src/services/skill-manifest.d.ts +41 -0
- package/dist/src/services/skill-manifest.d.ts.map +1 -1
- package/dist/src/services/skill-manifest.js +93 -0
- package/dist/src/services/skill-manifest.js.map +1 -1
- package/dist/src/services/skill-manifest.test.js +27 -0
- package/dist/src/services/skill-manifest.test.js.map +1 -1
- package/dist/src/sync/SyncEngine.d.ts.map +1 -1
- package/dist/src/sync/SyncEngine.js +69 -77
- package/dist/src/sync/SyncEngine.js.map +1 -1
- package/dist/src/telemetry/agent-marker.d.ts +8 -0
- package/dist/src/telemetry/agent-marker.d.ts.map +1 -1
- package/dist/src/telemetry/agent-marker.js +7 -0
- package/dist/src/telemetry/agent-marker.js.map +1 -1
- package/dist/src/telemetry/agent-marker.test.js +7 -1
- package/dist/src/telemetry/agent-marker.test.js.map +1 -1
- package/dist/src/telemetry/index.d.ts +1 -1
- package/dist/src/telemetry/index.d.ts.map +1 -1
- package/dist/src/telemetry/index.js +1 -1
- package/dist/src/telemetry/index.js.map +1 -1
- package/dist/src/telemetry/wrap.d.ts +6 -0
- package/dist/src/telemetry/wrap.d.ts.map +1 -1
- package/dist/src/telemetry/wrap.js +65 -10
- package/dist/src/telemetry/wrap.js.map +1 -1
- package/dist/src/telemetry/wrap.test.js +81 -1
- package/dist/src/telemetry/wrap.test.js.map +1 -1
- package/dist/src/telemetry/wrap.tool-call-sink.test.d.ts +26 -0
- package/dist/src/telemetry/wrap.tool-call-sink.test.d.ts.map +1 -0
- package/dist/src/telemetry/wrap.tool-call-sink.test.js +162 -0
- package/dist/src/telemetry/wrap.tool-call-sink.test.js.map +1 -0
- package/dist/src/types/skill.d.ts +9 -1
- package/dist/src/types/skill.d.ts.map +1 -1
- package/dist/src/utils/index.d.ts +1 -1
- package/dist/src/utils/index.d.ts.map +1 -1
- package/dist/src/utils/index.js +1 -1
- package/dist/src/utils/index.js.map +1 -1
- package/dist/src/utils/version-check.d.ts +61 -0
- package/dist/src/utils/version-check.d.ts.map +1 -1
- package/dist/src/utils/version-check.js +78 -1
- package/dist/src/utils/version-check.js.map +1 -1
- package/dist/src/utils/version-check.test.js +130 -1
- package/dist/src/utils/version-check.test.js.map +1 -1
- package/dist/tests/db/migration-runner-race.test.js +19 -9
- package/dist/tests/db/migration-runner-race.test.js.map +1 -1
- package/dist/tests/install/fan-out.test.js +27 -0
- package/dist/tests/install/fan-out.test.js.map +1 -1
- package/dist/tests/sync/SyncEngine.abort.test.js +15 -11
- package/dist/tests/sync/SyncEngine.abort.test.js.map +1 -1
- package/dist/tests/sync/SyncEngine.status.test.js +1 -1
- package/dist/tests/sync/SyncEngine.status.test.js.map +1 -1
- package/dist/tests/sync/SyncEngine.test.d.ts +11 -2
- package/dist/tests/sync/SyncEngine.test.d.ts.map +1 -1
- package/dist/tests/sync/SyncEngine.test.js +164 -13
- package/dist/tests/sync/SyncEngine.test.js.map +1 -1
- package/dist/tests/unit/migrations/migration-v18.test.d.ts +16 -0
- package/dist/tests/unit/migrations/migration-v18.test.d.ts.map +1 -0
- package/dist/tests/unit/migrations/migration-v18.test.js +192 -0
- package/dist/tests/unit/migrations/migration-v18.test.js.map +1 -0
- package/dist/tests/unit/services/skill-installation.content.test.js +65 -1
- package/dist/tests/unit/services/skill-installation.content.test.js.map +1 -1
- package/dist/tests/unit/services/skill-installation.service.test.js +9 -8
- package/dist/tests/unit/services/skill-installation.service.test.js.map +1 -1
- package/dist/tests/unit/sync-engine.source-aware.test.js +5 -5
- package/dist/tests/unit/sync-engine.source-aware.test.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-tool-profile.js","sourceRoot":"","sources":["../../../src/services/agent-tool-profile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,sFAAsF;AACtF,MAAM,CAAC,MAAM,0BAA0B,GAAG,yBAAyB,CAAA;AAEnE,uFAAuF;AACvF,MAAM,CAAC,MAAM,wBAAwB,GAAG,OAAO,CAAA;AAE/C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAsB;IACzD,QAAQ;IACR,WAAW;IACX,eAAe;IACf,iBAAiB;IACjB,iBAAiB;IACjB,gBAAgB;IAChB,eAAe;IACf,gBAAgB;IAChB,eAAe;IACf,YAAY;IACZ,kBAAkB;IAClB,uBAAuB;IACvB,wBAAwB;IACxB,wBAAwB;IACxB,aAAa;IACb,YAAY;
|
|
1
|
+
{"version":3,"file":"agent-tool-profile.js","sourceRoot":"","sources":["../../../src/services/agent-tool-profile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,sFAAsF;AACtF,MAAM,CAAC,MAAM,0BAA0B,GAAG,yBAAyB,CAAA;AAEnE,uFAAuF;AACvF,MAAM,CAAC,MAAM,wBAAwB,GAAG,OAAO,CAAA;AAE/C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAsB;IACzD,QAAQ;IACR,WAAW;IACX,eAAe;IACf,iBAAiB;IACjB,iBAAiB;IACjB,gBAAgB;IAChB,eAAe;IACf,gBAAgB;IAChB,eAAe;IACf,YAAY;IACZ,kBAAkB;IAClB,uBAAuB;IACvB,wBAAwB;IACxB,wBAAwB;IACxB,aAAa;IACb,YAAY;IACZ,8DAA8D;IAC9D,uEAAuE;IACvE,wEAAwE;IACxE,uEAAuE;IACvE,0EAA0E;IAC1E,2BAA2B;IAC3B,0BAA0B;CAC3B,CAAA"}
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
import { describe, expect, it } from 'vitest';
|
|
7
7
|
import { AGENT_TOOL_PROFILE_ENV_VAR, AGENT_TOOL_PROFILE_NAMES, AGENT_TOOL_PROFILE_VALUE, } from './agent-tool-profile.js';
|
|
8
8
|
describe('AGENT_TOOL_PROFILE_NAMES (core, QD-1 relocation)', () => {
|
|
9
|
-
it('has
|
|
10
|
-
expect(AGENT_TOOL_PROFILE_NAMES).toHaveLength(
|
|
9
|
+
it('has 17 entries', () => {
|
|
10
|
+
expect(AGENT_TOOL_PROFILE_NAMES).toHaveLength(17);
|
|
11
11
|
});
|
|
12
12
|
it('contains undo_apply', () => {
|
|
13
13
|
expect(AGENT_TOOL_PROFILE_NAMES).toContain('undo_apply');
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Shared skill content-hash comparator
|
|
3
|
+
* @module @skillsmith/core/services/skill-content-comparison
|
|
4
|
+
* @see SMI-6343 Wave 2 — repair the content-hash comparison
|
|
5
|
+
*
|
|
6
|
+
* A single comparison function, consumed by every "is this skill outdated"
|
|
7
|
+
* reader in the codebase (`packages/mcp-server/src/tools/outdated.ts`,
|
|
8
|
+
* `packages/mcp-server/src/tools/skill-updates.ts`,
|
|
9
|
+
* `packages/cli/src/utils/skills-directory.ts`) so they cannot drift apart
|
|
10
|
+
* on what "current" / "outdated" / "unknown" means again — exactly the class
|
|
11
|
+
* of bug an adversarial review caught in SMI-6343 Wave 1, where one
|
|
12
|
+
* implementation was fixed and its siblings were left broken.
|
|
13
|
+
*
|
|
14
|
+
* Deliberately narrow: this function only adjudicates "do these two hashes
|
|
15
|
+
* agree," given whatever installed-side and registry-side hash the caller
|
|
16
|
+
* already resolved (manifest-recorded hash, on-disk SHA-256, live registry
|
|
17
|
+
* lookup, cached `skill_versions` row — the callers differ on where the
|
|
18
|
+
* hash comes from, not on how two hashes are compared). Richer per-caller
|
|
19
|
+
* diagnosis (offline, quota-exhausted, network-error, identity-mismatch,
|
|
20
|
+
* etc.) is caller-specific context this function has no visibility into —
|
|
21
|
+
* callers layer their own explanation on top of the `reason` this returns.
|
|
22
|
+
*/
|
|
23
|
+
/** The three possible outcomes of comparing an installed hash to a registry hash. */
|
|
24
|
+
export type ContentComparisonOutcome = 'current' | 'outdated' | 'unknown';
|
|
25
|
+
/**
|
|
26
|
+
* Result of {@link compareSkillContentHashes}.
|
|
27
|
+
*/
|
|
28
|
+
export interface ContentComparisonResult {
|
|
29
|
+
/** current: hashes match. outdated: hashes differ. unknown: no comparison could be made. */
|
|
30
|
+
outcome: ContentComparisonOutcome;
|
|
31
|
+
/**
|
|
32
|
+
* Populated only when `outcome === 'unknown'` — explains why no
|
|
33
|
+
* comparison could be made (missing installed hash, missing registry
|
|
34
|
+
* hash, or both). Always `undefined` for `current`/`outdated`.
|
|
35
|
+
*/
|
|
36
|
+
reason?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Normalize a hash value: `null`/`undefined`/non-string/blank all collapse
|
|
40
|
+
* to `null` so every caller's "I don't have this" shape (a missing manifest
|
|
41
|
+
* field, an absent DB row, an `undefined` API response field) is handled
|
|
42
|
+
* uniformly regardless of how the caller spells "nothing here."
|
|
43
|
+
*/
|
|
44
|
+
export declare function normalizeHash(hash: string | null | undefined): string | null;
|
|
45
|
+
/**
|
|
46
|
+
* SMI-6343 (adversarial-review fix): return the first candidate that
|
|
47
|
+
* normalizes to a non-blank value, else `null`.
|
|
48
|
+
*
|
|
49
|
+
* Exists because a bare `a ?? b` precedence chain (the original shape at
|
|
50
|
+
* both call sites) only falls through on `null`/`undefined` — a blank
|
|
51
|
+
* string (`''`, whitespace-only) is neither, so `a ?? b` incorrectly
|
|
52
|
+
* "wins" with a value `normalizeHash()` would itself treat as absent,
|
|
53
|
+
* silently discarding a legitimate `b`. Every caller selecting between a
|
|
54
|
+
* manifest's `contentHash`/`originalContentHash` (or an on-disk fallback)
|
|
55
|
+
* should route through this instead of raw `??` chaining.
|
|
56
|
+
*/
|
|
57
|
+
export declare function firstNonBlankHash(...candidates: Array<string | null | undefined>): string | null;
|
|
58
|
+
/**
|
|
59
|
+
* Compare an installed-side content hash against a registry-side content
|
|
60
|
+
* hash and return a comparison outcome plus (when `unknown`) the reason no
|
|
61
|
+
* comparison could be made.
|
|
62
|
+
*
|
|
63
|
+
* @param installedHash SHA-256 hex digest of what is actually installed —
|
|
64
|
+
* e.g. a manifest's recorded `contentHash`/`originalContentHash`, or a
|
|
65
|
+
* freshly-computed hash of the on-disk SKILL.md.
|
|
66
|
+
* @param registryHash SHA-256 hex digest of the registry's current SKILL.md
|
|
67
|
+
* content — e.g. a live `lookupSkillFromRegistry()` result's
|
|
68
|
+
* `contentHash`, or the most-recently-synced `skill_versions.content_hash`.
|
|
69
|
+
*/
|
|
70
|
+
export declare function compareSkillContentHashes(installedHash: string | null | undefined, registryHash: string | null | undefined): ContentComparisonResult;
|
|
71
|
+
//# sourceMappingURL=skill-content-comparison.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-content-comparison.d.ts","sourceRoot":"","sources":["../../../src/services/skill-content-comparison.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,qFAAqF;AACrF,MAAM,MAAM,wBAAwB,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAAA;AAEzE;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,4FAA4F;IAC5F,OAAO,EAAE,wBAAwB,CAAA;IACjC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAI5E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,UAAU,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,MAAM,GAAG,IAAI,CAMhG;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CACvC,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACxC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACtC,uBAAuB,CAwBzB"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Shared skill content-hash comparator
|
|
3
|
+
* @module @skillsmith/core/services/skill-content-comparison
|
|
4
|
+
* @see SMI-6343 Wave 2 — repair the content-hash comparison
|
|
5
|
+
*
|
|
6
|
+
* A single comparison function, consumed by every "is this skill outdated"
|
|
7
|
+
* reader in the codebase (`packages/mcp-server/src/tools/outdated.ts`,
|
|
8
|
+
* `packages/mcp-server/src/tools/skill-updates.ts`,
|
|
9
|
+
* `packages/cli/src/utils/skills-directory.ts`) so they cannot drift apart
|
|
10
|
+
* on what "current" / "outdated" / "unknown" means again — exactly the class
|
|
11
|
+
* of bug an adversarial review caught in SMI-6343 Wave 1, where one
|
|
12
|
+
* implementation was fixed and its siblings were left broken.
|
|
13
|
+
*
|
|
14
|
+
* Deliberately narrow: this function only adjudicates "do these two hashes
|
|
15
|
+
* agree," given whatever installed-side and registry-side hash the caller
|
|
16
|
+
* already resolved (manifest-recorded hash, on-disk SHA-256, live registry
|
|
17
|
+
* lookup, cached `skill_versions` row — the callers differ on where the
|
|
18
|
+
* hash comes from, not on how two hashes are compared). Richer per-caller
|
|
19
|
+
* diagnosis (offline, quota-exhausted, network-error, identity-mismatch,
|
|
20
|
+
* etc.) is caller-specific context this function has no visibility into —
|
|
21
|
+
* callers layer their own explanation on top of the `reason` this returns.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Normalize a hash value: `null`/`undefined`/non-string/blank all collapse
|
|
25
|
+
* to `null` so every caller's "I don't have this" shape (a missing manifest
|
|
26
|
+
* field, an absent DB row, an `undefined` API response field) is handled
|
|
27
|
+
* uniformly regardless of how the caller spells "nothing here."
|
|
28
|
+
*/
|
|
29
|
+
export function normalizeHash(hash) {
|
|
30
|
+
if (typeof hash !== 'string')
|
|
31
|
+
return null;
|
|
32
|
+
const trimmed = hash.trim();
|
|
33
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* SMI-6343 (adversarial-review fix): return the first candidate that
|
|
37
|
+
* normalizes to a non-blank value, else `null`.
|
|
38
|
+
*
|
|
39
|
+
* Exists because a bare `a ?? b` precedence chain (the original shape at
|
|
40
|
+
* both call sites) only falls through on `null`/`undefined` — a blank
|
|
41
|
+
* string (`''`, whitespace-only) is neither, so `a ?? b` incorrectly
|
|
42
|
+
* "wins" with a value `normalizeHash()` would itself treat as absent,
|
|
43
|
+
* silently discarding a legitimate `b`. Every caller selecting between a
|
|
44
|
+
* manifest's `contentHash`/`originalContentHash` (or an on-disk fallback)
|
|
45
|
+
* should route through this instead of raw `??` chaining.
|
|
46
|
+
*/
|
|
47
|
+
export function firstNonBlankHash(...candidates) {
|
|
48
|
+
for (const candidate of candidates) {
|
|
49
|
+
const normalized = normalizeHash(candidate);
|
|
50
|
+
if (normalized !== null)
|
|
51
|
+
return normalized;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Compare an installed-side content hash against a registry-side content
|
|
57
|
+
* hash and return a comparison outcome plus (when `unknown`) the reason no
|
|
58
|
+
* comparison could be made.
|
|
59
|
+
*
|
|
60
|
+
* @param installedHash SHA-256 hex digest of what is actually installed —
|
|
61
|
+
* e.g. a manifest's recorded `contentHash`/`originalContentHash`, or a
|
|
62
|
+
* freshly-computed hash of the on-disk SKILL.md.
|
|
63
|
+
* @param registryHash SHA-256 hex digest of the registry's current SKILL.md
|
|
64
|
+
* content — e.g. a live `lookupSkillFromRegistry()` result's
|
|
65
|
+
* `contentHash`, or the most-recently-synced `skill_versions.content_hash`.
|
|
66
|
+
*/
|
|
67
|
+
export function compareSkillContentHashes(installedHash, registryHash) {
|
|
68
|
+
const installed = normalizeHash(installedHash);
|
|
69
|
+
const registry = normalizeHash(registryHash);
|
|
70
|
+
if (installed === null && registry === null) {
|
|
71
|
+
return {
|
|
72
|
+
outcome: 'unknown',
|
|
73
|
+
reason: 'No installed content hash and no registry content hash available for comparison.',
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (installed === null) {
|
|
77
|
+
return {
|
|
78
|
+
outcome: 'unknown',
|
|
79
|
+
reason: 'No installed content hash recorded — cannot compare against the registry.',
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (registry === null) {
|
|
83
|
+
return {
|
|
84
|
+
outcome: 'unknown',
|
|
85
|
+
reason: 'No registry content hash available — cannot compare against the installed skill.',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return installed === registry ? { outcome: 'current' } : { outcome: 'outdated' };
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=skill-content-comparison.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-content-comparison.js","sourceRoot":"","sources":["../../../src/services/skill-content-comparison.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAmBH;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,IAA+B;IAC3D,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IACzC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;IAC3B,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA;AAC5C,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAG,UAA4C;IAC/E,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,UAAU,GAAG,aAAa,CAAC,SAAS,CAAC,CAAA;QAC3C,IAAI,UAAU,KAAK,IAAI;YAAE,OAAO,UAAU,CAAA;IAC5C,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,yBAAyB,CACvC,aAAwC,EACxC,YAAuC;IAEvC,MAAM,SAAS,GAAG,aAAa,CAAC,aAAa,CAAC,CAAA;IAC9C,MAAM,QAAQ,GAAG,aAAa,CAAC,YAAY,CAAC,CAAA;IAE5C,IAAI,SAAS,KAAK,IAAI,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QAC5C,OAAO;YACL,OAAO,EAAE,SAAS;YAClB,MAAM,EAAE,kFAAkF;SAC3F,CAAA;IACH,CAAC;IACD,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QACvB,OAAO;YACL,OAAO,EAAE,SAAS;YAClB,MAAM,EAAE,2EAA2E;SACpF,CAAA;IACH,CAAC;IACD,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtB,OAAO;YACL,OAAO,EAAE,SAAS;YAClB,MAAM,EAAE,kFAAkF;SAC3F,CAAA;IACH,CAAC;IAED,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAA;AAClF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-content-comparison.test.d.ts","sourceRoot":"","sources":["../../../src/services/skill-content-comparison.test.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Unit tests for the shared skill content-hash comparator
|
|
3
|
+
* @see SMI-6343 Wave 2 — repair the content-hash comparison
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import { compareSkillContentHashes, firstNonBlankHash } from './skill-content-comparison.js';
|
|
7
|
+
describe('compareSkillContentHashes', () => {
|
|
8
|
+
it('returns current when both hashes match exactly', () => {
|
|
9
|
+
const result = compareSkillContentHashes('abc123', 'abc123');
|
|
10
|
+
expect(result.outcome).toBe('current');
|
|
11
|
+
expect(result.reason).toBeUndefined();
|
|
12
|
+
});
|
|
13
|
+
it('returns outdated when the hashes differ', () => {
|
|
14
|
+
const result = compareSkillContentHashes('abc123', 'def456');
|
|
15
|
+
expect(result.outcome).toBe('outdated');
|
|
16
|
+
expect(result.reason).toBeUndefined();
|
|
17
|
+
});
|
|
18
|
+
it('returns unknown with a reason when the installed hash is missing', () => {
|
|
19
|
+
const result = compareSkillContentHashes(null, 'def456');
|
|
20
|
+
expect(result.outcome).toBe('unknown');
|
|
21
|
+
expect(result.reason).toMatch(/no installed content hash/i);
|
|
22
|
+
});
|
|
23
|
+
it('returns unknown with a reason when the installed hash is undefined', () => {
|
|
24
|
+
const result = compareSkillContentHashes(undefined, 'def456');
|
|
25
|
+
expect(result.outcome).toBe('unknown');
|
|
26
|
+
expect(result.reason).toMatch(/no installed content hash/i);
|
|
27
|
+
});
|
|
28
|
+
it('returns unknown with a reason when the registry hash is missing', () => {
|
|
29
|
+
const result = compareSkillContentHashes('abc123', null);
|
|
30
|
+
expect(result.outcome).toBe('unknown');
|
|
31
|
+
expect(result.reason).toMatch(/no registry content hash/i);
|
|
32
|
+
});
|
|
33
|
+
it('returns unknown with a reason when the registry hash is undefined', () => {
|
|
34
|
+
const result = compareSkillContentHashes('abc123', undefined);
|
|
35
|
+
expect(result.outcome).toBe('unknown');
|
|
36
|
+
expect(result.reason).toMatch(/no registry content hash/i);
|
|
37
|
+
});
|
|
38
|
+
it('returns unknown when both hashes are missing', () => {
|
|
39
|
+
const result = compareSkillContentHashes(null, null);
|
|
40
|
+
expect(result.outcome).toBe('unknown');
|
|
41
|
+
expect(result.reason).toMatch(/no installed content hash and no registry content hash/i);
|
|
42
|
+
});
|
|
43
|
+
it('treats a blank/whitespace-only hash the same as missing', () => {
|
|
44
|
+
expect(compareSkillContentHashes(' ', 'abc123').outcome).toBe('unknown');
|
|
45
|
+
expect(compareSkillContentHashes('abc123', ' ').outcome).toBe('unknown');
|
|
46
|
+
expect(compareSkillContentHashes('', '').outcome).toBe('unknown');
|
|
47
|
+
});
|
|
48
|
+
it('is case-sensitive — differently-cased hex digests are treated as different hashes', () => {
|
|
49
|
+
const result = compareSkillContentHashes('ABC123', 'abc123');
|
|
50
|
+
expect(result.outcome).toBe('outdated');
|
|
51
|
+
});
|
|
52
|
+
it('trims surrounding whitespace before comparing', () => {
|
|
53
|
+
const result = compareSkillContentHashes(' abc123 ', 'abc123');
|
|
54
|
+
expect(result.outcome).toBe('current');
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe('firstNonBlankHash (adversarial-review addition, SMI-6343)', () => {
|
|
58
|
+
it('returns the first candidate when it is non-blank', () => {
|
|
59
|
+
expect(firstNonBlankHash('abc123', 'def456')).toBe('abc123');
|
|
60
|
+
});
|
|
61
|
+
it('falls through past null/undefined to the next candidate', () => {
|
|
62
|
+
expect(firstNonBlankHash(null, 'def456')).toBe('def456');
|
|
63
|
+
expect(firstNonBlankHash(undefined, 'def456')).toBe('def456');
|
|
64
|
+
});
|
|
65
|
+
it('falls through past a BLANK (whitespace-only) first candidate — the exact case a raw `??` chain gets wrong', () => {
|
|
66
|
+
// `' ' ?? 'def456'` would incorrectly return ' ' (?? only falls
|
|
67
|
+
// through on null/undefined) — this is the whole reason this helper
|
|
68
|
+
// exists instead of a raw `??` chain at each call site.
|
|
69
|
+
expect(firstNonBlankHash(' ', 'def456')).toBe('def456');
|
|
70
|
+
expect(firstNonBlankHash('', 'def456')).toBe('def456');
|
|
71
|
+
});
|
|
72
|
+
it('trims a winning candidate', () => {
|
|
73
|
+
expect(firstNonBlankHash(' abc123 ')).toBe('abc123');
|
|
74
|
+
});
|
|
75
|
+
it('returns null when every candidate is blank/null/undefined', () => {
|
|
76
|
+
expect(firstNonBlankHash(null, undefined, ' ', '')).toBeNull();
|
|
77
|
+
expect(firstNonBlankHash()).toBeNull();
|
|
78
|
+
});
|
|
79
|
+
it('supports three or more candidates (on-disk-hash fallback chains)', () => {
|
|
80
|
+
expect(firstNonBlankHash(' ', undefined, 'on-disk-hash')).toBe('on-disk-hash');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
//# sourceMappingURL=skill-content-comparison.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-content-comparison.test.js","sourceRoot":"","sources":["../../../src/services/skill-content-comparison.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Shared skill-identity contradiction-signal classification
|
|
3
|
+
* @module @skillsmith/core/services/skill-identity-classification
|
|
4
|
+
* @see SMI-6343 Wave 3 — tamper-check classification (AC#3)
|
|
5
|
+
*
|
|
6
|
+
* Three deterministic-or-best-effort "does this manifest entry's recorded
|
|
7
|
+
* identity contradict what's actually installed" signals, plus a
|
|
8
|
+
* "has this on-disk content been locally edited since install" check —
|
|
9
|
+
* consumed by BOTH `packages/mcp-server/src/tools/outdated.ts` (via
|
|
10
|
+
* `outdated.identity.ts`) and `packages/cli/src/commands/manage.update.ts`,
|
|
11
|
+
* so the two packages cannot drift into two independently-maintained
|
|
12
|
+
* implementations of the same contradiction logic (exactly the "sibling
|
|
13
|
+
* implementation" bug class both Wave 1 and Wave 2 of SMI-6343 already hit).
|
|
14
|
+
*
|
|
15
|
+
* Deliberately decoupled from either caller's own `RegistrySkillInfo` shape
|
|
16
|
+
* (mcp-server's `install.types.ts` and core's own `skill-installation.types.ts`
|
|
17
|
+
* both have independently-evolved versions) — callers adapt their own
|
|
18
|
+
* registry-lookup result into the minimal {@link RegistryLookupOutcome} shape
|
|
19
|
+
* this module needs.
|
|
20
|
+
*
|
|
21
|
+
* Every function here is synchronous and filesystem-free by design: signal 3
|
|
22
|
+
* (path containment) is a pure string check against an already-resolved
|
|
23
|
+
* expected root directory, not a `fs.realpath` call — the callers of this
|
|
24
|
+
* module (`outdated.ts`, `manage.update.ts`) only ever reach the "outdated"
|
|
25
|
+
* classification branch after already having successfully read the
|
|
26
|
+
* installed skill's SKILL.md this same iteration, so "does installPath
|
|
27
|
+
* exist" is already established by the time these signals run.
|
|
28
|
+
*/
|
|
29
|
+
import type { ContentComparisonOutcome } from './skill-content-comparison.js';
|
|
30
|
+
import type { ClientId } from '../install/paths.js';
|
|
31
|
+
/** Which of the three contradiction signals fired. */
|
|
32
|
+
export type IdentitySignal = 'owner-mismatch' | 'frontmatter-contradiction' | 'path-unresolved';
|
|
33
|
+
/** Why a classification could not be conclusively determined. */
|
|
34
|
+
export type IdentityInconclusiveReason = 'offline' | 'quota-exhausted' | 'network-error' | 'no-registry-record' | 'no-history';
|
|
35
|
+
/** The five-state classification `outdated.ts`/`manage.update.ts` report against a divergent entry. */
|
|
36
|
+
export type OutdatedClassificationState = 'current' | 'outdated' | 'local-drift' | 'identity-mismatch' | 'unknown';
|
|
37
|
+
/** Minimal registry-record shape signal 2 needs. */
|
|
38
|
+
export interface IdentityRegistryRecord {
|
|
39
|
+
author?: string | null;
|
|
40
|
+
name?: string | null;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The outcome of a caller's OWN registry lookup for the entry's claimed
|
|
44
|
+
* `id`, adapted into this module's minimal shape.
|
|
45
|
+
*
|
|
46
|
+
* `attempted: false` means the caller never even tried this run (offline,
|
|
47
|
+
* or an earlier skill in the same batch already exhausted quota) —
|
|
48
|
+
* `failureReason` in that case names WHY it wasn't attempted. `attempted:
|
|
49
|
+
* true` with `record: null` means the lookup completed but found nothing
|
|
50
|
+
* for this id (a distinct case from a failed lookup — see
|
|
51
|
+
* `no-registry-record` below).
|
|
52
|
+
*/
|
|
53
|
+
export interface RegistryLookupOutcome {
|
|
54
|
+
attempted: boolean;
|
|
55
|
+
record: IdentityRegistryRecord | null;
|
|
56
|
+
/** Populated when the lookup either wasn't attempted or didn't complete. */
|
|
57
|
+
failureReason?: 'offline' | 'quota-exhausted' | 'network-error' | null;
|
|
58
|
+
}
|
|
59
|
+
/** The subset of a manifest entry every signal needs. */
|
|
60
|
+
export interface ManifestEntryForIdentity {
|
|
61
|
+
id: string;
|
|
62
|
+
source: string;
|
|
63
|
+
installPath: string;
|
|
64
|
+
client?: ClientId;
|
|
65
|
+
contentHash?: string;
|
|
66
|
+
originalContentHash?: string;
|
|
67
|
+
}
|
|
68
|
+
/** Result of {@link classifyManifestEntryIdentity}. */
|
|
69
|
+
export interface IdentityClassificationResult {
|
|
70
|
+
/** Which signal fired. Null when no signal fired (regardless of `inconclusive`). */
|
|
71
|
+
signal: IdentitySignal | null;
|
|
72
|
+
/** True when signal 2 (frontmatter) could not be checked at all. */
|
|
73
|
+
inconclusive: boolean;
|
|
74
|
+
/** Populated only when `inconclusive` is true. */
|
|
75
|
+
inconclusiveReason: IdentityInconclusiveReason | null;
|
|
76
|
+
}
|
|
77
|
+
/** Result of {@link classifyDivergentEntry}. */
|
|
78
|
+
export interface DivergentEntryClassification {
|
|
79
|
+
state: Exclude<OutdatedClassificationState, 'current'>;
|
|
80
|
+
signal: IdentitySignal | null;
|
|
81
|
+
inconclusiveReason: IdentityInconclusiveReason | null;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Parse the owner segment out of a manifest `source` string. Real installs
|
|
85
|
+
* write `source` as `'github:' + owner + '/' + repo` (`skill-installation.
|
|
86
|
+
* service.ts`) — the `'github:'` prefix is optional here so a bare
|
|
87
|
+
* `owner/repo` (or a future non-GitHub source shape) still parses. Returns
|
|
88
|
+
* `null` for the `'unknown'` distrust sentinel, a raw URL, or anything else
|
|
89
|
+
* with no parseable `owner/name` shape.
|
|
90
|
+
*/
|
|
91
|
+
export declare function parseOwnerFromSource(source: string | undefined | null): string | null;
|
|
92
|
+
/**
|
|
93
|
+
* Parse the owner segment out of a manifest entry's `id`. Returns `null`
|
|
94
|
+
* for anything that isn't a clean `owner/name` pair — most importantly a
|
|
95
|
+
* raw GitHub URL, which a direct-URL install records as `id` verbatim (see
|
|
96
|
+
* {@link parseOwnerFromOwnerNamePair}).
|
|
97
|
+
*/
|
|
98
|
+
export declare function parseOwnerFromId(id: string | undefined | null): string | null;
|
|
99
|
+
/**
|
|
100
|
+
* Signal 1: `id` parses as `owner/name` and `source` names a DIFFERENT
|
|
101
|
+
* owner. Deterministic, offline, zero network dependency. Catches the real
|
|
102
|
+
* `linear` skill corruption (`source: "github:lobehub/lobehub"` vs
|
|
103
|
+
* `id: "wrsmith108/linear"`).
|
|
104
|
+
*/
|
|
105
|
+
export declare function detectOwnerMismatch(entry: Pick<ManifestEntryForIdentity, 'id' | 'source'>): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Signal 3: `installPath` is absent, or resolves OUTSIDE `expectedRootDir`
|
|
108
|
+
* (the claimed client's native install root, or a workspace-scoped
|
|
109
|
+
* equivalent the caller resolves — see each caller's own scope handling).
|
|
110
|
+
*
|
|
111
|
+
* Deliberately a pure string containment check (`path.relative`), not a
|
|
112
|
+
* filesystem call: both callers of this module only reach this signal after
|
|
113
|
+
* already having successfully read the entry's SKILL.md this same
|
|
114
|
+
* iteration, so filesystem existence is already established. This also
|
|
115
|
+
* catches the exact shape of the SMI-6343 Wave 1 test-fixture leak (an
|
|
116
|
+
* OS-temp-dir `installPath` written into the real manifest) via simple
|
|
117
|
+
* string comparison, with no need to resolve symlinks.
|
|
118
|
+
*
|
|
119
|
+
* NOT a symlink-escape check (Wave 3 adversarial review, considered and
|
|
120
|
+
* scoped out): a real `installPath` inside `expectedRootDir` that is
|
|
121
|
+
* ITSELF a symlink pointing elsewhere is a local-filesystem-tampering
|
|
122
|
+
* threat distinct from this signal's actual purpose (catching a manifest
|
|
123
|
+
* entry whose *recorded* path doesn't match a legitimate install
|
|
124
|
+
* location — a software-bug/registry-tamper shape, not local attacker
|
|
125
|
+
* capability); an attacker with write access to plant such a symlink
|
|
126
|
+
* inside the client root already has equivalent-or-greater capability to
|
|
127
|
+
* edit SKILL.md content directly, making a `fs.realpath` upgrade here
|
|
128
|
+
* (which would also force this whole synchronous, filesystem-free module
|
|
129
|
+
* to become async) address a threat this signal was never meant to cover.
|
|
130
|
+
*
|
|
131
|
+
* The parent-segment check below (`rel === '..' || rel.startsWith('..' +
|
|
132
|
+
* sep)`) is deliberately NOT a bare `rel.startsWith('..')`: a literal
|
|
133
|
+
* directory name that happens to start with two dots (e.g. `..cache`) is a
|
|
134
|
+
* legitimate contained child — `path.relative('/r', '/r/..cache')` returns
|
|
135
|
+
* `'..cache'`, which a bare `startsWith('..')` would misclassify as an
|
|
136
|
+
* escape (Wave 3 adversarial review finding).
|
|
137
|
+
*/
|
|
138
|
+
export declare function detectPathUnresolved(installPath: string | undefined | null, expectedRootDir: string): boolean;
|
|
139
|
+
/**
|
|
140
|
+
* Run all three contradiction signals against a manifest entry. Any one
|
|
141
|
+
* conclusively firing means `signal` is non-null — order (1, 3, 2) checks
|
|
142
|
+
* the two deterministic/offline signals first, only falling to the
|
|
143
|
+
* network-dependent signal 2 when neither of those already answered the
|
|
144
|
+
* question.
|
|
145
|
+
*/
|
|
146
|
+
export declare function classifyManifestEntryIdentity(params: {
|
|
147
|
+
entry: ManifestEntryForIdentity;
|
|
148
|
+
localContent: string | null;
|
|
149
|
+
expectedRootDir: string;
|
|
150
|
+
registryLookup: RegistryLookupOutcome;
|
|
151
|
+
}): IdentityClassificationResult;
|
|
152
|
+
/**
|
|
153
|
+
* Has the on-disk content changed since the manifest's own recorded
|
|
154
|
+
* install/update-time hash? This is what distinguishes a benign local edit
|
|
155
|
+
* (`local-drift`) from a genuine registry version bump (`outdated`) once
|
|
156
|
+
* no identity signal has fired.
|
|
157
|
+
*
|
|
158
|
+
* When the manifest never recorded a comparable hash at all (a legacy or
|
|
159
|
+
* adopted entry, `contentHash`/`originalContentHash` both absent), this
|
|
160
|
+
* deliberately defaults to `false` (no edit evidence) rather than `true`
|
|
161
|
+
* (conservative) — absence of a recorded hash is a data-quality gap, not
|
|
162
|
+
* positive evidence of tampering, and defaulting to `true` here would
|
|
163
|
+
* misclassify the large population of legacy/adopted entries that never had
|
|
164
|
+
* a hash recorded as `local-drift` even when they are genuinely just
|
|
165
|
+
* outdated (SMI-6343 Wave 3 review finding).
|
|
166
|
+
*/
|
|
167
|
+
export declare function hasRecordedLocalEdit(entry: Pick<ManifestEntryForIdentity, 'contentHash' | 'originalContentHash'>, localHash: string | null): boolean;
|
|
168
|
+
/**
|
|
169
|
+
* Classify an entry the caller has ALREADY determined is divergent from the
|
|
170
|
+
* registry (mcp-server: `compareSkillContentHashes(...).outcome ===
|
|
171
|
+
* 'outdated'`; CLI: `getSkillDiff()` found a version/content difference).
|
|
172
|
+
* Never returns `'current'` — callers own that trivial case themselves,
|
|
173
|
+
* since it never needs signal evaluation at all.
|
|
174
|
+
*/
|
|
175
|
+
export declare function classifyDivergentEntry(params: {
|
|
176
|
+
entry: ManifestEntryForIdentity;
|
|
177
|
+
localHash: string | null;
|
|
178
|
+
localContent: string | null;
|
|
179
|
+
expectedRootDir: string;
|
|
180
|
+
registryLookup: RegistryLookupOutcome;
|
|
181
|
+
}): DivergentEntryClassification;
|
|
182
|
+
/**
|
|
183
|
+
* Full 5-state classification given an already-computed content-comparison
|
|
184
|
+
* outcome (from `compareSkillContentHashes`) plus the caller-derived reason
|
|
185
|
+
* a plain `'unknown'` comparison outcome couldn't be resolved (offline,
|
|
186
|
+
* quota-exhausted, network-error, or no-history — the caller already tracks
|
|
187
|
+
* which applies; see `outdated.ts`'s `deriveUnknownReason`).
|
|
188
|
+
*/
|
|
189
|
+
export declare function classifyOutdatedState(params: {
|
|
190
|
+
comparisonOutcome: ContentComparisonOutcome;
|
|
191
|
+
unknownReasonWhenComparisonUnknown: IdentityInconclusiveReason;
|
|
192
|
+
entry: ManifestEntryForIdentity;
|
|
193
|
+
localHash: string | null;
|
|
194
|
+
localContent: string | null;
|
|
195
|
+
expectedRootDir: string;
|
|
196
|
+
registryLookup: RegistryLookupOutcome;
|
|
197
|
+
}): {
|
|
198
|
+
state: OutdatedClassificationState;
|
|
199
|
+
signal: IdentitySignal | null;
|
|
200
|
+
inconclusiveReason: IdentityInconclusiveReason | null;
|
|
201
|
+
};
|
|
202
|
+
//# sourceMappingURL=skill-identity-classification.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-identity-classification.d.ts","sourceRoot":"","sources":["../../../src/services/skill-identity-classification.ts"],"names":[],"mappings":"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"}
|