@wipcomputer/wip-release 1.9.39 → 1.9.41
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/cli.js +4 -0
- package/core.mjs +342 -1
- package/mcp-server.mjs +4 -0
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -21,6 +21,8 @@ const noPublish = args.includes('--no-publish');
|
|
|
21
21
|
const skipProductCheck = args.includes('--skip-product-check');
|
|
22
22
|
const skipStaleCheck = args.includes('--skip-stale-check');
|
|
23
23
|
const skipWorktreeCheck = args.includes('--skip-worktree-check');
|
|
24
|
+
const skipTechDocsCheck = args.includes('--skip-tech-docs-check');
|
|
25
|
+
const skipCoverageCheck = args.includes('--skip-coverage-check');
|
|
24
26
|
const notesFilePath = flag('notes-file');
|
|
25
27
|
let notes = flag('notes');
|
|
26
28
|
// Bug fix #121: use strict check, not truthiness. --notes="" is empty, not absent.
|
|
@@ -164,6 +166,8 @@ release({
|
|
|
164
166
|
skipProductCheck,
|
|
165
167
|
skipStaleCheck,
|
|
166
168
|
skipWorktreeCheck,
|
|
169
|
+
skipTechDocsCheck,
|
|
170
|
+
skipCoverageCheck,
|
|
167
171
|
}).catch(err => {
|
|
168
172
|
console.error(` ✗ ${err.message}`);
|
|
169
173
|
process.exit(1);
|
package/core.mjs
CHANGED
|
@@ -383,6 +383,259 @@ function checkProductDocs(repoPath) {
|
|
|
383
383
|
return { missing, ok: missing.length === 0, skipped: false };
|
|
384
384
|
}
|
|
385
385
|
|
|
386
|
+
/**
|
|
387
|
+
* Check that technical docs (SKILL.md, TECHNICAL.md) were updated
|
|
388
|
+
* when source code changed since last release tag.
|
|
389
|
+
* Returns { missing: string[], ok: boolean, skipped: boolean }.
|
|
390
|
+
*/
|
|
391
|
+
function checkTechnicalDocs(repoPath) {
|
|
392
|
+
try {
|
|
393
|
+
let lastTag;
|
|
394
|
+
try {
|
|
395
|
+
lastTag = execFileSync('git', ['describe', '--tags', '--abbrev=0'],
|
|
396
|
+
{ cwd: repoPath, encoding: 'utf8' }).trim();
|
|
397
|
+
} catch {
|
|
398
|
+
return { missing: [], ok: true, skipped: true }; // No tags yet
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const diff = execFileSync('git', ['diff', '--name-only', lastTag, 'HEAD'],
|
|
402
|
+
{ cwd: repoPath, encoding: 'utf8' });
|
|
403
|
+
const changedFiles = diff.split('\n').map(f => f.trim()).filter(Boolean);
|
|
404
|
+
|
|
405
|
+
// Find source code changes (*.mjs, *.js, *.ts) excluding non-source dirs
|
|
406
|
+
const excludePattern = /\/(node_modules|dist|_trash|examples)\//;
|
|
407
|
+
const sourcePattern = /\.(mjs|js|ts)$/;
|
|
408
|
+
const sourceChanges = changedFiles.filter(f =>
|
|
409
|
+
sourcePattern.test(f) && !excludePattern.test(f) && !f.startsWith('ai/')
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
if (sourceChanges.length === 0) {
|
|
413
|
+
return { missing: [], ok: true, skipped: false }; // No source changes
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Check if any doc files were also modified
|
|
417
|
+
const docChanges = changedFiles.filter(f =>
|
|
418
|
+
f === 'SKILL.md' || f === 'TECHNICAL.md' ||
|
|
419
|
+
/^tools\/[^/]+\/SKILL\.md$/.test(f) ||
|
|
420
|
+
/^tools\/[^/]+\/TECHNICAL\.md$/.test(f)
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
if (docChanges.length > 0) {
|
|
424
|
+
return { missing: [], ok: true, skipped: false }; // Docs updated
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Source changed but no doc updates
|
|
428
|
+
const missing = [];
|
|
429
|
+
const preview = sourceChanges.slice(0, 5).join(', ');
|
|
430
|
+
const more = sourceChanges.length > 5 ? ` (and ${sourceChanges.length - 5} more)` : '';
|
|
431
|
+
missing.push('Source files changed since last tag but no SKILL.md or TECHNICAL.md was updated');
|
|
432
|
+
missing.push(`Changed: ${preview}${more}`);
|
|
433
|
+
missing.push('Update SKILL.md or TECHNICAL.md to document these changes');
|
|
434
|
+
|
|
435
|
+
return { missing, ok: false, skipped: false };
|
|
436
|
+
} catch {
|
|
437
|
+
return { missing: [], ok: true, skipped: true }; // Graceful fallback
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Parse the interface coverage table from a markdown file.
|
|
443
|
+
* Returns array of { name, cli, module, mcp, openclaw, skill, ccHook } or null.
|
|
444
|
+
*/
|
|
445
|
+
function parseInterfaceCoverageTable(filePath) {
|
|
446
|
+
if (!existsSync(filePath)) return null;
|
|
447
|
+
const content = readFileSync(filePath, 'utf8');
|
|
448
|
+
const lines = content.split('\n');
|
|
449
|
+
|
|
450
|
+
const headerIdx = lines.findIndex(l => /^\|\s*#\s*\|\s*Tool\s*\|/i.test(l));
|
|
451
|
+
if (headerIdx === -1) return null;
|
|
452
|
+
|
|
453
|
+
const rows = [];
|
|
454
|
+
for (let i = headerIdx + 2; i < lines.length; i++) {
|
|
455
|
+
const line = lines[i].trim();
|
|
456
|
+
if (!line.startsWith('|')) break;
|
|
457
|
+
const cells = line.split('|').map(c => c.trim()).filter(c => c !== '');
|
|
458
|
+
if (cells.length < 8) continue;
|
|
459
|
+
// Skip category header rows (# cell is empty, non-numeric, or bold)
|
|
460
|
+
const num = cells[0].trim();
|
|
461
|
+
if (!num || /^\*\*/.test(num) || isNaN(parseInt(num))) continue;
|
|
462
|
+
rows.push({
|
|
463
|
+
name: cells[1].trim(),
|
|
464
|
+
cli: /^Y$/i.test(cells[2]),
|
|
465
|
+
module: /^Y$/i.test(cells[3]),
|
|
466
|
+
mcp: /^Y$/i.test(cells[4]),
|
|
467
|
+
openclaw: /^Y$/i.test(cells[5]),
|
|
468
|
+
skill: /^Y$/i.test(cells[6]),
|
|
469
|
+
ccHook: /^Y$/i.test(cells[7]),
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
return rows.length > 0 ? rows : null;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Read display name from a tool's SKILL.md frontmatter.
|
|
477
|
+
* Tries display-name, then name field. Falls back to null.
|
|
478
|
+
*/
|
|
479
|
+
function getToolDisplayName(toolPath) {
|
|
480
|
+
const skillPath = join(toolPath, 'SKILL.md');
|
|
481
|
+
if (!existsSync(skillPath)) return null;
|
|
482
|
+
try {
|
|
483
|
+
const content = readFileSync(skillPath, 'utf8');
|
|
484
|
+
const displayMatch = content.match(/^\s*display-name:\s*"?([^"\n]+)"?/m);
|
|
485
|
+
if (displayMatch) return displayMatch[1].trim();
|
|
486
|
+
const nameMatch = content.match(/^name:\s*"?([^"\n]+)"?/m);
|
|
487
|
+
if (nameMatch) return nameMatch[1].trim();
|
|
488
|
+
} catch {}
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Check that the interface coverage table in README.md and SKILL.md
|
|
494
|
+
* matches the actual interfaces detected in tools/*/.
|
|
495
|
+
* Returns { missing: string[], ok: boolean, skipped: boolean }.
|
|
496
|
+
*/
|
|
497
|
+
function checkInterfaceCoverage(repoPath) {
|
|
498
|
+
try {
|
|
499
|
+
// Only applies to toolbox repos
|
|
500
|
+
const toolsDir = join(repoPath, 'tools');
|
|
501
|
+
if (!existsSync(toolsDir)) return { missing: [], ok: true, skipped: true };
|
|
502
|
+
|
|
503
|
+
const entries = readdirSync(toolsDir, { withFileTypes: true });
|
|
504
|
+
const tools = entries
|
|
505
|
+
.filter(e => e.isDirectory() && existsSync(join(toolsDir, e.name, 'package.json')))
|
|
506
|
+
.map(e => ({ name: e.name, path: join(toolsDir, e.name) }));
|
|
507
|
+
|
|
508
|
+
if (tools.length === 0) return { missing: [], ok: true, skipped: true };
|
|
509
|
+
|
|
510
|
+
// Detect actual interfaces for each tool
|
|
511
|
+
const actualMap = {};
|
|
512
|
+
for (const tool of tools) {
|
|
513
|
+
const pkg = JSON.parse(readFileSync(join(tool.path, 'package.json'), 'utf8'));
|
|
514
|
+
actualMap[tool.name] = {
|
|
515
|
+
displayName: getToolDisplayName(tool.path) || tool.name,
|
|
516
|
+
cli: !!(pkg.bin),
|
|
517
|
+
module: !!(pkg.main || pkg.exports),
|
|
518
|
+
mcp: ['mcp-server.mjs', 'mcp-server.js', 'dist/mcp-server.js'].some(f => existsSync(join(tool.path, f))),
|
|
519
|
+
openclaw: existsSync(join(tool.path, 'openclaw.plugin.json')),
|
|
520
|
+
skill: existsSync(join(tool.path, 'SKILL.md')),
|
|
521
|
+
ccHook: !!(pkg.claudeCode?.hook) || existsSync(join(tool.path, 'guard.mjs')),
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const missing = [];
|
|
526
|
+
|
|
527
|
+
// Check both README.md and SKILL.md tables
|
|
528
|
+
for (const [label, filePath] of [['README.md', join(repoPath, 'README.md')], ['SKILL.md', join(repoPath, 'SKILL.md')]]) {
|
|
529
|
+
const tableRows = parseInterfaceCoverageTable(filePath);
|
|
530
|
+
if (!tableRows) continue;
|
|
531
|
+
|
|
532
|
+
// Tool count
|
|
533
|
+
if (tools.length !== tableRows.length) {
|
|
534
|
+
missing.push(`${label}: tool count mismatch (${tools.length} in tools/, ${tableRows.length} in table)`);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Check each actual tool against the table
|
|
538
|
+
for (const tool of tools) {
|
|
539
|
+
const actual = actualMap[tool.name];
|
|
540
|
+
const displayName = actual.displayName;
|
|
541
|
+
const tableRow = tableRows.find(r =>
|
|
542
|
+
r.name === displayName ||
|
|
543
|
+
r.name.toLowerCase() === displayName.toLowerCase() ||
|
|
544
|
+
r.name.toLowerCase().includes(tool.name.replace(/^wip-/, '').replace(/-/g, ' '))
|
|
545
|
+
);
|
|
546
|
+
|
|
547
|
+
if (!tableRow) {
|
|
548
|
+
missing.push(`${label}: ${tool.name} (${displayName}) missing from coverage table`);
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const ifaceMap = [
|
|
553
|
+
['cli', 'CLI'], ['module', 'Module'], ['mcp', 'MCP'],
|
|
554
|
+
['openclaw', 'OC Plugin'], ['skill', 'Skill'], ['ccHook', 'CC Hook']
|
|
555
|
+
];
|
|
556
|
+
|
|
557
|
+
for (const [key, name] of ifaceMap) {
|
|
558
|
+
if (actual[key] && !tableRow[key]) {
|
|
559
|
+
missing.push(`${label}: ${displayName} has ${name} but table says no`);
|
|
560
|
+
}
|
|
561
|
+
if (tableRow[key] && !actual[key]) {
|
|
562
|
+
missing.push(`${label}: ${displayName} marked ${name} in table but not detected`);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return { missing, ok: missing.length === 0, skipped: false };
|
|
569
|
+
} catch {
|
|
570
|
+
return { missing: [], ok: true, skipped: true }; // Graceful fallback
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Auto-update version/date lines in product docs before the release commit.
|
|
576
|
+
* Updates roadmap.md "Current version" and "Last updated",
|
|
577
|
+
* and readme-first-product.md "Last updated" and "What's Built (as of vX.Y.Z)".
|
|
578
|
+
* Returns number of files updated.
|
|
579
|
+
*/
|
|
580
|
+
function syncProductDocs(repoPath, newVersion) {
|
|
581
|
+
let updated = 0;
|
|
582
|
+
const today = new Date().toISOString().split('T')[0];
|
|
583
|
+
|
|
584
|
+
// 1. roadmap.md
|
|
585
|
+
const roadmapPath = join(repoPath, 'ai', 'product', 'plans-prds', 'roadmap.md');
|
|
586
|
+
if (existsSync(roadmapPath)) {
|
|
587
|
+
let content = readFileSync(roadmapPath, 'utf8');
|
|
588
|
+
let changed = false;
|
|
589
|
+
|
|
590
|
+
// Update "Current version: vX.Y.Z"
|
|
591
|
+
const versionRe = /(\*\*Current version:\*\*\s*)v[\d.]+/;
|
|
592
|
+
if (versionRe.test(content)) {
|
|
593
|
+
content = content.replace(versionRe, `$1v${newVersion}`);
|
|
594
|
+
changed = true;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Update "Last updated: YYYY-MM-DD"
|
|
598
|
+
const dateRe = /(\*\*Last updated:\*\*\s*)[\d-]+/;
|
|
599
|
+
if (dateRe.test(content)) {
|
|
600
|
+
content = content.replace(dateRe, `$1${today}`);
|
|
601
|
+
changed = true;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (changed) {
|
|
605
|
+
writeFileSync(roadmapPath, content);
|
|
606
|
+
updated++;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// 2. readme-first-product.md
|
|
611
|
+
const rfpPath = join(repoPath, 'ai', 'product', 'readme-first-product.md');
|
|
612
|
+
if (existsSync(rfpPath)) {
|
|
613
|
+
let content = readFileSync(rfpPath, 'utf8');
|
|
614
|
+
let changed = false;
|
|
615
|
+
|
|
616
|
+
// Update "Last updated: YYYY-MM-DD"
|
|
617
|
+
const dateRe = /(\*\*Last updated:\*\*\s*)[\d-]+/;
|
|
618
|
+
if (dateRe.test(content)) {
|
|
619
|
+
content = content.replace(dateRe, `$1${today}`);
|
|
620
|
+
changed = true;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Update "What's Built (as of vX.Y.Z)"
|
|
624
|
+
const builtRe = /(What's Built \(as of\s*)v[\d.]+(\))/;
|
|
625
|
+
if (builtRe.test(content)) {
|
|
626
|
+
content = content.replace(builtRe, `$1v${newVersion}$2`);
|
|
627
|
+
changed = true;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
if (changed) {
|
|
631
|
+
writeFileSync(rfpPath, content);
|
|
632
|
+
updated++;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return updated;
|
|
637
|
+
}
|
|
638
|
+
|
|
386
639
|
/**
|
|
387
640
|
* Build release notes with narrative first, commit details second.
|
|
388
641
|
*
|
|
@@ -753,7 +1006,7 @@ export function checkStaleBranches(repoPath, level) {
|
|
|
753
1006
|
/**
|
|
754
1007
|
* Run the full release pipeline.
|
|
755
1008
|
*/
|
|
756
|
-
export async function release({ repoPath, level, notes, notesSource, dryRun, noPublish, skipProductCheck, skipStaleCheck, skipWorktreeCheck }) {
|
|
1009
|
+
export async function release({ repoPath, level, notes, notesSource, dryRun, noPublish, skipProductCheck, skipStaleCheck, skipWorktreeCheck, skipTechDocsCheck, skipCoverageCheck }) {
|
|
757
1010
|
repoPath = repoPath || process.cwd();
|
|
758
1011
|
const currentVersion = detectCurrentVersion(repoPath);
|
|
759
1012
|
const newVersion = bumpSemver(currentVersion, level);
|
|
@@ -897,6 +1150,50 @@ export async function release({ repoPath, level, notes, notesSource, dryRun, noP
|
|
|
897
1150
|
}
|
|
898
1151
|
}
|
|
899
1152
|
|
|
1153
|
+
// 0.85. Technical docs check
|
|
1154
|
+
if (!skipTechDocsCheck) {
|
|
1155
|
+
const techDocsCheck = checkTechnicalDocs(repoPath);
|
|
1156
|
+
if (!techDocsCheck.skipped) {
|
|
1157
|
+
if (techDocsCheck.ok) {
|
|
1158
|
+
console.log(' ✓ Technical docs up to date');
|
|
1159
|
+
} else {
|
|
1160
|
+
const isMinorOrMajor = level === 'minor' || level === 'major';
|
|
1161
|
+
const prefix = isMinorOrMajor ? '✗' : '!';
|
|
1162
|
+
console.log(` ${prefix} Technical docs need attention:`);
|
|
1163
|
+
for (const m of techDocsCheck.missing) console.log(` - ${m}`);
|
|
1164
|
+
if (isMinorOrMajor) {
|
|
1165
|
+
console.log('');
|
|
1166
|
+
console.log(' Update SKILL.md or TECHNICAL.md before a minor/major release.');
|
|
1167
|
+
console.log(' Use --skip-tech-docs-check to override.');
|
|
1168
|
+
console.log('');
|
|
1169
|
+
return { currentVersion, newVersion, dryRun: false, failed: true };
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// 0.9. Interface coverage check
|
|
1176
|
+
if (!skipCoverageCheck) {
|
|
1177
|
+
const coverageCheck = checkInterfaceCoverage(repoPath);
|
|
1178
|
+
if (!coverageCheck.skipped) {
|
|
1179
|
+
if (coverageCheck.ok) {
|
|
1180
|
+
console.log(' ✓ Interface coverage table matches');
|
|
1181
|
+
} else {
|
|
1182
|
+
const isMinorOrMajor = level === 'minor' || level === 'major';
|
|
1183
|
+
const prefix = isMinorOrMajor ? '✗' : '!';
|
|
1184
|
+
console.log(` ${prefix} Interface coverage table has mismatches:`);
|
|
1185
|
+
for (const m of coverageCheck.missing) console.log(` - ${m}`);
|
|
1186
|
+
if (isMinorOrMajor) {
|
|
1187
|
+
console.log('');
|
|
1188
|
+
console.log(' Update the coverage table in README.md and SKILL.md.');
|
|
1189
|
+
console.log(' Use --skip-coverage-check to override.');
|
|
1190
|
+
console.log('');
|
|
1191
|
+
return { currentVersion, newVersion, dryRun: false, failed: true };
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
900
1197
|
if (dryRun) {
|
|
901
1198
|
// Product docs check (dry-run)
|
|
902
1199
|
if (!skipProductCheck) {
|
|
@@ -934,6 +1231,32 @@ export async function release({ repoPath, level, notes, notesSource, dryRun, noP
|
|
|
934
1231
|
console.log(' [dry run] ✓ No stale remote branches');
|
|
935
1232
|
}
|
|
936
1233
|
}
|
|
1234
|
+
// Technical docs check (dry-run)
|
|
1235
|
+
if (!skipTechDocsCheck) {
|
|
1236
|
+
const techDocsCheck = checkTechnicalDocs(repoPath);
|
|
1237
|
+
if (!techDocsCheck.skipped) {
|
|
1238
|
+
if (techDocsCheck.ok) {
|
|
1239
|
+
console.log(' [dry run] ✓ Technical docs up to date');
|
|
1240
|
+
} else {
|
|
1241
|
+
const isMinorOrMajor = level === 'minor' || level === 'major';
|
|
1242
|
+
console.log(` [dry run] ${isMinorOrMajor ? '✗ Would BLOCK' : '! Would WARN'}: technical docs need updates`);
|
|
1243
|
+
for (const m of techDocsCheck.missing) console.log(` - ${m}`);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
// Interface coverage check (dry-run)
|
|
1248
|
+
if (!skipCoverageCheck) {
|
|
1249
|
+
const coverageCheck = checkInterfaceCoverage(repoPath);
|
|
1250
|
+
if (!coverageCheck.skipped) {
|
|
1251
|
+
if (coverageCheck.ok) {
|
|
1252
|
+
console.log(' [dry run] ✓ Interface coverage table matches');
|
|
1253
|
+
} else {
|
|
1254
|
+
const isMinorOrMajor = level === 'minor' || level === 'major';
|
|
1255
|
+
console.log(` [dry run] ${isMinorOrMajor ? '✗ Would BLOCK' : '! Would WARN'}: interface coverage mismatches`);
|
|
1256
|
+
for (const m of coverageCheck.missing) console.log(` - ${m}`);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
937
1260
|
const hasSkill = existsSync(join(repoPath, 'SKILL.md'));
|
|
938
1261
|
console.log(` [dry run] Would bump package.json to ${newVersion}`);
|
|
939
1262
|
if (hasSkill) console.log(` [dry run] Would update SKILL.md version`);
|
|
@@ -1016,6 +1339,12 @@ export async function release({ repoPath, level, notes, notesSource, dryRun, noP
|
|
|
1016
1339
|
console.log(` ✓ Moved ${trashed} RELEASE-NOTES file(s) to _trash/`);
|
|
1017
1340
|
}
|
|
1018
1341
|
|
|
1342
|
+
// 3.75. Auto-update product docs version/date
|
|
1343
|
+
const docsUpdated = syncProductDocs(repoPath, newVersion);
|
|
1344
|
+
if (docsUpdated > 0) {
|
|
1345
|
+
console.log(` ✓ Product docs synced to v${newVersion} (${docsUpdated} file(s))`);
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1019
1348
|
// 4. Git commit + tag
|
|
1020
1349
|
gitCommitAndTag(repoPath, newVersion, notes);
|
|
1021
1350
|
console.log(` ✓ Committed and tagged v${newVersion}`);
|
|
@@ -1241,6 +1570,18 @@ export async function release({ repoPath, level, notes, notesSource, dryRun, noP
|
|
|
1241
1570
|
console.log(` ! Branch prune skipped: ${e.message}`);
|
|
1242
1571
|
}
|
|
1243
1572
|
|
|
1573
|
+
// Write release marker so branch guard blocks immediate install (#73)
|
|
1574
|
+
try {
|
|
1575
|
+
const markerDir = join(process.env.HOME || '', '.ldm', 'state');
|
|
1576
|
+
const { mkdirSync, writeFileSync } = await import('node:fs');
|
|
1577
|
+
mkdirSync(markerDir, { recursive: true });
|
|
1578
|
+
writeFileSync(join(markerDir, '.last-release'), JSON.stringify({
|
|
1579
|
+
repo: repoName,
|
|
1580
|
+
version: newVersion,
|
|
1581
|
+
timestamp: new Date().toISOString(),
|
|
1582
|
+
}) + '\n');
|
|
1583
|
+
} catch {}
|
|
1584
|
+
|
|
1244
1585
|
console.log('');
|
|
1245
1586
|
console.log(` Done. ${repoName} v${newVersion} released.`);
|
|
1246
1587
|
console.log('');
|
package/mcp-server.mjs
CHANGED
|
@@ -31,6 +31,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
31
31
|
dryRun: { type: 'boolean', description: 'Preview only, no changes', default: false },
|
|
32
32
|
noPublish: { type: 'boolean', description: 'Bump + tag only, skip npm/GitHub publish', default: false },
|
|
33
33
|
skipProductCheck: { type: 'boolean', description: 'Skip product doc freshness check', default: false },
|
|
34
|
+
skipTechDocsCheck: { type: 'boolean', description: 'Skip technical docs freshness check', default: false },
|
|
35
|
+
skipCoverageCheck: { type: 'boolean', description: 'Skip interface coverage table check', default: false },
|
|
34
36
|
},
|
|
35
37
|
required: ['level', 'notes'],
|
|
36
38
|
},
|
|
@@ -65,6 +67,8 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
65
67
|
notesSource: 'flag', // MCP always passes notes directly
|
|
66
68
|
noPublish: args.noPublish || false,
|
|
67
69
|
skipProductCheck: args.skipProductCheck || false,
|
|
70
|
+
skipTechDocsCheck: args.skipTechDocsCheck || false,
|
|
71
|
+
skipCoverageCheck: args.skipCoverageCheck || false,
|
|
68
72
|
});
|
|
69
73
|
return {
|
|
70
74
|
content: [{
|
package/package.json
CHANGED