release-skill 0.2.5 → 0.2.7
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/.kimi-plugin/plugin.json +1 -1
- package/CHANGELOG.md +42 -0
- package/INSTALL.md +40 -16
- package/INSTALL.zh-CN.md +34 -12
- package/README.md +42 -15
- package/README.zh-CN.md +36 -15
- package/adapters/claude/.claude-plugin/marketplace.json +1 -1
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/bin/release-skill.bundle.mjs +2671 -1552
- package/adapters/claude/schemas/.render-manifest.json +6 -6
- package/adapters/claude/schemas/release-plan.schema.json +67 -0
- package/adapters/claude/schemas/release-project.schema.json +6 -0
- package/adapters/claude/schemas/release-run.schema.json +65 -0
- package/adapters/claude/skills/release-prepare/SKILL.md +5 -0
- package/adapters/claude/skills/release-setup/SKILL.md +10 -1
- package/adapters/claude/skills/release-verify/SKILL.md +4 -2
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +2671 -1552
- package/adapters/codex/schemas/.render-manifest.json +6 -6
- package/adapters/codex/schemas/release-plan.schema.json +67 -0
- package/adapters/codex/schemas/release-project.schema.json +6 -0
- package/adapters/codex/schemas/release-run.schema.json +65 -0
- package/adapters/codex/skills/release-prepare/SKILL.md +5 -0
- package/adapters/codex/skills/release-setup/SKILL.md +10 -1
- package/adapters/codex/skills/release-verify/SKILL.md +4 -2
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +2671 -1552
- package/adapters/kimi/schemas/.render-manifest.json +6 -6
- package/adapters/kimi/schemas/release-plan.schema.json +67 -0
- package/adapters/kimi/schemas/release-project.schema.json +6 -0
- package/adapters/kimi/schemas/release-run.schema.json +65 -0
- package/adapters/kimi/skills/release-prepare/SKILL.md +5 -0
- package/adapters/kimi/skills/release-setup/SKILL.md +10 -1
- package/adapters/kimi/skills/release-verify/SKILL.md +4 -2
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +2671 -1552
- package/adapters/workbuddy/schemas/.render-manifest.json +6 -6
- package/adapters/workbuddy/schemas/release-plan.schema.json +67 -0
- package/adapters/workbuddy/schemas/release-project.schema.json +6 -0
- package/adapters/workbuddy/schemas/release-run.schema.json +65 -0
- package/adapters/workbuddy/skills/release-prepare/SKILL.md +5 -0
- package/adapters/workbuddy/skills/release-setup/SKILL.md +10 -1
- package/adapters/workbuddy/skills/release-verify/SKILL.md +4 -2
- package/bin/release-skill.bundle.mjs +2671 -1552
- package/package.json +1 -1
- package/references/.render-manifest.json +4 -4
- package/references/02-project-config.md +28 -2
- package/references/05-evidence-and-errors.md +6 -0
- package/schemas/.render-manifest.json +6 -6
- package/schemas/release-plan.schema.json +67 -0
- package/schemas/release-project.schema.json +6 -0
- package/schemas/release-run.schema.json +65 -0
- package/skills/release-prepare/SKILL.md +5 -0
- package/skills/release-setup/SKILL.md +10 -1
- package/skills/release-verify/SKILL.md +4 -2
- package/skills-src/release-prepare/SKILL.md +5 -0
- package/skills-src/release-setup/SKILL.md +10 -1
- package/skills-src/release-verify/SKILL.md +4 -2
- package/src/adapters/npm.mjs +54 -2
- package/src/adapters/plugin-marketplace.mjs +4 -29
- package/src/commands/prepare.mjs +161 -4
- package/src/commands/publish.mjs +74 -2
- package/src/commands/reconcile.mjs +58 -0
- package/src/commands/setup.mjs +194 -0
- package/src/commands/verify.mjs +60 -1
- package/src/core/errors.mjs +12 -0
- package/src/core/source-authority.mjs +547 -0
- package/src/npm/npm-entry-closure.mjs +195 -0
- package/src/platforms/codebuddy.mjs +19 -11
- package/src/platforms/codex.mjs +18 -10
- package/src/platforms/kimi.mjs +26 -39
- package/src/snapshot/frozen.mjs +51 -19
package/src/commands/setup.mjs
CHANGED
|
@@ -24,6 +24,7 @@ import addFormats from 'ajv-formats';
|
|
|
24
24
|
import { canonicalJson, sha256Hex } from '../core/digest.mjs';
|
|
25
25
|
import { acquireProjectLock } from '../artifacts/project-lock.mjs';
|
|
26
26
|
import { getPlatform, PLATFORMS } from '../platforms/registry.mjs';
|
|
27
|
+
import { checkNpmEntryClosure } from '../npm/npm-entry-closure.mjs';
|
|
27
28
|
import {
|
|
28
29
|
CONFIG_EXISTS,
|
|
29
30
|
CONFIG_INVALID,
|
|
@@ -201,6 +202,8 @@ function summarizeLegacyReleaseConfig(value, path) {
|
|
|
201
202
|
tagPrefix: optionalString(repo.tagPrefix),
|
|
202
203
|
npmPackage: optionalString(repo.npmPackage),
|
|
203
204
|
npmPackageDeclared: Object.hasOwn(repo, 'npmPackage'),
|
|
205
|
+
npmRequiredPathCandidates: stringList(repo.npmRequiredPackagePaths),
|
|
206
|
+
npmRequiredPathsDeclared: Object.hasOwn(repo, 'npmRequiredPackagePaths'),
|
|
204
207
|
docsSource: optionalString(repo.docsSource),
|
|
205
208
|
requiredPathCandidates: stringList(repo.requiredPackagePaths),
|
|
206
209
|
snapshotCommands: Array.isArray(repo.snapshotCommands) ? repo.snapshotCommands : [],
|
|
@@ -223,6 +226,8 @@ function summarizeLegacyReleaseConfig(value, path) {
|
|
|
223
226
|
npmPackageDeclared: plugins.some((plugin) => (
|
|
224
227
|
plugin && typeof plugin === 'object' && !Array.isArray(plugin) && Object.hasOwn(plugin, 'npmPackage')
|
|
225
228
|
)),
|
|
229
|
+
npmRequiredPathCandidates: stringList(value.npmRequiredPackagePaths),
|
|
230
|
+
npmRequiredPathsDeclared: Object.hasOwn(value, 'npmRequiredPackagePaths'),
|
|
226
231
|
docsSource: null,
|
|
227
232
|
requiredPathCandidates: stringList(value.requiredPaths),
|
|
228
233
|
snapshotCommands: Array.isArray(value.snapshotCommands) ? value.snapshotCommands : [],
|
|
@@ -467,6 +472,111 @@ async function discoverUnitGit(unitAbsDir, parentRoot) {
|
|
|
467
472
|
};
|
|
468
473
|
}
|
|
469
474
|
|
|
475
|
+
async function pathIsGitIgnored(unitAbsDir, target) {
|
|
476
|
+
// --no-index also classifies generated paths that do not exist yet.
|
|
477
|
+
try {
|
|
478
|
+
await execFile('git', ['check-ignore', '--no-index', '--quiet', '--', target], {
|
|
479
|
+
cwd: unitAbsDir,
|
|
480
|
+
shell: false,
|
|
481
|
+
timeout: 5000,
|
|
482
|
+
});
|
|
483
|
+
return true;
|
|
484
|
+
} catch {
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
async function classifyNpmEntryCandidate(unitAbsDir, target, trackedFiles) {
|
|
490
|
+
const tracked = trackedFiles.includes(target);
|
|
491
|
+
const ignored = await pathIsGitIgnored(unitAbsDir, target);
|
|
492
|
+
let stat = null;
|
|
493
|
+
try {
|
|
494
|
+
stat = await lstat(join(unitAbsDir, target));
|
|
495
|
+
} catch (error) {
|
|
496
|
+
if (error.code !== 'ENOENT') throw error;
|
|
497
|
+
}
|
|
498
|
+
const exists = stat !== null;
|
|
499
|
+
const regular = stat?.isFile() === true && stat?.isSymbolicLink() !== true;
|
|
500
|
+
const state = exists && !regular
|
|
501
|
+
? 'NON_REGULAR'
|
|
502
|
+
: exists && tracked
|
|
503
|
+
? 'TRACKED_PRESENT'
|
|
504
|
+
: exists && ignored
|
|
505
|
+
? 'IGNORED_PRESENT'
|
|
506
|
+
: exists
|
|
507
|
+
? 'UNTRACKED_PRESENT'
|
|
508
|
+
: ignored
|
|
509
|
+
? 'IGNORED_MISSING'
|
|
510
|
+
: 'MISSING';
|
|
511
|
+
return { path: target, state, exists, tracked, ignored };
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
async function discoverNpmEntryCandidates(root, pkg, matchingLegacyUnits, unitGitEntry) {
|
|
515
|
+
const emptyIndex = new Map();
|
|
516
|
+
const semantic = checkNpmEntryClosure(pkg.entryManifest, emptyIndex);
|
|
517
|
+
const byPath = new Map();
|
|
518
|
+
const add = (target, source) => {
|
|
519
|
+
const current = byPath.get(target) ?? { path: target, sources: new Set() };
|
|
520
|
+
current.sources.add(source);
|
|
521
|
+
byPath.set(target, current);
|
|
522
|
+
};
|
|
523
|
+
for (const entry of semantic.entries) add(entry.target, `package.json:${entry.field}`);
|
|
524
|
+
|
|
525
|
+
const diagnostics = [
|
|
526
|
+
...semantic.errors
|
|
527
|
+
.filter((error) => error.reason !== 'entry_missing')
|
|
528
|
+
.map((error) => ({ code: 'NPM_ENTRY_DECLARATION_INVALID', ...error })),
|
|
529
|
+
...semantic.diagnostics.map((diagnostic) => ({
|
|
530
|
+
code: 'NPM_ENTRY_WILDCARD_REQUIRES_REVIEW',
|
|
531
|
+
...diagnostic,
|
|
532
|
+
})),
|
|
533
|
+
];
|
|
534
|
+
const declaredLegacyPaths = new Set();
|
|
535
|
+
const legacyCoverageDeclared = matchingLegacyUnits.some((unit) => unit.npmRequiredPathsDeclared);
|
|
536
|
+
for (const legacy of matchingLegacyUnits) {
|
|
537
|
+
for (const target of legacy.npmRequiredPathCandidates) {
|
|
538
|
+
const checked = checkNpmEntryClosure({ main: target }, emptyIndex);
|
|
539
|
+
const normalized = checked.entries[0]?.target;
|
|
540
|
+
if (!normalized) {
|
|
541
|
+
diagnostics.push({
|
|
542
|
+
code: 'LEGACY_NPM_REQUIRED_PATH_INVALID',
|
|
543
|
+
path: target,
|
|
544
|
+
reason: checked.errors[0]?.reason ?? 'invalid_path',
|
|
545
|
+
});
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
declaredLegacyPaths.add(normalized);
|
|
549
|
+
add(normalized, 'public-release.json:npmRequiredPackagePaths');
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
if (legacyCoverageDeclared) {
|
|
553
|
+
for (const entry of semantic.entries) {
|
|
554
|
+
if (!declaredLegacyPaths.has(entry.target)) {
|
|
555
|
+
diagnostics.push({
|
|
556
|
+
code: 'LEGACY_NPM_ENTRY_COVERAGE_MISSING',
|
|
557
|
+
path: entry.target,
|
|
558
|
+
field: entry.field,
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const unitAbsDir = resolve(root, pkg.directory);
|
|
565
|
+
const candidates = [];
|
|
566
|
+
for (const item of [...byPath.values()].sort((a, b) => a.path.localeCompare(b.path))) {
|
|
567
|
+
candidates.push({
|
|
568
|
+
...await classifyNpmEntryCandidate(
|
|
569
|
+
unitAbsDir,
|
|
570
|
+
item.path,
|
|
571
|
+
unitGitEntry?.trackedFiles ?? [],
|
|
572
|
+
),
|
|
573
|
+
sources: [...item.sources].sort(),
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
diagnostics.sort((a, b) => canonicalJson(a).localeCompare(canonicalJson(b)));
|
|
577
|
+
return { candidates, diagnostics };
|
|
578
|
+
}
|
|
579
|
+
|
|
470
580
|
async function discoverFacts(root) {
|
|
471
581
|
const files = await walkDiscoveryFiles(root);
|
|
472
582
|
const packageFiles = files.filter((path) => (
|
|
@@ -494,6 +604,11 @@ async function discoverFacts(root) {
|
|
|
494
604
|
private: pkg.private === true,
|
|
495
605
|
repository: parseGithubRepo(pkg.repository),
|
|
496
606
|
publishRegistry: typeof pkg.publishConfig?.registry === 'string' ? pkg.publishConfig.registry : null,
|
|
607
|
+
entryManifest: Object.fromEntries(
|
|
608
|
+
['bin', 'main', 'module', 'types', 'typings', 'exports']
|
|
609
|
+
.filter((field) => Object.hasOwn(pkg, field))
|
|
610
|
+
.map((field) => [field, pkg[field]]),
|
|
611
|
+
),
|
|
497
612
|
files: Array.isArray(pkg.files) ? pkg.files.filter((item) => typeof item === 'string').sort() : [],
|
|
498
613
|
scripts: Object.fromEntries(Object.entries(pkg.scripts ?? {})
|
|
499
614
|
.filter(([, value]) => typeof value === 'string')
|
|
@@ -557,6 +672,22 @@ async function discoverFacts(root) {
|
|
|
557
672
|
for (const pkg of packages) {
|
|
558
673
|
const unitAbsDir = resolve(root, pkg.directory);
|
|
559
674
|
unitGit[pkg.directory] = await discoverUnitGit(unitAbsDir, root);
|
|
675
|
+
const inferredId = safeUnitId(pkg, pkg.directory);
|
|
676
|
+
const matchingLegacyUnits = legacyReleaseConfigs
|
|
677
|
+
.flatMap((config) => config.releaseUnits)
|
|
678
|
+
.filter((unit) => (
|
|
679
|
+
unit.id === inferredId ||
|
|
680
|
+
unit.source === pkg.directory ||
|
|
681
|
+
unit.source === dirname(pkg.path)
|
|
682
|
+
));
|
|
683
|
+
const npmDiscovery = await discoverNpmEntryCandidates(
|
|
684
|
+
root,
|
|
685
|
+
pkg,
|
|
686
|
+
matchingLegacyUnits,
|
|
687
|
+
unitGit[pkg.directory],
|
|
688
|
+
);
|
|
689
|
+
pkg.npmEntryCandidates = npmDiscovery.candidates;
|
|
690
|
+
pkg.npmEntryDiagnostics = npmDiscovery.diagnostics;
|
|
560
691
|
}
|
|
561
692
|
|
|
562
693
|
return { git, packages, manifests, skills, legacyReleaseConfigs, fileDigests, unitGit };
|
|
@@ -871,6 +1002,8 @@ function buildCandidates(facts) {
|
|
|
871
1002
|
publicFileMappingCandidates: mappingResult.mappings,
|
|
872
1003
|
publicFileMappingConflicts: mappingResult.conflicts,
|
|
873
1004
|
requiredPublicFileCandidates,
|
|
1005
|
+
npmEntryCandidates: pkg.npmEntryCandidates ?? [],
|
|
1006
|
+
npmEntryDiagnostics: pkg.npmEntryDiagnostics ?? [],
|
|
874
1007
|
entrySkillCandidates: entrySkillCandidatesForUnit(facts, pkg, id),
|
|
875
1008
|
...(hasAuthorityConflict ? {
|
|
876
1009
|
authorityConflict: {
|
|
@@ -976,6 +1109,21 @@ function buildRecommendedProposal(facts, candidates) {
|
|
|
976
1109
|
const legacyOwner = facts.legacyReleaseConfigs
|
|
977
1110
|
.map((c) => c.owner)
|
|
978
1111
|
.find(Boolean);
|
|
1112
|
+
const sourceRepositoryCandidates = [...new Set(
|
|
1113
|
+
facts.git.remotes.map((remote) => remote.repo).filter(Boolean),
|
|
1114
|
+
)].sort();
|
|
1115
|
+
if (sourceRepositoryCandidates.length !== 1) {
|
|
1116
|
+
return {
|
|
1117
|
+
answers: null,
|
|
1118
|
+
conflicts: [{
|
|
1119
|
+
code: sourceRepositoryCandidates.length === 0
|
|
1120
|
+
? 'SOURCE_REPOSITORY_MISSING'
|
|
1121
|
+
: 'SOURCE_REPOSITORY_AMBIGUOUS',
|
|
1122
|
+
candidates: sourceRepositoryCandidates,
|
|
1123
|
+
}],
|
|
1124
|
+
assumptions,
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
979
1127
|
|
|
980
1128
|
const units = [];
|
|
981
1129
|
for (const unit of candidates.units) {
|
|
@@ -1012,6 +1160,24 @@ function buildRecommendedProposal(facts, candidates) {
|
|
|
1012
1160
|
assumptions,
|
|
1013
1161
|
};
|
|
1014
1162
|
}
|
|
1163
|
+
if (
|
|
1164
|
+
unit.distributionCandidates.includes('npm') &&
|
|
1165
|
+
(
|
|
1166
|
+
(unit.npmEntryCandidates ?? []).some((candidate) => candidate.state !== 'TRACKED_PRESENT') ||
|
|
1167
|
+
(unit.npmEntryDiagnostics ?? []).length > 0
|
|
1168
|
+
)
|
|
1169
|
+
) {
|
|
1170
|
+
return {
|
|
1171
|
+
answers: null,
|
|
1172
|
+
conflicts: [{
|
|
1173
|
+
code: 'NPM_ENTRY_REVIEW_REQUIRED',
|
|
1174
|
+
unit: unit.id,
|
|
1175
|
+
candidates: unit.npmEntryCandidates,
|
|
1176
|
+
diagnostics: unit.npmEntryDiagnostics,
|
|
1177
|
+
}],
|
|
1178
|
+
assumptions,
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1015
1181
|
|
|
1016
1182
|
// Infer npm publisher: prefer legacy owner, then parse from repo slug
|
|
1017
1183
|
const repoOwner = publicRepo.includes('/') ? publicRepo.split('/')[0] : null;
|
|
@@ -1156,6 +1322,7 @@ function buildRecommendedProposal(facts, candidates) {
|
|
|
1156
1322
|
project: {
|
|
1157
1323
|
name: facts.packages[0]?.name ?? 'project',
|
|
1158
1324
|
defaultBranch: facts.legacyReleaseConfigs[0]?.defaultBranch ?? facts.git.branch ?? 'main',
|
|
1325
|
+
sourceRepository: sourceRepositoryCandidates[0],
|
|
1159
1326
|
},
|
|
1160
1327
|
releaseUnits: units,
|
|
1161
1328
|
...(selectedGateIds.length > 0 ? {
|
|
@@ -1229,6 +1396,15 @@ function buildDecisionsRequired(candidates, localOnly) {
|
|
|
1229
1396
|
id: `unit:${unit.id}:distributions-and-files`,
|
|
1230
1397
|
description: `逐项确认渠道 ${JSON.stringify(unit.distributionCandidates)}、公开文件边界和 requiredPublicFiles;候选不是授权。`,
|
|
1231
1398
|
});
|
|
1399
|
+
if (
|
|
1400
|
+
(unit.npmEntryCandidates ?? []).length > 0 ||
|
|
1401
|
+
(unit.npmEntryDiagnostics ?? []).length > 0
|
|
1402
|
+
) {
|
|
1403
|
+
decisions.push({
|
|
1404
|
+
id: `unit:${unit.id}:npm-entry-closure`,
|
|
1405
|
+
description: '审阅 package.json 入口及旧 npmRequiredPackagePaths 的 tracked/untracked/ignored/missing 状态;setup 只报告候选,不自动写入 publicFiles 或 requiredPublicFiles。',
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1232
1408
|
}
|
|
1233
1409
|
decisions.push({
|
|
1234
1410
|
id: 'verification-gates',
|
|
@@ -1255,6 +1431,8 @@ function buildCompactSummary(report) {
|
|
|
1255
1431
|
distributionCandidates: [...unit.distributionCandidates].sort(),
|
|
1256
1432
|
publicFileMappingCount: (unit.publicFileMappingCandidates ?? []).length,
|
|
1257
1433
|
requiredPublicFileCount: (unit.requiredPublicFileCandidates ?? []).length,
|
|
1434
|
+
npmEntryCandidates: unit.npmEntryCandidates ?? [],
|
|
1435
|
+
npmEntryDiagnostics: unit.npmEntryDiagnostics ?? [],
|
|
1258
1436
|
})),
|
|
1259
1437
|
gateCandidates: (report.gateCandidates ?? []).map((gate) => ({
|
|
1260
1438
|
id: gate.id,
|
|
@@ -1503,6 +1681,12 @@ export async function setupProject({ root, answersPath, write = false, confirmSe
|
|
|
1503
1681
|
...(unconfiguredGateCandidateIds.length > 0
|
|
1504
1682
|
? ['存在未配置的验证候选;逐项审阅副作用后决定是否人工注册。']
|
|
1505
1683
|
: []),
|
|
1684
|
+
...(candidates.units.some((unit) => (
|
|
1685
|
+
(unit.npmEntryCandidates ?? []).some((candidate) => candidate.state !== 'TRACKED_PRESENT') ||
|
|
1686
|
+
(unit.npmEntryDiagnostics ?? []).length > 0
|
|
1687
|
+
))
|
|
1688
|
+
? ['npm 入口候选存在未跟踪、被忽略、缺失、非普通文件或声明覆盖疑点;人工增量修复配置或构建流程,setup 不自动改写。']
|
|
1689
|
+
: []),
|
|
1506
1690
|
],
|
|
1507
1691
|
},
|
|
1508
1692
|
recommendedGateIds: [],
|
|
@@ -1517,6 +1701,9 @@ export async function setupProject({ root, answersPath, write = false, confirmSe
|
|
|
1517
1701
|
|
|
1518
1702
|
const facts = await discoverFacts(rootReal);
|
|
1519
1703
|
const candidates = buildCandidates(facts);
|
|
1704
|
+
const sourceRepositoryCandidates = [...new Set(
|
|
1705
|
+
facts.git.remotes.map((remote) => remote.repo).filter(Boolean),
|
|
1706
|
+
)].sort();
|
|
1520
1707
|
let answers = null;
|
|
1521
1708
|
if (answersPath) {
|
|
1522
1709
|
const resolvedAnswers = isAbsolute(answersPath) ? answersPath : resolve(rootReal, answersPath);
|
|
@@ -1527,6 +1714,7 @@ export async function setupProject({ root, answersPath, write = false, confirmSe
|
|
|
1527
1714
|
const digestAuthority = {
|
|
1528
1715
|
setupVersion: 1,
|
|
1529
1716
|
facts,
|
|
1717
|
+
sourceRepositoryCandidates,
|
|
1530
1718
|
releaseUnitCandidates: candidates.units,
|
|
1531
1719
|
gateCandidates: candidates.gates,
|
|
1532
1720
|
selectedGateIds,
|
|
@@ -1599,6 +1787,9 @@ export async function setupProject({ root, answersPath, write = false, confirmSe
|
|
|
1599
1787
|
const lockedAuthority = {
|
|
1600
1788
|
setupVersion: 1,
|
|
1601
1789
|
facts: lockedFacts,
|
|
1790
|
+
sourceRepositoryCandidates: [...new Set(
|
|
1791
|
+
lockedFacts.git.remotes.map((remote) => remote.repo).filter(Boolean),
|
|
1792
|
+
)].sort(),
|
|
1602
1793
|
releaseUnitCandidates: lockedCandidates.units,
|
|
1603
1794
|
gateCandidates: lockedCandidates.gates,
|
|
1604
1795
|
selectedGateIds: lockedAnswers.selectedGateIds,
|
|
@@ -1622,6 +1813,9 @@ export async function setupProject({ root, answersPath, write = false, confirmSe
|
|
|
1622
1813
|
const finalAuthority = {
|
|
1623
1814
|
setupVersion: 1,
|
|
1624
1815
|
facts: finalFacts,
|
|
1816
|
+
sourceRepositoryCandidates: [...new Set(
|
|
1817
|
+
finalFacts.git.remotes.map((remote) => remote.repo).filter(Boolean),
|
|
1818
|
+
)].sort(),
|
|
1625
1819
|
releaseUnitCandidates: finalCandidates.units,
|
|
1626
1820
|
gateCandidates: finalCandidates.gates,
|
|
1627
1821
|
selectedGateIds: finalAnswers.selectedGateIds,
|
package/src/commands/verify.mjs
CHANGED
|
@@ -41,8 +41,10 @@ import {
|
|
|
41
41
|
import {
|
|
42
42
|
ReleaseError,
|
|
43
43
|
GATE_FAILED,
|
|
44
|
+
CONFIG_MISSING,
|
|
44
45
|
POST_PUBLISH_VERIFY_FAILED,
|
|
45
46
|
} from '../core/errors.mjs';
|
|
47
|
+
import { verifySourceAuthorityReceipt } from '../core/source-authority.mjs';
|
|
46
48
|
import { assertTransition, PUBLISHED, VERIFIED } from '../core/state-machine.mjs';
|
|
47
49
|
import { resolveUnitScopedPath } from '../snapshot/public-path.mjs';
|
|
48
50
|
import {
|
|
@@ -61,6 +63,10 @@ import {
|
|
|
61
63
|
shouldSkipVerification,
|
|
62
64
|
INSTALLATION_CONTRACT_ALGORITHM_VERSION,
|
|
63
65
|
} from '../core/installation-contract.mjs';
|
|
66
|
+
import {
|
|
67
|
+
buildDirectoryFileIndex,
|
|
68
|
+
checkNpmEntryClosure,
|
|
69
|
+
} from '../npm/npm-entry-closure.mjs';
|
|
64
70
|
|
|
65
71
|
// ---------------------------------------------------------------------------
|
|
66
72
|
// Constants
|
|
@@ -292,6 +298,30 @@ export async function runSmokeTest(plan, root, options = {}) {
|
|
|
292
298
|
}
|
|
293
299
|
|
|
294
300
|
const pkgRoot = join(installDir, 'node_modules', pkgName);
|
|
301
|
+
|
|
302
|
+
// Entry closure check: verify all declared entry points (bin, main,
|
|
303
|
+
// module, types, typings, exports) exist as regular files in the
|
|
304
|
+
// installed package. This catches incident-class tarballs where
|
|
305
|
+
// name/version are correct but distribution files are missing.
|
|
306
|
+
{
|
|
307
|
+
const dirIndex = await buildDirectoryFileIndex(pkgRoot);
|
|
308
|
+
const closureResult = checkNpmEntryClosure(installedPkg, dirIndex);
|
|
309
|
+
if (closureResult.errors.length > 0) {
|
|
310
|
+
return {
|
|
311
|
+
passed: false,
|
|
312
|
+
details: {
|
|
313
|
+
gate: 'npm-entry-closure',
|
|
314
|
+
error: `npm entry closure check failed for ${packageAtVersion}: ${closureResult.errors.map((e) => e.message).join('; ')}`,
|
|
315
|
+
packageAtVersion,
|
|
316
|
+
unitId,
|
|
317
|
+
entries: closureResult.entries,
|
|
318
|
+
errors: closureResult.errors,
|
|
319
|
+
diagnostics: closureResult.diagnostics,
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
295
325
|
if (plan.skillResourceClosure) {
|
|
296
326
|
const expectedUnitReceipt = plan.skillResourceClosure.unitReceipts
|
|
297
327
|
.find((item) => item.unitId === unitId);
|
|
@@ -654,6 +684,13 @@ export async function verifyRelease(options) {
|
|
|
654
684
|
);
|
|
655
685
|
}
|
|
656
686
|
validatePlan(plan);
|
|
687
|
+
if (plan.production?.mode === 'github-npm-v1' && !plan.sourceAuthority) {
|
|
688
|
+
throw new ReleaseError(
|
|
689
|
+
CONFIG_MISSING,
|
|
690
|
+
'production verify requires a frozen sourceAuthority binding; the release must be re-prepared before publish',
|
|
691
|
+
{ gate: 'source-authority' },
|
|
692
|
+
);
|
|
693
|
+
}
|
|
657
694
|
|
|
658
695
|
// --- Set up directories ---
|
|
659
696
|
const runId = `verify-${Date.now()}`;
|
|
@@ -777,6 +814,29 @@ export async function verifyRelease(options) {
|
|
|
777
814
|
// Validate checkpoint mapping
|
|
778
815
|
validateRunCheckpointMapping(sourceRun, plan.externalActions ?? []);
|
|
779
816
|
|
|
817
|
+
// --- Source authority receipt verification ---
|
|
818
|
+
// If the plan declares sourceAuthority, the source publish run must
|
|
819
|
+
// contain a matching CONSISTENT receipt bound to this planDigest.
|
|
820
|
+
if (plan.sourceAuthority) {
|
|
821
|
+
await evidence.append({ phase: 'source-authority-receipt', status: 'started' });
|
|
822
|
+
|
|
823
|
+
const receiptResult = verifySourceAuthorityReceipt({ plan, run: sourceRun });
|
|
824
|
+
if (!receiptResult.passed) {
|
|
825
|
+
await evidence.append({
|
|
826
|
+
phase: 'source-authority-receipt',
|
|
827
|
+
status: 'failed',
|
|
828
|
+
reason: receiptResult.reason,
|
|
829
|
+
});
|
|
830
|
+
throw new ReleaseError(
|
|
831
|
+
GATE_FAILED,
|
|
832
|
+
`source authority receipt verification failed: ${receiptResult.reason}`,
|
|
833
|
+
{ reason: receiptResult.reason },
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
await evidence.append({ phase: 'source-authority-receipt', status: 'passed' });
|
|
838
|
+
}
|
|
839
|
+
|
|
780
840
|
// All checkpoints must be succeeded or skipped (no failed/pending),
|
|
781
841
|
// except marketplace install checkpoints which are re-verified by verify
|
|
782
842
|
// itself via consumer verification (human attestation or automatic).
|
|
@@ -1193,7 +1253,6 @@ export async function verifyRelease(options) {
|
|
|
1193
1253
|
}
|
|
1194
1254
|
: {
|
|
1195
1255
|
HOME: resolve(runDir, 'consumers', `kimi-${action.parameters.plugin}`),
|
|
1196
|
-
KIMI_CODE_HOME: resolve(runDir, 'consumers', `kimi-${action.parameters.plugin}`),
|
|
1197
1256
|
},
|
|
1198
1257
|
}));
|
|
1199
1258
|
} else {
|
package/src/core/errors.mjs
CHANGED
|
@@ -88,6 +88,12 @@ const EXIT_CODE_MAP = Object.freeze({
|
|
|
88
88
|
RELEASE_DOCS_REFRESH_STALE: 45,
|
|
89
89
|
RELEASE_DOCS_STALE: 46,
|
|
90
90
|
CONSUMER_VERIFICATION_DEFERRED: 47,
|
|
91
|
+
CONFIG_MISSING: 48,
|
|
92
|
+
REMOTE_UNAVAILABLE: 49,
|
|
93
|
+
REF_MISSING: 50,
|
|
94
|
+
NOT_DEFAULT: 51,
|
|
95
|
+
CONTENT_MISMATCH: 52,
|
|
96
|
+
DIRTY_SOURCE_INPUT: 53,
|
|
91
97
|
});
|
|
92
98
|
|
|
93
99
|
// ---- Error code constants ----
|
|
@@ -130,6 +136,12 @@ export const RELEASE_DOCS_CONFLICT = 'RELEASE_DOCS_CONFLICT';
|
|
|
130
136
|
export const RELEASE_DOCS_REFRESH_STALE = 'RELEASE_DOCS_REFRESH_STALE';
|
|
131
137
|
export const RELEASE_DOCS_STALE = 'RELEASE_DOCS_STALE';
|
|
132
138
|
export const CONSUMER_VERIFICATION_DEFERRED = 'CONSUMER_VERIFICATION_DEFERRED';
|
|
139
|
+
export const CONFIG_MISSING = 'CONFIG_MISSING';
|
|
140
|
+
export const REMOTE_UNAVAILABLE = 'REMOTE_UNAVAILABLE';
|
|
141
|
+
export const REF_MISSING = 'REF_MISSING';
|
|
142
|
+
export const NOT_DEFAULT = 'NOT_DEFAULT';
|
|
143
|
+
export const CONTENT_MISMATCH = 'CONTENT_MISMATCH';
|
|
144
|
+
export const DIRTY_SOURCE_INPUT = 'DIRTY_SOURCE_INPUT';
|
|
133
145
|
|
|
134
146
|
/**
|
|
135
147
|
* Typed error for release-skill operations.
|