@@ -37,7 +37,7 @@ const CURRENT_TAG_PREFIX = "memos-local-plugin-v";
3737const TAG_PREFIXES = [ CURRENT_TAG_PREFIX , "openclaw-local-plugin-v" ] ;
3838const RELEASE_NOTES_MARKER = "doc-agent-release-notes-json" ;
3939const RELEASE_CATEGORY_ORDER = [ "Added" , "Improved" , "Fixed" ] ;
40- const MAX_DRAFT_REPAIR_ATTEMPTS = 2 ;
40+ const MAX_DRAFT_REPAIR_ATTEMPTS = 3 ;
4141const RELEASE_TO_DOC_CATEGORY = {
4242 Added : "New Features" ,
4343 Improved : "Improvements" ,
@@ -72,6 +72,12 @@ export function displayVersion(raw) {
7272 return value ? `v${ value } ` : "" ;
7373}
7474
75+ export function isLegacyPackageOnlyRelease ( { targetVersion, npmDistTag = "" } = { } ) {
76+ const parsed = parseSemver ( targetVersion ) ;
77+ const distTag = String ( npmDistTag || "" ) . trim ( ) ;
78+ return Boolean ( parsed ?. prerelease ) || Boolean ( distTag && distTag !== "latest" ) ;
79+ }
80+
7581export function versionFromTag ( tag ) {
7682 for ( const prefix of TAG_PREFIXES ) {
7783 if ( tag . startsWith ( prefix ) ) {
@@ -83,7 +89,7 @@ export function versionFromTag(tag) {
8389
8490export function parseSemver ( version ) {
8591 const cleaned = cleanVersion ( version ) ;
86- const match = cleaned . match ( / ^ ( \d + ) \. ( \d + ) \. ( \d + ) (?: [ - + ] ( [ 0 - 9 A - Z a - z . - ] + ) ) ? $ / ) ;
92+ const match = cleaned . match ( / ^ ( \d + ) \. ( \d + ) \. ( \d + ) (?: - ( [ 0 - 9 A - Z a - z . - ] + ) ) ? (?: \+ [ 0 - 9 A - Z a - z . - ] + ) ? $ / ) ;
8793 if ( ! match ) return null ;
8894 return {
8995 major : Number ( match [ 1 ] ) ,
@@ -173,10 +179,16 @@ function listProductTags() {
173179 . filter ( ( item ) => item . version && parseSemver ( item . version ) ) ;
174180}
175181
176- export function findPreviousTag ( targetVersion , currentTag ) {
177- const candidates = listProductTags ( )
182+ export function selectPreviousTag (
183+ tags ,
184+ targetVersion ,
185+ currentTag ,
186+ { includePrerelease = true } = { } ,
187+ ) {
188+ const candidates = tags
178189 . filter ( ( item ) => item . tag !== currentTag )
179190 . filter ( ( item ) => compareSemver ( item . version , targetVersion ) < 0 )
191+ . filter ( ( item ) => includePrerelease || ! parseSemver ( item . version ) ?. prerelease )
180192 . sort ( ( a , b ) => {
181193 const versionOrder = compareSemver ( b . version , a . version ) ;
182194 if ( versionOrder !== 0 ) return versionOrder ;
@@ -187,6 +199,10 @@ export function findPreviousTag(targetVersion, currentTag) {
187199 return candidates [ 0 ] ?. tag || "" ;
188200}
189201
202+ export function findPreviousTag ( targetVersion , currentTag , options = { } ) {
203+ return selectPreviousTag ( listProductTags ( ) , targetVersion , currentTag , options ) ;
204+ }
205+
190206function parseCommits ( previousTag , currentRef ) {
191207 const text = sh ( [
192208 "log" ,
@@ -428,6 +444,17 @@ export function ensureSourceHint(notes) {
428444 return notes . includes ( "doc-agent: source-id=" ) ? notes : `${ notes . trim ( ) } \n\n${ hint } \n` ;
429445}
430446
447+ export function validateLegacyPackageNotes ( notes ) {
448+ const text = String ( notes || "" ) . trim ( ) ;
449+ if ( ! / ^ # # C h a n g e l o g \s * $ / m. test ( text ) ) {
450+ fail ( "Legacy package release notes must contain a '## Changelog' heading." ) ;
451+ }
452+ if ( / d o c - a g e n t : \s * s o u r c e - i d = | d o c - a g e n t - r e l e a s e - n o t e s - j s o n / . test ( text ) ) {
453+ fail ( "Legacy package release notes must not include Doc Agent source hints or docs payloads." ) ;
454+ }
455+ return `${ text } \n` ;
456+ }
457+
431458function normalizeReleaseCategory ( value ) {
432459 const text = String ( value || "" ) . trim ( ) ;
433460 return RELEASE_CATEGORY_ORDER . includes ( text ) ? text : "" ;
@@ -1007,6 +1034,54 @@ function markdownFromReleaseItems(items, coverage) {
10071034 return `${ lines . join ( "\n" ) . trim ( ) } \n` ;
10081035}
10091036
1037+ export function legacyPackageDraftFromEvidence ( evidence , { npmDistTag = "" } = { } ) {
1038+ const version = evidence ?. target_version || "" ;
1039+ const targetPackageVersion = cleanVersion ( version ) ;
1040+ const gitRef = evidence ?. git_ref || "" ;
1041+ const previousTag = evidence ?. previous_tag || "" ;
1042+ const currentTag = evidence ?. current_tag || "" ;
1043+ const distTag = String ( npmDistTag || "" ) . trim ( ) || "beta" ;
1044+ const changedFileCount = Array . isArray ( evidence ?. changed_files ) ? evidence . changed_files . length : 0 ;
1045+ const commitCount = Array . isArray ( evidence ?. commits ) ? evidence . commits . length : 0 ;
1046+ const packageChanges = Array . isArray ( evidence ?. package_changes ) ? evidence . package_changes : [ ] ;
1047+ const versionChange = packageChanges . find ( ( item ) => item . field === "version" ) ;
1048+ const lines = [
1049+ "## Changelog" ,
1050+ "" ,
1051+ "### Prerelease" ,
1052+ `- Published ${ PRODUCT_TITLE . en } ${ version } as a package prerelease for validation through the npm \`${ distTag } \` dist-tag.` ,
1053+ "" ,
1054+ "### Release Evidence" ,
1055+ `- Package tag: ${ currentTag } ` ,
1056+ `- Previous package tag: ${ previousTag } ` ,
1057+ `- Source commit: ${ gitRef } ` ,
1058+ `- Local plugin commits: ${ commitCount } ` ,
1059+ `- Local plugin changed files: ${ changedFileCount } ` ,
1060+ ] ;
1061+ const previousPackageVersion = versionChange ?. before || "unknown" ;
1062+ lines . push ( `- Package version: ${ previousPackageVersion } -> ${ targetPackageVersion } ` ) ;
1063+ lines . push ( "" ) ;
1064+ lines . push ( "This legacy prerelease is package-only and does not update the MemOS-Docs Plugin tab." ) ;
1065+ return {
1066+ ok : true ,
1067+ needs_review : false ,
1068+ confidence : "legacy-package-only" ,
1069+ release_items : [ ] ,
1070+ coverage : {
1071+ needs_review : false ,
1072+ required_count : 0 ,
1073+ covered_required_count : 0 ,
1074+ missing_required_count : 0 ,
1075+ covered_refs : [ ] ,
1076+ missing_required : [ ] ,
1077+ invalid_item_refs : [ ] ,
1078+ policy : "legacy local-plugin prereleases are package-only and do not create docs payloads" ,
1079+ } ,
1080+ warnings : [ "legacy package-only prerelease skipped Doc Agent draft and docs payload generation" ] ,
1081+ release_notes_markdown : `${ lines . join ( "\n" ) . trim ( ) } \n` ,
1082+ } ;
1083+ }
1084+
10101085export function postprocessDraftFromEvidence ( draft , evidence ) {
10111086 const inputItems = Array . isArray ( draft ?. release_items )
10121087 ? draft . release_items . map ( normalizeReleaseItem ) . filter ( Boolean )
@@ -1290,21 +1365,28 @@ export async function main() {
12901365 if ( ! targetVersion ) fail ( "RELEASE_VERSION is required." ) ;
12911366
12921367 const currentTag = process . env . RELEASE_TAG || `${ CURRENT_TAG_PREFIX } ${ targetVersion } ` ;
1368+ const npmDistTag = String ( process . env . NPM_DIST_TAG || "" ) . trim ( ) ;
1369+ const legacyPackageOnly = isLegacyPackageOnlyRelease ( { targetVersion, npmDistTag } ) ;
12931370 const notesPath =
12941371 process . env . RELEASE_NOTES_FILE ||
12951372 join ( tmpdir ( ) , `memos-local-plugin-${ targetVersion } -release-notes.md` ) ;
12961373 mkdirSync ( dirname ( notesPath ) , { recursive : true } ) ;
12971374
12981375 const manualNotes = String ( process . env . MANUAL_RELEASE_NOTES || "" ) . trim ( ) ;
12991376 if ( manualNotes ) {
1300- writeFileSync ( notesPath , ensureSourceHint ( validateManualNotes ( manualNotes ) ) , "utf8" ) ;
1377+ const notes = legacyPackageOnly
1378+ ? validateLegacyPackageNotes ( manualNotes )
1379+ : ensureSourceHint ( validateManualNotes ( manualNotes ) ) ;
1380+ writeFileSync ( notesPath , notes , "utf8" ) ;
13011381 appendOutput ( "release_notes_file" , notesPath ) ;
13021382 appendOutput ( "draft_used" , "false" ) ;
13031383 console . log ( `Using manually provided release notes: ${ notesPath } ` ) ;
13041384 return ;
13051385 }
13061386
1307- const previousTag = findPreviousTag ( targetVersion , currentTag ) ;
1387+ const previousTag = findPreviousTag ( targetVersion , currentTag , {
1388+ includePrerelease : legacyPackageOnly ,
1389+ } ) ;
13081390 if ( ! previousTag ) {
13091391 fail ( `Cannot find a previous local plugin tag before ${ currentTag } .` ) ;
13101392 }
@@ -1314,6 +1396,31 @@ export async function main() {
13141396 const evidencePath = join ( tmpdir ( ) , `memos-local-plugin-${ targetVersion } -evidence.json` ) ;
13151397 writeFileSync ( evidencePath , JSON . stringify ( evidenceForInspection ( evidence ) , null , 2 ) , "utf8" ) ;
13161398
1399+ if ( legacyPackageOnly ) {
1400+ const draft = legacyPackageDraftFromEvidence ( evidence , { npmDistTag } ) ;
1401+ const draftPath = join ( tmpdir ( ) , `memos-local-plugin-${ targetVersion } -release-notes-draft.json` ) ;
1402+ writeFileSync ( draftPath , JSON . stringify ( draftForInspection ( draft ) , null , 2 ) , "utf8" ) ;
1403+ writeFileSync ( notesPath , draft . release_notes_markdown , "utf8" ) ;
1404+
1405+ appendOutput ( "release_notes_file" , notesPath ) ;
1406+ appendOutput ( "evidence_file" , evidencePath ) ;
1407+ appendOutput ( "draft_file" , draftPath ) ;
1408+ appendOutput ( "draft_used" , "false" ) ;
1409+ appendOutput ( "previous_tag" , previousTag ) ;
1410+ appendOutput ( "current_tag" , currentTag ) ;
1411+ appendOutput ( "current_ref" , currentRef ) ;
1412+ appendOutput ( "draft_confidence" , draft . confidence ) ;
1413+ appendOutput ( "missing_required_count" , "0" ) ;
1414+ appendOutput ( "validation_attempt_count" , "0" ) ;
1415+ appendOutput ( "repair_attempt_count" , "0" ) ;
1416+
1417+ console . log ( `Generated package-only prerelease notes without Doc Agent: ${ notesPath } ` ) ;
1418+ console . log ( `Previous tag: ${ previousTag } ` ) ;
1419+ console . log ( `Current tag: ${ currentTag } ` ) ;
1420+ console . log ( `Current evidence ref: ${ currentRef } ` ) ;
1421+ return ;
1422+ }
1423+
13171424 const draft = await requestValidatedDraft ( evidence ) ;
13181425 if ( ! draft . ok || draft . needs_review ) {
13191426 fail ( `Postprocessed release notes require review: ${ JSON . stringify ( draft . validation_report || draft . coverage || { } ) } ` ) ;
0 commit comments