@skillsmith/core 0.8.0 → 0.8.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 +14 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/analytics/ROIDashboardService.compute.d.ts +17 -0
- package/dist/src/analytics/ROIDashboardService.compute.d.ts.map +1 -0
- package/dist/src/analytics/ROIDashboardService.compute.js +134 -0
- package/dist/src/analytics/ROIDashboardService.compute.js.map +1 -0
- package/dist/src/analytics/ROIDashboardService.d.ts +0 -8
- package/dist/src/analytics/ROIDashboardService.d.ts.map +1 -1
- package/dist/src/analytics/ROIDashboardService.js +9 -138
- package/dist/src/analytics/ROIDashboardService.js.map +1 -1
- package/dist/src/api/client.types.d.ts +5 -0
- package/dist/src/api/client.types.d.ts.map +1 -1
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.d.ts.map +1 -1
- package/dist/src/api/index.js +4 -0
- package/dist/src/api/index.js.map +1 -1
- package/dist/src/api/schemas.compatibility.test.d.ts +11 -0
- package/dist/src/api/schemas.compatibility.test.d.ts.map +1 -0
- package/dist/src/api/schemas.compatibility.test.js +38 -0
- package/dist/src/api/schemas.compatibility.test.js.map +1 -0
- package/dist/src/api/schemas.d.ts +16 -9
- package/dist/src/api/schemas.d.ts.map +1 -1
- package/dist/src/api/schemas.js +13 -4
- package/dist/src/api/schemas.js.map +1 -1
- package/dist/src/api/schemas.license.test.d.ts +11 -0
- package/dist/src/api/schemas.license.test.d.ts.map +1 -0
- package/dist/src/api/schemas.license.test.js +51 -0
- package/dist/src/api/schemas.license.test.js.map +1 -0
- package/dist/src/api/types.d.ts +18 -3
- package/dist/src/api/types.d.ts.map +1 -1
- package/dist/src/api/types.js +29 -2
- package/dist/src/api/types.js.map +1 -1
- package/dist/src/audit/remote-audit.d.ts +41 -0
- package/dist/src/audit/remote-audit.d.ts.map +1 -1
- package/dist/src/audit/remote-audit.js +71 -28
- package/dist/src/audit/remote-audit.js.map +1 -1
- package/dist/src/audit/remote-audit.test.js +89 -1
- package/dist/src/audit/remote-audit.test.js.map +1 -1
- package/dist/src/compatibility/slugs.d.ts +34 -0
- package/dist/src/compatibility/slugs.d.ts.map +1 -0
- package/dist/src/compatibility/slugs.js +57 -0
- package/dist/src/compatibility/slugs.js.map +1 -0
- package/dist/src/db/schema-sql.d.ts +1 -1
- package/dist/src/db/schema-sql.d.ts.map +1 -1
- package/dist/src/db/schema-sql.js +1 -1
- package/dist/src/exports/types.d.ts +1 -0
- package/dist/src/exports/types.d.ts.map +1 -1
- package/dist/src/exports/types.js +4 -0
- package/dist/src/exports/types.js.map +1 -1
- package/dist/src/index.d.ts +6 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/indexer/GitHubIndexer.d.ts.map +1 -1
- package/dist/src/indexer/GitHubIndexer.js +1 -1
- package/dist/src/indexer/GitHubIndexer.js.map +1 -1
- package/dist/src/provenance/SourceRecoveryService.d.ts +61 -0
- package/dist/src/provenance/SourceRecoveryService.d.ts.map +1 -0
- package/dist/src/provenance/SourceRecoveryService.js +209 -0
- package/dist/src/provenance/SourceRecoveryService.js.map +1 -0
- package/dist/src/provenance/backfill.d.ts +47 -0
- package/dist/src/provenance/backfill.d.ts.map +1 -0
- package/dist/src/provenance/backfill.js +206 -0
- package/dist/src/provenance/backfill.js.map +1 -0
- package/dist/src/provenance/git-config.d.ts +22 -0
- package/dist/src/provenance/git-config.d.ts.map +1 -0
- package/dist/src/provenance/git-config.js +117 -0
- package/dist/src/provenance/git-config.js.map +1 -0
- package/dist/src/provenance/index.d.ts +13 -0
- package/dist/src/provenance/index.d.ts.map +1 -0
- package/dist/src/provenance/index.js +13 -0
- package/dist/src/provenance/index.js.map +1 -0
- package/dist/src/provenance/local-skill-scan.d.ts +35 -0
- package/dist/src/provenance/local-skill-scan.d.ts.map +1 -0
- package/dist/src/provenance/local-skill-scan.js +79 -0
- package/dist/src/provenance/local-skill-scan.js.map +1 -0
- package/dist/src/provenance/name-variants.d.ts +32 -0
- package/dist/src/provenance/name-variants.d.ts.map +1 -0
- package/dist/src/provenance/name-variants.js +59 -0
- package/dist/src/provenance/name-variants.js.map +1 -0
- package/dist/src/provenance/plugin-manifest.d.ts +16 -0
- package/dist/src/provenance/plugin-manifest.d.ts.map +1 -0
- package/dist/src/provenance/plugin-manifest.js +52 -0
- package/dist/src/provenance/plugin-manifest.js.map +1 -0
- package/dist/src/provenance/types.d.ts +98 -0
- package/dist/src/provenance/types.d.ts.map +1 -0
- package/dist/src/provenance/types.js +21 -0
- package/dist/src/provenance/types.js.map +1 -0
- package/dist/src/repositories/SkillRepository.d.ts.map +1 -1
- package/dist/src/repositories/SkillRepository.js +3 -1
- package/dist/src/repositories/SkillRepository.js.map +1 -1
- package/dist/src/scripts/__tests__/scan-imported-skills.test.js +10 -0
- package/dist/src/scripts/__tests__/scan-imported-skills.test.js.map +1 -1
- package/dist/src/scripts/validation/normalizers.d.ts.map +1 -1
- package/dist/src/scripts/validation/normalizers.js +9 -7
- package/dist/src/scripts/validation/normalizers.js.map +1 -1
- package/dist/src/scripts/validation/types.d.ts +8 -2
- package/dist/src/scripts/validation/types.d.ts.map +1 -1
- package/dist/src/scripts/validation/types.js +10 -2
- package/dist/src/scripts/validation/types.js.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.d.ts +0 -7
- package/dist/src/security/scanner/SecurityScanner.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.exec.d.ts +56 -0
- package/dist/src/security/scanner/SecurityScanner.exec.d.ts.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.exec.js +255 -0
- package/dist/src/security/scanner/SecurityScanner.exec.js.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.helpers.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.helpers.js +17 -1
- package/dist/src/security/scanner/SecurityScanner.helpers.js.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.js +34 -199
- package/dist/src/security/scanner/SecurityScanner.js.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.scanners.d.ts +22 -0
- package/dist/src/security/scanner/SecurityScanner.scanners.d.ts.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.scanners.js +213 -0
- package/dist/src/security/scanner/SecurityScanner.scanners.js.map +1 -0
- package/dist/src/security/scanner/index.d.ts +1 -1
- package/dist/src/security/scanner/index.d.ts.map +1 -1
- package/dist/src/security/scanner/index.js +1 -1
- package/dist/src/security/scanner/index.js.map +1 -1
- package/dist/src/security/scanner/patterns.d.ts +21 -0
- package/dist/src/security/scanner/patterns.d.ts.map +1 -1
- package/dist/src/security/scanner/patterns.js +34 -0
- package/dist/src/security/scanner/patterns.js.map +1 -1
- package/dist/src/security/scanner/types.d.ts +3 -1
- package/dist/src/security/scanner/types.d.ts.map +1 -1
- package/dist/src/security/scanner/weights.d.ts.map +1 -1
- package/dist/src/security/scanner/weights.js +6 -0
- package/dist/src/security/scanner/weights.js.map +1 -1
- package/dist/src/services/SearchService.helpers.d.ts.map +1 -1
- package/dist/src/services/SearchService.helpers.js +2 -0
- package/dist/src/services/SearchService.helpers.js.map +1 -1
- package/dist/src/services/SearchService.types.d.ts +1 -0
- package/dist/src/services/SearchService.types.d.ts.map +1 -1
- package/dist/src/services/skill-installation.helpers.d.ts +2 -26
- package/dist/src/services/skill-installation.helpers.d.ts.map +1 -1
- package/dist/src/services/skill-installation.helpers.js +2 -120
- package/dist/src/services/skill-installation.helpers.js.map +1 -1
- package/dist/src/services/skill-installation.io.d.ts +43 -0
- package/dist/src/services/skill-installation.io.d.ts.map +1 -0
- package/dist/src/services/skill-installation.io.js +176 -0
- package/dist/src/services/skill-installation.io.js.map +1 -0
- package/dist/src/services/skill-installation.service.d.ts.map +1 -1
- package/dist/src/services/skill-installation.service.js +51 -113
- package/dist/src/services/skill-installation.service.js.map +1 -1
- package/dist/src/services/skill-installation.types.d.ts.map +1 -1
- package/dist/src/services/skill-installation.types.js +9 -0
- package/dist/src/services/skill-installation.types.js.map +1 -1
- package/dist/src/services/skill-installation.validate.d.ts +44 -0
- package/dist/src/services/skill-installation.validate.d.ts.map +1 -0
- package/dist/src/services/skill-installation.validate.js +134 -0
- package/dist/src/services/skill-installation.validate.js.map +1 -0
- package/dist/src/telemetry/wrap.d.ts +1 -1
- package/dist/src/telemetry/wrap.d.ts.map +1 -1
- package/dist/src/telemetry/wrap.js.map +1 -1
- package/dist/src/types/skill.d.ts +6 -1
- package/dist/src/types/skill.d.ts.map +1 -1
- package/dist/src/types/skill.js.map +1 -1
- package/dist/src/types.d.ts +25 -1
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +3 -1
- package/dist/src/types.js.map +1 -1
- package/dist/tests/GitHubIndexer.edge-cases.test.js +2 -2
- package/dist/tests/GitHubIndexer.repository.test.d.ts +10 -0
- package/dist/tests/GitHubIndexer.repository.test.d.ts.map +1 -0
- package/dist/tests/GitHubIndexer.repository.test.js +190 -0
- package/dist/tests/GitHubIndexer.repository.test.js.map +1 -0
- package/dist/tests/GitHubIndexer.test.d.ts +2 -1
- package/dist/tests/GitHubIndexer.test.d.ts.map +1 -1
- package/dist/tests/GitHubIndexer.test.js +5 -183
- package/dist/tests/GitHubIndexer.test.js.map +1 -1
- package/dist/tests/QuarantineRepository.review-state-machine.test.d.ts +14 -0
- package/dist/tests/QuarantineRepository.review-state-machine.test.d.ts.map +1 -0
- package/dist/tests/QuarantineRepository.review-state-machine.test.js +296 -0
- package/dist/tests/QuarantineRepository.review-state-machine.test.js.map +1 -0
- package/dist/tests/RawUrlSourceAdapter.security.test.js +3 -2
- package/dist/tests/RawUrlSourceAdapter.security.test.js.map +1 -1
- package/dist/tests/SecurityScanner.exec.test.d.ts +11 -0
- package/dist/tests/SecurityScanner.exec.test.d.ts.map +1 -0
- package/dist/tests/SecurityScanner.exec.test.js +190 -0
- package/dist/tests/SecurityScanner.exec.test.js.map +1 -0
- package/dist/tests/SwarmIndexer.test.js +2 -2
- package/dist/tests/api/client.validation.test.js +7 -4
- package/dist/tests/api/client.validation.test.js.map +1 -1
- package/dist/tests/fixtures/api-responses/index.js +1 -1
- package/dist/tests/fixtures/api-responses/index.js.map +1 -1
- package/dist/tests/integration/QuarantineService.concurrency.test.d.ts +34 -0
- package/dist/tests/integration/QuarantineService.concurrency.test.d.ts.map +1 -0
- package/dist/tests/integration/QuarantineService.concurrency.test.js +280 -0
- package/dist/tests/integration/QuarantineService.concurrency.test.js.map +1 -0
- package/dist/tests/integration/openapi-contract.test.d.ts +16 -0
- package/dist/tests/integration/openapi-contract.test.d.ts.map +1 -0
- package/dist/tests/integration/openapi-contract.test.js +231 -0
- package/dist/tests/integration/openapi-contract.test.js.map +1 -0
- package/dist/tests/provenance/SourceRecoveryService.test.d.ts +2 -0
- package/dist/tests/provenance/SourceRecoveryService.test.d.ts.map +1 -0
- package/dist/tests/provenance/SourceRecoveryService.test.js +205 -0
- package/dist/tests/provenance/SourceRecoveryService.test.js.map +1 -0
- package/dist/tests/provenance/backfill.test.d.ts +2 -0
- package/dist/tests/provenance/backfill.test.d.ts.map +1 -0
- package/dist/tests/provenance/backfill.test.js +296 -0
- package/dist/tests/provenance/backfill.test.js.map +1 -0
- package/dist/tests/provenance/git-config.test.d.ts +2 -0
- package/dist/tests/provenance/git-config.test.d.ts.map +1 -0
- package/dist/tests/provenance/git-config.test.js +73 -0
- package/dist/tests/provenance/git-config.test.js.map +1 -0
- package/dist/tests/provenance/local-skill-scan.test.d.ts +2 -0
- package/dist/tests/provenance/local-skill-scan.test.d.ts.map +1 -0
- package/dist/tests/provenance/local-skill-scan.test.js +51 -0
- package/dist/tests/provenance/local-skill-scan.test.js.map +1 -0
- package/dist/tests/provenance/name-variants.test.d.ts +2 -0
- package/dist/tests/provenance/name-variants.test.d.ts.map +1 -0
- package/dist/tests/provenance/name-variants.test.js +51 -0
- package/dist/tests/provenance/name-variants.test.js.map +1 -0
- package/dist/tests/provenance/plugin-manifest.test.d.ts +2 -0
- package/dist/tests/provenance/plugin-manifest.test.d.ts.map +1 -0
- package/dist/tests/provenance/plugin-manifest.test.js +49 -0
- package/dist/tests/provenance/plugin-manifest.test.js.map +1 -0
- package/dist/tests/scripts/import-to-database.test.js +7 -1
- package/dist/tests/scripts/import-to-database.test.js.map +1 -1
- package/dist/tests/security/scanner-regression-guard.test.js +5 -1
- package/dist/tests/security/scanner-regression-guard.test.js.map +1 -1
- package/dist/tests/security.test.js +45 -0
- package/dist/tests/security.test.js.map +1 -1
- package/dist/tests/services/aidefence-feedback.test.js +2 -0
- package/dist/tests/services/aidefence-feedback.test.js.map +1 -1
- package/dist/tests/services/dep-quarantine-check.test.js +1 -1
- package/dist/tests/services/dep-quarantine-check.test.js.map +1 -1
- package/dist/tests/skill-scanner/allowlist.test.js +8 -1
- package/dist/tests/skill-scanner/allowlist.test.js.map +1 -1
- package/dist/tests/unit/services/skill-installation.gap1.test.d.ts +12 -0
- package/dist/tests/unit/services/skill-installation.gap1.test.d.ts.map +1 -0
- package/dist/tests/unit/services/skill-installation.gap1.test.js +96 -0
- package/dist/tests/unit/services/skill-installation.gap1.test.js.map +1 -0
- package/dist/tests/unit/services/skill-installation.io.test.d.ts +11 -0
- package/dist/tests/unit/services/skill-installation.io.test.d.ts.map +1 -0
- package/dist/tests/unit/services/skill-installation.io.test.js +130 -0
- package/dist/tests/unit/services/skill-installation.io.test.js.map +1 -0
- package/dist/tests/validate-skills.dedup.test.d.ts +5 -0
- package/dist/tests/validate-skills.dedup.test.d.ts.map +1 -0
- package/dist/tests/validate-skills.dedup.test.js +241 -0
- package/dist/tests/validate-skills.dedup.test.js.map +1 -0
- package/dist/tests/validate-skills.normalizers.test.d.ts +5 -0
- package/dist/tests/validate-skills.normalizers.test.d.ts.map +1 -0
- package/dist/tests/validate-skills.normalizers.test.js +109 -0
- package/dist/tests/validate-skills.normalizers.test.js.map +1 -0
- package/dist/tests/validate-skills.test.d.ts +1 -1
- package/dist/tests/validate-skills.test.js +7 -345
- package/dist/tests/validate-skills.test.js.map +1 -1
- package/package.json +19 -8
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tiered source-recovery orchestrator.
|
|
3
|
+
* @module @skillsmith/core/provenance/SourceRecoveryService
|
|
4
|
+
* @see SMI-5407
|
|
5
|
+
*
|
|
6
|
+
* Resolves the canonical GitHub source of each locally-installed skill by
|
|
7
|
+
* trying tiers in confidence order and short-circuiting on the first hit:
|
|
8
|
+
* git remote (exact) -> plugin manifest (high) -> registry name
|
|
9
|
+
* (medium/low, review-only) -> embedding (opt-in) -> hints (opt-in) -> unknown.
|
|
10
|
+
*
|
|
11
|
+
* The git and plugin tiers are local file reads: they resolve the SOURCE
|
|
12
|
+
* before any injected dependency is invoked. They then make a best-effort,
|
|
13
|
+
* OFFLINE call to the optional `findRegistryIdByRepoUrl` (local catalog lookup)
|
|
14
|
+
* to enrich the manifest id with a registry UUID (SMI-5411) -- never network,
|
|
15
|
+
* never throws. An offline run never touches the network.
|
|
16
|
+
*/
|
|
17
|
+
import * as os from 'os';
|
|
18
|
+
import * as path from 'path';
|
|
19
|
+
import { SkillParser } from '../indexer/SkillParser.js';
|
|
20
|
+
import { parseGitConfigRemote, normalizeGitHubRemote } from './git-config.js';
|
|
21
|
+
import { parsePluginManifestRepository } from './plugin-manifest.js';
|
|
22
|
+
import { scanLocalSkills } from './local-skill-scan.js';
|
|
23
|
+
const frontmatterParser = new SkillParser({ requireName: false });
|
|
24
|
+
/** Default install root for locally-installed skills. */
|
|
25
|
+
export function defaultSkillsRoot() {
|
|
26
|
+
return path.join(os.homedir(), '.claude', 'skills');
|
|
27
|
+
}
|
|
28
|
+
function candidateSource(candidate) {
|
|
29
|
+
return { owner: candidate.owner, repo: candidate.repo, url: candidate.url };
|
|
30
|
+
}
|
|
31
|
+
function resolvedResult(skillName, installPath, source, method, confidence, registryId, candidates = []) {
|
|
32
|
+
return {
|
|
33
|
+
skillName,
|
|
34
|
+
installPath,
|
|
35
|
+
recoveredSource: source,
|
|
36
|
+
registryId,
|
|
37
|
+
method,
|
|
38
|
+
confidence,
|
|
39
|
+
candidates,
|
|
40
|
+
status: 'recovered',
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function unknownResult(skillName, installPath) {
|
|
44
|
+
return {
|
|
45
|
+
skillName,
|
|
46
|
+
installPath,
|
|
47
|
+
recoveredSource: null,
|
|
48
|
+
registryId: null,
|
|
49
|
+
method: null,
|
|
50
|
+
confidence: 'unknown',
|
|
51
|
+
candidates: [],
|
|
52
|
+
status: 'unknown',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function backupResult(skillName, installPath) {
|
|
56
|
+
return {
|
|
57
|
+
skillName,
|
|
58
|
+
installPath,
|
|
59
|
+
recoveredSource: null,
|
|
60
|
+
registryId: null,
|
|
61
|
+
method: null,
|
|
62
|
+
confidence: 'unknown',
|
|
63
|
+
candidates: [],
|
|
64
|
+
status: 'skipped_backup',
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** GitHub usernames: 1-39 chars, alphanumeric or hyphen, no leading/trailing hyphen. */
|
|
68
|
+
function isPlausibleOwner(value) {
|
|
69
|
+
return /^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,37}[a-zA-Z0-9])?$/.test(value);
|
|
70
|
+
}
|
|
71
|
+
function computeSummary(skills) {
|
|
72
|
+
const summary = {
|
|
73
|
+
total: skills.length,
|
|
74
|
+
recovered: 0,
|
|
75
|
+
already_tracked: 0,
|
|
76
|
+
unknown: 0,
|
|
77
|
+
skipped_backup: 0,
|
|
78
|
+
};
|
|
79
|
+
for (const skill of skills) {
|
|
80
|
+
summary[skill.status] += 1;
|
|
81
|
+
}
|
|
82
|
+
return summary;
|
|
83
|
+
}
|
|
84
|
+
export class SourceRecoveryService {
|
|
85
|
+
deps;
|
|
86
|
+
constructor(deps) {
|
|
87
|
+
this.deps = deps;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Best-effort OFFLINE registry-UUID enrichment for a git/plugin source URL.
|
|
91
|
+
* Returns the catalog UUID for `url`'s repo when the optional dep is wired and
|
|
92
|
+
* a match exists; null otherwise. NEVER throws -- a missing dep, a no-match, or
|
|
93
|
+
* a dep failure all degrade to null so the already-resolved (exact/high) source
|
|
94
|
+
* is never lost. SMI-5411.
|
|
95
|
+
*/
|
|
96
|
+
async enrichRegistryId(url) {
|
|
97
|
+
if (!this.deps.findRegistryIdByRepoUrl)
|
|
98
|
+
return null;
|
|
99
|
+
try {
|
|
100
|
+
return await this.deps.findRegistryIdByRepoUrl(url);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** Scan `skillsRoot` and resolve every (non-filtered) skill's source. */
|
|
107
|
+
async recoverSources(opts = {}) {
|
|
108
|
+
const skillsRoot = opts.skillsRoot ?? defaultSkillsRoot();
|
|
109
|
+
const entries = await scanLocalSkills(skillsRoot);
|
|
110
|
+
const only = opts.only;
|
|
111
|
+
const filtered = only && only.length > 0 ? entries.filter((e) => only.includes(e.skillName)) : entries;
|
|
112
|
+
const skills = [];
|
|
113
|
+
for (const entry of filtered) {
|
|
114
|
+
if (entry.isBackup) {
|
|
115
|
+
skills.push(backupResult(entry.skillName, entry.dir));
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
skills.push(await this.recoverOne(entry.dir, entry.skillName, entry.skillMd, {
|
|
119
|
+
enableEmbedding: opts.enableEmbedding,
|
|
120
|
+
enableCatalogHint: opts.enableCatalogHint,
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
return { skills, summary: computeSummary(skills) };
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Resolve a single skill directory. Tiers short-circuit on the first hit; the
|
|
127
|
+
* git and plugin tiers resolve the source before any dependency call, then
|
|
128
|
+
* best-effort enrich the manifest id with a registry UUID via the optional,
|
|
129
|
+
* OFFLINE `findRegistryIdByRepoUrl`.
|
|
130
|
+
*/
|
|
131
|
+
async recoverOne(dir, skillName, skillMd, opts = {}) {
|
|
132
|
+
// Tier 1: git remote (exact) -- source is offline; the id enrichment is a
|
|
133
|
+
// best-effort offline catalog lookup (SMI-5411).
|
|
134
|
+
const git = parseGitConfigRemote(dir);
|
|
135
|
+
if (git) {
|
|
136
|
+
const registryId = await this.enrichRegistryId(git.url);
|
|
137
|
+
return resolvedResult(skillName, dir, git, 'git-remote', 'exact', registryId);
|
|
138
|
+
}
|
|
139
|
+
// Tier 2: plugin manifest (high) -- same offline id enrichment.
|
|
140
|
+
const plugin = parsePluginManifestRepository(dir);
|
|
141
|
+
if (plugin) {
|
|
142
|
+
const registryId = await this.enrichRegistryId(plugin.url);
|
|
143
|
+
return resolvedResult(skillName, dir, plugin, 'plugin-json', 'high', registryId);
|
|
144
|
+
}
|
|
145
|
+
// Tier 3: registry name match (medium/low) -- REVIEW ONLY.
|
|
146
|
+
const candidates = await this.deps.findCandidatesByName(skillName);
|
|
147
|
+
if (candidates.length === 1) {
|
|
148
|
+
const candidate = candidates[0];
|
|
149
|
+
return resolvedResult(skillName, dir, candidateSource(candidate), 'registry-name', 'medium', candidate.id);
|
|
150
|
+
}
|
|
151
|
+
if (candidates.length > 1) {
|
|
152
|
+
return this.resolveAmbiguous(dir, skillName, skillMd, candidates, opts);
|
|
153
|
+
}
|
|
154
|
+
// Tier 5: hints (opt-in, low).
|
|
155
|
+
if (opts.enableCatalogHint) {
|
|
156
|
+
const hint = await this.resolveHint(dir, skillName, skillMd);
|
|
157
|
+
if (hint)
|
|
158
|
+
return hint;
|
|
159
|
+
}
|
|
160
|
+
// Tier 6: unknown.
|
|
161
|
+
return unknownResult(skillName, dir);
|
|
162
|
+
}
|
|
163
|
+
/** Tier 4: embedding tiebreak over an ambiguous candidate set (opt-in). */
|
|
164
|
+
async resolveAmbiguous(dir, skillName, skillMd, candidates, opts) {
|
|
165
|
+
if (opts.enableEmbedding && this.deps.rankByEmbedding && skillMd) {
|
|
166
|
+
const ranked = await this.deps.rankByEmbedding(skillName, skillMd, candidates);
|
|
167
|
+
if (ranked.length > 0) {
|
|
168
|
+
const top = ranked[0];
|
|
169
|
+
return resolvedResult(skillName, dir, candidateSource(top), 'registry-embedding', 'low', top.id, ranked);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Ambiguous and unresolved: surface the candidates for review.
|
|
173
|
+
return {
|
|
174
|
+
skillName,
|
|
175
|
+
installPath: dir,
|
|
176
|
+
recoveredSource: null,
|
|
177
|
+
registryId: null,
|
|
178
|
+
method: 'registry-name',
|
|
179
|
+
confidence: 'low',
|
|
180
|
+
candidates,
|
|
181
|
+
status: 'unknown',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/** Tier 5 helper: catalog repo_url then a speculative frontmatter author. */
|
|
185
|
+
async resolveHint(dir, skillName, skillMd) {
|
|
186
|
+
if (this.deps.lookupCatalogRepoUrl) {
|
|
187
|
+
const url = await this.deps.lookupCatalogRepoUrl(skillName);
|
|
188
|
+
if (url) {
|
|
189
|
+
const source = normalizeGitHubRemote(url);
|
|
190
|
+
if (source)
|
|
191
|
+
return resolvedResult(skillName, dir, source, 'catalog-db', 'low', null);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (skillMd) {
|
|
195
|
+
const fm = frontmatterParser.extractFrontmatter(skillMd);
|
|
196
|
+
const author = typeof fm?.author === 'string' ? fm.author.trim() : '';
|
|
197
|
+
if (author && isPlausibleOwner(author)) {
|
|
198
|
+
const source = {
|
|
199
|
+
owner: author,
|
|
200
|
+
repo: skillName,
|
|
201
|
+
url: `https://github.com/${author}/${skillName}`,
|
|
202
|
+
};
|
|
203
|
+
return resolvedResult(skillName, dir, source, 'author-hint', 'low', null);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=SourceRecoveryService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SourceRecoveryService.js","sourceRoot":"","sources":["../../../src/provenance/SourceRecoveryService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAA;AACxB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAE5B,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAEvD,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AAC7E,OAAO,EAAE,6BAA6B,EAAE,MAAM,sBAAsB,CAAA;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AA4BvD,MAAM,iBAAiB,GAAG,IAAI,WAAW,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAA;AAEjE,yDAAyD;AACzD,MAAM,UAAU,iBAAiB;IAC/B,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAA;AACrD,CAAC;AAED,SAAS,eAAe,CAAC,SAA4B;IACnD,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,CAAC,GAAG,EAAE,CAAA;AAC7E,CAAC;AAED,SAAS,cAAc,CACrB,SAAiB,EACjB,WAAmB,EACnB,MAAuB,EACvB,MAAqC,EACrC,UAA6C,EAC7C,UAAyB,EACzB,aAAkC,EAAE;IAEpC,OAAO;QACL,SAAS;QACT,WAAW;QACX,eAAe,EAAE,MAAM;QACvB,UAAU;QACV,MAAM;QACN,UAAU;QACV,UAAU;QACV,MAAM,EAAE,WAAW;KACpB,CAAA;AACH,CAAC;AAED,SAAS,aAAa,CAAC,SAAiB,EAAE,WAAmB;IAC3D,OAAO;QACL,SAAS;QACT,WAAW;QACX,eAAe,EAAE,IAAI;QACrB,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE,IAAI;QACZ,UAAU,EAAE,SAAS;QACrB,UAAU,EAAE,EAAE;QACd,MAAM,EAAE,SAAS;KAClB,CAAA;AACH,CAAC;AAED,SAAS,YAAY,CAAC,SAAiB,EAAE,WAAmB;IAC1D,OAAO;QACL,SAAS;QACT,WAAW;QACX,eAAe,EAAE,IAAI;QACrB,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE,IAAI;QACZ,UAAU,EAAE,SAAS;QACrB,UAAU,EAAE,EAAE;QACd,MAAM,EAAE,gBAAgB;KACzB,CAAA;AACH,CAAC;AAED,wFAAwF;AACxF,SAAS,gBAAgB,CAAC,KAAa;IACrC,OAAO,iDAAiD,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AACtE,CAAC;AAED,SAAS,cAAc,CAAC,MAA6B;IACnD,MAAM,OAAO,GAAoB;QAC/B,KAAK,EAAE,MAAM,CAAC,MAAM;QACpB,SAAS,EAAE,CAAC;QACZ,eAAe,EAAE,CAAC;QAClB,OAAO,EAAE,CAAC;QACV,cAAc,EAAE,CAAC;KAClB,CAAA;IACD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAC5B,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,MAAM,OAAO,qBAAqB;IACH;IAA7B,YAA6B,IAAkB;QAAlB,SAAI,GAAJ,IAAI,CAAc;IAAG,CAAC;IAEnD;;;;;;OAMG;IACK,KAAK,CAAC,gBAAgB,CAAC,GAAW;QACxC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,uBAAuB;YAAE,OAAO,IAAI,CAAA;QACnD,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,CAAA;QACrD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,KAAK,CAAC,cAAc,CAAC,OAA8B,EAAE;QACnD,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,iBAAiB,EAAE,CAAA;QACzD,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,UAAU,CAAC,CAAA;QACjD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAA;QACtB,MAAM,QAAQ,GACZ,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAA;QAEvF,MAAM,MAAM,GAA0B,EAAE,CAAA;QACxC,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;YAC7B,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACnB,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAA;gBACrD,SAAQ;YACV,CAAC;YACD,MAAM,CAAC,IAAI,CACT,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE;gBAC/D,eAAe,EAAE,IAAI,CAAC,eAAe;gBACrC,iBAAiB,EAAE,IAAI,CAAC,iBAAiB;aAC1C,CAAC,CACH,CAAA;QACH,CAAC;QAED,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,CAAC,MAAM,CAAC,EAAE,CAAA;IACpD,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,UAAU,CACd,GAAW,EACX,SAAiB,EACjB,OAAsB,EACtB,OAA0B,EAAE;QAE5B,0EAA0E;QAC1E,iDAAiD;QACjD,MAAM,GAAG,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAA;QACrC,IAAI,GAAG,EAAE,CAAC;YACR,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YACvD,OAAO,cAAc,CAAC,SAAS,EAAE,GAAG,EAAE,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,CAAC,CAAA;QAC/E,CAAC;QAED,gEAAgE;QAChE,MAAM,MAAM,GAAG,6BAA6B,CAAC,GAAG,CAAC,CAAA;QACjD,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YAC1D,OAAO,cAAc,CAAC,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,UAAU,CAAC,CAAA;QAClF,CAAC;QAED,2DAA2D;QAC3D,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAA;QAClE,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,MAAM,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC,CAAA;YAC/B,OAAO,cAAc,CACnB,SAAS,EACT,GAAG,EACH,eAAe,CAAC,SAAS,CAAC,EAC1B,eAAe,EACf,QAAQ,EACR,SAAS,CAAC,EAAE,CACb,CAAA;QACH,CAAC;QACD,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1B,OAAO,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,CAAC,CAAA;QACzE,CAAC;QAED,+BAA+B;QAC/B,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,CAAC,CAAA;YAC5D,IAAI,IAAI;gBAAE,OAAO,IAAI,CAAA;QACvB,CAAC;QAED,mBAAmB;QACnB,OAAO,aAAa,CAAC,SAAS,EAAE,GAAG,CAAC,CAAA;IACtC,CAAC;IAED,2EAA2E;IACnE,KAAK,CAAC,gBAAgB,CAC5B,GAAW,EACX,SAAiB,EACjB,OAAsB,EACtB,UAA+B,EAC/B,IAAuB;QAEvB,IAAI,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC,IAAI,CAAC,eAAe,IAAI,OAAO,EAAE,CAAC;YACjE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,OAAO,EAAE,UAAU,CAAC,CAAA;YAC9E,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACtB,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;gBACrB,OAAO,cAAc,CACnB,SAAS,EACT,GAAG,EACH,eAAe,CAAC,GAAG,CAAC,EACpB,oBAAoB,EACpB,KAAK,EACL,GAAG,CAAC,EAAE,EACN,MAAM,CACP,CAAA;YACH,CAAC;QACH,CAAC;QACD,+DAA+D;QAC/D,OAAO;YACL,SAAS;YACT,WAAW,EAAE,GAAG;YAChB,eAAe,EAAE,IAAI;YACrB,UAAU,EAAE,IAAI;YAChB,MAAM,EAAE,eAAe;YACvB,UAAU,EAAE,KAAK;YACjB,UAAU;YACV,MAAM,EAAE,SAAS;SAClB,CAAA;IACH,CAAC;IAED,6EAA6E;IACrE,KAAK,CAAC,WAAW,CACvB,GAAW,EACX,SAAiB,EACjB,OAAsB;QAEtB,IAAI,IAAI,CAAC,IAAI,CAAC,oBAAoB,EAAE,CAAC;YACnC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAA;YAC3D,IAAI,GAAG,EAAE,CAAC;gBACR,MAAM,MAAM,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAA;gBACzC,IAAI,MAAM;oBAAE,OAAO,cAAc,CAAC,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,IAAI,CAAC,CAAA;YACtF,CAAC;QACH,CAAC;QAED,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,EAAE,GAAG,iBAAiB,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAA;YACxD,MAAM,MAAM,GAAG,OAAO,EAAE,EAAE,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;YACrE,IAAI,MAAM,IAAI,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC;gBACvC,MAAM,MAAM,GAAoB;oBAC9B,KAAK,EAAE,MAAM;oBACb,IAAI,EAAE,SAAS;oBACf,GAAG,EAAE,sBAAsB,MAAM,IAAI,SAAS,EAAE;iBACjD,CAAA;gBACD,OAAO,cAAc,CAAC,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,IAAI,CAAC,CAAA;YAC3E,CAAC;QACH,CAAC;QAED,OAAO,IAAI,CAAA;IACb,CAAC;CACF"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Backfill recovered sources into the skill manifest.
|
|
3
|
+
* @module @skillsmith/core/provenance/backfill
|
|
4
|
+
* @see SMI-5407
|
|
5
|
+
*
|
|
6
|
+
* Plans manifest entries from a {@link RecoveryReport} and (when `apply`)
|
|
7
|
+
* writes them via a single `ManifestManager.updateSafely` merge that ADDs or
|
|
8
|
+
* fills-missing `source`/`id` but NEVER clobbers a healthy existing entry --
|
|
9
|
+
* even when the recovered value differs. Idempotent: a second run is a no-op.
|
|
10
|
+
*
|
|
11
|
+
* Backfill keys (load-bearing, per plan-review):
|
|
12
|
+
* - `id` = registry UUID when a registry tier resolved it, else `owner/skill-name`.
|
|
13
|
+
* - `source` = `https://github.com/<owner>/<repo>` (the form buildRawUrl accepts).
|
|
14
|
+
*/
|
|
15
|
+
import type { SkillManifestEntry } from '../services/skill-installation.types.js';
|
|
16
|
+
import type { RecoveryConfidence, RecoveryReport } from './types.js';
|
|
17
|
+
/** Options controlling a backfill run. */
|
|
18
|
+
export interface BackfillOptions {
|
|
19
|
+
/** Manifest path. Defaults to `~/.skillsmith/manifest.json`. */
|
|
20
|
+
manifestPath?: string;
|
|
21
|
+
/** Minimum confidence to auto-qualify (default `high`). */
|
|
22
|
+
minConfidence?: RecoveryConfidence;
|
|
23
|
+
/** Write changes. When false (default), the run is a dry-run plan only. */
|
|
24
|
+
apply?: boolean;
|
|
25
|
+
/** Opt-in: write `repository:` into non-git skills' SKILL.md frontmatter. */
|
|
26
|
+
writeFrontmatter?: boolean;
|
|
27
|
+
/** Explicit `<dirName> -> 'owner/repo'` overrides (resolved user-specified). */
|
|
28
|
+
setOverrides?: Record<string, string>;
|
|
29
|
+
/** Injected `lastUpdated` ISO timestamp for deterministic tests. */
|
|
30
|
+
now?: string;
|
|
31
|
+
/** Injected content hasher (defaults to the shared `hashContent`). */
|
|
32
|
+
hashContent?: (content: string) => string;
|
|
33
|
+
}
|
|
34
|
+
/** Outcome of a backfill run. */
|
|
35
|
+
export interface BackfillOutcome {
|
|
36
|
+
/** Entries planned for write (all that qualify), regardless of `apply`. */
|
|
37
|
+
planned: SkillManifestEntry[];
|
|
38
|
+
/** Skill names actually written (apply only). */
|
|
39
|
+
written: string[];
|
|
40
|
+
/** Skill names skipped (unqualified, bad override, or healthy/clobber-protected). */
|
|
41
|
+
skipped: string[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Plan and (optionally) apply manifest backfill for recovered sources.
|
|
45
|
+
*/
|
|
46
|
+
export declare function backfillManifest(report: RecoveryReport, opts?: BackfillOptions): Promise<BackfillOutcome>;
|
|
47
|
+
//# sourceMappingURL=backfill.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backfill.d.ts","sourceRoot":"","sources":["../../../src/provenance/backfill.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AASH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAA;AAEjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAuB,MAAM,YAAY,CAAA;AAEzF,0CAA0C;AAC1C,MAAM,WAAW,eAAe;IAC9B,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,2DAA2D;IAC3D,aAAa,CAAC,EAAE,kBAAkB,CAAA;IAClC,2EAA2E;IAC3E,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,gFAAgF;IAChF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACrC,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,sEAAsE;IACtE,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAA;CAC1C;AAED,iCAAiC;AACjC,MAAM,WAAW,eAAe;IAC9B,2EAA2E;IAC3E,OAAO,EAAE,kBAAkB,EAAE,CAAA;IAC7B,iDAAiD;IACjD,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,qFAAqF;IACrF,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAkKD;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,cAAc,EACtB,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,eAAe,CAAC,CAmD1B"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Backfill recovered sources into the skill manifest.
|
|
3
|
+
* @module @skillsmith/core/provenance/backfill
|
|
4
|
+
* @see SMI-5407
|
|
5
|
+
*
|
|
6
|
+
* Plans manifest entries from a {@link RecoveryReport} and (when `apply`)
|
|
7
|
+
* writes them via a single `ManifestManager.updateSafely` merge that ADDs or
|
|
8
|
+
* fills-missing `source`/`id` but NEVER clobbers a healthy existing entry --
|
|
9
|
+
* even when the recovered value differs. Idempotent: a second run is a no-op.
|
|
10
|
+
*
|
|
11
|
+
* Backfill keys (load-bearing, per plan-review):
|
|
12
|
+
* - `id` = registry UUID when a registry tier resolved it, else `owner/skill-name`.
|
|
13
|
+
* - `source` = `https://github.com/<owner>/<repo>` (the form buildRawUrl accepts).
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync } from 'fs';
|
|
16
|
+
import * as fs from 'fs/promises';
|
|
17
|
+
import * as os from 'os';
|
|
18
|
+
import * as path from 'path';
|
|
19
|
+
import { ManifestManager } from '../services/skill-manifest.js';
|
|
20
|
+
import { hashContent as defaultHashContent } from '../services/skill-installation.helpers.js';
|
|
21
|
+
const CONFIDENCE_RANK = {
|
|
22
|
+
unknown: 0,
|
|
23
|
+
low: 1,
|
|
24
|
+
medium: 2,
|
|
25
|
+
high: 3,
|
|
26
|
+
exact: 4,
|
|
27
|
+
'user-specified': 5,
|
|
28
|
+
};
|
|
29
|
+
function defaultManifestPath() {
|
|
30
|
+
return path.join(os.homedir(), '.skillsmith', 'manifest.json');
|
|
31
|
+
}
|
|
32
|
+
function nonEmpty(value) {
|
|
33
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
34
|
+
}
|
|
35
|
+
/** A manifest entry is "healthy" once it carries both a non-empty source and id. */
|
|
36
|
+
function isHealthy(entry) {
|
|
37
|
+
return nonEmpty(entry.source) && nonEmpty(entry.id);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Does a result qualify for backfill? The `minConfidence` floor IS the gate
|
|
41
|
+
* (default `high` -> only exact/high auto-write). Lowering it to `medium`
|
|
42
|
+
* admits a single registry-name match (which carries the registry UUID, so
|
|
43
|
+
* `skill_outdated` can resolve it); ambiguous multi-candidate results never
|
|
44
|
+
* qualify because their `recoveredSource` is null (planResult drops them).
|
|
45
|
+
* `unknown` never qualifies. SMI-5407.
|
|
46
|
+
*/
|
|
47
|
+
function qualifies(confidence, minConfidence) {
|
|
48
|
+
if (confidence === 'unknown')
|
|
49
|
+
return false;
|
|
50
|
+
return CONFIDENCE_RANK[confidence] >= CONFIDENCE_RANK[minConfidence];
|
|
51
|
+
}
|
|
52
|
+
/** Parse an `owner/repo` override spec. */
|
|
53
|
+
function parseOwnerRepo(spec) {
|
|
54
|
+
const parts = spec.split('/').filter(Boolean);
|
|
55
|
+
if (parts.length !== 2)
|
|
56
|
+
return null;
|
|
57
|
+
return { owner: parts[0], repo: parts[1] };
|
|
58
|
+
}
|
|
59
|
+
/** Build a complete manifest entry, deriving installedAt/hash from SKILL.md. */
|
|
60
|
+
async function buildEntry(params) {
|
|
61
|
+
const { installPath, skillName, owner, sourceUrl, registryId, now, hash } = params;
|
|
62
|
+
const skillMdPath = path.join(installPath, 'SKILL.md');
|
|
63
|
+
let installedAt = now;
|
|
64
|
+
let originalContentHash;
|
|
65
|
+
try {
|
|
66
|
+
const stat = await fs.stat(skillMdPath);
|
|
67
|
+
installedAt = stat.mtime.toISOString();
|
|
68
|
+
const content = await fs.readFile(skillMdPath, 'utf-8');
|
|
69
|
+
originalContentHash = hash(content);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// SKILL.md unreadable -- fall back to `now` and leave hash undefined.
|
|
73
|
+
}
|
|
74
|
+
const id = nonEmpty(registryId ?? undefined) ? registryId : `${owner}/${skillName}`;
|
|
75
|
+
return {
|
|
76
|
+
id,
|
|
77
|
+
name: skillName,
|
|
78
|
+
version: '1.0.0',
|
|
79
|
+
source: sourceUrl,
|
|
80
|
+
installPath,
|
|
81
|
+
installedAt,
|
|
82
|
+
lastUpdated: now,
|
|
83
|
+
originalContentHash,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/** Plan one entry from a result; returns null when it does not qualify. */
|
|
87
|
+
async function planResult(result, overrides, minConfidence, now, hash) {
|
|
88
|
+
const override = overrides[result.skillName];
|
|
89
|
+
if (override) {
|
|
90
|
+
const ownerRepo = parseOwnerRepo(override);
|
|
91
|
+
if (!ownerRepo)
|
|
92
|
+
return null;
|
|
93
|
+
return buildEntry({
|
|
94
|
+
installPath: result.installPath,
|
|
95
|
+
skillName: result.skillName,
|
|
96
|
+
owner: ownerRepo.owner,
|
|
97
|
+
sourceUrl: `https://github.com/${ownerRepo.owner}/${ownerRepo.repo}`,
|
|
98
|
+
registryId: null,
|
|
99
|
+
now,
|
|
100
|
+
hash,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (!qualifies(result.confidence, minConfidence) || !result.recoveredSource)
|
|
104
|
+
return null;
|
|
105
|
+
return buildEntry({
|
|
106
|
+
installPath: result.installPath,
|
|
107
|
+
skillName: result.skillName,
|
|
108
|
+
owner: result.recoveredSource.owner,
|
|
109
|
+
sourceUrl: result.recoveredSource.url,
|
|
110
|
+
registryId: result.registryId,
|
|
111
|
+
now,
|
|
112
|
+
hash,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/** Fill missing fields from `planned` onto an unhealthy `existing` entry. */
|
|
116
|
+
function mergeEntry(existing, planned) {
|
|
117
|
+
return {
|
|
118
|
+
...existing,
|
|
119
|
+
id: nonEmpty(existing.id) ? existing.id : planned.id,
|
|
120
|
+
name: nonEmpty(existing.name) ? existing.name : planned.name,
|
|
121
|
+
version: nonEmpty(existing.version) ? existing.version : planned.version,
|
|
122
|
+
source: planned.source, // existing is unhealthy (no source) -- fill it
|
|
123
|
+
installPath: nonEmpty(existing.installPath) ? existing.installPath : planned.installPath,
|
|
124
|
+
installedAt: nonEmpty(existing.installedAt) ? existing.installedAt : planned.installedAt,
|
|
125
|
+
lastUpdated: planned.lastUpdated,
|
|
126
|
+
originalContentHash: existing.originalContentHash ?? planned.originalContentHash,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Write `repository: <url>` into a non-git skill's SKILL.md frontmatter.
|
|
131
|
+
* Skips git checkouts and skills that already declare a `repository:`.
|
|
132
|
+
*/
|
|
133
|
+
async function maybeWriteFrontmatter(dir, sourceUrl) {
|
|
134
|
+
if (existsSync(path.join(dir, '.git', 'config')))
|
|
135
|
+
return false;
|
|
136
|
+
const skillMdPath = path.join(dir, 'SKILL.md');
|
|
137
|
+
let content;
|
|
138
|
+
try {
|
|
139
|
+
content = await fs.readFile(skillMdPath, 'utf-8');
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
if (!content.startsWith('---'))
|
|
145
|
+
return false;
|
|
146
|
+
const endIndex = content.indexOf('\n---', 3);
|
|
147
|
+
if (endIndex === -1)
|
|
148
|
+
return false;
|
|
149
|
+
const block = content.slice(0, endIndex);
|
|
150
|
+
if (/^repository\s*:/m.test(block))
|
|
151
|
+
return false;
|
|
152
|
+
const updated = block + `\nrepository: ${sourceUrl}` + content.slice(endIndex);
|
|
153
|
+
await fs.writeFile(skillMdPath, updated, 'utf-8');
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Plan and (optionally) apply manifest backfill for recovered sources.
|
|
158
|
+
*/
|
|
159
|
+
export async function backfillManifest(report, opts = {}) {
|
|
160
|
+
const manifestPath = opts.manifestPath ?? defaultManifestPath();
|
|
161
|
+
const minConfidence = opts.minConfidence ?? 'high';
|
|
162
|
+
const apply = opts.apply ?? false;
|
|
163
|
+
const now = opts.now ?? new Date().toISOString();
|
|
164
|
+
const hash = opts.hashContent ?? defaultHashContent;
|
|
165
|
+
const overrides = opts.setOverrides ?? {};
|
|
166
|
+
const planned = [];
|
|
167
|
+
const plannedByName = new Map();
|
|
168
|
+
const skipped = [];
|
|
169
|
+
for (const result of report.skills) {
|
|
170
|
+
const entry = await planResult(result, overrides, minConfidence, now, hash);
|
|
171
|
+
if (!entry) {
|
|
172
|
+
skipped.push(result.skillName);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
planned.push(entry);
|
|
176
|
+
plannedByName.set(result.skillName, entry);
|
|
177
|
+
}
|
|
178
|
+
// Dry-run, or nothing qualified: never touch the manifest.
|
|
179
|
+
if (!apply || plannedByName.size === 0) {
|
|
180
|
+
return { planned, written: [], skipped };
|
|
181
|
+
}
|
|
182
|
+
const written = [];
|
|
183
|
+
const manager = new ManifestManager(manifestPath);
|
|
184
|
+
await manager.updateSafely((manifest) => {
|
|
185
|
+
const installed = { ...manifest.installedSkills };
|
|
186
|
+
for (const [name, entry] of plannedByName) {
|
|
187
|
+
const existing = installed[name];
|
|
188
|
+
if (existing && isHealthy(existing)) {
|
|
189
|
+
skipped.push(name); // never clobber a healthy entry
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
installed[name] = existing ? mergeEntry(existing, entry) : entry;
|
|
193
|
+
written.push(name);
|
|
194
|
+
}
|
|
195
|
+
return { ...manifest, installedSkills: installed };
|
|
196
|
+
});
|
|
197
|
+
if (opts.writeFrontmatter) {
|
|
198
|
+
for (const name of written) {
|
|
199
|
+
const entry = plannedByName.get(name);
|
|
200
|
+
if (entry)
|
|
201
|
+
await maybeWriteFrontmatter(entry.installPath, entry.source);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return { planned, written, skipped };
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=backfill.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backfill.js","sourceRoot":"","sources":["../../../src/provenance/backfill.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Recover a skill's GitHub source from its `.git/config` origin.
|
|
3
|
+
* @module @skillsmith/core/provenance/git-config
|
|
4
|
+
* @see SMI-5407
|
|
5
|
+
*
|
|
6
|
+
* Offline only. No git binary, no network: we parse the INI-style config file
|
|
7
|
+
* directly. Normalizes both scp-like (`git@github.com:owner/repo.git`) and URL
|
|
8
|
+
* (`https://github.com/owner/repo.git`) remotes. Non-github hosts return null.
|
|
9
|
+
*/
|
|
10
|
+
import type { RecoveredSource } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Normalize a raw git remote URL into a canonical {@link RecoveredSource}.
|
|
13
|
+
* Returns null for non-github hosts or unparseable shapes.
|
|
14
|
+
*/
|
|
15
|
+
export declare function normalizeGitHubRemote(rawUrl: string): RecoveredSource | null;
|
|
16
|
+
/**
|
|
17
|
+
* Read `<skillDir>/.git/config` and recover the origin remote's GitHub source.
|
|
18
|
+
* Returns null when the file is missing, has no origin url, or the remote is
|
|
19
|
+
* not a github.com repository.
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseGitConfigRemote(skillDir: string): RecoveredSource | null;
|
|
22
|
+
//# sourceMappingURL=git-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-config.d.ts","sourceRoot":"","sources":["../../../src/provenance/git-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AA6BjD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAgC5E;AAoBD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAW7E"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Recover a skill's GitHub source from its `.git/config` origin.
|
|
3
|
+
* @module @skillsmith/core/provenance/git-config
|
|
4
|
+
* @see SMI-5407
|
|
5
|
+
*
|
|
6
|
+
* Offline only. No git binary, no network: we parse the INI-style config file
|
|
7
|
+
* directly. Normalizes both scp-like (`git@github.com:owner/repo.git`) and URL
|
|
8
|
+
* (`https://github.com/owner/repo.git`) remotes. Non-github hosts return null.
|
|
9
|
+
*/
|
|
10
|
+
import * as fs from 'fs';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
const GITHUB_HOSTS = new Set(['github.com', 'www.github.com']);
|
|
13
|
+
/**
|
|
14
|
+
* Strip a trailing `.git` and any trailing slashes from a repo segment.
|
|
15
|
+
* Uses string iteration rather than a `/\/+$/`-style regex to avoid CodeQL
|
|
16
|
+
* `js/polynomial-redos` on the user-controlled `.git/config` content. SMI-5407.
|
|
17
|
+
*/
|
|
18
|
+
function stripGitSuffix(repo) {
|
|
19
|
+
let end = repo.length;
|
|
20
|
+
while (end > 0 && repo[end - 1] === '/')
|
|
21
|
+
end--;
|
|
22
|
+
const trimmed = repo.slice(0, end);
|
|
23
|
+
if (trimmed.length >= 4 && trimmed.slice(-4).toLowerCase() === '.git') {
|
|
24
|
+
return trimmed.slice(0, -4);
|
|
25
|
+
}
|
|
26
|
+
return trimmed;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A safe GitHub owner/repo segment: ASCII alphanumeric plus `.`/`_`/`-`, and
|
|
30
|
+
* never `.`/`..`. Without this, a crafted `.git/config`
|
|
31
|
+
* `url = git@github.com:../evil` yields owner `..`, corrupting the stored
|
|
32
|
+
* `owner/repo` id + URL. SMI-5407 governance.
|
|
33
|
+
*/
|
|
34
|
+
function isSafeSegment(s) {
|
|
35
|
+
return /^[A-Za-z0-9._-]+$/.test(s) && s !== '.' && s !== '..';
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Normalize a raw git remote URL into a canonical {@link RecoveredSource}.
|
|
39
|
+
* Returns null for non-github hosts or unparseable shapes.
|
|
40
|
+
*/
|
|
41
|
+
export function normalizeGitHubRemote(rawUrl) {
|
|
42
|
+
const trimmed = rawUrl.trim();
|
|
43
|
+
if (!trimmed)
|
|
44
|
+
return null;
|
|
45
|
+
let owner;
|
|
46
|
+
let repo;
|
|
47
|
+
// scp-like syntax: git@github.com:owner/repo(.git)
|
|
48
|
+
const scp = /^[^@]+@([^:]+):(.+)$/.exec(trimmed);
|
|
49
|
+
if (scp && !trimmed.includes('://')) {
|
|
50
|
+
if (!GITHUB_HOSTS.has(scp[1].toLowerCase()))
|
|
51
|
+
return null;
|
|
52
|
+
const segments = scp[2].split('/').filter(Boolean);
|
|
53
|
+
if (segments.length < 2)
|
|
54
|
+
return null;
|
|
55
|
+
owner = segments[0];
|
|
56
|
+
repo = stripGitSuffix(segments[1]);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
let parsed;
|
|
60
|
+
try {
|
|
61
|
+
parsed = new URL(trimmed);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
if (!GITHUB_HOSTS.has(parsed.hostname.toLowerCase()))
|
|
67
|
+
return null;
|
|
68
|
+
const segments = parsed.pathname.split('/').filter(Boolean);
|
|
69
|
+
if (segments.length < 2)
|
|
70
|
+
return null;
|
|
71
|
+
owner = segments[0];
|
|
72
|
+
repo = stripGitSuffix(segments[1]);
|
|
73
|
+
}
|
|
74
|
+
if (!owner || !repo)
|
|
75
|
+
return null;
|
|
76
|
+
if (!isSafeSegment(owner) || !isSafeSegment(repo))
|
|
77
|
+
return null;
|
|
78
|
+
return { owner, repo, url: `https://github.com/${owner}/${repo}` };
|
|
79
|
+
}
|
|
80
|
+
/** Extract the `url` of the `[remote "origin"]` section from a git config. */
|
|
81
|
+
function extractOriginUrl(config) {
|
|
82
|
+
let inOrigin = false;
|
|
83
|
+
for (const line of config.split('\n')) {
|
|
84
|
+
const trimmed = line.trim();
|
|
85
|
+
const section = /^\[(.+)\]$/.exec(trimmed);
|
|
86
|
+
if (section) {
|
|
87
|
+
inOrigin = /^remote\s+"origin"$/.test(section[1].trim());
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (inOrigin) {
|
|
91
|
+
const match = /^url\s*=\s*(.+)$/.exec(trimmed);
|
|
92
|
+
if (match)
|
|
93
|
+
return match[1].trim();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Read `<skillDir>/.git/config` and recover the origin remote's GitHub source.
|
|
100
|
+
* Returns null when the file is missing, has no origin url, or the remote is
|
|
101
|
+
* not a github.com repository.
|
|
102
|
+
*/
|
|
103
|
+
export function parseGitConfigRemote(skillDir) {
|
|
104
|
+
const configPath = path.join(skillDir, '.git', 'config');
|
|
105
|
+
let content;
|
|
106
|
+
try {
|
|
107
|
+
content = fs.readFileSync(configPath, 'utf-8');
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const url = extractOriginUrl(content);
|
|
113
|
+
if (!url)
|
|
114
|
+
return null;
|
|
115
|
+
return normalizeGitHubRemote(url);
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=git-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-config.js","sourceRoot":"","sources":["../../../src/provenance/git-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Public surface of the skill source provenance recovery module.
|
|
3
|
+
* @module @skillsmith/core/provenance
|
|
4
|
+
* @see SMI-5407
|
|
5
|
+
*/
|
|
6
|
+
export { parseGitConfigRemote, normalizeGitHubRemote } from './git-config.js';
|
|
7
|
+
export { parsePluginManifestRepository } from './plugin-manifest.js';
|
|
8
|
+
export { scanLocalSkills, type LocalSkillEntry } from './local-skill-scan.js';
|
|
9
|
+
export { SourceRecoveryService, defaultSkillsRoot, type RecoverSourcesOptions, type RecoverOneOptions, } from './SourceRecoveryService.js';
|
|
10
|
+
export { backfillManifest, type BackfillOptions, type BackfillOutcome } from './backfill.js';
|
|
11
|
+
export { normalizeSkillName, skillNameVariants } from './name-variants.js';
|
|
12
|
+
export { METHOD_LABELS, type RecoveryMethod, type RecoveryConfidence, type RecoveredSource, type RecoveryCandidate, type SkillRecoveryStatus, type SkillRecoveryResult, type RecoverySummary, type RecoveryReport, type RecoveryDeps, } from './types.js';
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/provenance/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AAC7E,OAAO,EAAE,6BAA6B,EAAE,MAAM,sBAAsB,CAAA;AACpE,OAAO,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC7E,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,GACvB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAAE,gBAAgB,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA;AAC5F,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAC1E,OAAO,EACL,aAAa,EACb,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,YAAY,GAClB,MAAM,YAAY,CAAA"}
|