release-skill 0.9.22 → 0.9.24

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 (220) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codebuddy-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +2 -2
  4. package/.cursor-plugin/plugin.json +1 -1
  5. package/.kimi-plugin/plugin.json +1 -1
  6. package/.qoder-plugin/plugin.json +1 -1
  7. package/CHANGELOG.md +61 -2
  8. package/INSTALL.md +4 -4
  9. package/INSTALL.zh-CN.md +4 -4
  10. package/README.md +53 -32
  11. package/README.zh-CN.md +44 -27
  12. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  13. package/adapters/claude/bin/consumer-contract-vectors.json +7 -7
  14. package/adapters/claude/bin/error-codes.json +1 -1
  15. package/adapters/claude/bin/foundation-resource-binding.json +1 -1
  16. package/adapters/claude/bin/registry.json +50 -1
  17. package/adapters/claude/bin/release-skill.bundle.mjs +2680 -2180
  18. package/adapters/claude/bin/rules.json +1 -1
  19. package/adapters/claude/skills/release-assess/SKILL.md +17 -3
  20. package/adapters/claude/skills/release-config/SKILL.md +6 -0
  21. package/adapters/claude/skills/release-docs/SKILL.md +6 -0
  22. package/adapters/claude/skills/release-help/SKILL.md +7 -2
  23. package/adapters/claude/skills/release-marketplace/SKILL.md +6 -0
  24. package/adapters/claude/skills/release-prepare/SKILL.md +8 -2
  25. package/adapters/claude/skills/release-publish/SKILL.md +7 -1
  26. package/adapters/claude/skills/release-reconcile/SKILL.md +6 -0
  27. package/adapters/claude/skills/release-setup/SKILL.md +9 -1
  28. package/adapters/claude/skills/release-verify/SKILL.md +6 -0
  29. package/adapters/claude/src/schemas/file-set-apply-request.schema.json +78 -0
  30. package/adapters/claude/src/schemas/file-set-event-payload.schema.json +55 -0
  31. package/adapters/claude/src/schemas/file-set-prune-request.schema.json +17 -0
  32. package/adapters/claude/src/schemas/file-set-recovery-request.schema.json +29 -0
  33. package/adapters/claude/src/schemas/file-set-result.schema.json +79 -0
  34. package/adapters/claude/src/schemas/professional-conclusion.schema.json +132 -0
  35. package/adapters/claude/src/schemas/project-manifest.schema.json +91 -0
  36. package/adapters/claude/src/schemas/state-store-recovery-observation.schema.json +129 -0
  37. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  38. package/adapters/codex/bin/consumer-contract-vectors.json +7 -7
  39. package/adapters/codex/bin/error-codes.json +1 -1
  40. package/adapters/codex/bin/foundation-resource-binding.json +1 -1
  41. package/adapters/codex/bin/registry.json +50 -1
  42. package/adapters/codex/bin/release-skill.bundle.mjs +2680 -2180
  43. package/adapters/codex/bin/rules.json +1 -1
  44. package/adapters/codex/skills/release-assess/SKILL.md +17 -3
  45. package/adapters/codex/skills/release-config/SKILL.md +6 -0
  46. package/adapters/codex/skills/release-docs/SKILL.md +6 -0
  47. package/adapters/codex/skills/release-help/SKILL.md +9 -3
  48. package/adapters/codex/skills/release-marketplace/SKILL.md +6 -0
  49. package/adapters/codex/skills/release-prepare/SKILL.md +8 -2
  50. package/adapters/codex/skills/release-publish/SKILL.md +7 -1
  51. package/adapters/codex/skills/release-reconcile/SKILL.md +6 -0
  52. package/adapters/codex/skills/release-setup/SKILL.md +9 -1
  53. package/adapters/codex/skills/release-verify/SKILL.md +6 -0
  54. package/adapters/codex/src/schemas/file-set-apply-request.schema.json +78 -0
  55. package/adapters/codex/src/schemas/file-set-event-payload.schema.json +55 -0
  56. package/adapters/codex/src/schemas/file-set-prune-request.schema.json +17 -0
  57. package/adapters/codex/src/schemas/file-set-recovery-request.schema.json +29 -0
  58. package/adapters/codex/src/schemas/file-set-result.schema.json +79 -0
  59. package/adapters/codex/src/schemas/professional-conclusion.schema.json +132 -0
  60. package/adapters/codex/src/schemas/project-manifest.schema.json +91 -0
  61. package/adapters/codex/src/schemas/state-store-recovery-observation.schema.json +129 -0
  62. package/adapters/cursor/.cursor-plugin/plugin.json +1 -1
  63. package/adapters/cursor/bin/consumer-contract-vectors.json +7 -7
  64. package/adapters/cursor/bin/error-codes.json +1 -1
  65. package/adapters/cursor/bin/foundation-resource-binding.json +1 -1
  66. package/adapters/cursor/bin/registry.json +50 -1
  67. package/adapters/cursor/bin/release-skill.bundle.mjs +2680 -2180
  68. package/adapters/cursor/bin/rules.json +1 -1
  69. package/adapters/cursor/skills/release-assess/SKILL.md +17 -3
  70. package/adapters/cursor/skills/release-config/SKILL.md +6 -0
  71. package/adapters/cursor/skills/release-docs/SKILL.md +6 -0
  72. package/adapters/cursor/skills/release-help/SKILL.md +9 -3
  73. package/adapters/cursor/skills/release-marketplace/SKILL.md +6 -0
  74. package/adapters/cursor/skills/release-prepare/SKILL.md +8 -2
  75. package/adapters/cursor/skills/release-publish/SKILL.md +7 -1
  76. package/adapters/cursor/skills/release-reconcile/SKILL.md +6 -0
  77. package/adapters/cursor/skills/release-setup/SKILL.md +9 -1
  78. package/adapters/cursor/skills/release-verify/SKILL.md +6 -0
  79. package/adapters/cursor/src/schemas/file-set-apply-request.schema.json +78 -0
  80. package/adapters/cursor/src/schemas/file-set-event-payload.schema.json +55 -0
  81. package/adapters/cursor/src/schemas/file-set-prune-request.schema.json +17 -0
  82. package/adapters/cursor/src/schemas/file-set-recovery-request.schema.json +29 -0
  83. package/adapters/cursor/src/schemas/file-set-result.schema.json +79 -0
  84. package/adapters/cursor/src/schemas/professional-conclusion.schema.json +132 -0
  85. package/adapters/cursor/src/schemas/project-manifest.schema.json +91 -0
  86. package/adapters/cursor/src/schemas/state-store-recovery-observation.schema.json +129 -0
  87. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  88. package/adapters/kimi/bin/consumer-contract-vectors.json +7 -7
  89. package/adapters/kimi/bin/error-codes.json +1 -1
  90. package/adapters/kimi/bin/foundation-resource-binding.json +1 -1
  91. package/adapters/kimi/bin/registry.json +50 -1
  92. package/adapters/kimi/bin/release-skill.bundle.mjs +2680 -2180
  93. package/adapters/kimi/bin/rules.json +1 -1
  94. package/adapters/kimi/skills/release-assess/SKILL.md +17 -3
  95. package/adapters/kimi/skills/release-config/SKILL.md +6 -0
  96. package/adapters/kimi/skills/release-docs/SKILL.md +6 -0
  97. package/adapters/kimi/skills/release-help/SKILL.md +9 -3
  98. package/adapters/kimi/skills/release-marketplace/SKILL.md +6 -0
  99. package/adapters/kimi/skills/release-prepare/SKILL.md +8 -2
  100. package/adapters/kimi/skills/release-publish/SKILL.md +7 -1
  101. package/adapters/kimi/skills/release-reconcile/SKILL.md +6 -0
  102. package/adapters/kimi/skills/release-setup/SKILL.md +9 -1
  103. package/adapters/kimi/skills/release-verify/SKILL.md +6 -0
  104. package/adapters/kimi/src/schemas/file-set-apply-request.schema.json +78 -0
  105. package/adapters/kimi/src/schemas/file-set-event-payload.schema.json +55 -0
  106. package/adapters/kimi/src/schemas/file-set-prune-request.schema.json +17 -0
  107. package/adapters/kimi/src/schemas/file-set-recovery-request.schema.json +29 -0
  108. package/adapters/kimi/src/schemas/file-set-result.schema.json +79 -0
  109. package/adapters/kimi/src/schemas/professional-conclusion.schema.json +132 -0
  110. package/adapters/kimi/src/schemas/project-manifest.schema.json +91 -0
  111. package/adapters/kimi/src/schemas/state-store-recovery-observation.schema.json +129 -0
  112. package/adapters/qoder/.qoder-plugin/plugin.json +1 -1
  113. package/adapters/qoder/bin/consumer-contract-vectors.json +7 -7
  114. package/adapters/qoder/bin/error-codes.json +1 -1
  115. package/adapters/qoder/bin/foundation-resource-binding.json +1 -1
  116. package/adapters/qoder/bin/registry.json +50 -1
  117. package/adapters/qoder/bin/release-skill.bundle.mjs +2680 -2180
  118. package/adapters/qoder/bin/rules.json +1 -1
  119. package/adapters/qoder/skills/release-assess/SKILL.md +17 -3
  120. package/adapters/qoder/skills/release-config/SKILL.md +6 -0
  121. package/adapters/qoder/skills/release-docs/SKILL.md +6 -0
  122. package/adapters/qoder/skills/release-help/SKILL.md +9 -3
  123. package/adapters/qoder/skills/release-marketplace/SKILL.md +6 -0
  124. package/adapters/qoder/skills/release-prepare/SKILL.md +8 -2
  125. package/adapters/qoder/skills/release-publish/SKILL.md +7 -1
  126. package/adapters/qoder/skills/release-reconcile/SKILL.md +6 -0
  127. package/adapters/qoder/skills/release-setup/SKILL.md +9 -1
  128. package/adapters/qoder/skills/release-verify/SKILL.md +6 -0
  129. package/adapters/qoder/src/schemas/file-set-apply-request.schema.json +78 -0
  130. package/adapters/qoder/src/schemas/file-set-event-payload.schema.json +55 -0
  131. package/adapters/qoder/src/schemas/file-set-prune-request.schema.json +17 -0
  132. package/adapters/qoder/src/schemas/file-set-recovery-request.schema.json +29 -0
  133. package/adapters/qoder/src/schemas/file-set-result.schema.json +79 -0
  134. package/adapters/qoder/src/schemas/professional-conclusion.schema.json +132 -0
  135. package/adapters/qoder/src/schemas/project-manifest.schema.json +91 -0
  136. package/adapters/qoder/src/schemas/state-store-recovery-observation.schema.json +129 -0
  137. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
  138. package/adapters/workbuddy/bin/consumer-contract-vectors.json +7 -7
  139. package/adapters/workbuddy/bin/error-codes.json +1 -1
  140. package/adapters/workbuddy/bin/foundation-resource-binding.json +1 -1
  141. package/adapters/workbuddy/bin/registry.json +50 -1
  142. package/adapters/workbuddy/bin/release-skill.bundle.mjs +2680 -2180
  143. package/adapters/workbuddy/bin/rules.json +1 -1
  144. package/adapters/workbuddy/skills/release-assess/SKILL.md +17 -3
  145. package/adapters/workbuddy/skills/release-config/SKILL.md +6 -0
  146. package/adapters/workbuddy/skills/release-docs/SKILL.md +6 -0
  147. package/adapters/workbuddy/skills/release-help/SKILL.md +7 -2
  148. package/adapters/workbuddy/skills/release-marketplace/SKILL.md +6 -0
  149. package/adapters/workbuddy/skills/release-prepare/SKILL.md +8 -2
  150. package/adapters/workbuddy/skills/release-publish/SKILL.md +7 -1
  151. package/adapters/workbuddy/skills/release-reconcile/SKILL.md +6 -0
  152. package/adapters/workbuddy/skills/release-setup/SKILL.md +9 -1
  153. package/adapters/workbuddy/skills/release-verify/SKILL.md +6 -0
  154. package/adapters/workbuddy/src/schemas/file-set-apply-request.schema.json +78 -0
  155. package/adapters/workbuddy/src/schemas/file-set-event-payload.schema.json +55 -0
  156. package/adapters/workbuddy/src/schemas/file-set-prune-request.schema.json +17 -0
  157. package/adapters/workbuddy/src/schemas/file-set-recovery-request.schema.json +29 -0
  158. package/adapters/workbuddy/src/schemas/file-set-result.schema.json +79 -0
  159. package/adapters/workbuddy/src/schemas/professional-conclusion.schema.json +132 -0
  160. package/adapters/workbuddy/src/schemas/project-manifest.schema.json +91 -0
  161. package/adapters/workbuddy/src/schemas/state-store-recovery-observation.schema.json +129 -0
  162. package/bin/consumer-contract-vectors.json +7 -7
  163. package/bin/error-codes.json +1 -1
  164. package/bin/foundation-resource-binding.json +1 -1
  165. package/bin/registry.json +50 -1
  166. package/bin/release-skill-cli.mjs +80 -5
  167. package/bin/release-skill.bundle.mjs +2680 -2180
  168. package/bin/rules.json +1 -1
  169. package/package.json +4 -4
  170. package/platform-manifest.json +4 -4
  171. package/references/05-evidence-and-errors.md +2 -2
  172. package/scripts/build-bundle.mjs +2 -1
  173. package/scripts/sync-public-files.mjs +1 -1
  174. package/skills/release-assess/SKILL.md +17 -3
  175. package/skills/release-config/SKILL.md +6 -0
  176. package/skills/release-docs/SKILL.md +6 -0
  177. package/skills/release-help/SKILL.md +7 -2
  178. package/skills/release-marketplace/SKILL.md +6 -0
  179. package/skills/release-prepare/SKILL.md +8 -2
  180. package/skills/release-publish/SKILL.md +7 -1
  181. package/skills/release-reconcile/SKILL.md +6 -0
  182. package/skills/release-setup/SKILL.md +9 -1
  183. package/skills/release-verify/SKILL.md +6 -0
  184. package/skills-src/release-assess/SKILL.md +17 -3
  185. package/skills-src/release-config/SKILL.md +6 -0
  186. package/skills-src/release-docs/SKILL.md +6 -0
  187. package/skills-src/release-help/SKILL.md +7 -2
  188. package/skills-src/release-marketplace/SKILL.md +6 -0
  189. package/skills-src/release-prepare/SKILL.md +8 -2
  190. package/skills-src/release-publish/SKILL.md +7 -1
  191. package/skills-src/release-reconcile/SKILL.md +6 -0
  192. package/skills-src/release-setup/SKILL.md +9 -1
  193. package/skills-src/release-verify/SKILL.md +6 -0
  194. package/src/adapters/plugin-marketplace.mjs +7 -12
  195. package/src/artifacts/producer-registry.mjs +4 -3
  196. package/src/commands/assess.mjs +23 -3
  197. package/src/commands/post-release-local.mjs +2 -2
  198. package/src/commands/prepare.mjs +5 -30
  199. package/src/commands/professional-proof.mjs +506 -0
  200. package/src/commands/setup.mjs +16 -5
  201. package/src/commands/verify-records.mjs +14 -0
  202. package/src/core/baseline.mjs +9 -1
  203. package/src/core/derived-artifact-gates.mjs +38 -9
  204. package/src/core/redact.mjs +12 -5
  205. package/src/core/source-authority.mjs +28 -3
  206. package/src/npm/npm-entry-closure.mjs +71 -9
  207. package/src/producers/build-adapters.mjs +18 -5
  208. package/src/producers/foundation-resource-projection.mjs +3 -3
  209. package/src/producers/render-public-assets.mjs +2 -1
  210. package/src/producers/sync-skills.mjs +2 -1
  211. package/src/schemas/file-set-apply-request.schema.json +78 -0
  212. package/src/schemas/file-set-event-payload.schema.json +55 -0
  213. package/src/schemas/file-set-prune-request.schema.json +17 -0
  214. package/src/schemas/file-set-recovery-request.schema.json +29 -0
  215. package/src/schemas/file-set-result.schema.json +79 -0
  216. package/src/schemas/professional-conclusion.schema.json +132 -0
  217. package/src/schemas/project-manifest.schema.json +91 -0
  218. package/src/schemas/state-store-recovery-observation.schema.json +129 -0
  219. package/src/snapshot/frozen.mjs +19 -14
  220. package/src/core/run-retention.mjs +0 -227
@@ -10,7 +10,10 @@
10
10
  * freshness gate:
11
11
  *
12
12
  * - adapter gate: runs `scripts/build-adapters.mjs --check` (drift list,
13
- * exit 1 on drift — the same supported check the scripts surface offers);
13
+ * exit 1 on drift — the same supported check the scripts surface offers).
14
+ * Exit 2 and an explicit process start failure mean that check did not
15
+ * finish, so freshness is unknown. Timeout stays timeout. Other gates
16
+ * keep their existing numeric-exit mapping.
14
17
  * - platform manifest gate: runs
15
18
  * `scripts/generate-platform-manifest.mjs --check`;
16
19
  * - fact-pin gate: runs the version fact pins of
@@ -193,13 +196,15 @@ export async function checkDerivedArtifactGate(kind, pkgRoot, options = {}) {
193
196
  durationMs: Date.now() - startedAt,
194
197
  };
195
198
  } catch (err) {
196
- // Timeout / spawn failure fail closed too: an undecidable gate is drift.
199
+ // Timeout stays timeout. Adapter exit 2 and an explicit spawn failure
200
+ // mean the check did not finish. Every other non-zero result, including
201
+ // other gates' numeric exits, remains drift.
197
202
  const stdoutTail = boundedOutputTail(err?.stdout ?? '');
198
203
  const stderrTail = boundedOutputTail(err?.stderr ?? err?.message ?? '');
199
204
  return {
200
205
  applicable: true,
201
206
  fresh: false,
202
- reason: err?.killed || err?.code === 'ETIMEDOUT' ? 'timeout' : 'drift',
207
+ reason: failureReason(kind, err),
203
208
  artifact: gate.artifact,
204
209
  exitCode: typeof err?.code === 'number' ? err.code : null,
205
210
  stdoutTail,
@@ -209,6 +214,25 @@ export async function checkDerivedArtifactGate(kind, pkgRoot, options = {}) {
209
214
  }
210
215
  }
211
216
 
217
+ function failureReason(kind, err) {
218
+ if (err?.killed || err?.code === 'ETIMEDOUT') return 'timeout';
219
+ if (kind === 'adapters' && (isProcessStartFailure(err) || err?.code === 2)) {
220
+ return 'execution-failed';
221
+ }
222
+ return 'drift';
223
+ }
224
+
225
+ function isProcessStartFailure(err) {
226
+ const syscall = err?.syscall;
227
+ if (syscall === 'spawn' || syscall === 'spawnSync') return true;
228
+ const executable = err?.path;
229
+ // Node 22 execFile sets syscall to "spawn <executable>" when the file cannot be started.
230
+ return typeof syscall === 'string'
231
+ && typeof executable === 'string'
232
+ && executable.length > 0
233
+ && syscall === `spawn ${executable}`;
234
+ }
235
+
212
236
  /** Adapter freshness decision (build-adapters --check). */
213
237
  export function checkAdapterFreshness(pkgRoot, options = {}) {
214
238
  return checkDerivedArtifactGate('adapters', pkgRoot, options);
@@ -230,14 +254,19 @@ async function assertGate(kind, pkgRoot, options = {}) {
230
254
  return result;
231
255
  }
232
256
  const gate = GATES[kind];
233
- const subject = kind === 'adapters'
234
- ? 'adapters/ is out of sync with its sources (build-adapters --check reported drift)'
235
- : kind === 'platform-manifest'
236
- ? 'platform-manifest.json is out of sync with the current package tree (generate-platform-manifest --check reported drift)'
237
- : 'the release-docs-self-bootstrap fact pins are stale (the hermetic fact-pin check failed)';
257
+ const unfinishedAdapterCheck = kind === 'adapters'
258
+ && (result.reason === 'execution-failed' || result.reason === 'timeout');
259
+ const subject = unfinishedAdapterCheck
260
+ ? `adapter freshness cannot be determined because build-adapters --check did not finish (${result.reason})`
261
+ : kind === 'adapters'
262
+ ? 'adapters/ is out of sync with its sources (build-adapters --check reported drift)'
263
+ : kind === 'platform-manifest'
264
+ ? 'platform-manifest.json is out of sync with the current package tree (generate-platform-manifest --check reported drift)'
265
+ : 'the release-docs-self-bootstrap fact pins are stale (the hermetic fact-pin check failed)';
266
+ const remediation = unfinishedAdapterCheck ? '' : ` ${gate.remediation}`;
238
267
  throw new ReleaseError(
239
268
  DERIVED_ARTIFACT_STALE,
240
- `${subject}. ${DERIVED_ARTIFACT_PREGATE_NOTE} ${gate.remediation}`,
269
+ `${subject}. ${DERIVED_ARTIFACT_PREGATE_NOTE}${remediation}`,
241
270
  {
242
271
  artifact: result.artifact,
243
272
  reason: result.reason,
@@ -54,12 +54,19 @@ export const REDACTED_PATH_PLACEHOLDER = '<redacted-path>';
54
54
  // (b) UNC paths (\\server\share...);
55
55
  // (c) POSIX absolute paths: a '/' anchored at the start of the string or
56
56
  // preceded by a delimiter (whitespace, quote, '=', ':', ',', '(', '[',
57
- // '{', '<'), running until whitespace or a quote. The left boundary keeps
58
- // redaction from firing inside relative fragments ('src/core/x.mjs'),
59
- // fragment-anchored JSON pointers ('#/required'), or protocol-relative
60
- // URLs ('https://x/y').
57
+ // '{', '<'). Non-final '/'-separated segments may contain spaces (a
58
+ // volume name such as /Volumes/Samsung 990 pro 4T/src/file), so the
59
+ // token continues across those spaces and stops before trailing prose
60
+ // ('plan.json rejected: ...'). The left boundary keeps redaction from
61
+ // firing inside relative fragments ('src/core/x.mjs'), fragment-anchored
62
+ // JSON pointers ('#/required'), or protocol-relative URLs ('https://x/y').
63
+ // Windows drive and UNC alternatives still stop at whitespace; only the
64
+ // POSIX alternative crosses spaces, and only in non-final segments.
65
+ // A segment starting with a space right after '/' (e.g. /Volumes/ 990/x)
66
+ // and Windows drive/UNC tokens still truncate at that whitespace (known
67
+ // limitation).
61
68
  const PATH_TOKEN_RE =
62
- /(?<=^|[\s'"=:,([{<])(?:[A-Za-z]:[\\/][^\s'"]*|\\\\[^\s'"]+|\/[^\s'"]+)/g;
69
+ /(?<=^|[\s'"=:,([{<])(?:[A-Za-z]:[\\/][^\s'"]*|\\\\[^\s'"]+|\/(?:[^/\s'"]+(?:[ ]+[^/\s'"]+)*\/)*[^/\s'"]*)/g;
63
70
 
64
71
  // Trailing prose punctuation that may cling to a token and must survive.
65
72
  const TRAILING_PUNCT_RE = /^(.*?)([.!,;:)\]}>]*)$/;
@@ -272,6 +272,21 @@ function normalizeLocalGitMode(mode) {
272
272
  return (mode & 0o111) === 0 ? '100644' : '100755';
273
273
  }
274
274
 
275
+ function gitPathSegments(value) {
276
+ return String(value).replaceAll('\\', '/').replace(/\/+$/u, '').split('/').filter(Boolean);
277
+ }
278
+
279
+ function sharesDirectoryBoundary(recordPath, requestedPath) {
280
+ const recordSegments = gitPathSegments(recordPath);
281
+ const requestedSegments = gitPathSegments(requestedPath);
282
+ const limit = Math.min(recordSegments.length, requestedSegments.length);
283
+ if (limit === 0) return false;
284
+ for (let index = 0; index < limit; index += 1) {
285
+ if (recordSegments[index] !== requestedSegments[index]) return false;
286
+ }
287
+ return true;
288
+ }
289
+
275
290
  /**
276
291
  * Check only frozen source-input paths for staged, unstaged or untracked
277
292
  * changes. Unrelated dirty files remain allowed.
@@ -310,11 +325,21 @@ export async function checkSourceInputDirty({ closure, root, execFn = execFile }
310
325
  if (record.length < 4) continue;
311
326
  const status = record.slice(0, 2);
312
327
  const recordPath = record.slice(3);
328
+ let renamedPath = '';
329
+ if (status.startsWith('R') || status.startsWith('C')) {
330
+ renamedPath = records[index + 1] ?? '';
331
+ index += 1;
332
+ }
313
333
  // Ignored inputs are still untracked inputs. A file explicitly selected
314
334
  // for publication must not escape the prepare gate merely because a
315
- // .gitignore rule hides it from ordinary status output.
316
- dirtyPaths.push(recordPath);
317
- if (status.startsWith('R') || status.startsWith('C')) index += 1;
335
+ // .gitignore rule hides it from ordinary status output. A porcelain row
336
+ // counts only when its path shares a full directory segment with a
337
+ // requested closure path. A character prefix is not a selection.
338
+ for (const candidate of [recordPath, renamedPath]) {
339
+ if (candidate && paths.some((requested) => sharesDirectoryBoundary(candidate, requested))) {
340
+ dirtyPaths.push(candidate);
341
+ }
342
+ }
318
343
  }
319
344
  return {
320
345
  dirty: dirtyPaths.length > 0,
@@ -84,6 +84,37 @@ function checkTarget({ field, target, fileIndex, requireDotSlash = false }) {
84
84
  };
85
85
  }
86
86
 
87
+ // Identity mapping whose only star is the final character, for example
88
+ // "./schemas/*" -> "./schemas/*". Node replaces that star with any non-empty
89
+ // string, including "/". Other shapes stay rejected.
90
+ function isDirectTrailingStarMapping(key, value) {
91
+ return typeof value === 'string'
92
+ && key === value
93
+ && key.startsWith('./')
94
+ && key.indexOf('*') === key.length - 1;
95
+ }
96
+
97
+ function validateTrailingStarPrefix(pattern) {
98
+ const head = pattern.slice(0, -1);
99
+ if (head === './') return {};
100
+ const staticTarget = head.endsWith('/') ? head.slice(0, -1) : head;
101
+ const validated = validateRelativeTarget(staticTarget, 'exports', { requireDotSlash: true });
102
+ if (validated.error) return validated;
103
+ return {};
104
+ }
105
+
106
+ function matchingIndexPaths(pattern, fileIndex) {
107
+ const prefix = pattern.slice(0, -1);
108
+ const matched = [];
109
+ for (const path of fileIndex.keys()) {
110
+ if (typeof path !== 'string') continue;
111
+ const subpath = `./${path}`;
112
+ if (subpath.startsWith(prefix) && subpath.length >= pattern.length) matched.push(path);
113
+ }
114
+ matched.sort();
115
+ return matched;
116
+ }
117
+
87
118
  function collectExports(value, location, result) {
88
119
  if (value === null) return;
89
120
  if (typeof value === 'string') {
@@ -117,14 +148,18 @@ function collectExports(value, location, result) {
117
148
  for (const [key, child] of Object.entries(value)) {
118
149
  const childLocation = `${location}.${key}`;
119
150
  if (key.includes('*')) {
120
- result.errors.push(
121
- entryError(
122
- 'exports',
123
- key,
124
- 'unsupported_entry_shape',
125
- `${childLocation} uses a wildcard subpath that the static gate cannot verify`,
126
- ),
127
- );
151
+ if (isDirectTrailingStarMapping(key, child)) {
152
+ result.patterns.push({ location: childLocation, pattern: key });
153
+ } else {
154
+ result.errors.push(
155
+ entryError(
156
+ 'exports',
157
+ key,
158
+ 'unsupported_entry_shape',
159
+ `${childLocation} uses a wildcard subpath that the static gate cannot verify`,
160
+ ),
161
+ );
162
+ }
128
163
  continue;
129
164
  }
130
165
  collectExports(child, childLocation, result);
@@ -166,7 +201,7 @@ export function checkNpmEntryClosure(manifest, fileIndex) {
166
201
  }
167
202
 
168
203
  if (manifest.exports !== undefined) {
169
- const exportsResult = { targets: [], errors: [], diagnostics: [] };
204
+ const exportsResult = { targets: [], patterns: [], errors: [], diagnostics: [] };
170
205
  collectExports(manifest.exports, 'exports', exportsResult);
171
206
  result.errors.push(...exportsResult.errors);
172
207
  result.diagnostics.push(...exportsResult.diagnostics);
@@ -180,6 +215,33 @@ export function checkNpmEntryClosure(manifest, fileIndex) {
180
215
  result.entries.push(...checked.entries);
181
216
  result.errors.push(...checked.errors);
182
217
  }
218
+ for (const pattern of exportsResult.patterns) {
219
+ const validated = validateTrailingStarPrefix(pattern.pattern);
220
+ if (validated.error) {
221
+ result.errors.push(validated.error);
222
+ continue;
223
+ }
224
+ const matched = matchingIndexPaths(pattern.pattern, fileIndex);
225
+ if (matched.length === 0) {
226
+ result.errors.push(entryError(
227
+ 'exports',
228
+ pattern.pattern,
229
+ 'empty_pattern_match',
230
+ `${pattern.location} matched no files in the package file index`,
231
+ ));
232
+ continue;
233
+ }
234
+ for (const path of matched) {
235
+ const checked = checkTarget({
236
+ field: 'exports',
237
+ target: `./${path}`,
238
+ fileIndex,
239
+ requireDotSlash: true,
240
+ });
241
+ result.entries.push(...checked.entries);
242
+ result.errors.push(...checked.errors);
243
+ }
244
+ }
183
245
  }
184
246
 
185
247
  return result;
@@ -22,6 +22,7 @@
22
22
  import { readdir, mkdir, lstat, realpath, open, rm } from 'node:fs/promises';
23
23
  import { constants as fsConstants } from 'node:fs';
24
24
  import { join, dirname, relative, isAbsolute, sep, resolve, posix as pathPosix } from 'node:path';
25
+ import { fileURLToPath } from 'node:url';
25
26
 
26
27
  import { sha256Hex } from '../core/digest.mjs';
27
28
  import { digestDocument } from 'skill-family-contracts';
@@ -392,11 +393,23 @@ function renderSkillForPlatform(content, platformName) {
392
393
  '${CLAUDE_PLUGIN_ROOT}/bin/release-skill.mjs',
393
394
  '$RELEASE_SKILL_ENTRY',
394
395
  );
395
- const renderedPreamble = rendered.includes('$RELEASE_SKILL_LOCAL_FINISH_ENTRY')
396
- ? preamble
396
+ const hasMainEntry = rendered.includes('$RELEASE_SKILL_ENTRY');
397
+ const hasFinishEntry = rendered.includes('$RELEASE_SKILL_LOCAL_FINISH_ENTRY');
398
+ const mainBindingLine = '> 令 `RELEASE_SKILL_ENTRY=PLUGIN_ROOT/bin/release-skill.mjs`,对入口执行 `realpath` containment、`lstat` 非符号链接且为普通文件校验。\n';
399
+ const finishBindingLine = '> 令 `RELEASE_SKILL_LOCAL_FINISH_ENTRY=PLUGIN_ROOT/bin/release-skill-local-finish.mjs`,对入口执行 `realpath` containment、`lstat` 非符号链接且为普通文件校验。\n';
400
+ const mainShellLine = '> 每一次 shell 工具调用都必须在同一个调用中用上述已验证绝对值设置 `RELEASE_SKILL_ENTRY`,然后执行 `node "$RELEASE_SKILL_ENTRY" ...`;不得依赖前一次 shell 的变量。\n';
401
+ const dualShellLine = '> 每一次 shell 工具调用都必须在同一个调用中用上述已验证绝对值设置 `RELEASE_SKILL_ENTRY` 与 `RELEASE_SKILL_LOCAL_FINISH_ENTRY`,然后执行 `node "$RELEASE_SKILL_ENTRY" ...` 或 `node "$RELEASE_SKILL_LOCAL_FINISH_ENTRY" ...`;不得依赖前一次 shell 的变量。\n';
402
+ let renderedPreamble = preamble;
403
+ if (hasFinishEntry && hasMainEntry) {
404
+ if (!preamble.includes(mainBindingLine) || !preamble.includes(mainShellLine)) {
405
+ throw new Error(`${hostLabel} dual-entry rendering requires the original main-entry protocol lines`);
406
+ }
407
+ renderedPreamble = preamble.replace(`${mainBindingLine}${mainShellLine}`, `${mainBindingLine}${finishBindingLine}${dualShellLine}`);
408
+ } else if (hasFinishEntry) {
409
+ renderedPreamble = preamble
397
410
  .replaceAll('RELEASE_SKILL_ENTRY', 'RELEASE_SKILL_LOCAL_FINISH_ENTRY')
398
- .replaceAll('release-skill.mjs', 'release-skill-local-finish.mjs')
399
- : preamble;
411
+ .replaceAll('release-skill.mjs', 'release-skill-local-finish.mjs');
412
+ }
400
413
  if (platformName === 'cursor' && /\$\{(?:CLAUDE|CURSOR|CODEBUDDY|KIMI)[A-Z_]*\}/u.test(rendered)) {
401
414
  throw new Error('Cursor rendering contains a residual host-specific placeholder');
402
415
  }
@@ -701,7 +714,7 @@ async function generateAdapterFiles(platform, skills, templateJson, root, market
701
714
  * @param {string} [options.platformFilter] - Only generate for this platform name.
702
715
  */
703
716
  export async function produceBuildAdapters({ inputs, output, outputRoots, platformFilter } = {}) {
704
- const defaultRoot = new URL('../..', import.meta.url).pathname;
717
+ const defaultRoot = fileURLToPath(new URL('../..', import.meta.url));
705
718
  const requestedRoot = inputs ?? defaultRoot;
706
719
 
707
720
  // ── Phase 1: Validate input root is not a symlink ──
@@ -55,9 +55,9 @@ import { digestBytes, publishFileOrReplace, readFileStrict, withTemporaryWorkspa
55
55
  import { canonicalJson } from '../core/digest.mjs';
56
56
 
57
57
  export const FOUNDATION_PACKAGES = Object.freeze({
58
- 'skill-family-contracts': '0.21.0',
59
- 'skill-family-engineering-kit': '0.21.0',
60
- 'skill-family-harness-node': '0.21.0',
58
+ 'skill-family-contracts': '0.22.0',
59
+ 'skill-family-engineering-kit': '0.22.0',
60
+ 'skill-family-harness-node': '0.22.0',
61
61
  });
62
62
 
63
63
  export const BINDING_RECORD_PATH = 'bin/foundation-resource-binding.json';
@@ -13,6 +13,7 @@
13
13
 
14
14
  import { readFile, writeFile, readdir, stat, mkdir } from 'node:fs/promises';
15
15
  import { join, relative } from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
16
17
 
17
18
  import { canonicalJson, sha256Hex } from '../core/digest.mjs';
18
19
  import { digestDocument } from 'skill-family-contracts';
@@ -111,7 +112,7 @@ async function renderDir(srcDir, outDir, pathPrefix) {
111
112
  * @returns {Promise<{outputs: Array, outputManifestDigest: string}>}
112
113
  */
113
114
  export async function produceRenderPublicAssets({ inputs, output } = {}) {
114
- const defaultRoot = new URL('../../..', import.meta.url).pathname;
115
+ const defaultRoot = fileURLToPath(new URL('../../..', import.meta.url));
115
116
  const root = inputs ?? defaultRoot;
116
117
 
117
118
  const srcDirs = [
@@ -11,6 +11,7 @@
11
11
 
12
12
  import { readdir, readFile, writeFile, mkdir } from 'node:fs/promises';
13
13
  import { join } from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
14
15
 
15
16
  import { canonicalJson, sha256Hex } from '../core/digest.mjs';
16
17
  import { digestDocument } from 'skill-family-contracts';
@@ -44,7 +45,7 @@ export function computeSyncSkillsDigest(sourceBytes) {
44
45
  * @param {string} options.output - Output directory path.
45
46
  */
46
47
  export async function produceSyncSkills({ inputs, output } = {}) {
47
- const defaultSrc = new URL('../../skills-src', import.meta.url).pathname;
48
+ const defaultSrc = fileURLToPath(new URL('../../skills-src', import.meta.url));
48
49
  const srcDir = inputs ?? defaultSrc;
49
50
 
50
51
  const skillDirs = [];
@@ -0,0 +1,78 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://contracts.skill-family.example/v1/file-set-apply-request.json",
4
+ "title": "File set apply request",
5
+ "description": "Frozen JSON projection. Runtime checks bind bytes, cumulative capacity, modes, canonical paths, filesystem identities and cooperative ownership before business writes.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["schemaVersion", "kind", "operationId", "root", "rootBinding", "recoveryRelPath", "allowAdjacentStaging", "environment", "validationTimeoutMs", "operations"],
9
+ "properties": {
10
+ "schemaVersion": { "const": 1 },
11
+ "kind": { "const": "skill-family.file-set-apply-request" },
12
+ "operationId": { "$ref": "#/$defs/operationId" },
13
+ "root": { "$ref": "#/$defs/root" },
14
+ "rootBinding": { "$ref": "https://contracts.skill-family.example/v1/filesystem-root-binding.json" },
15
+ "recoveryRelPath": { "const": ".foundation-file-apply" },
16
+ "allowAdjacentStaging": { "const": true },
17
+ "environment": { "$ref": "#/$defs/environment" },
18
+ "validationTimeoutMs": { "type": "integer", "minimum": 1, "maximum": 2147483647 },
19
+ "operations": {
20
+ "type": "array", "minItems": 1, "maxItems": 1024, "uniqueItems": true,
21
+ "items": { "$ref": "#/$defs/operation" }
22
+ }
23
+ },
24
+ "$defs": {
25
+ "operationId": { "type": "string", "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" },
26
+ "root": { "type": "string", "pattern": "^/(?!.*(?:^|/)(?:\\.|\\.\\.)(?:/|$))(?!.*//)(?!.*[\\\\\\u0000])(?:[^/]+(?:/[^/]+)*)?$" },
27
+ "path": { "type": "string", "minLength": 1, "pattern": "^(?!/)(?!.*(?:^|/)(?:\\.|\\.\\.|\\.git|\\.foundation-file-apply)(?:/|$))(?!.*[\\\\\\u0000])[^/]+(?:/[^/]+)*$" },
28
+ "environment": {
29
+ "type": "object", "additionalProperties": false,
30
+ "required": ["filesystem", "cooperativeWriters"],
31
+ "properties": {
32
+ "filesystem": { "enum": ["apfs", "ext4"] },
33
+ "cooperativeWriters": { "const": true }
34
+ }
35
+ },
36
+ "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
37
+ "size": { "type": "integer", "minimum": 0, "maximum": 268435456 },
38
+ "mode": { "type": "integer", "minimum": 256, "maximum": 511 },
39
+ "next": {
40
+ "type": "object", "additionalProperties": false,
41
+ "required": ["sha256", "size", "mode"],
42
+ "properties": {
43
+ "sha256": { "$ref": "#/$defs/sha256" },
44
+ "size": { "$ref": "#/$defs/size" },
45
+ "mode": { "$ref": "#/$defs/mode" }
46
+ }
47
+ },
48
+ "present": {
49
+ "type": "object", "additionalProperties": false,
50
+ "required": ["exists", "sha256", "size", "mode"],
51
+ "properties": {
52
+ "exists": { "const": true },
53
+ "sha256": { "$ref": "#/$defs/sha256" },
54
+ "size": { "$ref": "#/$defs/size" },
55
+ "mode": { "$ref": "#/$defs/mode" }
56
+ }
57
+ },
58
+ "absent": {
59
+ "type": "object", "additionalProperties": false,
60
+ "required": ["exists"], "properties": { "exists": { "const": false } }
61
+ },
62
+ "operation": {
63
+ "type": "object", "additionalProperties": false,
64
+ "required": ["path", "action", "expected", "next"],
65
+ "properties": {
66
+ "path": { "$ref": "#/$defs/path" },
67
+ "action": { "enum": ["create", "replace", "delete"] },
68
+ "expected": { "oneOf": [{ "$ref": "#/$defs/absent" }, { "$ref": "#/$defs/present" }] },
69
+ "next": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/next" }] }
70
+ },
71
+ "allOf": [
72
+ { "if": { "properties": { "action": { "const": "create" } }, "required": ["action"] }, "then": { "properties": { "expected": { "$ref": "#/$defs/absent" }, "next": { "$ref": "#/$defs/next" } } } },
73
+ { "if": { "properties": { "action": { "const": "replace" } }, "required": ["action"] }, "then": { "properties": { "expected": { "$ref": "#/$defs/present" }, "next": { "$ref": "#/$defs/next" } } } },
74
+ { "if": { "properties": { "action": { "const": "delete" } }, "required": ["action"] }, "then": { "properties": { "expected": { "$ref": "#/$defs/present" }, "next": { "type": "null" } } } }
75
+ ]
76
+ }
77
+ }
78
+ }
@@ -0,0 +1,55 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://contracts.skill-family.example/v1/file-set-event-payload.json",
4
+ "title": "File set event payload",
5
+ "description": "File recovery facts within the existing state-event envelope. Event order, request and path bindings are checked by Harness; no second sequence or hash chain is introduced.",
6
+ "type": "object", "additionalProperties": false,
7
+ "required": ["schemaVersion", "kind", "operationId", "type"],
8
+ "properties": {
9
+ "schemaVersion": { "const": 1 }, "kind": { "const": "skill-family.file-set-event-payload" },
10
+ "operationId": { "$ref": "https://contracts.skill-family.example/v1/file-set-apply-request.json#/$defs/operationId" },
11
+ "type": { "enum": ["prepared", "apply-intent", "applied", "restore-intent", "restored", "validation-started", "validation-finished", "committed", "rolled-back", "recovery-required", "prune-intent", "pruned"] },
12
+ "request": { "$ref": "https://contracts.skill-family.example/v1/file-set-apply-request.json" },
13
+ "requestDigest": { "$ref": "https://contracts.skill-family.example/v1/file-set-apply-request.json#/$defs/sha256" },
14
+ "rootBinding": { "$ref": "https://contracts.skill-family.example/v1/filesystem-root-binding.json" },
15
+ "materialIdentity": { "$ref": "#/$defs/directoryIdentity" },
16
+ "entries": { "type": "array", "minItems": 1, "maxItems": 1024, "items": { "$ref": "#/$defs/materialEntry" } },
17
+ "index": { "type": "integer", "minimum": 0, "maximum": 1023 },
18
+ "current": { "enum": ["original", "expected-new"] },
19
+ "validation": { "$ref": "https://contracts.skill-family.example/v1/file-set-result.json#/$defs/validation" },
20
+ "paths": { "$ref": "https://contracts.skill-family.example/v1/file-set-result.json#/$defs/paths" },
21
+ "errorKinds": { "$ref": "https://contracts.skill-family.example/v1/file-set-result.json#/$defs/errorKinds" }
22
+ },
23
+ "allOf": [
24
+ { "if": { "properties": { "type": { "const": "prepared" } }, "required": ["type"] },
25
+ "then": { "properties": { "request": {}, "requestDigest": {}, "rootBinding": {}, "materialIdentity": {}, "entries": {} }, "required": ["request", "requestDigest", "rootBinding", "materialIdentity", "entries"] },
26
+ "else": { "properties": { "request": false, "requestDigest": false, "rootBinding": false, "materialIdentity": false, "entries": false } } },
27
+ { "if": { "properties": { "type": { "enum": ["apply-intent", "applied", "restore-intent", "restored"] } }, "required": ["type"] },
28
+ "then": { "properties": { "index": {} }, "required": ["index"] }, "else": { "properties": { "index": false } } },
29
+ { "if": { "properties": { "type": { "enum": ["applied", "restored"] } }, "required": ["type"] },
30
+ "then": { "properties": { "current": {} }, "required": ["current"] }, "else": { "properties": { "current": false } } },
31
+ { "if": { "properties": { "type": { "const": "applied" } }, "required": ["type"] },
32
+ "then": { "properties": { "current": { "const": "expected-new" } } } },
33
+ { "if": { "properties": { "type": { "const": "restored" } }, "required": ["type"] },
34
+ "then": { "properties": { "current": { "const": "original" } } } },
35
+ { "if": { "properties": { "type": { "const": "validation-finished" } }, "required": ["type"] },
36
+ "then": { "properties": { "validation": {} }, "required": ["validation"] }, "else": { "properties": { "validation": false } } },
37
+ { "if": { "properties": { "type": { "enum": ["committed", "rolled-back", "recovery-required"] } }, "required": ["type"] },
38
+ "then": { "properties": { "paths": {}, "errorKinds": {} }, "required": ["paths", "errorKinds"] }, "else": { "properties": { "paths": false, "errorKinds": false } } }
39
+ ],
40
+ "$defs": {
41
+ "directoryIdentity": {
42
+ "$ref": "https://contracts.skill-family.example/v1/state-store-recovery-observation.json#/$defs/identity",
43
+ "type": "object", "properties": { "type": { "const": "directory" } }
44
+ },
45
+ "materialEntry": {
46
+ "type": "object", "additionalProperties": false,
47
+ "required": ["before", "after", "parentIdentity"],
48
+ "properties": {
49
+ "before": { "anyOf": [{ "type": "null" }, { "$ref": "https://contracts.skill-family.example/v1/file-set-apply-request.json#/$defs/next" }] },
50
+ "after": { "anyOf": [{ "type": "null" }, { "$ref": "https://contracts.skill-family.example/v1/file-set-apply-request.json#/$defs/next" }] },
51
+ "parentIdentity": { "$ref": "#/$defs/directoryIdentity" }
52
+ }
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://contracts.skill-family.example/v1/file-set-prune-request.json",
4
+ "title": "File set prune request",
5
+ "type": "object", "additionalProperties": false,
6
+ "required": ["schemaVersion", "kind", "operationId", "root", "rootBinding", "recoveryRelPath", "environment"],
7
+ "properties": {
8
+ "schemaVersion": { "const": 1 },
9
+ "kind": { "const": "skill-family.file-set-prune-request" },
10
+ "operationId": { "$ref": "https://contracts.skill-family.example/v1/file-set-apply-request.json#/$defs/operationId" },
11
+ "root": { "$ref": "https://contracts.skill-family.example/v1/file-set-apply-request.json#/$defs/root" },
12
+ "rootBinding": { "$ref": "https://contracts.skill-family.example/v1/filesystem-root-binding.json" },
13
+ "recoveryRelPath": { "const": ".foundation-file-apply" },
14
+ "environment": { "$ref": "https://contracts.skill-family.example/v1/file-set-apply-request.json#/$defs/environment" },
15
+ "maintenance": { "$ref": "https://contracts.skill-family.example/v1/file-set-recovery-request.json#/$defs/maintenance" }
16
+ }
17
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://contracts.skill-family.example/v1/file-set-recovery-request.json",
4
+ "title": "File set recovery request",
5
+ "type": "object", "additionalProperties": false,
6
+ "required": ["schemaVersion", "kind", "operationId", "root", "rootBinding", "recoveryRelPath", "environment", "allowAdjacentStaging", "maintenance"],
7
+ "properties": {
8
+ "schemaVersion": { "const": 1 },
9
+ "kind": { "const": "skill-family.file-set-recovery-request" },
10
+ "operationId": { "$ref": "https://contracts.skill-family.example/v1/file-set-apply-request.json#/$defs/operationId" },
11
+ "root": { "$ref": "https://contracts.skill-family.example/v1/file-set-apply-request.json#/$defs/root" },
12
+ "rootBinding": { "$ref": "https://contracts.skill-family.example/v1/filesystem-root-binding.json" },
13
+ "recoveryRelPath": { "const": ".foundation-file-apply" },
14
+ "environment": { "$ref": "https://contracts.skill-family.example/v1/file-set-apply-request.json#/$defs/environment" },
15
+ "allowAdjacentStaging": { "const": true },
16
+ "maintenance": { "$ref": "#/$defs/maintenance" }
17
+ },
18
+ "$defs": {
19
+ "maintenance": {
20
+ "type": "object", "additionalProperties": false,
21
+ "required": ["observation", "confirmAllParticipantsStopped", "confirmExclusiveMaintenance"],
22
+ "properties": {
23
+ "observation": { "$ref": "https://contracts.skill-family.example/v1/state-store-recovery-observation.json" },
24
+ "confirmAllParticipantsStopped": { "const": true },
25
+ "confirmExclusiveMaintenance": { "const": true }
26
+ }
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,79 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://contracts.skill-family.example/v1/file-set-result.json",
4
+ "title": "File set result",
5
+ "type": "object", "additionalProperties": false,
6
+ "required": ["schemaVersion", "kind", "operationId", "operation", "outcome", "preflight", "businessWrite", "validation", "recovery", "paths", "errors", "materials"],
7
+ "properties": {
8
+ "schemaVersion": { "const": 1 }, "kind": { "const": "skill-family.file-set-result" },
9
+ "operationId": { "anyOf": [{ "type": "null" }, { "$ref": "https://contracts.skill-family.example/v1/file-set-apply-request.json#/$defs/operationId" }] },
10
+ "operation": { "enum": ["apply", "recover", "prune"] },
11
+ "outcome": { "enum": ["committed", "commit-unconfirmed", "rejected", "rolled-back", "recovery-required", "already-committed", "already-rolled-back", "pruned", "already-pruned"] },
12
+ "preflight": { "enum": ["not-run", "failed", "passed"] },
13
+ "businessWrite": { "enum": ["none", "started", "unknown"] },
14
+ "validation": { "$ref": "#/$defs/validation" },
15
+ "recovery": {
16
+ "type": "object", "additionalProperties": false, "required": ["attempted", "status"],
17
+ "properties": { "attempted": { "type": "boolean" }, "status": { "enum": ["not-needed", "complete", "partial", "blocked"] } }
18
+ },
19
+ "paths": { "$ref": "#/$defs/paths" },
20
+ "errors": { "type": "array", "items": { "$ref": "#/$defs/error" } },
21
+ "materials": {
22
+ "type": "object", "additionalProperties": false,
23
+ "required": ["relPath", "status", "durable", "adjacentStaging"],
24
+ "properties": {
25
+ "relPath": { "const": ".foundation-file-apply" },
26
+ "status": { "enum": ["none", "retained", "partial", "pruned"] },
27
+ "durable": { "type": "boolean" },
28
+ "adjacentStaging": { "enum": ["none-observed", "possible-unknown"] }
29
+ }
30
+ }
31
+ },
32
+ "allOf": [{
33
+ "if": { "properties": { "operationId": { "type": "null" } }, "required": ["operationId"] },
34
+ "then": { "properties": { "paths": { "type": "array", "maxItems": 0 } } }
35
+ }],
36
+ "$defs": {
37
+ "errorKind": { "enum": ["invalid-request", "unsupported-environment", "unsafe-path", "precondition-mismatch", "locked", "unfinished-operation", "operation-id-reused", "commit-unconfirmed", "persistence-failed", "apply-failed", "validation-failed", "validation-incomplete", "restore-conflict", "restore-failed", "recovery-corrupt", "root-changed", "ownership-lost", "operation-not-found", "prune-refused", "lock-release-failed"] },
38
+ "errorKinds": { "type": "array", "items": { "$ref": "#/$defs/errorKind" } },
39
+ "error": {
40
+ "type": "object", "additionalProperties": false, "required": ["code", "message", "details"],
41
+ "properties": {
42
+ "code": { "const": "SFC2004" }, "message": { "type": "string", "minLength": 1 },
43
+ "details": {
44
+ "type": "object", "additionalProperties": false, "required": ["kind", "phase"],
45
+ "properties": {
46
+ "kind": { "$ref": "#/$defs/errorKind" },
47
+ "phase": { "enum": ["input", "preflight", "prepare", "apply", "validate", "restore", "finalize", "prune"] },
48
+ "path": { "$ref": "https://contracts.skill-family.example/v1/file-set-apply-request.json#/$defs/path" }
49
+ }
50
+ }
51
+ }
52
+ },
53
+ "validation": {
54
+ "type": "object", "additionalProperties": false, "required": ["status", "reason"],
55
+ "properties": {
56
+ "status": { "enum": ["not-run", "passed", "failed", "incomplete"] },
57
+ "reason": { "enum": [null, "returned-false", "exception", "timeout", "invalid-result", "interrupted"] }
58
+ },
59
+ "allOf": [
60
+ { "if": { "properties": { "status": { "enum": ["not-run", "passed"] } }, "required": ["status"] }, "then": { "properties": { "reason": { "type": "null" } } }, "else": { "properties": { "reason": { "type": "string" } } } },
61
+ { "if": { "properties": { "status": { "const": "failed" } }, "required": ["status"] }, "then": { "properties": { "reason": { "const": "returned-false" } } } },
62
+ { "if": { "properties": { "status": { "const": "incomplete" } }, "required": ["status"] }, "then": { "properties": { "reason": { "enum": ["exception", "timeout", "invalid-result", "interrupted"] } } } }
63
+ ]
64
+ },
65
+ "paths": { "type": "array", "maxItems": 1024, "items": { "$ref": "#/$defs/pathResult" } },
66
+ "pathResult": {
67
+ "type": "object", "additionalProperties": false,
68
+ "required": ["path", "action", "apply", "restore", "current", "errorKinds"],
69
+ "properties": {
70
+ "path": { "$ref": "https://contracts.skill-family.example/v1/file-set-apply-request.json#/$defs/path" },
71
+ "action": { "enum": ["create", "replace", "delete"] },
72
+ "apply": { "enum": ["not-started", "intent-recorded", "applied", "unknown"] },
73
+ "restore": { "enum": ["not-needed", "restored", "already-original", "conflict", "failed", "unknown"] },
74
+ "current": { "enum": ["original", "expected-new", "other", "unknown"] },
75
+ "errorKinds": { "$ref": "#/$defs/errorKinds" }
76
+ }
77
+ }
78
+ }
79
+ }