diff --git a/rhodium-standard-repositories/satellites/rsr-certifier/README.adoc b/rhodium-standard-repositories/satellites/rsr-certifier/README.adoc index d020d037..42432b8b 100644 --- a/rhodium-standard-repositories/satellites/rsr-certifier/README.adoc +++ b/rhodium-standard-repositories/satellites/rsr-certifier/README.adoc @@ -267,21 +267,27 @@ comment_on_pr = true === LSP Methods +The server (`lsp/src/main.rs`) does not expose custom `rsr/*` requests. +It answers the standard `workspace/executeCommand` with the commands +below (returning `RsrComplianceResult { tier, tier_code, score, checks }` +for the compliance command), and publishes diagnostics on document +open/save. + [cols="2,4"] |=== |Method |Description -|`rsr/getCompliance` -|Get current compliance status +|`workspace/executeCommand` — `rsr.checkCompliance` +|Run the compliance audit; returns the current `RsrComplianceResult` -|`rsr/runCheck` -|Run specific compliance check +|`workspace/executeCommand` — `rsr.generateBadge` +|Generate the certification badge -|`rsr/getTier` -|Get current certification tier +|`workspace/executeCommand` — `rsr.initConfig` +|Scaffold a `.rsr.toml` -|`rsr/suggestFixes` -|Get fix suggestions for failures +|`textDocument/publishDiagnostics` +|Compliance findings, pushed on document open and save |=== == IDE Integration diff --git a/rhodium-standard-repositories/satellites/rsr-certifier/extensions/vscode/out/extension.cjs b/rhodium-standard-repositories/satellites/rsr-certifier/extensions/vscode/out/extension.cjs index 61997db7..e34957c6 100644 --- a/rhodium-standard-repositories/satellites/rsr-certifier/extensions/vscode/out/extension.cjs +++ b/rhodium-standard-repositories/satellites/rsr-certifier/extensions/vscode/out/extension.cjs @@ -8,7 +8,7 @@ "use strict"; -const _wasmBase64 = "AGFzbQEAAAABdhVgBH9/f38Bf2ACf38Bf2ABfwF/YAN/f38Bf2AAAX9gBX9/f39/AX9gA39/fwF/YAR/f39/AX9gBX9/f39/AX9gAn9/AX9gAAF/YAABf2AAAX9gAAF/YAABf2ABfwF/YAF/AX9gAX8Bf2AAAX9gAX8Bf2AAAX8CxwkrFndhc2lfc25hcHNob3RfcHJldmlldzEIZmRfd3JpdGUAAAZWc2NvZGUPcmVnaXN0ZXJDb21tYW5kAAEGVnNjb2RlEGdldENvbmZpZ3VyYXRpb24AAgZWc2NvZGUWd29ya3NwYWNlQ29uZmlnR2V0Qm9vbAADBlZzY29kZRh3b3Jrc3BhY2VDb25maWdHZXRTdHJpbmcAAwZWc2NvZGUQc2hvd0Vycm9yTWVzc2FnZQACBlZzY29kZRJzaG93V2FybmluZ01lc3NhZ2UAAgZWc2NvZGUWc2hvd0luZm9ybWF0aW9uTWVzc2FnZQACBlZzY29kZQ5jcmVhdGVUZXJtaW5hbAACBlZzY29kZQx0ZXJtaW5hbFNob3cAAgZWc2NvZGUQdGVybWluYWxTZW5kVGV4dAABBlZzY29kZRBwdXNoU3Vic2NyaXB0aW9uAAEGVnNjb2RlCmNvbnNvbGVMb2cAAgZWc2NvZGUIZXhlY1N5bmMAAgZWc2NvZGUMc3RyaW5nQ29uY2F0AAEGVnNjb2RlDnN0cmluZ0VuZHNXaXRoAAEGVnNjb2RlE3N0cmluZ1JlcGxhY2VTdWZmaXgAAwZWc2NvZGUNc3RyaW5nSXNFbXB0eQACBlZzY29kZRh3b3Jrc3BhY2VGb2xkZXJGaXJzdFBhdGgABAZWc2NvZGULdXJpRnJvbVBhdGgAAgZWc2NvZGULdXJpSm9pblBhdGgAAQZWc2NvZGUHdXJpUGF0aAACBlZzY29kZQtmc1dyaXRlRmlsZQABBlZzY29kZRBvcGVuVGV4dERvY3VtZW50AAIGVnNjb2RlEHNob3dUZXh0RG9jdW1lbnQAAgZWc2NvZGUTY3JlYXRlU3RhdHVzQmFySXRlbQABBlZzY29kZRRzdGF0dXNCYXJJdGVtU2V0VGV4dAABBlZzY29kZRdzdGF0dXNCYXJJdGVtU2V0VG9vbHRpcAABBlZzY29kZRdzdGF0dXNCYXJJdGVtU2V0Q29tbWFuZAABBlZzY29kZSRzdGF0dXNCYXJJdGVtU2V0QmFja2dyb3VuZENvbG9yVGhlbWUAAQZWc2NvZGURc3RhdHVzQmFySXRlbVNob3cAAgZWc2NvZGUZc3RhdHVzQmFySXRlbUFzRGlzcG9zYWJsZQACBlZzY29kZRpjcmVhdGVEaWFnbm9zdGljQ29sbGVjdGlvbgACBlZzY29kZSBkaWFnbm9zdGljQ29sbGVjdGlvbkFzRGlzcG9zYWJsZQACBlZzY29kZRJjcmVhdGVXZWJ2aWV3UGFuZWwAAwZWc2NvZGUTd2Vidmlld1BhbmVsU2V0SHRtbAABBlZzY29kZRJjbGlwYm9hcmRXcml0ZVRleHQAAgZWc2NvZGUVb25EaWRTYXZlVGV4dERvY3VtZW50AAIGVnNjb2RlCHBhdGhKb2luAAEGVnNjb2RlD3Byb2Nlc3NQbGF0Zm9ybQAEBlZzY29kZRVleHRlbnNpb25BYnNvbHV0ZVBhdGgAARRWc2NvZGVMYW5ndWFnZUNsaWVudBFuZXdMYW5ndWFnZUNsaWVudAAFFFZzY29kZUxhbmd1YWdlQ2xpZW50E2xhbmd1YWdlQ2xpZW50U3RhcnQAAgMQDwYHCAkKCwwNDg8QERITFAQBAAUDAQABBgEAB5QBCAZtZW1vcnkCABhoYW5kbGVyX2NoZWNrX2NvbXBsaWFuY2UALxNoYW5kbGVyX2luaXRfY29uZmlnADATaGFuZGxlcl9zaG93X3JlcG9ydAAyFmhhbmRsZXJfZ2VuZXJhdGVfYmFkZ2UAMw9oYW5kbGVyX29uX3NhdmUANwhhY3RpdmF0ZQA4CmRlYWN0aXZhdGUAOQkBAArvBg8QACAAIAEQDiACEA4PGkEACxQAIAAgARAOIAIQDiADEA4PGkEACxgAIAAgARAOIAIQDiADEA4gBBAODxpBAAscAQF/IAAQCCECIAIQCRogAiABEAoaQQAPGkEACzkBAn8QEiEAIAAQEUEBRgR/QYAQEAUaQQEPGkEABUEACxpBnBAgAEGrEBArIQFBsBAgARAuDxpBAAuxAQEPfxASIQAgABARQQFGBH9BgBAQBRpBAQ8aQQAFQQALGkG9EBACIQEgAUHEEEHSEBAEIQJB3BAhA0HIESEEQegRIQVBgxIhBkGzEyEHQb4UIQggBCACIAUQKyEJIAMgCSAGIAcgCBAtIQogABATIQsgC0H2FBAUIQwgDCAKEBYhDSANQQBHBH9BgxUQBRogDQ8aQQAFQQALGiAMEBchDiAOEBgaQaAVEAcaQQAPGkEACxgBAn9ByBUhAEHmGCEBIAAgARAODxpBAAsdAQF/QYIcQY8cQQEQIiEAIAAQMRAjGkEADxpBAAs4AQJ/EBIhACAAEBFBAUYEf0GAEBAFGkEBDxpBAAVBAAsaQagcIQEgARAkGkGJHRAHGkEADxpBAAttAQd/Qb0QEAIhASABQbAdQb4dEAQhAiACEBFBAEYEfyACDxpBAAVBAAsaECchA0HCHSEEQc0dIQVB3B0gBBAmIQZB3B0gBRAmIQcgA0HmHRAPQQFGBH8gACAHECgPGkEABSAAIAYQKA8aQQALC1IBBH8gABA0IQFB7x0gAUGrEBAOEA4hAiACEA0hAyADQQBHBH9B+h0QBhpBAQ8aQQAFQQALGkG1HkHFHiABQb4dQQAQKSEEIAQQKhpBAA8aQQALYQECf0G9EBACIQEgAUHmHkEBEANBAEYEf0EADxpBAAVBAAsaQQFB5AAQGSECIAJB+x4QGhogAkGSHxAbGiACQd8fEBwaIAJBvh0QHRogAhAeGiAAIAIQHxALGkEADxpBAAsIAEEADxpBAAt/AQJ/QfEfEAwaIAAQNhogAEGdIEEAEAEQCxogAEG0IEEBEAEQCxogAEHfH0ECEAEQCxogAEHGIEEDEAEQCxpBvRAQICEBIAAgARAhEAsaIAAQNRpBvRAQAiECIAJB2yBBARADQQBHBH8gAEEEECUQCxpBAAVBAAsaQQAPGkEACwgAQQAPGkEACwvlEikAQYAQCxwYAAAATm8gd29ya3NwYWNlIGZvbGRlciBvcGVuAEGcEAsPCwAAAHJzciBjaGVjayAiAEGrEAsFAQAAACIAQbAQCw0JAAAAUlNSIENoZWNrAEG9EAsHAwAAAHJzcgBBxBALDgoAAAB0YXJnZXRUaWVyAEHSEAsKBgAAAHNpbHZlcgBB3BALbGgAAAAjIFJTUiAoUmhvZGl1bSBTdGFuZGFyZCBSZXBvc2l0b3J5KSBDb25maWd1cmF0aW9uCiMgaHR0cHM6Ly9naXRodWIuY29tL0h5cGVycG9seW1hdGgvZ2l0LXJzci1jZXJ0aWZpZWQKCgBByBELIBwAAABbY29tcGxpYW5jZV0KdGFyZ2V0X3RpZXIgPSAiAEHoEQsbFwAAACIKc3RyaWN0X21vZGUgPSBmYWxzZQoKAEGDEguwAawAAABbY2hlY2tzXQojIExpY2Vuc2UgY29uZmlndXJhdGlvbgpsaWNlbnNlLnJlcXVpcmVkID0gdHJ1ZQpsaWNlbnNlLmFsbG93ZWQgPSBbIk1JVCIsICJBcGFjaGUtMi4wIiwgIkdQTC0zLjAiLCAiQlNELTMtQ2xhdXNlIl0KCiMgUkVBRE1FIHJlcXVpcmVtZW50cwpyZWFkbWUubWluX2xlbmd0aCA9IDEwMAoKAEGzEwuLAYcAAABbaWdub3JlXQojIFBhdGhzIHRvIGV4Y2x1ZGUgZnJvbSBjb21wbGlhbmNlIHNjYW5uaW5nCnBhdGhzID0gWwogICAgInZlbmRvci8iLAogICAgInRoaXJkX3BhcnR5LyIsCiAgICAibm9kZV9tb2R1bGVzLyIsCiAgICAiLmdpdC8iLApdCgoAQb4UCzg0AAAAW2JhZGdlc10Kc3R5bGUgPSAiZmxhdC1zcXVhcmUiCmluY2x1ZGVfc2NvcmUgPSB0cnVlCgBB9hQLDQkAAAAucnNyLnRvbWwAQYMVCx0ZAAAARmFpbGVkIHRvIHdyaXRlIC5yc3IudG9tbABBoBULKCQAAABSU1IgY29uZmlndXJhdGlvbiBjcmVhdGVkOiAucnNyLnRvbWwAQcgVC54DmgEAADwhRE9DVFlQRSBodG1sPjxodG1sPjxoZWFkPjxzdHlsZT5ib2R5e2ZvbnQtZmFtaWx5OnZhcigtLXZzY29kZS1mb250LWZhbWlseSk7cGFkZGluZzoyMHB4fWgxe2NvbG9yOnZhcigtLXZzY29kZS1mb3JlZ3JvdW5kKX0udGllcntmb250LXNpemU6MjRweDttYXJnaW46MjBweCAwfS50aWVyLnNpbHZlcntjb2xvcjojQzBDMEMwfS5zY29yZXtmb250LXNpemU6MThweDtjb2xvcjp2YXIoLS12c2NvZGUtZGVzY3JpcHRpb25Gb3JlZ3JvdW5kKX0uY2hlY2tze21hcmdpbi10b3A6MjBweH0uY2hlY2t7cGFkZGluZzoxMHB4O21hcmdpbjo1cHggMDtib3JkZXItcmFkaXVzOjRweH0uY2hlY2sucGxhY2Vob2xkZXJ7YmFja2dyb3VuZDp2YXIoLS12c2NvZGUtdGV4dEJsb2NrUXVvdGUtYmFja2dyb3VuZCl9PC9zdHlsZT48L2hlYWQ+AEHmGAucA5gBAAA8Ym9keT48aDE+UlNSIENvbXBsaWFuY2UgUmVwb3J0PC9oMT48ZGl2IGNsYXNzPSJ0aWVyIHNpbHZlciI+4piGIFNJTFZFUjwvZGl2PjxkaXYgY2xhc3M9InNjb3JlIj5SdW4gPGNvZGU+UlNSOiBDaGVjayBDb21wbGlhbmNlPC9jb2RlPiBpbiBhIHRlcm1pbmFsIGZvciBsaXZlIHJlc3VsdHMuPC9kaXY+PGRpdiBjbGFzcz0iY2hlY2tzIj48aDI+Q2hlY2tzPC9oMj48ZGl2IGNsYXNzPSJjaGVjayBwbGFjZWhvbGRlciI+TGl2ZSBpbi1wYW5lbCByZXN1bHRzIHJlcXVpcmUgYW4gYXN5bmMtZXh0ZXJuIEFCSSAoc2VlIGFmZmluZXNjcmlwdCM2NCkuIFRoZSBDTEkgcGF0aCB2aWEgPGNvZGU+cnNyIGNoZWNrPC9jb2RlPiByZXBvcnRzIGZ1bGwgY29tcGxpYW5jZSBzdGF0dXMuPC9kaXY+PC9kaXY+PC9ib2R5PjwvaHRtbD4AQYIcCw0JAAAAcnNyUmVwb3J0AEGPHAsZFQAAAFJTUiBDb21wbGlhbmNlIFJlcG9ydABBqBwLYV0AAABbIVtSU1IgQ2VydGlmaWNhdGlvbl0oaHR0cHM6Ly9yc3ItY2VydGlmaWVkLmRldi9iYWRnZS9zaWx2ZXIuc3ZnKV0oaHR0cHM6Ly9yc3ItY2VydGlmaWVkLmRldikAQYkdCycjAAAAQmFkZ2UgbWFya2Rvd24gY29waWVkIHRvIGNsaXBib2FyZCEAQbAdCw4KAAAAc2VydmVyUGF0aABBvh0LBAAAAAAAQcIdCwsHAAAAcnNyLWxzcABBzR0LDwsAAAByc3ItbHNwLmV4ZQBB3B0LCgYAAABzZXJ2ZXIAQeYdCwkFAAAAd2luMzIAQe8dCwsHAAAAd2hpY2ggIgBB+h0LOzcAAABSU1IgTFNQIHNlcnZlciBub3QgZm91bmQuIFNvbWUgZmVhdHVyZXMgbWF5IGJlIGxpbWl0ZWQuAEG1HgsQDAAAAHJzckNlcnRpZmllZABBxR4LIR0AAABSU1ItQ2VydGlmaWVkIExhbmd1YWdlIFNlcnZlcgBB5h4LFREAAABzaG93U3RhdHVzQmFySXRlbQBB+x4LFxMAAADimIYgUlNSOiBTSUxWRVIgNzUlAEGSHwtNSQAAAFJTUiBDb21wbGlhbmNlOiBzaWx2ZXIgdGllciAoNzUlIHNjb3JlLCBwbGFjZWhvbGRlcikKQ2xpY2sgdG8gdmlldyByZXBvcnQAQd8fCxIOAAAAcnNyLnNob3dSZXBvcnQAQfEfCywoAAAAUlNSLUNlcnRpZmllZCBleHRlbnNpb24gaXMgYWN0aXZhdGluZy4uLgBBnSALFxMAAAByc3IuY2hlY2tDb21wbGlhbmNlAEG0IAsSDgAAAHJzci5pbml0Q29uZmlnAEHGIAsVEQAAAHJzci5nZW5lcmF0ZUJhZGdlAEHbIAsPCwAAAGNoZWNrT25TYXZlAIcBFmFmZmluZXNjcmlwdC5vd25lcnNoaXAPAAAAKwAAAAMAAAAALAAAAAQAAAAAAC0AAAAFAAAAAAAALgAAAAIAAAAvAAAAAAAwAAAAAAAxAAAAAAAyAAAAAAAzAAAAAAA0AAAAAQAANQAAAAEAADYAAAABAAA3AAAAAAA4AAAAAQAAOQAAAAAA"; +const _wasmBase64 = "AGFzbQEAAAAB3wEmYAR/f39/AX9gAn9/AX9gAX8Bf2ADf39/AX9gAAF/YAV/f39/fwF/YAN/f38Bf2AFf39/f38Bf2ACf38Bf2ABfwF/YAF/AX9gAX8Bf2ADf39/AX9gAn9/AX9gAX8Bf2ADf39/AX9gA39/fwF/YAJ/fwF/YAABf2ABfwF/YAF/AX9gBH9/f38Bf2ACf38Bf2ACf38Bf2ACf38Bf2ABfwF/YAJ/fwF/YAABf2AAAX9gAX8Bf2ABfwF/YAF/AX9gAn9/AX9gAn9/AX9gAn9/AX9gAn9/AX9gAn9/AX9gAAF/AvEJLRZ3YXNpX3NuYXBzaG90X3ByZXZpZXcxCGZkX3dyaXRlAAAGVnNjb2RlD3JlZ2lzdGVyQ29tbWFuZAABBlZzY29kZRBnZXRDb25maWd1cmF0aW9uAAIGVnNjb2RlFndvcmtzcGFjZUNvbmZpZ0dldEJvb2wAAwZWc2NvZGUYd29ya3NwYWNlQ29uZmlnR2V0U3RyaW5nAAMGVnNjb2RlEHNob3dFcnJvck1lc3NhZ2UAAgZWc2NvZGUWc2hvd0luZm9ybWF0aW9uTWVzc2FnZQACBlZzY29kZQ5jcmVhdGVUZXJtaW5hbAACBlZzY29kZQx0ZXJtaW5hbFNob3cAAgZWc2NvZGUQdGVybWluYWxTZW5kVGV4dAABBlZzY29kZRBwdXNoU3Vic2NyaXB0aW9uAAEGVnNjb2RlCmNvbnNvbGVMb2cAAgZWc2NvZGUIZXhlY1N5bmMAAgZWc2NvZGUMc3RyaW5nQ29uY2F0AAEGVnNjb2RlDnN0cmluZ0VuZHNXaXRoAAEGVnNjb2RlDXN0cmluZ0lzRW1wdHkAAgZWc2NvZGUYd29ya3NwYWNlRm9sZGVyRmlyc3RQYXRoAAQGVnNjb2RlC3VyaUZyb21QYXRoAAIGVnNjb2RlC3VyaUpvaW5QYXRoAAEGVnNjb2RlC2ZzV3JpdGVGaWxlAAEGVnNjb2RlEG9wZW5UZXh0RG9jdW1lbnQAAgZWc2NvZGUQc2hvd1RleHREb2N1bWVudAACBlZzY29kZRNjcmVhdGVTdGF0dXNCYXJJdGVtAAEGVnNjb2RlFHN0YXR1c0Jhckl0ZW1TZXRUZXh0AAEGVnNjb2RlF3N0YXR1c0Jhckl0ZW1TZXRUb29sdGlwAAEGVnNjb2RlF3N0YXR1c0Jhckl0ZW1TZXRDb21tYW5kAAEGVnNjb2RlEXN0YXR1c0Jhckl0ZW1TaG93AAIGVnNjb2RlGXN0YXR1c0Jhckl0ZW1Bc0Rpc3Bvc2FibGUAAgZWc2NvZGUaY3JlYXRlRGlhZ25vc3RpY0NvbGxlY3Rpb24AAgZWc2NvZGUgZGlhZ25vc3RpY0NvbGxlY3Rpb25Bc0Rpc3Bvc2FibGUAAgZWc2NvZGUSY3JlYXRlV2Vidmlld1BhbmVsAAMGVnNjb2RlE3dlYnZpZXdQYW5lbFNldEh0bWwAAQZWc2NvZGUSY2xpcGJvYXJkV3JpdGVUZXh0AAIGVnNjb2RlFW9uRGlkU2F2ZVRleHREb2N1bWVudAACBlZzY29kZQhwYXRoSm9pbgABBlZzY29kZQxwYXRoQmFzZW5hbWUAAgZWc2NvZGUPcHJvY2Vzc1BsYXRmb3JtAAQGVnNjb2RlFWV4dGVuc2lvbkFic29sdXRlUGF0aAABBlZzY29kZQxodHRwUG9zdEpzb24AAQZWc2NvZGUMdGhlbmFibGVUaGVuAAEGVnNjb2RlEnRoZW5hYmxlUmVzdWx0SnNvbgACBlZzY29kZQlqc29uRmllbGQAARRWc2NvZGVMYW5ndWFnZUNsaWVudBFuZXdMYW5ndWFnZUNsaWVudAAFFFZzY29kZUxhbmd1YWdlQ2xpZW50E2xhbmd1YWdlQ2xpZW50U3RhcnQAAhRWc2NvZGVMYW5ndWFnZUNsaWVudBlsYW5ndWFnZUNsaWVudFNlbmRSZXF1ZXN0AAMDISAGBwgJCgsMDQ4PEBITFBUWGBkbHB0eHyURFxogISIjJAQFAXABCAgFAwEAAQYHAX8BQYAICwciAwZtZW1vcnkCAAhhY3RpdmF0ZQBDCmRlYWN0aXZhdGUARAkOAQBBAAsIRUZHSElKS0wKwA8gEAAgACABEA0gAhANDxpBAAsYACAAIAEQDSACEA0gAxANIAQQDQ8aQQALHAEBfyAAEAchAiACEAgaIAIgARAJGkEADxpBAAtOACAAEA9BAUYEf0EBDxpBAAVBAAsaIABBgBAQKRAPQQBGBH9BAQ8aQQAFQQALGiAAQYsQECkQD0EARgR/QQEPGkEABUEACxpBAA8aQQALRQECfyAAQZQQECkhASABEA9BAEYEfyABDxpBAAVBAAsaIABB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Cw8LAAAAcnNyIGNoZWNrICIAQYIWCwUBAAAAIgBBhxYLDQkAAABSU1IgQ2hlY2sAQZQWCwsHAAAAcnNyIExTUABBnxYLNDAAAAB7ImNvbW1hbmQiOiJyc3IuY2hlY2tDb21wbGlhbmNlIiwiYXJndW1lbnRzIjpbXX0AQdMWCxwYAAAAd29ya3NwYWNlL2V4ZWN1dGVDb21tYW5kAEHvFgsHAwAAAHJzcgBB9hYLDwsAAABib2pFbmRwb2ludABBhRcLOTUAAABodHRwOi8vMTI3LjAuMC4xOjc3MDAvY2FydHJpZGdlL3JlcG9zeXN0ZW0tbWNwL2ludm9rZQBBvhcLDAgAAAByZXBvTmFtZQBByhcLPTkAAAB7InRvb2wiOiJyZXBvc3lzdGVtX3J1bl9hdWRpdCIsImFyZ3VtZW50cyI6eyJyZXBvX25hbWUiOiIAQYcYCwcDAAAAIn19AEGOGAsWEgAAAEJvSiByZXBvc3lzdGVtLW1jcABBpBgLHBgAAABObyB3b3Jrc3BhY2UgZm9sZGVyIG9wZW4AQcAYCxcTAAAAJChzeW5jfnNwaW4pIFJTUuKApgBB1xgLDQkAAAByc3JSZXBvcnQAQeQYCxkVAAAAUlNSIENvbXBsaWFuY2UgUmVwb3J0AEH9GAsOCgAAAHRhcmdldFRpZXIAQYsZCwoGAAAAc2lsdmVyAEGVGQtsaAAAACMgUlNSIChSaG9kaXVtIFN0YW5kYXJkIFJlcG9zaXRvcnkpIENvbmZpZ3VyYXRpb24KIyBodHRwczovL2dpdGh1Yi5jb20vSHlwZXJwb2x5bWF0aC9naXQtcnNyLWNlcnRpZmllZAoKAEGBGgsgHAAAAFtjb21wbGlhbmNlXQp0YXJnZXRfdGllciA9ICIAQaEaCxsXAAAAIgpzdHJpY3RfbW9kZSA9IGZhbHNlCgoAQbwaC7ABrAAAAFtjaGVja3NdCiMgTGljZW5zZSBjb25maWd1cmF0aW9uCmxpY2Vuc2UucmVxdWlyZWQgPSB0cnVlCmxpY2Vuc2UuYWxsb3dlZCA9IFsiTUlUIiwgIkFwYWNoZS0yLjAiLCAiR1BMLTMuMCIsICJCU0QtMy1DbGF1c2UiXQoKIyBSRUFETUUgcmVxdWlyZW1lbnRzCnJlYWRtZS5taW5fbGVuZ3RoID0gMTAwCgoAQewbC4sBhwAAAFtpZ25vcmVdCiMgUGF0aHMgdG8gZXhjbHVkZSBmcm9tIGNvbXBsaWFuY2Ugc2Nhbm5pbmcKcGF0aHMgPSBbCiAgICAidmVuZG9yLyIsCiAgICAidGhpcmRfcGFydHkvIiwKICAgICJub2RlX21vZHVsZXMvIiwKICAgICIuZ2l0LyIsCl0KCgBB9xwLODQAAABbYmFkZ2VzXQpzdHlsZSA9ICJmbGF0LXNxdWFyZSIKaW5jbHVkZV9zY29yZSA9IHRydWUKAEGvHQsNCQAAAC5yc3IudG9tbABBvB0LHRkAAABGYWlsZWQgdG8gd3JpdGUgLnJzci50b21sAEHZHQsoJAAAAFJTUiBjb25maWd1cmF0aW9uIGNyZWF0ZWQ6IC5yc3IudG9tbABBgR4LVlIAAABbIVtSU1IgQ2VydGlmaWVkXShodHRwczovL3Jzci1jZXJ0aWZpZWQuZGV2L2JhZGdlLnN2ZyldKGh0dHBzOi8vcnNyLWNlcnRpZmllZC5kZXYpAEHXHgtOSgAAAFJTUiBiYWRnZSBjb3BpZWQuIFJ1biAiUlNSOiBDaGVjayBDb21wbGlhbmNlIiBmb3IgdGhlIHRpZXItc3BlY2lmaWMgYmFkZ2UuAEGlHwsOCgAAAHNlcnZlclBhdGgAQbMfCwoGAAAAc2VydmVyAEG9HwsLBwAAAHJzci1sc3AAQcgfCw8LAAAAcnNyLWxzcC5leGUAQdcfCwkFAAAAd2luMzIAQeAfCzk1AAAAUlNSLUNlcnRpZmllZCBleHRlbnNpb24gYWN0aXZhdGluZyAoQm9KLWZpcnN0IDMtdGllcikAQZkgCxMPAAAAJCh2ZXJpZmllZCkgUlNSAEGsIAsiHgAAAFJTUiBjb21wbGlhbmNlIOKAlCBydW4gYSBjaGVjawBBziALEg4AAAByc3Iuc2hvd1JlcG9ydABB4CALFREAAABzaG93U3RhdHVzQmFySXRlbQBB9SALCwcAAAB3aGljaCAiAEGAIQsQDAAAAHJzckNlcnRpZmllZABBkCELIR0AAABSU1ItQ2VydGlmaWVkIExhbmd1YWdlIFNlcnZlcgBBsSELFxMAAAByc3IuY2hlY2tDb21wbGlhbmNlAEHIIQsSDgAAAHJzci5pbml0Q29uZmlnAEHaIQsVEQAAAHJzci5nZW5lcmF0ZUJhZGdlAEHvIQsPCwAAAGNoZWNrT25TYXZlANIBFmFmZmluZXNjcmlwdC5vd25lcnNoaXAYAAAALQAAAAMAAAAALgAAAAUAAAAAAAAvAAAAAgAAADAAAAABAAAxAAAAAQAAMgAAAAEAADMAAAADAAAAADQAAAACAAAANQAAAAEAADYAAAADAAAAADcAAAADAAAAADgAAAAAADkAAAABAAA6AAAAAQAAOwAAAAQAAAAAADwAAAACAAAAPQAAAAIAAAA+AAAAAQAAPwAAAAAAQAAAAAAAQQAAAAEAAEIAAAABAABDAAAAAQAARAAAAAAA"; const _wasmBytes = Buffer.from(_wasmBase64, "base64"); // Per-process opaque-handle table for host objects (ExtensionContext, @@ -51,10 +51,10 @@ function _buildImports() { return 0; }, }; - // Phase 2 hook: callers can replace exports.extraImports with a function - // returning a `{ ModuleName: { exportName: fn, ... } }` map of concrete - // host bindings (e.g. the @hyperpolymath/affine-vscode adapter). Default - // is empty so the shim works standalone. + // Phase 2 hook: a caller may install an `extraImports` factory on the + // exports object returning a `{ ModuleName: { exportName: fn, ... } }` + // map of concrete host bindings (this is what the --vscode-extension + // wiring installs). Default is empty so the shim works standalone. const extras = (typeof exports.extraImports === "function") ? exports.extraImports() : {}; diff --git a/rhodium-standard-repositories/satellites/rsr-certifier/extensions/vscode/src/affine-vscode-adapter.cjs b/rhodium-standard-repositories/satellites/rsr-certifier/extensions/vscode/src/affine-vscode-adapter.cjs index e7dca038..5cb0e581 100644 --- a/rhodium-standard-repositories/satellites/rsr-certifier/extensions/vscode/src/affine-vscode-adapter.cjs +++ b/rhodium-standard-repositories/satellites/rsr-certifier/extensions/vscode/src/affine-vscode-adapter.cjs @@ -1,20 +1,30 @@ -// SPDX-License-Identifier: PMPL-1.0-or-later +// SPDX-License-Identifier: MIT // Copyright (c) 2026 Jonathan D.A. Jewell // +// VENDORED from affinescript `packages/affine-vscode/mod.js` (the +// canonical, contract-matched runtime) — kept byte-for-byte except this +// header, so the post-#199 closure-pointer + #205 thenable + #210 +// httpPostJson + #211 jsonField conventions stay in lock-step with the +// compiler. MIT here to match the rsr-certifier subtree (same author). +// Do not hand-edit; re-vendor from affinescript main when bumping. +// // affine-vscode: JS-side adapter for stdlib/Vscode.affine + stdlib/VscodeLanguageClient.affine. // -// Issue #35 Phase 2 deliverable. Plug this into your Node-CJS extension's -// `_extraImports()` so each `extern fn` declared in the bindings resolves -// to the right vscode API call. +// Issue #35 Phase 2 deliverable. Resolves each `extern fn` declared in the +// bindings to the right vscode API call. +// +// Preferred wiring (issue #105): compile with `--vscode-extension` and the +// generated .cjs installs `exports.extraImports` calling this adapter +// automatically — no hand-written entry point. // -// Usage from a hand-written .cjs (until Phase 3 automates the wiring): +// Manual wiring (fallback), from a hand-written .cjs: // -// const vscodeBindings = require("@hyperpolymath/affine-vscode")( +// const shim = require("./extension.cjs"); +// shim.extraImports = () => require("@hyperpolymath/affine-vscode")( // require("vscode"), // require("vscode-languageclient/node"), -// instance, // WebAssembly.Instance, set after instantiate +// shim, // the .cjs shim module (hostShim) // ); -// // Pass vscodeBindings into the Wasm imports map under "env". // // The adapter maintains a per-process JS-side handle table keyed by Int // so opaque handles passed across the FFI boundary survive round-trips. @@ -30,6 +40,8 @@ module.exports = function makeVscodeBindings(vscode, lcModule, hostShim) { const reg = (obj) => hostShim._registerHandle(obj); const get = (h) => hostShim._getHandle(h); const getInstance = () => hostShim._instance; + // Settled host-Thenable values, keyed by Thenable handle (issue #205). + const __thenableResults = new Map(); // ── String marshalling ───────────────────────────────────────────── // AffineScript's WASM 1.0 codegen stores string literals at the offset @@ -44,16 +56,28 @@ module.exports = function makeVscodeBindings(vscode, lcModule, hostShim) { return new TextDecoder("utf-8").decode(bytes); } - // ── Wasm-table callbacks → JS callable ───────────────────────────── - // Wasm function-pointer args (e.g. command handlers) come in as table - // indices. Wrap each in a JS thunk that re-enters the Wasm module. - function wrapHandler(idx) { + // ── Wasm closure callbacks → JS callable ─────────────────────────── + // Post-#199 (function-value callback ABI) a handler arrives as a + // *closure pointer*, not a bare table index: an 8-byte heap pair + // [i32 function_id @ +0][i32 env_ptr @ +4] (codegen.ml). To invoke, + // read the pair from exported memory, look the compiled lambda up in + // __indirect_function_table by function_id, and call it with env_ptr + // as the first argument (the closure calling convention), zero-filling + // any further declared params (e.g. the `Unit` handler arg). + function wrapHandler(closurePtr) { return () => { const inst = getInstance(); - const tbl = inst && inst.exports && inst.exports.__indirect_function_table; + if (!inst || !inst.exports || !inst.exports.memory) return; + const tbl = inst.exports.__indirect_function_table; if (!tbl) return; - const fn = tbl.get(idx); - if (typeof fn === "function") fn(); + const dv = new DataView(inst.exports.memory.buffer); + const fnId = dv.getInt32(closurePtr, true); + const envPtr = dv.getInt32(closurePtr + 4, true); + const fn = tbl.get(fnId); + if (typeof fn !== "function") return; + const args = [envPtr]; + while (args.length < fn.length) args.push(0); + return fn(...args); }; } @@ -63,9 +87,9 @@ module.exports = function makeVscodeBindings(vscode, lcModule, hostShim) { // import map's top-level keys must match. const Vscode = { // ── vscode.commands ────────────────────────────────────────────── - registerCommand: (namePtr, handlerIdx) => { + registerCommand: (namePtr, handlerPtr) => { const name = readString(namePtr); - const handler = wrapHandler(handlerIdx); + const handler = wrapHandler(handlerPtr); const disposable = vscode.commands.registerCommand(name, handler); return reg(disposable); }, @@ -299,8 +323,8 @@ module.exports = function makeVscodeBindings(vscode, lcModule, hostShim) { }, // ── Events ───────────────────────────────────────────────────── - onDidSaveTextDocument: (handlerIdx) => { - const thunk = wrapHandler(handlerIdx); + onDidSaveTextDocument: (handlerPtr) => { + const thunk = wrapHandler(handlerPtr); // The vscode event ships a TextDocument; we deliberately drop it at // the FFI boundary (see Vscode.affine docstring). Handlers that // need the saved file path can call editorActiveFilePath(). @@ -318,6 +342,76 @@ module.exports = function makeVscodeBindings(vscode, lcModule, hostShim) { const ctx = get(ctxHandle); return reg(ctx ? ctx.asAbsolutePath(readString(relPtr)) : ""); }, + + // ── Thenable resolution (issue #205) ─────────────────────────── + // The wasm guest cannot await; these let it observe a settled host + // Thenable. thenableThen registers the guest closure (reusing the + // #199 closure-pointer marshalling via wrapHandler) and stores the + // settled value keyed by the Thenable handle; thenableResultJson + // returns it JSON-encoded (same reg(string) return convention as + // every other `-> String` extern). + thenableThen: (tHandle, onSettlePtr) => { + const thenable = get(tHandle); + const cb = wrapHandler(onSettlePtr); + if (!thenable || typeof thenable.then !== "function") { + return reg({ dispose() {} }); + } + Promise.resolve(thenable).then( + (val) => { __thenableResults.set(tHandle, val); try { cb(); } catch (_e) {} }, + (err) => { + __thenableResults.set(tHandle, { __error: String(err) }); + try { cb(); } catch (_e) {} + } + ); + return reg({ dispose() {} }); + }, + thenableResultJson: (tHandle) => { + if (!__thenableResults.has(tHandle)) return reg(""); + try { return reg(JSON.stringify(__thenableResults.get(tHandle))); } + catch (_e) { return reg(""); } + }, + + // `httpPostJson(url, body_json)` — out-of-process JSON POST for BoJ + // cartridge calls (e.g. boj-server :7700 reposystem_run_audit). Like + // languageClientSendRequest, we register the response Thenable in the + // handle table and let the guest observe it via thenableThen / + // thenableResultJson. Resolves with the parsed JSON body so + // thenableResultJson re-serialises it consistently; a non-JSON or + // failed response settles as { __error } (same shape thenableThen + // uses for rejections), so the guest can branch to its fallback. + httpPostJson: (urlPtr, bodyPtr) => { + const url = readString(urlPtr); + const body = readString(bodyPtr); + const doFetch = (typeof fetch === "function") + ? fetch(url, { + method: "POST", + headers: { "content-type": "application/json" }, + body, + }).then((r) => r.json()) + : Promise.reject(new Error("fetch unavailable")); + return reg(doFetch.catch((err) => ({ __error: String(err) }))); + }, + + // `jsonField(json, key)` — minimal one-level JSON field read for + // guests with no JSON parser. Mirrors thenableResultJson's + // synchronous reg(string) shape; "" on parse failure / non-object / + // missing key (the guest treats "" as absent). Scalars are coerced + // to their string form; objects/arrays are re-serialised so the + // guest can at least detect presence / pass them on. + jsonField: (jsonPtr, keyPtr) => { + const raw = readString(jsonPtr); + const key = readString(keyPtr); + try { + const obj = JSON.parse(raw); + if (obj === null || typeof obj !== "object") return reg(""); + if (!(key in obj)) return reg(""); + const v = obj[key]; + if (v === null || v === undefined) return reg(""); + return reg(typeof v === "object" ? JSON.stringify(v) : String(v)); + } catch (_e) { + return reg(""); + } + }, }; const VscodeLanguageClient = { @@ -342,6 +436,27 @@ module.exports = function makeVscodeBindings(vscode, lcModule, hostShim) { if (c) c.stop(); return 0; }, + // `LanguageClient.sendRequest(method, params)` (issue #103). `params` + // arrives as a JSON string (the binding's synchronous extern shape); + // we parse it, invoke the LSP request, and register the returned + // Thenable in the handle table. The consumer awaits it on the + // source-to-source path (the wasm path additionally needs the + // thenable-resolution primitives — tracked in #199). An empty or + // malformed params string is treated as no params. + languageClientSendRequest: (cHandle, methodPtr, paramsJsonPtr) => { + const c = get(cHandle); + if (!c) return 0; + const method = readString(methodPtr); + const raw = readString(paramsJsonPtr); + let params; + if (raw && raw.length > 0) { + try { params = JSON.parse(raw); } catch (_e) { params = undefined; } + } + const thenable = params === undefined + ? c.sendRequest(method) + : c.sendRequest(method, params); + return reg(thenable); + }, }; return { Vscode, VscodeLanguageClient }; diff --git a/rhodium-standard-repositories/satellites/rsr-certifier/extensions/vscode/src/extension.affine b/rhodium-standard-repositories/satellites/rsr-certifier/extensions/vscode/src/extension.affine index aaccf1b9..63756cd6 100644 --- a/rhodium-standard-repositories/satellites/rsr-certifier/extensions/vscode/src/extension.affine +++ b/rhodium-standard-repositories/satellites/rsr-certifier/extensions/vscode/src/extension.affine @@ -1,14 +1,41 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2026 Jonathan D.A. Jewell // -// RSR-Certified VS Code extension — port of extension.ts to AffineScript. -// Closes the standards side of affinescript#64. Unblock conditions: -// - affinescript#35 (Node-target codegen + Vscode.affine bindings) -// - affinescript#102 (vscode-binding surface expansion for this port) +// RSR-Certified VS Code extension — AffineScript port of extension.ts. +// Closes the standards side of affinescript#64. // -// Source of truth for out/extension.cjs, produced by +// Compliance resolution is THREE-TIER, BoJ-first +// (hyperpolymath/boj-server is the canonical provider for tooling +// surfaces; a standalone rsr LSP / CLI are fallbacks only): +// +// 1. PRIMARY — BoJ `reposystem-mcp` cartridge, tool +// `reposystem_run_audit`, via an in-process JSON POST to +// boj-server (`httpPostJson`, affinescript#210). Endpoint + +// repo name are configurable (`rsr.bojEndpoint`,`rsr.repoName`). +// 2. FALLBACK — the standalone rsr LSP server, via +// `languageClientSendRequest "workspace/executeCommand"` with +// command `rsr.checkCompliance` (the method the server actually +// answers; README's `rsr/getCompliance` is doc drift). Settled +// with the #205 thenable primitives (`thenableThen` #199 closure +// callback + `thenableResultJson`), result fields read with +// `jsonField` (affinescript#211 — the guest has no JSON parser). +// 3. LAST RESORT — `rsr` CLI in a terminal (the always-reachable +// floor; the extension is never worse than CLI-only). +// +// Each tier's result is observed asynchronously: command handlers +// return immediately; the #205 settle callback updates the status bar +// (and, for the report command, the webview) when the host Thenable +// resolves. On error/empty/unusable payload a tier cascades to the +// next; the CLI tier always succeeds at *launching* the check. +// +// NOT wired: `vscode.window.withProgress` — `withProgressNotification` +// is declared in stdlib but has no wasm/Node runtime impl yet, so +// calling it would fault. Progress UI is a tracked follow-up; its +// absence does not affect correctness of the three tiers. +// +// Source of truth for out/extension.cjs: // affinescript compile src/extension.affine -o out/extension.cjs -// The runtime entry the host loads is src/index.cjs (vendored adapter + +// Runtime entry the host loads is src/index.cjs (vendored adapter + // extraImports wiring); package.json `main` points there. use Vscode::{ @@ -17,7 +44,6 @@ use Vscode::{ workspaceConfigGetBool, workspaceConfigGetString, showErrorMessage, - showWarningMessage, showInformationMessage, createTerminal, terminalShow, @@ -27,12 +53,10 @@ use Vscode::{ execSync, stringConcat, stringEndsWith, - stringReplaceSuffix, stringIsEmpty, workspaceFolderFirstPath, uriFromPath, uriJoinPath, - uriPath, fsWriteFile, openTextDocument, showTextDocument, @@ -40,7 +64,6 @@ use Vscode::{ statusBarItemSetText, statusBarItemSetTooltip, statusBarItemSetCommand, - statusBarItemSetBackgroundColorTheme, statusBarItemShow, statusBarItemAsDisposable, createDiagnosticCollection, @@ -50,12 +73,18 @@ use Vscode::{ clipboardWriteText, onDidSaveTextDocument, pathJoin, + pathBasename, processPlatform, - extensionAbsolutePath + extensionAbsolutePath, + httpPostJson, + thenableThen, + thenableResultJson, + jsonField }; use VscodeLanguageClient::{ newLanguageClient, - languageClientStart + languageClientStart, + languageClientSendRequest }; // ── String-building helpers ────────────────────────────────────────── @@ -64,10 +93,6 @@ fn concat3(a: String, b: String, c: String) -> String { return stringConcat(stringConcat(a, b), c); } -fn concat4(a: String, b: String, c: String, d: String) -> String { - return stringConcat(stringConcat(stringConcat(a, b), c), d); -} - fn concat5(a: String, b: String, c: String, d: String, e: String) -> String { return stringConcat(stringConcat(stringConcat(stringConcat(a, b), c), d), e); } @@ -79,31 +104,190 @@ fn run_in_terminal(name: String, cmd: String) -> Int { return 0; } -// ── Command handlers ───────────────────────────────────────────────── +// ── Result interpretation ──────────────────────────────────────────── // -// All four commands shell out to the `rsr` CLI in a fresh terminal. The -// original TS extension had a "preferred" path that used a custom LSP -// request (`rsr/getCompliance`) + `vscode.window.withProgress` for live -// in-process compliance results, with a CLI fallback for the no-LSP -// case. The current AffineScript extern-call ABI is synchronous, so -// Thenable-returning APIs (sendRequest, withProgress) cannot be bound; -// we use the CLI-fallback path unconditionally. The downstream effects -// of the LSP path (status-bar mutation, diagnostic-collection updates) -// are also absent here for the same reason. The status bar still shows -// a static initial value set in activate(); the diagnostic collection -// is created so it can join subscriptions for clean disposal. - -pub fn handler_check_compliance() -> Int { +// Both the BoJ audit payload and the LSP RsrComplianceResult are JSON +// objects; we only ever need a few top-level scalars, read one at a +// time with jsonField (empty string = absent). A payload is an "error" +// if it is empty (not settled / not JSON), carries the httpPostJson +// reject sentinel `__error`, or a server `error` field. + +fn json_is_error(json: String) -> Int { + if stringIsEmpty(json) == 1 { + return 1; + } + if stringIsEmpty(jsonField(json, "__error")) == 0 { + return 1; + } + if stringIsEmpty(jsonField(json, "error")) == 0 { + return 1; + } + return 0; +} + +// Pick the most specific tier label available across both shapes. +fn pick_tier(json: String) -> String { + let t = jsonField(json, "tier"); + if stringIsEmpty(t) == 0 { + return t; + } + let tc = jsonField(json, "tier_code"); + if stringIsEmpty(tc) == 0 { + return tc; + } + return ""; +} + +// Pick a numeric-ish summary: LSP `score`, else BoJ audit count fields. +fn pick_score(json: String) -> String { + let s = jsonField(json, "score"); + if stringIsEmpty(s) == 0 { + return s; + } + let cp = jsonField(json, "checks_passed"); + if stringIsEmpty(cp) == 0 { + return cp; + } + return jsonField(json, "passed"); +} + +// Render a settled payload to the status bar. Returns 1 if the payload +// carried something usable (so the caller knows not to cascade), 0 if +// it was unusable and the next tier should be tried. +fn render_status(sb: StatusBarItem, source: String, json: String) -> Int { + if json_is_error(json) == 1 { + return 0; + } + let tier = pick_tier(json); + let score = pick_score(json); + if stringIsEmpty(tier) == 1 { + if stringIsEmpty(score) == 1 { + return 0; + } + } + let mut tier_txt = tier; + if stringIsEmpty(tier_txt) == 1 { + tier_txt = "RSR"; + } + let head = stringConcat(stringConcat("$(verified) ", tier_txt), " "); + let text = stringConcat(head, score); + let _ = statusBarItemSetText(sb, text); + let _ = statusBarItemSetTooltip(sb, + concat5("RSR compliance via ", source, "\ntier: ", + tier_txt, "\nClick to open the report")); + return 1; +} + +// Build report HTML from a settled payload (honest: shows whatever the +// provider returned, or a neutral "run a check" panel if unavailable). +fn report_html(source: String, json: String) -> String { + let style = ""; + if json_is_error(json) == 1 { + return stringConcat( + stringConcat("", style), + "

RSR Compliance Report

No live result yet. Run RSR: Check Compliance (BoJ \xE2\x86\x92 LSP \xE2\x86\x92 CLI).

"); + } + let tier = pick_tier(json); + let score = pick_score(json); + let rows = concat5( + "

tier: ", tier, "

score: ", + score, "

"); + return concat5( + "", style, + "

RSR Compliance Report

source: ", + source, + stringConcat("

", stringConcat(rows, ""))); +} + +// ── Tier 3: CLI (always-reachable floor) ───────────────────────────── + +fn tier_cli(ws: String) -> Int { + let cmd = stringConcat(stringConcat("rsr check \"", ws), "\""); + return run_in_terminal("RSR Check", cmd); +} + +// ── Tier 2: standalone rsr LSP (dogfoods #199/#205/jsonField) ───────── + +fn settle_lsp(t: Thenable, sb: StatusBarItem, ws: String) -> Int { + let json = thenableResultJson(t); + if render_status(sb, "rsr LSP", json) == 1 { + return 0; + } + return tier_cli(ws); +} + +fn tier_lsp(client: LanguageClient, sb: StatusBarItem, ws: String) -> Int { + let params = "{\"command\":\"rsr.checkCompliance\",\"arguments\":[]}"; + let t = languageClientSendRequest(client, "workspace/executeCommand", params); + let _ = thenableThen(t, fn(u: Unit) => settle_lsp(t, sb, ws)); + return 0; +} + +// ── Tier 1: BoJ reposystem-mcp (PRIMARY, dogfoods httpPostJson) ─────── + +fn boj_endpoint() -> String { + let cfg = getConfiguration("rsr"); + return workspaceConfigGetString(cfg, "bojEndpoint", + "http://127.0.0.1:7700/cartridge/reposystem-mcp/invoke"); +} + +fn boj_repo_name(ws: String) -> String { + let cfg = getConfiguration("rsr"); + let configured = workspaceConfigGetString(cfg, "repoName", ""); + if stringIsEmpty(configured) == 0 { + return configured; + } + return pathBasename(ws); +} + +fn boj_body(repo: String) -> String { + return stringConcat(stringConcat( + "{\"tool\":\"reposystem_run_audit\",\"arguments\":{\"repo_name\":\"", + repo), "\"}}"); +} + +fn settle_boj(t: Thenable, client: LanguageClient, + sb: StatusBarItem, ws: String) -> Int { + let json = thenableResultJson(t); + if render_status(sb, "BoJ reposystem-mcp", json) == 1 { + return 0; + } + return tier_lsp(client, sb, ws); +} + +fn refresh_compliance(client: LanguageClient, + sb: StatusBarItem) -> Int { let ws = workspaceFolderFirstPath(); if stringIsEmpty(ws) == 1 { let _ = showErrorMessage("No workspace folder open"); return 1; } - let cmd = concat3("rsr check \"", ws, "\""); - return run_in_terminal("RSR Check", cmd); + let _ = statusBarItemSetText(sb, "$(sync~spin) RSR\xE2\x80\xA6"); + let repo = boj_repo_name(ws); + let t = httpPostJson(boj_endpoint(), boj_body(repo)); + let _ = thenableThen(t, fn(u: Unit) => settle_boj(t, client, sb, ws)); + return 0; } -pub fn handler_init_config() -> Int { +// ── Report webview ─────────────────────────────────────────────────── + +fn settle_report(t: Thenable, panel: WebviewPanel) -> Int { + let _ = webviewPanelSetHtml(panel, report_html("BoJ reposystem-mcp", + thenableResultJson(t))); + return 0; +} + +fn cmd_show_report(ws: String) -> Int { + let panel = createWebviewPanel("rsrReport", "RSR Compliance Report", 1); + let _ = webviewPanelSetHtml(panel, report_html("", "")); + let t = httpPostJson(boj_endpoint(), boj_body(boj_repo_name(ws))); + let _ = thenableThen(t, fn(u: Unit) => settle_report(t, panel)); + return 0; +} + +// ── .rsr.toml scaffolding (unchanged; the CLI path always worked) ──── + +fn cmd_init_config() -> Int { let ws = workspaceFolderFirstPath(); if stringIsEmpty(ws) == 1 { let _ = showErrorMessage("No workspace folder open"); @@ -112,9 +296,6 @@ pub fn handler_init_config() -> Int { let cfg = getConfiguration("rsr"); let target_tier = workspaceConfigGetString(cfg, "targetTier", "silver"); - // Build the .rsr.toml body. Kept as a single chained string for - // simplicity; if/when AffineScript grows multi-line raw-string - // literals this becomes a heredoc. let line_header = "# RSR (Rhodium Standard Repository) Configuration\n# https://github.com/Hyperpolymath/git-rsr-certified\n\n"; let line_compl_open = "[compliance]\ntarget_tier = \""; let line_compl_close = "\"\nstrict_mode = false\n\n"; @@ -122,7 +303,7 @@ pub fn handler_init_config() -> Int { let line_ignore = "[ignore]\n# Paths to exclude from compliance scanning\npaths = [\n \"vendor/\",\n \"third_party/\",\n \"node_modules/\",\n \".git/\",\n]\n\n"; let line_badges = "[badges]\nstyle = \"flat-square\"\ninclude_score = true\n"; - let body_compl = concat3(line_compl_open, target_tier, line_compl_close); + let body_compl = stringConcat(stringConcat(line_compl_open, target_tier), line_compl_close); let body = concat5(line_header, body_compl, line_checks, line_ignore, line_badges); let ws_uri = uriFromPath(ws); @@ -139,45 +320,18 @@ pub fn handler_init_config() -> Int { return 0; } -/// Returns the constant report HTML. The original TS extension built -/// this dynamically from compliance-check data; in the absence of a -/// sendRequest binding (see top-of-file note) we render a static -/// placeholder with the same shape so users can see the panel works. -fn report_html() -> String { - let head = ""; - let body = "

RSR Compliance Report

\xE2\x98\x86 SILVER
Run RSR: Check Compliance in a terminal for live results.

Checks

Live in-panel results require an async-extern ABI (see affinescript#64). The CLI path via rsr check reports full compliance status.
"; - return stringConcat(head, body); -} - -pub fn handler_show_report() -> Int { - let panel = createWebviewPanel("rsrReport", "RSR Compliance Report", 1); - let _ = webviewPanelSetHtml(panel, report_html()); - return 0; -} - -pub fn handler_generate_badge() -> Int { - let ws = workspaceFolderFirstPath(); - if stringIsEmpty(ws) == 1 { - let _ = showErrorMessage("No workspace folder open"); - return 1; - } - // Hardcoded `silver` placeholder — matches the TS original's TODO - // ("Get actual tier from compliance check"). The real tier comes from - // the LSP sendRequest path which is unavailable in this port. - let badge = "[![RSR Certification](https://rsr-certified.dev/badge/silver.svg)](https://rsr-certified.dev)"; +// Badge: no longer fakes a "silver" tier (the old hardcoded TODO). We +// copy a tier-agnostic badge and tell the user to run a check; the +// tier-specific badge belongs to `rsr generateBadge` on the CLI/LSP. +fn cmd_generate_badge() -> Int { + let badge = "[![RSR Certified](https://rsr-certified.dev/badge.svg)](https://rsr-certified.dev)"; let _ = clipboardWriteText(badge); - let _ = showInformationMessage("Badge markdown copied to clipboard!"); + let _ = showInformationMessage( + "RSR badge copied. Run \"RSR: Check Compliance\" for the tier-specific badge."); return 0; } // ── LSP startup ────────────────────────────────────────────────────── -// -// Resolves the rsr-lsp binary path in this order: -// 1. `rsr.serverPath` config value if non-empty -// 2. bundled at /server/rsr-lsp[.exe] -// then starts the LanguageClient. Probe is `which`-based; failures -// surface through vscode-languageclient's own error UI rather than a -// pre-flight existsSync check (which would require an extra binding). fn resolve_server_path(ctx: ExtensionContext) -> String { let cfg = getConfiguration("rsr"); @@ -186,10 +340,8 @@ fn resolve_server_path(ctx: ExtensionContext) -> String { return configured; } let platform = processPlatform(); - let exe_name = "rsr-lsp"; - let exe_name_win = "rsr-lsp.exe"; - let rel = pathJoin("server", exe_name); - let rel_win = pathJoin("server", exe_name_win); + let rel = pathJoin("server", "rsr-lsp"); + let rel_win = pathJoin("server", "rsr-lsp.exe"); if stringEndsWith(platform, "win32") == 1 { return extensionAbsolutePath(ctx, rel_win); } else { @@ -197,109 +349,67 @@ fn resolve_server_path(ctx: ExtensionContext) -> String { } } -fn start_lsp(ctx: ExtensionContext) -> Int { - let server_path = resolve_server_path(ctx); - let probe_cmd = stringConcat("which \"", stringConcat(server_path, "\"")); - let probe_rc = execSync(probe_cmd); - if probe_rc != 0 { - let _ = showWarningMessage("RSR LSP server not found. Some features may be limited."); - return 1; - } - let client = newLanguageClient( - "rsrCertified", - "RSR-Certified Language Server", - server_path, - "", // no args - 0 // stdio transport - ); - let _ = languageClientStart(client); +// ── On-save (unchanged: the FFI boundary drops the TextDocument arg, +// so we cannot filter by basename; register a no-op so the disposable +// is still tracked and the rsr.checkOnSave flag keeps its shape) ───── + +fn handler_on_save(u: Unit) -> Int { return 0; } -// ── Status bar ─────────────────────────────────────────────────────── -// -// The original TS extension mutated the status-bar text on every -// compliance result (driven by the LSP sendRequest path that we cannot -// bind in the current ABI). Here we set a single initial value at -// activate() time and leave it for the session. Reading the live tier -// requires running the CLI from the command palette. +// ── Lifecycle ──────────────────────────────────────────────────────── + +pub fn activate(ctx: ExtensionContext) -> Int { + let _ = consoleLog("RSR-Certified extension activating (BoJ-first 3-tier)"); -fn setup_status_bar(ctx: ExtensionContext) -> Int { let cfg = getConfiguration("rsr"); - if workspaceConfigGetBool(cfg, "showStatusBarItem", 1) == 0 { - return 0; + + // Status bar (created first so command closures can capture it). + let sb = createStatusBarItem(1, 100); + let _ = statusBarItemSetText(sb, "$(verified) RSR"); + let _ = statusBarItemSetTooltip(sb, "RSR compliance — run a check"); + let _ = statusBarItemSetCommand(sb, "rsr.showReport"); + if workspaceConfigGetBool(cfg, "showStatusBarItem", 1) != 0 { + let _ = statusBarItemShow(sb); } - let item = createStatusBarItem(1, 100); // 1 = StatusBarAlignment.Right - let _ = statusBarItemSetText(item, "\xE2\x98\x86 RSR: SILVER 75%"); - let _ = statusBarItemSetTooltip(item, - "RSR Compliance: silver tier (75% score, placeholder)\nClick to view report"); - let _ = statusBarItemSetCommand(item, "rsr.showReport"); - let _ = statusBarItemSetBackgroundColorTheme(item, ""); // empty = no background - let _ = statusBarItemShow(item); - let _ = pushSubscription(ctx, statusBarItemAsDisposable(item)); - return 0; -} + let _ = pushSubscription(ctx, statusBarItemAsDisposable(sb)); -// ── On-save handler ────────────────────────────────────────────────── -// -// The original filtered saves by basename (README.md, LICENSE, etc.). -// The current Vscode.affine binding drops the TextDocument arg at the -// FFI boundary, so we cannot read the saved doc's path here. As a -// degradation, we either: -// - re-run check on every save (noisy), OR -// - don't register a save handler at all. -// We register a no-op handler that does nothing so the disposable is -// still pushed for clean tracking; users who want check-on-save can -// re-run the command manually. This preserves the `rsr.checkOnSave` -// config flag's *registration* shape without spawning a terminal for -// every keystroke-save. - -pub fn handler_on_save() -> Int { - return 0; -} + // LanguageClient for the fallback tier. Created unconditionally so + // the cascade always has a handle; if the binary is absent the + // sendRequest simply rejects and settle_lsp cascades to the CLI. + let server_path = resolve_server_path(ctx); + let probe = execSync(stringConcat(stringConcat("which \"", server_path), "\"")); + let client = newLanguageClient( + "rsrCertified", "RSR-Certified Language Server", + server_path, "", 0); + if probe == 0 { + let _ = languageClientStart(client); + } -// ── Lifecycle ──────────────────────────────────────────────────────── + // Commands — #199 function-value ABI: thin capturing closures over + // sb / client; the named helpers carry the logic. + let _ = pushSubscription(ctx, + registerCommand("rsr.checkCompliance", + fn(u: Unit) => refresh_compliance(client, sb))); + let _ = pushSubscription(ctx, + registerCommand("rsr.initConfig", fn(u: Unit) => cmd_init_config())); + let _ = pushSubscription(ctx, + registerCommand("rsr.showReport", + fn(u: Unit) => cmd_show_report(workspaceFolderFirstPath()))); + let _ = pushSubscription(ctx, + registerCommand("rsr.generateBadge", fn(u: Unit) => cmd_generate_badge())); -pub fn activate(ctx: ExtensionContext) -> Int { - let _ = consoleLog("RSR-Certified extension is activating..."); - - let _ = setup_status_bar(ctx); - - // Register commands. Wasm-table indices must match the order command - // handler functions appear in this file: - // 0 handler_check_compliance - // 1 handler_init_config - // 2 handler_show_report - // 3 handler_generate_badge - // 4 handler_on_save - let _ = pushSubscription(ctx, registerCommand("rsr.checkCompliance", 0)); - let _ = pushSubscription(ctx, registerCommand("rsr.initConfig", 1)); - let _ = pushSubscription(ctx, registerCommand("rsr.showReport", 2)); - let _ = pushSubscription(ctx, registerCommand("rsr.generateBadge", 3)); - - // Diagnostic collection — created so it can join subscriptions for - // disposal-on-deactivate. Empty by design (population would require - // the sendRequest path). let dc = createDiagnosticCollection("rsr"); let _ = pushSubscription(ctx, diagnosticCollectionAsDisposable(dc)); - let _ = start_lsp(ctx); - - // Honour the rsr.checkOnSave config flag's registration shape (no-op - // handler under the current ABI — see handler_on_save docstring). - let cfg = getConfiguration("rsr"); if workspaceConfigGetBool(cfg, "checkOnSave", 1) != 0 { - let _ = pushSubscription(ctx, onDidSaveTextDocument(4)); + let _ = pushSubscription(ctx, + onDidSaveTextDocument(fn(u: Unit) => handler_on_save(u))); } return 0; } pub fn deactivate() -> Int { - // LanguageClient handle is not retained across activations under the - // current binding shape; the host process exit closes the server - // pipe. Status-bar item, diagnostic collection, webview panels, and - // command disposables are all pushed to ctx.subscriptions and - // released by the host. return 0; }