release-skill 0.9.37 → 0.10.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/plugin.json +1 -1
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/.cursor-plugin/plugin.json +1 -1
- package/.kimi-plugin/plugin.json +1 -1
- package/.qoder-plugin/plugin.json +1 -1
- package/CHANGELOG.md +56 -0
- package/INSTALL.md +2 -2
- package/INSTALL.zh-CN.md +2 -2
- package/README.md +26 -14
- package/README.zh-CN.md +26 -14
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/bin/release-skill.bundle.mjs +1918 -1450
- package/adapters/claude/schemas/release-plan.schema.json +1 -0
- package/adapters/claude/schemas/test-intake.schema.json +1 -0
- package/adapters/claude/skills/release-help/SKILL.md +36 -41
- package/adapters/claude/skills/release-prepare/SKILL.md +35 -3
- package/adapters/claude/skills/release-publish/SKILL.md +11 -1
- package/adapters/claude/skills/release-reconcile/SKILL.md +14 -0
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +1918 -1450
- package/adapters/codex/schemas/release-plan.schema.json +1 -0
- package/adapters/codex/schemas/test-intake.schema.json +1 -0
- package/adapters/codex/skills/release-help/SKILL.md +36 -41
- package/adapters/codex/skills/release-prepare/SKILL.md +35 -3
- package/adapters/codex/skills/release-publish/SKILL.md +11 -1
- package/adapters/codex/skills/release-reconcile/SKILL.md +14 -0
- package/adapters/cursor/.cursor-plugin/plugin.json +1 -1
- package/adapters/cursor/bin/release-skill.bundle.mjs +1918 -1450
- package/adapters/cursor/schemas/release-plan.schema.json +1 -0
- package/adapters/cursor/schemas/test-intake.schema.json +1 -0
- package/adapters/cursor/skills/release-help/SKILL.md +36 -41
- package/adapters/cursor/skills/release-prepare/SKILL.md +35 -3
- package/adapters/cursor/skills/release-publish/SKILL.md +11 -1
- package/adapters/cursor/skills/release-reconcile/SKILL.md +14 -0
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +1918 -1450
- package/adapters/kimi/schemas/release-plan.schema.json +1 -0
- package/adapters/kimi/schemas/test-intake.schema.json +1 -0
- package/adapters/kimi/skills/release-help/SKILL.md +36 -41
- package/adapters/kimi/skills/release-prepare/SKILL.md +35 -3
- package/adapters/kimi/skills/release-publish/SKILL.md +11 -1
- package/adapters/kimi/skills/release-reconcile/SKILL.md +14 -0
- package/adapters/qoder/.qoder-plugin/plugin.json +1 -1
- package/adapters/qoder/bin/release-skill.bundle.mjs +1918 -1450
- package/adapters/qoder/schemas/release-plan.schema.json +1 -0
- package/adapters/qoder/schemas/test-intake.schema.json +1 -0
- package/adapters/qoder/skills/release-help/SKILL.md +36 -41
- package/adapters/qoder/skills/release-prepare/SKILL.md +35 -3
- package/adapters/qoder/skills/release-publish/SKILL.md +11 -1
- package/adapters/qoder/skills/release-reconcile/SKILL.md +14 -0
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +1918 -1450
- package/adapters/workbuddy/schemas/release-plan.schema.json +1 -0
- package/adapters/workbuddy/schemas/test-intake.schema.json +1 -0
- package/adapters/workbuddy/skills/release-help/SKILL.md +36 -41
- package/adapters/workbuddy/skills/release-prepare/SKILL.md +35 -3
- package/adapters/workbuddy/skills/release-publish/SKILL.md +11 -1
- package/adapters/workbuddy/skills/release-reconcile/SKILL.md +14 -0
- package/bin/release-skill-cli.mjs +5 -0
- package/bin/release-skill.bundle.mjs +1918 -1450
- package/package.json +3 -2
- package/platform-manifest.json +4 -4
- package/references/01-state-machine.md +26 -0
- package/references/02-project-config.md +20 -0
- package/schemas/release-plan.schema.json +1 -0
- package/schemas/test-intake.schema.json +1 -0
- package/skills/release-help/SKILL.md +36 -41
- package/skills/release-prepare/SKILL.md +35 -3
- package/skills/release-publish/SKILL.md +11 -1
- package/skills/release-reconcile/SKILL.md +14 -0
- package/skills-src/release-help/SKILL.md +36 -41
- package/skills-src/release-prepare/SKILL.md +35 -3
- package/skills-src/release-publish/SKILL.md +11 -1
- package/skills-src/release-reconcile/SKILL.md +14 -0
- package/src/commands/approve.mjs +33 -24
- package/src/commands/post-release-finish.mjs +9 -0
- package/src/commands/prepare.mjs +9 -2
- package/src/commands/publish.mjs +10 -2
- package/src/commands/reconcile.mjs +2 -0
- package/src/commands/route.mjs +2 -159
- package/src/core/plan-replacement.mjs +371 -0
- package/src/core/pre-authority-failure.mjs +242 -0
- package/src/core/test-candidate.mjs +15 -71
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readFormalPlans, bindReplacement, canonicalFormalPlanPath, overlaps } from './plan-replacement.mjs';
|
|
1
2
|
/**
|
|
2
3
|
* One test-intake candidate: exclusive save, strict read, and the no-git
|
|
3
4
|
* handoff decision shared by prepare and ship.
|
|
@@ -271,78 +272,12 @@ function targetVersionsOf(plan) {
|
|
|
271
272
|
.filter((version) => typeof version === 'string' && version.length > 0))];
|
|
272
273
|
}
|
|
273
274
|
|
|
274
|
-
function sameTargetVersions(left, right) {
|
|
275
|
-
return left.length === right.length && left.every((version) => right.includes(version));
|
|
276
|
-
}
|
|
277
|
-
|
|
278
275
|
/**
|
|
279
276
|
* Formal plans under `.release-skill/plans`. The convenience pointer is not
|
|
280
277
|
* an acceptance basis. Does not run git.
|
|
281
278
|
*
|
|
282
279
|
* @param {string} root
|
|
283
280
|
*/
|
|
284
|
-
async function readFormalPlans(root) {
|
|
285
|
-
const plansPath = resolve(root, '.release-skill', 'plans');
|
|
286
|
-
let names;
|
|
287
|
-
try {
|
|
288
|
-
names = await readdir(plansPath);
|
|
289
|
-
} catch (err) {
|
|
290
|
-
if (err?.code === 'ENOENT') {
|
|
291
|
-
try {
|
|
292
|
-
await lstat(plansPath);
|
|
293
|
-
} catch (statError) {
|
|
294
|
-
if (statError?.code === 'ENOENT') return [];
|
|
295
|
-
throw new ReleaseError(CONFIG_INVALID, 'formal test acceptance plans could not be read', {
|
|
296
|
-
reason: 'TEST_CANDIDATE_AUTHORITY_INVALID',
|
|
297
|
-
field: 'plansPath',
|
|
298
|
-
cause: statError?.code ?? 'unknown',
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
throw new ReleaseError(CONFIG_INVALID, 'formal test acceptance plans could not be read', {
|
|
303
|
-
reason: 'TEST_CANDIDATE_AUTHORITY_INVALID',
|
|
304
|
-
field: 'plansPath',
|
|
305
|
-
cause: err?.code ?? 'unknown',
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
const plans = [];
|
|
309
|
-
for (const name of names) {
|
|
310
|
-
if (!name.endsWith('.json')) continue;
|
|
311
|
-
const rel = `.release-skill/plans/${name}`;
|
|
312
|
-
let receipt;
|
|
313
|
-
try {
|
|
314
|
-
receipt = await readFileStrict(root, rel, { encoding: 'utf8' });
|
|
315
|
-
} catch (err) {
|
|
316
|
-
const cause = harnessKind(err) ?? 'unknown';
|
|
317
|
-
throw new ReleaseError(CONFIG_INVALID, 'formal test acceptance plan could not be read', {
|
|
318
|
-
reason: 'TEST_CANDIDATE_AUTHORITY_INVALID',
|
|
319
|
-
field: 'planPath',
|
|
320
|
-
path: rel,
|
|
321
|
-
cause,
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
let plan;
|
|
325
|
-
try {
|
|
326
|
-
plan = JSON.parse(receipt.content);
|
|
327
|
-
} catch {
|
|
328
|
-
throw new ReleaseError(CONFIG_INVALID, 'formal test acceptance plan is not valid JSON', {
|
|
329
|
-
reason: 'TEST_CANDIDATE_AUTHORITY_INVALID',
|
|
330
|
-
field: 'planPath',
|
|
331
|
-
path: rel,
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
plans.push({
|
|
335
|
-
planPath: rel,
|
|
336
|
-
planDigest: typeof plan?.digest === 'string' ? plan.digest : name.slice(0, -'.json'.length),
|
|
337
|
-
status: plan?.status,
|
|
338
|
-
subjectDigest: typeof plan?.testAcceptance?.subjectDigest === 'string'
|
|
339
|
-
? plan.testAcceptance.subjectDigest
|
|
340
|
-
: null,
|
|
341
|
-
targetVersions: targetVersionsOf(plan),
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
return plans;
|
|
345
|
-
}
|
|
346
281
|
|
|
347
282
|
async function sameAuthorityFile(left, right) {
|
|
348
283
|
const identity = async (value) => {
|
|
@@ -432,10 +367,11 @@ export async function decideTestCandidateHandoff({
|
|
|
432
367
|
expectedSubjectDigest,
|
|
433
368
|
configDigest,
|
|
434
369
|
extraDrifts = [],
|
|
370
|
+
replaceUnpublishedPlan,
|
|
435
371
|
}) {
|
|
436
372
|
if (!config?.testIntake) {
|
|
437
|
-
if (candidatePath) {
|
|
438
|
-
throw new ReleaseError(CONFIG_INVALID, 'candidate path requires testIntake', {
|
|
373
|
+
if (candidatePath || replaceUnpublishedPlan !== undefined) {
|
|
374
|
+
throw new ReleaseError(CONFIG_INVALID, 'candidate or replacement path requires testIntake', {
|
|
439
375
|
reason: 'TEST_CANDIDATE_WITHOUT_INTAKE',
|
|
440
376
|
field: 'candidatePath',
|
|
441
377
|
});
|
|
@@ -449,7 +385,8 @@ export async function decideTestCandidateHandoff({
|
|
|
449
385
|
});
|
|
450
386
|
}
|
|
451
387
|
if (!candidatePath) {
|
|
452
|
-
const
|
|
388
|
+
const bound = await bindReplacement({ root, prepared, replaceUnpublishedPlan, resultFile: requirements.resultFile });
|
|
389
|
+
const saved = await writeTestCandidate({ root, runDir, prepared: bound, requirements, prepareOptions });
|
|
453
390
|
const subjectDigest = computePlanDigest(saved.candidate.prepared);
|
|
454
391
|
return {
|
|
455
392
|
kind: 'save-candidate',
|
|
@@ -483,6 +420,9 @@ export async function decideTestCandidateHandoff({
|
|
|
483
420
|
});
|
|
484
421
|
}
|
|
485
422
|
|
|
423
|
+
if (replaceUnpublishedPlan !== undefined && canonicalFormalPlanPath(root, replaceUnpublishedPlan) !== `.release-skill/plans/${loaded.candidate.prepared.replacesPlanDigest}.json`) {
|
|
424
|
+
throw new ReleaseError(GATE_FAILED, 'replacement selection differs from frozen candidate');
|
|
425
|
+
}
|
|
486
426
|
const options = loaded.candidate.prepareOptions;
|
|
487
427
|
const drifts = [...extraDrifts];
|
|
488
428
|
if (loaded.candidate.writerVersion !== WRITER_VERSION) drifts.push('writerVersion');
|
|
@@ -510,10 +450,13 @@ export async function decideTestCandidateHandoff({
|
|
|
510
450
|
});
|
|
511
451
|
}
|
|
512
452
|
|
|
513
|
-
const formalPlans = await readFormalPlans(root);
|
|
453
|
+
const formalPlans = await readFormalPlans(root, loaded.candidate.prepared);
|
|
514
454
|
const targetVersions = targetVersionsOf(loaded.candidate.prepared);
|
|
455
|
+
const predecessor = loaded.candidate.prepared.replacesPlanDigest;
|
|
456
|
+
const retired = new Set(formalPlans.map((entry) => entry.plan.replacesPlanDigest).filter(Boolean));
|
|
457
|
+
if (predecessor) retired.add(predecessor);
|
|
515
458
|
const differentBasis = formalPlans.filter((plan) => (
|
|
516
|
-
|
|
459
|
+
!retired.has(plan.planDigest) && overlaps(plan.plan, loaded.candidate.prepared) && plan.subjectDigest !== subjectDigest
|
|
517
460
|
));
|
|
518
461
|
if (differentBasis.length > 0) {
|
|
519
462
|
throw new ReleaseError(GATE_FAILED, 'a different formal acceptance basis already exists for this target', {
|
|
@@ -543,6 +486,7 @@ export async function decideTestCandidateHandoff({
|
|
|
543
486
|
};
|
|
544
487
|
}
|
|
545
488
|
|
|
489
|
+
await bindReplacement({ root, prepared: loaded.candidate.prepared, replaceUnpublishedPlan, resultFile: requirements.resultFile });
|
|
546
490
|
const acceptance = await acceptTestResults({
|
|
547
491
|
root,
|
|
548
492
|
requirements,
|