release-skill 0.6.2 → 0.7.0
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 +48 -0
- package/CONTRIBUTING.md +1 -1
- package/INSTALL.md +47 -2
- package/INSTALL.zh-CN.md +29 -2
- package/README.md +137 -12
- package/README.zh-CN.md +110 -9
- package/adapters/claude/.claude-plugin/marketplace.json +1 -1
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/bin/error-codes.json +1 -1
- package/adapters/claude/bin/kernel-protocol.json +1 -1
- package/adapters/claude/bin/license-texts/Apache-2.0.txt +201 -0
- package/adapters/claude/bin/license-texts/MIT.txt +21 -0
- package/adapters/claude/bin/registry.json +64 -1
- package/adapters/claude/bin/release-skill.bundle.mjs +50570 -42208
- package/adapters/claude/bin/rules.json +1 -1
- package/adapters/claude/schemas/.render-manifest.json +10 -6
- package/adapters/claude/schemas/postpublish-approval-record.schema.json +47 -0
- package/adapters/claude/schemas/release-plan.schema.json +200 -4
- package/adapters/claude/schemas/release-project.schema.json +86 -5
- package/adapters/claude/schemas/release-run.schema.json +11 -6
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/error-codes.json +1 -1
- package/adapters/codex/bin/kernel-protocol.json +1 -1
- package/adapters/codex/bin/license-texts/Apache-2.0.txt +201 -0
- package/adapters/codex/bin/license-texts/MIT.txt +21 -0
- package/adapters/codex/bin/registry.json +64 -1
- package/adapters/codex/bin/release-skill.bundle.mjs +50570 -42208
- package/adapters/codex/bin/rules.json +1 -1
- package/adapters/codex/schemas/.render-manifest.json +10 -6
- package/adapters/codex/schemas/postpublish-approval-record.schema.json +47 -0
- package/adapters/codex/schemas/release-plan.schema.json +200 -4
- package/adapters/codex/schemas/release-project.schema.json +86 -5
- package/adapters/codex/schemas/release-run.schema.json +11 -6
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/error-codes.json +1 -1
- package/adapters/kimi/bin/kernel-protocol.json +1 -1
- package/adapters/kimi/bin/license-texts/Apache-2.0.txt +201 -0
- package/adapters/kimi/bin/license-texts/MIT.txt +21 -0
- package/adapters/kimi/bin/registry.json +64 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +50570 -42208
- package/adapters/kimi/bin/rules.json +1 -1
- package/adapters/kimi/schemas/.render-manifest.json +10 -6
- package/adapters/kimi/schemas/postpublish-approval-record.schema.json +47 -0
- package/adapters/kimi/schemas/release-plan.schema.json +200 -4
- package/adapters/kimi/schemas/release-project.schema.json +86 -5
- package/adapters/kimi/schemas/release-run.schema.json +11 -6
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/error-codes.json +1 -1
- package/adapters/workbuddy/bin/kernel-protocol.json +1 -1
- package/adapters/workbuddy/bin/license-texts/Apache-2.0.txt +201 -0
- package/adapters/workbuddy/bin/license-texts/MIT.txt +21 -0
- package/adapters/workbuddy/bin/registry.json +64 -1
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +50570 -42208
- package/adapters/workbuddy/bin/rules.json +1 -1
- package/adapters/workbuddy/schemas/.render-manifest.json +10 -6
- package/adapters/workbuddy/schemas/postpublish-approval-record.schema.json +47 -0
- package/adapters/workbuddy/schemas/release-plan.schema.json +200 -4
- package/adapters/workbuddy/schemas/release-project.schema.json +86 -5
- package/adapters/workbuddy/schemas/release-run.schema.json +11 -6
- package/bin/error-codes.json +1 -1
- package/bin/kernel-protocol.json +1 -1
- package/bin/license-texts/Apache-2.0.txt +201 -0
- package/bin/license-texts/MIT.txt +21 -0
- package/bin/registry.json +64 -1
- package/bin/release-skill-cli.mjs +187 -9
- package/bin/release-skill.bundle.mjs +50570 -42208
- package/bin/rules.json +1 -1
- package/package.json +6 -4
- package/platform-manifest.json +4 -4
- package/references/.render-manifest.json +5 -5
- package/references/01-state-machine.md +22 -2
- package/references/02-project-config.md +1 -1
- package/references/06-adapter-contract.md +6 -3
- package/schemas/.render-manifest.json +10 -6
- package/schemas/postpublish-approval-record.schema.json +47 -0
- package/schemas/release-plan.schema.json +200 -4
- package/schemas/release-project.schema.json +86 -5
- package/schemas/release-run.schema.json +11 -6
- package/src/adapters/distribute-git.mjs +17 -11
- package/src/commands/approve.mjs +163 -1
- package/src/commands/distribute.mjs +649 -89
- package/src/commands/postverify.mjs +808 -0
- package/src/commands/prepare.mjs +456 -45
- package/src/commands/setup.mjs +715 -0
- package/src/commands/ship.mjs +152 -5
- package/src/commands/verify.mjs +92 -15
- package/src/core/approval.mjs +93 -68
- package/src/core/bounded-output.mjs +46 -0
- package/src/core/derived-artifact-gates.mjs +258 -0
- package/src/core/docs-refresh-preset.mjs +171 -0
- package/src/core/errors.mjs +4 -0
- package/src/core/evidence.mjs +8 -1
- package/src/core/git-url-policy.mjs +214 -0
- package/src/core/hooks.mjs +28 -0
- package/src/core/marketplace-registry-entry.mjs +175 -0
- package/src/core/notify-handoff.mjs +76 -0
- package/src/core/postpublish-approval.mjs +241 -0
- package/src/core/postpublish-bundle.mjs +342 -0
- package/src/core/postpublish-projection.mjs +210 -0
- package/src/core/postpublish.mjs +449 -25
- package/src/core/preset-executor.mjs +175 -0
- package/src/core/preset-gitwrite.mjs +484 -0
- package/src/core/presets.mjs +754 -0
- package/src/core/proposal-inbox.mjs +646 -0
- package/src/core/redact.mjs +16 -3
- package/src/core/run.mjs +91 -6
- package/src/producers/build-adapters.mjs +28 -14
package/src/core/run.mjs
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* @module core/run
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { lstat, mkdir, readFile } from 'node:fs/promises';
|
|
15
|
+
import { lstat, mkdir, readFile, stat } from 'node:fs/promises';
|
|
16
16
|
import { realpathSync } from 'node:fs';
|
|
17
17
|
import { dirname, join, resolve, basename, relative, isAbsolute } from 'node:path';
|
|
18
18
|
import Ajv from 'ajv';
|
|
@@ -120,22 +120,107 @@ export function validateRun(run, options = {}) {
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Resolve a run path that may point at a run DIRECTORY (O6, 2026-08-18
|
|
125
|
+
* release-cycle investigation §3.2): verify `--run` ergonomically accepts a
|
|
126
|
+
* run directory and binds its `release-run.json`; a regular file input is
|
|
127
|
+
* returned unchanged so every existing file-path caller keeps its exact
|
|
128
|
+
* behaviour (compat).
|
|
129
|
+
*
|
|
130
|
+
* Fail-closed rules for the directory form:
|
|
131
|
+
* - a symlinked directory input is rejected (never silently dereferenced);
|
|
132
|
+
* - a directory without `release-run.json` names the expected file.
|
|
133
|
+
* A missing path is returned unchanged so the legacy readFile error surfaces.
|
|
134
|
+
*
|
|
135
|
+
* @param {string} runPath - Run file or run directory path.
|
|
136
|
+
* @returns {Promise<string>} The resolved run FILE path.
|
|
137
|
+
* @throws {ReleaseError} GATE_FAILED for symlinked or release-run.json-less
|
|
138
|
+
* directory inputs.
|
|
139
|
+
*/
|
|
140
|
+
export async function resolveRunPath(runPath) {
|
|
141
|
+
let stats;
|
|
142
|
+
try {
|
|
143
|
+
stats = await lstat(runPath);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
if (err?.code === 'ENOENT') return runPath; // legacy readFile error path
|
|
146
|
+
throw new ReleaseError(
|
|
147
|
+
GATE_FAILED,
|
|
148
|
+
`cannot inspect release run path: ${err.message}`,
|
|
149
|
+
{ runPath, cause: err.code },
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (stats.isFile()) return runPath; // unchanged file behaviour (compat)
|
|
154
|
+
|
|
155
|
+
if (stats.isSymbolicLink()) {
|
|
156
|
+
// A symlink that ultimately points at a directory would otherwise surface
|
|
157
|
+
// a raw EISDIR; resolve the target type and fail closed for directories.
|
|
158
|
+
let targetStats;
|
|
159
|
+
try {
|
|
160
|
+
targetStats = await stat(runPath);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
if (err?.code === 'ENOENT') return runPath; // dangling: legacy error
|
|
163
|
+
throw new ReleaseError(
|
|
164
|
+
GATE_FAILED,
|
|
165
|
+
`cannot inspect release run path: ${err.message}`,
|
|
166
|
+
{ runPath, cause: err.code },
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
if (targetStats.isDirectory()) {
|
|
170
|
+
throw new ReleaseError(
|
|
171
|
+
GATE_FAILED,
|
|
172
|
+
'release run path is a symlinked directory; pass the run file (release-run.json) or the real run directory',
|
|
173
|
+
{ runPath },
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
return runPath; // symlinked FILE keeps the legacy readFile behaviour
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (stats.isDirectory()) {
|
|
180
|
+
const candidate = join(runPath, 'release-run.json');
|
|
181
|
+
try {
|
|
182
|
+
const candidateStats = await lstat(candidate);
|
|
183
|
+
if (!candidateStats.isFile()) {
|
|
184
|
+
throw new ReleaseError(
|
|
185
|
+
GATE_FAILED,
|
|
186
|
+
`release run directory does not contain a regular release-run.json: ${runPath}`,
|
|
187
|
+
{ runPath, expected: 'release-run.json' },
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
} catch (err) {
|
|
191
|
+
if (err instanceof ReleaseError) throw err;
|
|
192
|
+
throw new ReleaseError(
|
|
193
|
+
GATE_FAILED,
|
|
194
|
+
`release run directory must contain release-run.json: ${err.message}`,
|
|
195
|
+
{ runPath, expected: 'release-run.json', cause: err.code },
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
return candidate;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return runPath; // anything else keeps the legacy readFile error path
|
|
202
|
+
}
|
|
203
|
+
|
|
123
204
|
/**
|
|
124
205
|
* Load, parse, and validate a run file from disk.
|
|
125
206
|
*
|
|
126
|
-
*
|
|
207
|
+
* Accepts either the run file itself or its containing run directory (O6);
|
|
208
|
+
* a directory input resolves to `<dir>/release-run.json` via resolveRunPath.
|
|
209
|
+
*
|
|
210
|
+
* @param {string} runPath - Absolute path to the run file or run directory.
|
|
127
211
|
* @returns {Promise<Object>} The validated run object.
|
|
128
212
|
* @throws {ReleaseError} GATE_FAILED if the file cannot be read, parsed, or validated.
|
|
129
213
|
*/
|
|
130
214
|
export async function loadRun(runPath, options = {}) {
|
|
215
|
+
const resolvedRunPath = await resolveRunPath(runPath);
|
|
131
216
|
let raw;
|
|
132
217
|
try {
|
|
133
|
-
raw = await readFile(
|
|
218
|
+
raw = await readFile(resolvedRunPath, 'utf8');
|
|
134
219
|
} catch (err) {
|
|
135
220
|
throw new ReleaseError(
|
|
136
221
|
GATE_FAILED,
|
|
137
222
|
`cannot read release run: ${err.message}`,
|
|
138
|
-
{ runPath, cause: err.code },
|
|
223
|
+
{ runPath: resolvedRunPath, cause: err.code },
|
|
139
224
|
);
|
|
140
225
|
}
|
|
141
226
|
|
|
@@ -146,13 +231,13 @@ export async function loadRun(runPath, options = {}) {
|
|
|
146
231
|
throw new ReleaseError(
|
|
147
232
|
GATE_FAILED,
|
|
148
233
|
`release run is not valid JSON: ${err.message}`,
|
|
149
|
-
{ runPath },
|
|
234
|
+
{ runPath: resolvedRunPath },
|
|
150
235
|
);
|
|
151
236
|
}
|
|
152
237
|
|
|
153
238
|
validateRun(run, options);
|
|
154
239
|
if (options.authorityPlanPath) {
|
|
155
|
-
assertImmutableRunAuthority(
|
|
240
|
+
assertImmutableRunAuthority(resolvedRunPath, options.authorityPlanPath, run);
|
|
156
241
|
}
|
|
157
242
|
return run;
|
|
158
243
|
}
|
|
@@ -394,8 +394,16 @@ function renderSkillForPlatform(content, platformName) {
|
|
|
394
394
|
/**
|
|
395
395
|
* Collect binary file paths from a source directory recursively.
|
|
396
396
|
* Returns array of relative paths.
|
|
397
|
+
*
|
|
398
|
+
* `excludedTopDirs` names top-level directories that are skipped entirely.
|
|
399
|
+
* It only exists for subtrees that are already excluded from the shipped
|
|
400
|
+
* output (see the native/safe-write build/ exclusion below): their contents
|
|
401
|
+
* are never read and never shipped, so descending into them would only let
|
|
402
|
+
* transient, regeneratable artifacts (node-gyp creates a
|
|
403
|
+
* build/node_gyp_bins/python3 symlink while a rebuild is running) fail the
|
|
404
|
+
* collection without protecting any shipped byte.
|
|
397
405
|
*/
|
|
398
|
-
async function collectBinaryFiles(baseDir, physicalRoot, relPrefix = '') {
|
|
406
|
+
async function collectBinaryFiles(baseDir, physicalRoot, relPrefix = '', excludedTopDirs) {
|
|
399
407
|
const results = [];
|
|
400
408
|
const physicalDir = await assertTrustedDirectory(
|
|
401
409
|
baseDir,
|
|
@@ -409,6 +417,7 @@ async function collectBinaryFiles(baseDir, physicalRoot, relPrefix = '') {
|
|
|
409
417
|
throw new Error(`SECURITY: symlink in resource tree: ${relPath}`);
|
|
410
418
|
}
|
|
411
419
|
if (entry.isDirectory()) {
|
|
420
|
+
if (relPrefix === '' && excludedTopDirs?.has(entry.name)) continue;
|
|
412
421
|
results.push(...await collectBinaryFiles(join(physicalDir, entry.name), physicalRoot, relPath));
|
|
413
422
|
} else if (entry.isFile()) {
|
|
414
423
|
results.push({ relPath, sourcePath: join(physicalDir, entry.name) });
|
|
@@ -495,23 +504,24 @@ async function generateAdapterFiles(platform, skills, templateJson, root, market
|
|
|
495
504
|
// Copy bin/ entry point and bundle (text files). The self-contained
|
|
496
505
|
// bundle ships runtime sidecars next to it (JSON resources read via
|
|
497
506
|
// new URL(<rel>, import.meta.url) by inlined Foundation modules, e.g.
|
|
498
|
-
// error-codes.json
|
|
499
|
-
//
|
|
500
|
-
//
|
|
501
|
-
//
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
507
|
+
// error-codes.json; since F-06/T6 the inlined Engineering Kit also
|
|
508
|
+
// carries license-text sidecars under bin/license-texts/); enumerate
|
|
509
|
+
// the directory recursively so every sidecar is replicated
|
|
510
|
+
// deterministically. The source CLI (release-skill-cli.mjs) is never
|
|
511
|
+
// shipped into adapters. The wrapper and the bundle are required
|
|
512
|
+
// entries: a missing one fails closed before any write.
|
|
513
|
+
const binFileEntries = await collectBinaryFiles(join(root, 'bin'), root);
|
|
514
|
+
const binRelPaths = binFileEntries.map(({ relPath }) => relPath);
|
|
505
515
|
for (const required of ['release-skill.mjs', 'release-skill.bundle.mjs']) {
|
|
506
|
-
if (!
|
|
516
|
+
if (!binRelPaths.includes(required)) {
|
|
507
517
|
throw new Error(`SECURITY: required bundle entry is missing: bin/${required}`);
|
|
508
518
|
}
|
|
509
519
|
}
|
|
510
|
-
for (const
|
|
511
|
-
|
|
512
|
-
const content = await readTrustedFile(
|
|
520
|
+
for (const { relPath: rel, sourcePath } of binFileEntries) {
|
|
521
|
+
if (rel === 'release-skill-cli.mjs') continue;
|
|
522
|
+
const content = await readTrustedFile(sourcePath, root, `bin/${rel}`);
|
|
513
523
|
files.push({
|
|
514
|
-
relPath: join('bin',
|
|
524
|
+
relPath: join('bin', rel),
|
|
515
525
|
content,
|
|
516
526
|
isBinary: false,
|
|
517
527
|
});
|
|
@@ -537,8 +547,12 @@ async function generateAdapterFiles(platform, skills, templateJson, root, market
|
|
|
537
547
|
// Copy native/safe-write/ directory (prebuilds.json, prebuilds/, src/).
|
|
538
548
|
// Exclude build/ — it contains platform-specific build artifacts that are
|
|
539
549
|
// regenerated by node-gyp and should not be in the published adapter.
|
|
550
|
+
// Collection never descends into build/: it is output-excluded anyway, and
|
|
551
|
+
// a concurrent node-gyp rebuild transiently places symlinked helper bins
|
|
552
|
+
// there (node_gyp_bins/python3) that must not fail the derived-artifact
|
|
553
|
+
// gates.
|
|
540
554
|
const nativeBase = join(root, 'native', 'safe-write');
|
|
541
|
-
const nativeFiles = await collectBinaryFiles(nativeBase, root);
|
|
555
|
+
const nativeFiles = await collectBinaryFiles(nativeBase, root, '', new Set(['build']));
|
|
542
556
|
if (!nativeFiles.some(({ relPath }) => relPath === 'prebuilds.json')) {
|
|
543
557
|
throw new Error('SECURITY: required native resource is missing: native/safe-write/prebuilds.json');
|
|
544
558
|
}
|