release-skill 0.6.3 → 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 +25 -0
- package/INSTALL.md +2 -2
- package/INSTALL.zh-CN.md +2 -2
- package/README.md +25 -17
- package/README.zh-CN.md +16 -14
- 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 +34860 -31252
- package/adapters/claude/bin/rules.json +1 -1
- package/adapters/claude/schemas/.render-manifest.json +6 -6
- package/adapters/claude/schemas/release-plan.schema.json +136 -4
- package/adapters/claude/schemas/release-project.schema.json +13 -4
- 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 +34860 -31252
- package/adapters/codex/bin/rules.json +1 -1
- package/adapters/codex/schemas/.render-manifest.json +6 -6
- package/adapters/codex/schemas/release-plan.schema.json +136 -4
- package/adapters/codex/schemas/release-project.schema.json +13 -4
- 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 +34860 -31252
- package/adapters/kimi/bin/rules.json +1 -1
- package/adapters/kimi/schemas/.render-manifest.json +6 -6
- package/adapters/kimi/schemas/release-plan.schema.json +136 -4
- package/adapters/kimi/schemas/release-project.schema.json +13 -4
- 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 +34860 -31252
- package/adapters/workbuddy/bin/rules.json +1 -1
- package/adapters/workbuddy/schemas/.render-manifest.json +6 -6
- package/adapters/workbuddy/schemas/release-plan.schema.json +136 -4
- package/adapters/workbuddy/schemas/release-project.schema.json +13 -4
- 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 +6 -6
- package/bin/release-skill.bundle.mjs +34860 -31252
- package/bin/rules.json +1 -1
- package/package.json +5 -4
- package/platform-manifest.json +2 -2
- package/references/02-project-config.md +1 -1
- package/references/06-adapter-contract.md +6 -3
- package/schemas/.render-manifest.json +6 -6
- package/schemas/release-plan.schema.json +136 -4
- package/schemas/release-project.schema.json +13 -4
- package/src/adapters/distribute-git.mjs +17 -11
- package/src/commands/approve.mjs +6 -10
- package/src/commands/distribute.mjs +251 -69
- package/src/commands/postverify.mjs +81 -7
- package/src/commands/prepare.mjs +176 -3
- package/src/core/docs-refresh-preset.mjs +8 -4
- package/src/core/evidence.mjs +8 -1
- package/src/core/git-url-policy.mjs +214 -0
- package/src/core/marketplace-registry-entry.mjs +4 -3
- package/src/core/postpublish-approval.mjs +132 -1
- package/src/core/postpublish-bundle.mjs +342 -0
- package/src/core/postpublish-projection.mjs +210 -0
- package/src/core/postpublish.mjs +26 -19
- package/src/core/preset-executor.mjs +25 -6
- package/src/core/preset-gitwrite.mjs +37 -16
- package/src/core/presets.mjs +67 -19
- package/src/core/proposal-inbox.mjs +36 -20
- package/src/core/redact.mjs +16 -3
- package/src/producers/build-adapters.mjs +28 -14
|
@@ -48,6 +48,11 @@ import {
|
|
|
48
48
|
POST_PUBLISH_VERIFY_FAILED,
|
|
49
49
|
} from './errors.mjs';
|
|
50
50
|
import { preflightPresetWorkspace, assertPresetWorkspaceExecution } from './presets.mjs';
|
|
51
|
+
import {
|
|
52
|
+
checkGitRemoteUrl,
|
|
53
|
+
describeGitRemoteUrlFailure,
|
|
54
|
+
redactUrlCredentialsIfPresent,
|
|
55
|
+
} from './git-url-policy.mjs';
|
|
51
56
|
|
|
52
57
|
const execFileAsync = promisify(execFileCb);
|
|
53
58
|
|
|
@@ -60,7 +65,6 @@ const GIT_TIMEOUT_MS = 120_000;
|
|
|
60
65
|
const TRANSFER_TIMEOUT_MS = 300_000;
|
|
61
66
|
const TMP_PREFIX = 'release-skill-proposal-';
|
|
62
67
|
|
|
63
|
-
const SAFE_REMOTE_URL_RE = /^(?:https?|file):\/\/.+\.git$/;
|
|
64
68
|
const SAFE_BRANCH_RE = /^[A-Za-z0-9][A-Za-z0-9._/-]*$/;
|
|
65
69
|
const SHA_RE = /^[a-f0-9]{40}$/;
|
|
66
70
|
|
|
@@ -105,11 +109,12 @@ function hasControlChars(value) {
|
|
|
105
109
|
}
|
|
106
110
|
|
|
107
111
|
function assertSafeRemoteUrl(remoteUrl) {
|
|
108
|
-
|
|
112
|
+
const verdict = checkGitRemoteUrl(remoteUrl);
|
|
113
|
+
if (!verdict.ok) {
|
|
109
114
|
throw new ReleaseError(
|
|
110
115
|
GATE_FAILED,
|
|
111
|
-
|
|
112
|
-
{
|
|
116
|
+
`proposal-inbox remoteUrl ${describeGitRemoteUrlFailure(verdict.reason)}`,
|
|
117
|
+
{ reason: verdict.reason },
|
|
113
118
|
);
|
|
114
119
|
}
|
|
115
120
|
}
|
|
@@ -240,7 +245,8 @@ export function buildProposalDocument(context) {
|
|
|
240
245
|
* @returns {string}
|
|
241
246
|
*/
|
|
242
247
|
export function buildManualSyncPrompt({ remoteUrl, proposalPath, branch }) {
|
|
243
|
-
|
|
248
|
+
const safeRemoteUrl = redactUrlCredentialsIfPresent(remoteUrl);
|
|
249
|
+
return `manual sync required: review proposal ${proposalPath} in downstream repository ${safeRemoteUrl} (branch ${branch}); suggested action: open a pull request or apply the proposal through the downstream governance workflow`;
|
|
244
250
|
}
|
|
245
251
|
|
|
246
252
|
// ---------------------------------------------------------------------------
|
|
@@ -275,7 +281,7 @@ export async function crossCheckPushedCommit(exec, remoteUrl, branch, pushedComm
|
|
|
275
281
|
throw new ReleaseError(
|
|
276
282
|
POST_PUBLISH_VERIFY_FAILED,
|
|
277
283
|
`post-push cross-check failed: remote branch ${branch} is at ${observed ?? '<missing>'}, which disagrees with the pushed commit ${pushedCommit}`,
|
|
278
|
-
{ remoteUrl, branch, pushedCommit, observed },
|
|
284
|
+
{ remoteUrl: redactUrlCredentialsIfPresent(remoteUrl), branch, pushedCommit, observed },
|
|
279
285
|
);
|
|
280
286
|
}
|
|
281
287
|
return observed;
|
|
@@ -333,12 +339,13 @@ export async function deliverProposalGitPush(params) {
|
|
|
333
339
|
});
|
|
334
340
|
} catch (error) {
|
|
335
341
|
const classification = classifyNetFailure(stderrText(error));
|
|
342
|
+
const safeRemoteUrl = redactUrlCredentialsIfPresent(remoteUrl);
|
|
336
343
|
throw new ReleaseError(
|
|
337
344
|
classification === 'auth' ? REMOTE_CONFLICT : REMOTE_UNAVAILABLE,
|
|
338
345
|
classification === 'auth'
|
|
339
|
-
? `proposal-inbox remote refused authentication for ${
|
|
340
|
-
: `cannot reach proposal-inbox remote ${
|
|
341
|
-
{ remoteUrl, stderrTail: stderrTail(error) },
|
|
346
|
+
? `proposal-inbox remote refused authentication for ${safeRemoteUrl}; the host keychain credential is reused and never prompted, read, or retried`
|
|
347
|
+
: `cannot reach proposal-inbox remote ${safeRemoteUrl} — start VPN / check network; delivery fails closed and never retries with credentials`,
|
|
348
|
+
{ remoteUrl: safeRemoteUrl, stderrTail: stderrTail(error) },
|
|
342
349
|
);
|
|
343
350
|
}
|
|
344
351
|
|
|
@@ -375,8 +382,8 @@ export async function deliverProposalGitPush(params) {
|
|
|
375
382
|
}
|
|
376
383
|
throw new ReleaseError(
|
|
377
384
|
REMOTE_CONFLICT,
|
|
378
|
-
`proposal ${proposalPath} already exists in ${remoteUrl} with different content; overwriting a downstream proposal requires a human decision`,
|
|
379
|
-
{ remoteUrl, proposalPath },
|
|
385
|
+
`proposal ${proposalPath} already exists in ${redactUrlCredentialsIfPresent(remoteUrl)} with different content; overwriting a downstream proposal requires a human decision`,
|
|
386
|
+
{ remoteUrl: redactUrlCredentialsIfPresent(remoteUrl), proposalPath },
|
|
380
387
|
);
|
|
381
388
|
}
|
|
382
389
|
|
|
@@ -401,12 +408,13 @@ export async function deliverProposalGitPush(params) {
|
|
|
401
408
|
});
|
|
402
409
|
} catch (error) {
|
|
403
410
|
const classification = classifyNetFailure(stderrText(error));
|
|
411
|
+
const safeRemoteUrl = redactUrlCredentialsIfPresent(remoteUrl);
|
|
404
412
|
throw new ReleaseError(
|
|
405
413
|
classification === 'auth' ? REMOTE_CONFLICT : REMOTE_UNAVAILABLE,
|
|
406
414
|
classification === 'auth'
|
|
407
|
-
? `proposal-inbox push refused authentication for ${
|
|
408
|
-
: `proposal-inbox push to ${
|
|
409
|
-
{ remoteUrl, branch, stderrTail: stderrTail(error) },
|
|
415
|
+
? `proposal-inbox push refused authentication for ${safeRemoteUrl}; the host keychain credential is reused and never prompted, read, or retried`
|
|
416
|
+
: `proposal-inbox push to ${safeRemoteUrl} failed — start VPN / check network; delivery fails closed and never retries with credentials`,
|
|
417
|
+
{ remoteUrl: safeRemoteUrl, branch, stderrTail: stderrTail(error) },
|
|
410
418
|
);
|
|
411
419
|
}
|
|
412
420
|
|
|
@@ -581,29 +589,37 @@ export async function deliverProposalLocalFile(params) {
|
|
|
581
589
|
* deliver it into the local checkout (never pushed). Shared by the
|
|
582
590
|
* distribute saga and the postVerify run.
|
|
583
591
|
*
|
|
592
|
+
* F-04: `releaseWorkspaceRoot` is the real project root — the ONLY
|
|
593
|
+
* resolution basis for `config.target.workspace` and the release-workspace
|
|
594
|
+
* write exclusion. The detached execution worktree is never passed here.
|
|
595
|
+
*
|
|
584
596
|
* @param {object} params
|
|
585
597
|
* @param {object} params.hook - Declared hook entry (config.target bound).
|
|
586
598
|
* @param {object} params.contextProjection - The §2.3 context projection.
|
|
587
599
|
* @param {object} params.commitIdentity - Frozen commitIdentity.
|
|
588
|
-
* @param {string} params.
|
|
600
|
+
* @param {string} params.releaseWorkspaceRoot - Release workspace root
|
|
601
|
+
* (workspace resolution + release-workspace write exclusion).
|
|
589
602
|
* @param {Function} [params.exec] - Injectable git exec (tests).
|
|
590
603
|
* @returns {Promise<{ status: string, observation: object, proposalPath: string, manualSyncPrompt: string }>}
|
|
591
604
|
*/
|
|
592
605
|
export async function executeProposalInboxLocalFileHook(params) {
|
|
593
|
-
const { hook, contextProjection, commitIdentity,
|
|
606
|
+
const { hook, contextProjection, commitIdentity, releaseWorkspaceRoot, exec } = params ?? {};
|
|
594
607
|
const target = hook?.config?.target;
|
|
595
608
|
if (!target || typeof target.workspace !== 'string' || typeof target.branch !== 'string') {
|
|
596
609
|
throw new ReleaseError(GATE_FAILED, 'proposal-inbox local-file requires config.target.workspace and config.target.branch');
|
|
597
610
|
}
|
|
598
|
-
if (typeof
|
|
599
|
-
throw new ReleaseError(
|
|
611
|
+
if (typeof releaseWorkspaceRoot !== 'string' || releaseWorkspaceRoot.length === 0) {
|
|
612
|
+
throw new ReleaseError(
|
|
613
|
+
GATE_FAILED,
|
|
614
|
+
'proposal-inbox local-file requires the release workspace root (releaseWorkspaceRoot); the detached execution worktree is never the release workspace',
|
|
615
|
+
);
|
|
600
616
|
}
|
|
601
617
|
|
|
602
618
|
// §2.6 workspace execution checks: preflight realpath, TOCTOU re-check,
|
|
603
619
|
// release-workspace/runtime-directory exclusion — all before any write.
|
|
604
|
-
const preflight = await preflightPresetWorkspace(target.workspace, {
|
|
620
|
+
const preflight = await preflightPresetWorkspace(target.workspace, { releaseWorkspaceRoot });
|
|
605
621
|
const execution = await assertPresetWorkspaceExecution(target.workspace, {
|
|
606
|
-
|
|
622
|
+
releaseWorkspaceRoot,
|
|
607
623
|
preflightRealpath: preflight.realpath,
|
|
608
624
|
});
|
|
609
625
|
|
package/src/core/redact.mjs
CHANGED
|
@@ -23,16 +23,26 @@
|
|
|
23
23
|
* sha256 digests, stable field names, and fragment-anchored JSON pointers
|
|
24
24
|
* (#/required) are likewise preserved verbatim. Any other two-or-more
|
|
25
25
|
* segment '/'-led token stays redacted as the fail-closed default.
|
|
26
|
+
* - URL userinfo (F-05): strings additionally pass through the Git URL
|
|
27
|
+
* policy boundary (core/git-url-policy.mjs), which strips embedded
|
|
28
|
+
* username/password from any credential-bearing URL span and delegates the
|
|
29
|
+
* actual redaction to the Foundation redactor. Credential-free URLs and
|
|
30
|
+
* all non-URL text pass through unchanged, so error prose keeps its
|
|
31
|
+
* diagnostic value.
|
|
26
32
|
* - Arrays and plain objects: recursed; every string value is redacted.
|
|
27
33
|
* - Everything else (numbers, booleans, null, undefined, and non-plain
|
|
28
34
|
* objects such as Buffers/Dates/Maps) is returned untouched.
|
|
29
35
|
*
|
|
30
|
-
* Pure
|
|
31
|
-
*
|
|
36
|
+
* Pure; no imports from src/artifacts/* (no cycles, no cross-layer
|
|
37
|
+
* coupling). The only local dependency is the release-domain Git URL policy
|
|
38
|
+
* boundary, which itself only consumes the exact released Foundation
|
|
39
|
+
* package (no top-level await anywhere in this module graph).
|
|
32
40
|
*
|
|
33
41
|
* @module core/redact
|
|
34
42
|
*/
|
|
35
43
|
|
|
44
|
+
import { redactEmbeddedUrlCredentials } from './git-url-policy.mjs';
|
|
45
|
+
|
|
36
46
|
/** Stable placeholder substituted for every redacted absolute path. */
|
|
37
47
|
export const REDACTED_PATH_PLACEHOLDER = '<redacted-path>';
|
|
38
48
|
|
|
@@ -160,7 +170,7 @@ function isUncPath(token) {
|
|
|
160
170
|
* @returns {string}
|
|
161
171
|
*/
|
|
162
172
|
function redactString(input) {
|
|
163
|
-
|
|
173
|
+
const pathRedacted = input.replace(PATH_TOKEN_RE, (raw) => {
|
|
164
174
|
const match = TRAILING_PUNCT_RE.exec(raw);
|
|
165
175
|
const core = match[1];
|
|
166
176
|
const tail = match[2];
|
|
@@ -169,6 +179,9 @@ function redactString(input) {
|
|
|
169
179
|
}
|
|
170
180
|
return raw;
|
|
171
181
|
});
|
|
182
|
+
// F-05: strip embedded URL credentials (delegates to the Foundation
|
|
183
|
+
// redactor through the Git URL policy boundary).
|
|
184
|
+
return redactEmbeddedUrlCredentials(pathRedacted);
|
|
172
185
|
}
|
|
173
186
|
|
|
174
187
|
/**
|
|
@@ -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
|
}
|