release-skill 0.1.7 → 0.1.8

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.
Files changed (63) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +2 -2
  4. package/.kimi-plugin/plugin.json +27 -0
  5. package/CHANGELOG.md +22 -0
  6. package/INSTALL.md +73 -2
  7. package/INSTALL.zh-CN.md +94 -99
  8. package/README.md +49 -35
  9. package/README.zh-CN.md +82 -321
  10. package/adapters/claude/.claude-plugin/marketplace.json +1 -1
  11. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  12. package/adapters/claude/bin/release-skill.bundle.mjs +1061 -182
  13. package/adapters/claude/schemas/.render-manifest.json +4 -4
  14. package/adapters/claude/schemas/release-plan.schema.json +20 -2
  15. package/adapters/claude/schemas/release-project.schema.json +22 -4
  16. package/adapters/claude/schemas/release-run.schema.json +1 -0
  17. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  18. package/adapters/codex/bin/release-skill.bundle.mjs +1061 -182
  19. package/adapters/codex/schemas/.render-manifest.json +4 -4
  20. package/adapters/codex/schemas/release-plan.schema.json +20 -2
  21. package/adapters/codex/schemas/release-project.schema.json +22 -4
  22. package/adapters/codex/schemas/release-run.schema.json +1 -0
  23. package/adapters/kimi/.kimi-plugin/plugin.json +27 -0
  24. package/adapters/kimi/bin/release-skill.bundle.mjs +84415 -0
  25. package/adapters/kimi/bin/release-skill.mjs +54 -0
  26. package/adapters/kimi/native/safe-write/binding.gyp +41 -0
  27. package/adapters/kimi/native/safe-write/prebuilds/darwin-arm64/safe_write.node +0 -0
  28. package/adapters/kimi/native/safe-write/prebuilds.json +24 -0
  29. package/adapters/kimi/native/safe-write/src/safe_write.cc +2032 -0
  30. package/adapters/kimi/schemas/.render-manifest.json +37 -0
  31. package/adapters/kimi/schemas/approval-record.schema.json +115 -0
  32. package/adapters/kimi/schemas/artifact-lock.schema.json +111 -0
  33. package/adapters/kimi/schemas/artifact-plan.schema.json +52 -0
  34. package/adapters/kimi/schemas/artifact-policy.schema.json +76 -0
  35. package/adapters/kimi/schemas/evidence-event.schema.json +89 -0
  36. package/adapters/kimi/schemas/release-plan.schema.json +878 -0
  37. package/adapters/kimi/schemas/release-project.schema.json +895 -0
  38. package/adapters/kimi/schemas/release-run.schema.json +343 -0
  39. package/adapters/kimi/skills/release-assess/SKILL.md +58 -0
  40. package/adapters/kimi/skills/release-help/SKILL.md +84 -0
  41. package/adapters/kimi/skills/release-prepare/SKILL.md +99 -0
  42. package/adapters/kimi/skills/release-publish/SKILL.md +64 -0
  43. package/adapters/kimi/skills/release-reconcile/SKILL.md +80 -0
  44. package/adapters/kimi/skills/release-setup/SKILL.md +102 -0
  45. package/adapters/kimi/skills/release-verify/SKILL.md +77 -0
  46. package/bin/release-skill-cli.mjs +16 -4
  47. package/bin/release-skill.bundle.mjs +1061 -182
  48. package/package.json +4 -2
  49. package/schemas/.render-manifest.json +4 -4
  50. package/schemas/release-plan.schema.json +20 -2
  51. package/schemas/release-project.schema.json +22 -4
  52. package/schemas/release-run.schema.json +1 -0
  53. package/src/adapters/contract.mjs +1 -0
  54. package/src/adapters/plugin-marketplace.mjs +990 -30
  55. package/src/commands/assess.mjs +50 -1
  56. package/src/commands/prepare.mjs +65 -0
  57. package/src/commands/publish.mjs +3 -0
  58. package/src/commands/reconcile.mjs +3 -0
  59. package/src/commands/setup.mjs +10 -5
  60. package/src/commands/verify.mjs +16 -6
  61. package/src/core/plan.mjs +118 -0
  62. package/src/core/verification-gates.mjs +1 -1
  63. package/src/producers/build-adapters.mjs +38 -8
@@ -128,7 +128,8 @@ function identifyTopology(config) {
128
128
  const hasNpm = uniqueDistTypes.includes('npm');
129
129
  const hasPlugin =
130
130
  uniqueDistTypes.includes('claude-plugin') ||
131
- uniqueDistTypes.includes('codex-plugin');
131
+ uniqueDistTypes.includes('codex-plugin') ||
132
+ uniqueDistTypes.includes('kimi-plugin');
132
133
 
133
134
  if (units.length === 0) {
134
135
  type = 'no-release-units';
@@ -379,6 +380,54 @@ async function checkPluginManifests(root, config) {
379
380
  }
380
381
  }
381
382
  }
383
+
384
+ if (distributionTypes.has('kimi-plugin')) {
385
+ const manifestPath = resolve(unitRoot, '.kimi-plugin', 'plugin.json');
386
+ const displayPath = unitFile(unit, '.kimi-plugin/plugin.json');
387
+ const exists = await fileExists(manifestPath);
388
+ if (!exists) {
389
+ gaps.push(
390
+ createGap({
391
+ scope: GapScope.PROFILE,
392
+ category: GapCategory.MANIFEST,
393
+ severity: Severity.ERROR,
394
+ code: 'KIMI_MANIFEST_MISSING',
395
+ message: `发布单元 "${unit.id}" 缺少 .kimi-plugin/plugin.json 插件清单`,
396
+ file: displayPath,
397
+ }),
398
+ );
399
+ } else {
400
+ try {
401
+ const content = await readFile(manifestPath, 'utf8');
402
+ const manifest = JSON.parse(content);
403
+ const requiredFields = ['name', 'version', 'description'];
404
+ const missingFields = requiredFields.filter((f) => !(f in manifest));
405
+ if (missingFields.length > 0) {
406
+ gaps.push(
407
+ createGap({
408
+ scope: GapScope.PROFILE,
409
+ category: GapCategory.MANIFEST,
410
+ severity: Severity.ERROR,
411
+ code: 'KIMI_MANIFEST_INCOMPLETE',
412
+ message: `Kimi 插件清单缺少必填字段: ${missingFields.join(', ')}`,
413
+ file: displayPath,
414
+ }),
415
+ );
416
+ }
417
+ } catch {
418
+ gaps.push(
419
+ createGap({
420
+ scope: GapScope.PROFILE,
421
+ category: GapCategory.MANIFEST,
422
+ severity: Severity.ERROR,
423
+ code: 'KIMI_MANIFEST_INVALID',
424
+ message: '.kimi-plugin/plugin.json 解析失败',
425
+ file: displayPath,
426
+ }),
427
+ );
428
+ }
429
+ }
430
+ }
382
431
  }
383
432
 
384
433
  return gaps;
@@ -966,6 +966,35 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets) {
966
966
  status: 'PENDING',
967
967
  });
968
968
  }
969
+ const kimiDist = (unit.distributions ?? []).find((d) => d.type === 'kimi-plugin');
970
+ if (kimiDist) {
971
+ const identity = marketplaceIdentity(kimiDist);
972
+ const kimiTimeoutMs = Number.isInteger(kimiDist.timeoutMs) ? kimiDist.timeoutMs : 300000;
973
+ // Kimi Code has no non-interactive install/marketplace API, so the kimi
974
+ // action carries no marketplace identity (MINOR-1). plugin + entrySkill
975
+ // are the meaningful identity fields.
976
+ actions.push({
977
+ id: `kimi-marketplace-install-${unit.id}`,
978
+ type: 'kimi-marketplace-install',
979
+ adapter: 'plugin-marketplace',
980
+ unitId: unit.id,
981
+ parameters: {
982
+ consumer: 'kimi',
983
+ plugin: identity.plugin,
984
+ repo: unit.publicRepo,
985
+ version,
986
+ entrySkill: identity.entrySkill,
987
+ timeoutMs: kimiTimeoutMs,
988
+ },
989
+ expected: {
990
+ installed: true,
991
+ plugin: identity.plugin,
992
+ version,
993
+ entrySkill: identity.entrySkill,
994
+ },
995
+ status: 'PENDING',
996
+ });
997
+ }
969
998
  }
970
999
  return actions;
971
1000
  }
@@ -1180,6 +1209,42 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets) {
1180
1209
  status: 'PENDING',
1181
1210
  });
1182
1211
  }
1212
+ const kimiDist = (unit.distributions ?? []).find((d) => d.type === 'kimi-plugin');
1213
+ if (kimiDist) {
1214
+ const identity = marketplaceIdentity(kimiDist);
1215
+ const kimiTimeoutMs = Number.isInteger(kimiDist.timeoutMs) ? kimiDist.timeoutMs : 300000;
1216
+ // No marketplace identity for kimi (MINOR-1): Kimi Code has an interactive
1217
+ // marketplace but no non-interactive install API.
1218
+ actions.push({
1219
+ id: `kimi-marketplace-install-${unit.id}`,
1220
+ type: 'kimi-marketplace-install',
1221
+ adapter: 'plugin-marketplace',
1222
+ unitId: unit.id,
1223
+ parameters: {
1224
+ consumer: 'kimi',
1225
+ plugin: identity.plugin,
1226
+ repo: unit.publicRepo,
1227
+ ref: resolvedTag,
1228
+ version: unitVersion,
1229
+ entrySkill: identity.entrySkill,
1230
+ snapshotPath: asset.snapshotPath,
1231
+ manifestDigest: asset.manifestDigest,
1232
+ timeoutMs: kimiTimeoutMs,
1233
+ },
1234
+ expected: {
1235
+ installed: true,
1236
+ consumer: 'kimi',
1237
+ plugin: identity.plugin,
1238
+ repo: unit.publicRepo,
1239
+ version: unitVersion,
1240
+ ref: resolvedTag,
1241
+ entrySkill: identity.entrySkill,
1242
+ entrySkillFound: true,
1243
+ manifestDigest: asset.manifestDigest,
1244
+ },
1245
+ status: 'PENDING',
1246
+ });
1247
+ }
1183
1248
  }
1184
1249
 
1185
1250
  return actions;
@@ -81,6 +81,7 @@ const CHECKPOINT_ORDER = [
81
81
  'github-release',
82
82
  'claude-marketplace-install',
83
83
  'codex-marketplace-install',
84
+ 'kimi-marketplace-install',
84
85
  ];
85
86
 
86
87
  /**
@@ -99,11 +100,13 @@ const ADAPTER_ACTION_TYPE_MAP = {
99
100
  'github-release': 'github-release',
100
101
  'claude-marketplace-install': 'claude-marketplace-install',
101
102
  'codex-marketplace-install': 'codex-marketplace-install',
103
+ 'kimi-marketplace-install': 'kimi-marketplace-install',
102
104
  };
103
105
 
104
106
  const MARKETPLACE_TYPES = new Set([
105
107
  'claude-marketplace-install',
106
108
  'codex-marketplace-install',
109
+ 'kimi-marketplace-install',
107
110
  ]);
108
111
 
109
112
  // ---------------------------------------------------------------------------
@@ -77,6 +77,7 @@ const CHECKPOINT_ORDER = [
77
77
  'github-release',
78
78
  'claude-marketplace-install',
79
79
  'codex-marketplace-install',
80
+ 'kimi-marketplace-install',
80
81
  ];
81
82
 
82
83
  /**
@@ -92,6 +93,7 @@ const ADAPTER_ACTION_TYPE_MAP = {
92
93
  'github-release': 'github-release',
93
94
  'claude-marketplace-install': 'claude-marketplace-install',
94
95
  'codex-marketplace-install': 'codex-marketplace-install',
96
+ 'kimi-marketplace-install': 'kimi-marketplace-install',
95
97
  };
96
98
 
97
99
  /**
@@ -102,6 +104,7 @@ const ADAPTER_ACTION_TYPE_MAP = {
102
104
  const MARKETPLACE_TYPES = new Set([
103
105
  'claude-marketplace-install',
104
106
  'codex-marketplace-install',
107
+ 'kimi-marketplace-install',
105
108
  ]);
106
109
 
107
110
  // ---------------------------------------------------------------------------
@@ -33,7 +33,7 @@ import { readTrustedPackageResource } from '../core/trusted-resource.mjs';
33
33
 
34
34
  const execFile = promisify(execFileCb);
35
35
  const SKIP_DIRS = new Set([
36
- '.git', '.release-skill', '.worktrees', '.claude', '.codex', '.cache', '.tmp',
36
+ '.git', '.release-skill', '.worktrees', '.claude', '.codex', '.kimi', '.cache', '.tmp',
37
37
  '.pytest_cache', '.mypy_cache', '.ruff_cache', '.tox', '.venv', 'venv',
38
38
  'node_modules', 'dist', 'coverage', 'build', 'out', 'tmp', 'temp',
39
39
  'runs', 'test', 'tests', 'test-fixtures', 'fixtures', 'examples',
@@ -88,6 +88,7 @@ async function walkDiscoveryFiles(root, maxDepth = 8) {
88
88
  /^CHANGELOG(?:\.|$)/i.test(child.name) ||
89
89
  absolute.endsWith('/.claude-plugin/plugin.json') ||
90
90
  absolute.endsWith('/.codex-plugin/plugin.json') ||
91
+ absolute.endsWith('/.kimi-plugin/plugin.json') ||
91
92
  absolute.endsWith('/.claude-plugin/marketplace.json') ||
92
93
  absolute.endsWith('/.codex-plugin/marketplace.json'))
93
94
  ) {
@@ -422,7 +423,7 @@ async function discoverFacts(root) {
422
423
  const files = await walkDiscoveryFiles(root);
423
424
  const packageFiles = files.filter((path) => (
424
425
  basename(path) === 'package.json' &&
425
- !/[\\/]adapters[\\/](?:claude|codex)[\\/]package\.json$/.test(path)
426
+ !/[\\/]adapters[\\/](?:claude|codex|kimi)[\\/]package\.json$/.test(path)
426
427
  ));
427
428
  const pluginFiles = files.filter((path) => path.endsWith('/plugin.json'));
428
429
  const marketplaceFiles = files.filter((path) => path.endsWith('/marketplace.json'));
@@ -458,7 +459,9 @@ async function discoverFacts(root) {
458
459
  const value = await readJsonBounded(path, 'discovered plugin manifest');
459
460
  manifests.push({
460
461
  path: safeRelative(root, path),
461
- host: path.includes('/.claude-plugin/') ? 'claude' : 'codex',
462
+ host: path.includes('/.claude-plugin/') ? 'claude'
463
+ : path.includes('/.kimi-plugin/') ? 'kimi'
464
+ : 'codex',
462
465
  kind: path.endsWith('/marketplace.json') ? 'marketplace' : 'plugin',
463
466
  name: typeof value.name === 'string' ? value.name : null,
464
467
  version: typeof value.version === 'string' ? value.version : null,
@@ -483,7 +486,8 @@ async function discoverFacts(root) {
483
486
  name: skillIndex >= 0 ? segments[skillIndex + 1] ?? null : null,
484
487
  host: relPath.includes('/adapters/claude/') ? 'claude'
485
488
  : relPath.includes('/adapters/codex/') ? 'codex'
486
- : 'shared',
489
+ : relPath.includes('/adapters/kimi/') ? 'kimi'
490
+ : 'shared',
487
491
  };
488
492
  })
489
493
  .filter((item) => item.name)
@@ -651,7 +655,7 @@ function buildCandidates(facts) {
651
655
  const ids = new Set();
652
656
  const knownFiles = new Set(facts.fileDigests.map((file) => file.path));
653
657
  const manifestRoots = facts.manifests.map((manifest) => {
654
- const match = manifest.path.match(/^(.*?)(?:\/)?(?:\.claude-plugin|\.codex-plugin)\/(?:plugin|marketplace)\.json$/);
658
+ const match = manifest.path.match(/^(.*?)(?:\/)?(?:\.claude-plugin|\.codex-plugin|\.kimi-plugin)\/(?:plugin|marketplace)\.json$/);
655
659
  return { ...manifest, root: match?.[1] || '.' };
656
660
  });
657
661
  const manifestOwners = new Map();
@@ -698,6 +702,7 @@ function buildCandidates(facts) {
698
702
  ) distributions.push('npm');
699
703
  if (pluginHosts.includes('claude')) distributions.push('claude-plugin');
700
704
  if (pluginHosts.includes('codex')) distributions.push('codex-plugin');
705
+ if (pluginHosts.includes('kimi')) distributions.push('kimi-plugin');
701
706
  if (pkg.private && matchingLegacyUnits.length === 0 && facts.legacyReleaseConfigs.length > 0) continue;
702
707
  if (pkg.private && distributions.length === 0) continue;
703
708
 
@@ -68,6 +68,7 @@ const ADAPTER_ACTION_TYPE_MAP = {
68
68
  'github-release': 'github-release',
69
69
  'claude-marketplace-install': 'claude-marketplace-install',
70
70
  'codex-marketplace-install': 'codex-marketplace-install',
71
+ 'kimi-marketplace-install': 'kimi-marketplace-install',
71
72
  };
72
73
 
73
74
  // ---------------------------------------------------------------------------
@@ -706,7 +707,8 @@ export async function verifyRelease(options) {
706
707
  // =======================================================================
707
708
  // Step 3: Verify all actions via adapters
708
709
  //
709
- // Marketplace actions (claude-marketplace-install, codex-marketplace-install)
710
+ // Marketplace actions (claude-marketplace-install, codex-marketplace-install,
711
+ // kimi-marketplace-install)
710
712
  // are verified as fresh, isolated consumer installs in verify's own runDir.
711
713
  // This ensures verify does not read the publish run's consumer install
712
714
  // directories or evidence.
@@ -721,6 +723,7 @@ export async function verifyRelease(options) {
721
723
  const MARKETPLACE_TYPES = new Set([
722
724
  'claude-marketplace-install',
723
725
  'codex-marketplace-install',
726
+ 'kimi-marketplace-install',
724
727
  ]);
725
728
 
726
729
  for (const action of actions) {
@@ -831,7 +834,9 @@ export async function verifyRelease(options) {
831
834
 
832
835
  const distribution = action.type === 'claude-marketplace-install'
833
836
  ? 'claude-plugin'
834
- : 'codex-plugin';
837
+ : action.type === 'codex-marketplace-install'
838
+ ? 'codex-plugin'
839
+ : 'kimi-plugin';
835
840
  const installPath = verifyResult.observation?.installPath;
836
841
  consumerGateResults.push(...await runConsumerVerificationGates({
837
842
  plan,
@@ -845,10 +850,15 @@ export async function verifyRelease(options) {
845
850
  HOME: resolve(runDir, 'consumers', `claude-${action.parameters.plugin}`),
846
851
  CLAUDE_CONFIG_DIR: resolve(runDir, 'consumers', `claude-${action.parameters.plugin}`, '.claude'),
847
852
  }
848
- : {
849
- HOME: resolve(runDir, 'consumers', `codex-${action.parameters.plugin}`),
850
- CODEX_HOME: resolve(runDir, 'consumers', `codex-${action.parameters.plugin}`),
851
- },
853
+ : action.type === 'codex-marketplace-install'
854
+ ? {
855
+ HOME: resolve(runDir, 'consumers', `codex-${action.parameters.plugin}`),
856
+ CODEX_HOME: resolve(runDir, 'consumers', `codex-${action.parameters.plugin}`),
857
+ }
858
+ : {
859
+ HOME: resolve(runDir, 'consumers', `kimi-${action.parameters.plugin}`),
860
+ KIMI_CODE_HOME: resolve(runDir, 'consumers', `kimi-${action.parameters.plugin}`),
861
+ },
852
862
  }));
853
863
  } else {
854
864
  // --- Non-marketplace: read-only adapter.verify() ---
package/src/core/plan.mjs CHANGED
@@ -285,6 +285,7 @@ const EXPECTED_ADAPTER = {
285
285
  'npm-publish': 'npm',
286
286
  'claude-marketplace-install': 'plugin-marketplace',
287
287
  'codex-marketplace-install': 'plugin-marketplace',
288
+ 'kimi-marketplace-install': 'plugin-marketplace',
288
289
  'set-default-branch': 'git-github',
289
290
  };
290
291
 
@@ -301,6 +302,7 @@ const REQUIRED_ACTION_TYPES = ['push-snapshot', 'create-tag', 'github-release'];
301
302
  * - 1 npm-publish (adapter: npm) — only if the unit has an npm distribution
302
303
  * - 1 claude-marketplace-install (adapter: plugin-marketplace) — only if the unit has a claude-plugin distribution
303
304
  * - 1 codex-marketplace-install (adapter: plugin-marketplace) — only if the unit has a codex-plugin distribution
305
+ * - 1 kimi-marketplace-install (adapter: plugin-marketplace) — only if the unit has a kimi-plugin distribution
304
306
  *
305
307
  * Every action must bind to the correct unitId, correct adapter, correct
306
308
  * version, correct publicRepo (where applicable), and correct tag derived
@@ -821,6 +823,122 @@ export function validatePlanActionCompleteness(plan, options = {}) {
821
823
  }
822
824
  }
823
825
 
826
+ // kimi-marketplace-install: only required if unit has kimi-plugin distribution
827
+ const kimiDist = distributions.find((d) => d.type === 'kimi-plugin');
828
+ if (kimiDist) {
829
+ const plugin = kimiDist.plugin;
830
+ // marketplace is NOT a required identity field for kimi: Kimi Code has an
831
+ // interactive marketplace but no non-interactive install API, so the kimi
832
+ // action carries no executable marketplace identity (MINOR-1). Only plugin
833
+ // and entrySkill are required; a declared marketplace is tolerated as an
834
+ // optional legacy value but never bound as a required condition.
835
+ const marketplace = kimiDist.marketplace;
836
+ const entrySkill = kimiDist.entrySkill;
837
+ if (!plugin || !entrySkill) {
838
+ failures.push(`unit "${unitId}": kimi-plugin distribution requires plugin and entrySkill`);
839
+ }
840
+ expectedCount++;
841
+ const expectedActionId = `kimi-marketplace-install-${unitId}`;
842
+ const kimiActions = actions.filter(
843
+ (a) => a.unitId === unitId && a.type === 'kimi-marketplace-install',
844
+ );
845
+
846
+ if (kimiActions.length === 0) {
847
+ failures.push(
848
+ `unit "${unitId}": kimi-plugin distribution declared but "kimi-marketplace-install" action is missing`,
849
+ );
850
+ } else if (kimiActions.length > 1) {
851
+ failures.push(
852
+ `unit "${unitId}": duplicate kimi-marketplace-install actions (${kimiActions.length} found, expected 1)`,
853
+ );
854
+ } else {
855
+ const action = kimiActions[0];
856
+
857
+ if (action.id !== expectedActionId) {
858
+ failures.push(
859
+ `unit "${unitId}", action "${action.id}": id is "${action.id}", expected "${expectedActionId}"`,
860
+ );
861
+ }
862
+ if (action.unitId !== unitId) {
863
+ failures.push(
864
+ `unit "${unitId}", action "${action.id}": unitId is "${action.unitId}", expected "${unitId}"`,
865
+ );
866
+ }
867
+ if (action.adapter !== 'plugin-marketplace') {
868
+ failures.push(
869
+ `unit "${unitId}", action "${action.id}": adapter is "${action.adapter}", expected "plugin-marketplace"`,
870
+ );
871
+ }
872
+ if (action.status !== 'PENDING') {
873
+ failures.push(
874
+ `unit "${unitId}", action "${action.id}": status is "${action.status ?? '(missing)'}", expected "PENDING"`,
875
+ );
876
+ }
877
+
878
+ // Parameter checks
879
+ _checkRequired(action, 'parameters.consumer', action.parameters?.consumer, 'kimi', unitId, failures);
880
+ _checkRequired(action, 'parameters.plugin', action.parameters?.plugin, plugin, unitId, failures);
881
+ // marketplace is optional for kimi (MINOR-1). If a legacy plan still
882
+ // carries it, it must be consistent with the declared distribution but
883
+ // is never a required identity condition.
884
+ if (action.parameters?.marketplace !== undefined && marketplace !== undefined
885
+ && action.parameters.marketplace !== marketplace) {
886
+ failures.push(
887
+ `unit "${unitId}", action "${action.id}": parameters.marketplace is "${action.parameters.marketplace}", expected optional legacy value "${marketplace}"`,
888
+ );
889
+ }
890
+ _checkRequired(action, 'parameters.repo', action.parameters?.repo, publicRepo, unitId, failures);
891
+ _checkRequired(action, 'parameters.version', action.parameters?.version, targetVersion, unitId, failures);
892
+ _checkRequired(action, 'parameters.entrySkill', action.parameters?.entrySkill, entrySkill, unitId, failures);
893
+ if (production) {
894
+ _checkRequired(action, 'parameters.snapshotPath', action.parameters?.snapshotPath, frozen?.path, unitId, failures);
895
+ _checkRequired(action, 'parameters.ref', action.parameters?.ref, expectedTag, unitId, failures);
896
+ _checkRequired(action, 'parameters.manifestDigest', action.parameters?.manifestDigest, frozen?.manifestDigest, unitId, failures);
897
+ }
898
+ // timeoutMs is mandatory for all marketplace install actions.
899
+ // Legacy plans (pre-v0.1.5) lack this field; legacyCompatibility
900
+ // relaxes the check for reconcile/verify paths only, and only when
901
+ // the property is genuinely absent (undefined). An explicit null is
902
+ // NOT "absent" -- it is an invalid value and always fails closed,
903
+ // like strings or out-of-range numbers, even in legacyCompatibility.
904
+ {
905
+ const raw = action.parameters?.timeoutMs;
906
+ if (raw === undefined) {
907
+ if (!options.legacyCompatibility) {
908
+ failures.push(
909
+ `unit "${unitId}", action "${action.id}": parameters.timeoutMs is missing, expected a valid timeout (30000-900000)`,
910
+ );
911
+ }
912
+ } else {
913
+ // Field is present -- always validate range/type, even in legacy mode
914
+ if (typeof raw !== 'number' || !Number.isFinite(raw) || !Number.isInteger(raw)) {
915
+ failures.push(
916
+ `unit "${unitId}", action "${action.id}": parameters.timeoutMs must be a finite integer, got: ${JSON.stringify(raw)}`,
917
+ );
918
+ } else if (raw < 30000 || raw > 900000) {
919
+ failures.push(
920
+ `unit "${unitId}", action "${action.id}": parameters.timeoutMs must be between 30000 and 900000, got: ${raw}`,
921
+ );
922
+ }
923
+ }
924
+ }
925
+
926
+ // Expected checks. marketplace is NOT part of the kimi expected
927
+ // identity (MINOR-1): the kimi observation never binds a marketplace.
928
+ _checkRequired(action, 'expected.installed', action.expected?.installed, true, unitId, failures);
929
+ _checkRequired(action, 'expected.plugin', action.expected?.plugin, plugin, unitId, failures);
930
+ _checkRequired(action, 'expected.version', action.expected?.version, targetVersion, unitId, failures);
931
+ _checkRequired(action, 'expected.entrySkill', action.expected?.entrySkill, entrySkill, unitId, failures);
932
+ if (production) {
933
+ _checkRequired(action, 'expected.consumer', action.expected?.consumer, 'kimi', unitId, failures);
934
+ _checkRequired(action, 'expected.repo', action.expected?.repo, publicRepo, unitId, failures);
935
+ _checkRequired(action, 'expected.ref', action.expected?.ref, expectedTag, unitId, failures);
936
+ _checkRequired(action, 'expected.entrySkillFound', action.expected?.entrySkillFound, true, unitId, failures);
937
+ _checkRequired(action, 'expected.manifestDigest', action.expected?.manifestDigest, frozen?.manifestDigest, unitId, failures);
938
+ }
939
+ }
940
+ }
941
+
824
942
  // set-default-branch: only required when productionConfig.setAsDefaultBranch is true
825
943
  if (branchStrategy === 'initialize-default-branch') {
826
944
  const oldBranch = productionConfig.expectedCurrentDefaultBranch;
@@ -57,7 +57,7 @@ function validateGate(gate) {
57
57
  if (!gate.scope || typeof gate.scope.unit !== 'string') {
58
58
  throw gateError(gate, 'must declare scope.unit');
59
59
  }
60
- if (gate.phase === 'consumer-verify' && !['npm', 'claude-plugin', 'codex-plugin'].includes(gate.scope.distribution)) {
60
+ if (gate.phase === 'consumer-verify' && !['npm', 'claude-plugin', 'codex-plugin', 'kimi-plugin'].includes(gate.scope.distribution)) {
61
61
  throw gateError(gate, 'consumer-verify must declare a supported scope.distribution');
62
62
  }
63
63
  if (!Array.isArray(gate.command) || gate.command.length === 0 || gate.command.some((value) => typeof value !== 'string')) {
@@ -2,10 +2,10 @@
2
2
  * Pure producer: build adapters from skills and plugin templates.
3
3
  *
4
4
  * Reads skill metadata from skills-src/ and plugin.json templates,
5
- * generates self-contained adapter directories for each platform (claude, codex).
5
+ * generates self-contained adapter directories for each platform (claude, codex, kimi).
6
6
  *
7
7
  * Each adapter root contains:
8
- * - Plugin manifest (.claude-plugin/ or .codex-plugin/)
8
+ * - Plugin manifest (.claude-plugin/, .codex-plugin/, or .kimi-plugin/)
9
9
  * - Skills with host-specific root resolution
10
10
  * - bin/release-skill.mjs (wrapper) + bin/release-skill.bundle.mjs (self-contained bundle)
11
11
  * - schemas/ (JSON Schema files)
@@ -205,6 +205,12 @@ const PLATFORMS = [
205
205
  templateFileName: 'plugin.json',
206
206
  hasMarketplace: false,
207
207
  },
208
+ {
209
+ name: 'kimi',
210
+ pluginDirName: '.kimi-plugin',
211
+ templateFileName: 'plugin.json',
212
+ hasMarketplace: false,
213
+ },
208
214
  ];
209
215
 
210
216
  const REQUIRED_SCHEMA_FILES = Object.freeze([
@@ -290,37 +296,61 @@ const CODEX_PREAMBLE = `\
290
296
  >\n\
291
297
  `;
292
298
 
299
+ /**
300
+ * Preamble prepended to every Kimi Code SKILL.md.
301
+ *
302
+ * Kimi Code's official Agent Skills contract defines the body placeholder
303
+ * `${KIMI_SKILL_DIR}`: the host expands it to the absolute directory containing
304
+ * the current SKILL.md before sending the body to the agent. This is the ONLY
305
+ * authoritative input for locating the entry point — the agent must NOT guess
306
+ * any undocumented host-metadata path. The literal placeholder is emitted
307
+ * verbatim (the generator does not pre-expand it); the host expands it at load
308
+ * time. Generated skills live at `<pluginRoot>/skills/<name>/SKILL.md`, so the
309
+ * plugin root is two levels above the expanded skill directory.
310
+ */
311
+ const KIMI_PREAMBLE = `\
312
+ > **Kimi Code 安装入口解析协议**:Kimi Code 官方技能契约提供正文占位符 \`\${KIMI_SKILL_DIR}\`,宿主在向 Agent 发送正文前会将其展开为当前 \`SKILL.md\` 所在目录的绝对路径。必须把展开后的字面量作为当前技能目录的唯一权威输入,记为 \`SKILL_DIR\`。\n\
313
+ > 禁止从工作目录、可执行搜索路径、源码仓库、shell 调用上下文或任何未记载的宿主元数据路径猜测技能目录。若正文中的 \`\${KIMI_SKILL_DIR}\` 未被宿主展开(仍是字面量占位符),立即停止并报告安装定位失败。\n\
314
+ > 对 \`SKILL_DIR\` 执行 \`realpath\`,取其目录向上两级得到 \`PLUGIN_ROOT\`;校验真实技能路径匹配 \`PLUGIN_ROOT/skills/*/SKILL.md\` 且仍位于插件根内(路径包含检查)。\n\
315
+ > 令 \`RELEASE_SKILL_ENTRY=PLUGIN_ROOT/bin/release-skill.mjs\`,对入口执行 \`realpath\` containment、\`lstat\` 非符号链接且为普通文件校验。\n\
316
+ > 每一次 shell 工具调用都必须在同一个调用中用上述已验证绝对值设置 \`RELEASE_SKILL_ENTRY\`,然后执行 \`node "$RELEASE_SKILL_ENTRY" ...\`;不得依赖前一次 shell 的变量。\n\
317
+ >\n\
318
+ `;
319
+
293
320
  /**
294
321
  * Render SKILL.md content for a specific platform.
295
322
  *
296
323
  * - Claude: verbatim copy (uses ${CLAUDE_PLUGIN_ROOT})
297
324
  * - Codex: replaces the Claude-only path with $RELEASE_SKILL_ENTRY,
298
325
  * and prepends the path resolution protocol preamble.
326
+ * - Kimi: same substitution as Codex, with the Kimi Code preamble.
299
327
  *
300
328
  * @param {string} content - Canonical SKILL.md content.
301
- * @param {string} platformName - 'claude' or 'codex'.
329
+ * @param {string} platformName - 'claude', 'codex', or 'kimi'.
302
330
  * @returns {string}
303
331
  */
304
332
  function renderSkillForPlatform(content, platformName) {
305
333
  if (platformName === 'claude') return content;
306
334
 
307
- if (platformName === 'codex') {
335
+ if (platformName === 'codex' || platformName === 'kimi') {
336
+ const hostLabel = platformName === 'codex' ? 'Codex' : 'Kimi';
337
+ const preamble = platformName === 'codex' ? CODEX_PREAMBLE : KIMI_PREAMBLE;
308
338
  let rendered = content.replaceAll(
309
339
  '${CLAUDE_PLUGIN_ROOT}/bin/release-skill.mjs',
310
340
  '$RELEASE_SKILL_ENTRY',
311
341
  );
312
- // A remaining bare host-root reference has no safe Codex equivalent.
342
+ // A remaining bare host-root reference has no safe path-derived equivalent.
313
343
  if (rendered.includes('${CLAUDE_PLUGIN_ROOT}')) {
314
- throw new Error('Codex rendering found an unsupported bare CLAUDE_PLUGIN_ROOT reference');
344
+ throw new Error(`${hostLabel} rendering found an unsupported bare CLAUDE_PLUGIN_ROOT reference`);
315
345
  }
316
346
  // Preserve YAML frontmatter as the first bytes so the host can discover
317
347
  // the skill. Insert the host-specific protocol immediately after it.
318
348
  const frontmatterEnd = rendered.indexOf('\n---\n', 4);
319
349
  if (!rendered.startsWith('---\n') || frontmatterEnd < 0) {
320
- throw new Error('Codex rendering requires a valid leading YAML frontmatter block');
350
+ throw new Error(`${hostLabel} rendering requires a valid leading YAML frontmatter block`);
321
351
  }
322
352
  const insertionPoint = frontmatterEnd + '\n---\n'.length;
323
- return `${rendered.slice(0, insertionPoint)}\n${CODEX_PREAMBLE}${rendered.slice(insertionPoint)}`;
353
+ return `${rendered.slice(0, insertionPoint)}\n${preamble}${rendered.slice(insertionPoint)}`;
324
354
  }
325
355
 
326
356
  return content;