@polderlabs/bizar 10.1.0 → 10.2.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/README.md +0 -2
- package/bizar-dash/dist/assets/{ActivityFeed-Da-JCrE6.js → ActivityFeed-JjmOY2AQ.js} +1 -1
- package/bizar-dash/dist/assets/{ActivityFeed-Da-JCrE6.js.map → ActivityFeed-JjmOY2AQ.js.map} +1 -1
- package/bizar-dash/dist/assets/{ActivityView-nIRsSETb.js → ActivityView-BIIMvGWo.js} +1 -1
- package/bizar-dash/dist/assets/{ActivityView-nIRsSETb.js.map → ActivityView-BIIMvGWo.js.map} +1 -1
- package/bizar-dash/dist/assets/{AdminView-DP1NMxmG.js → AdminView-DRidJtkq.js} +1 -1
- package/bizar-dash/dist/assets/{AdminView-DP1NMxmG.js.map → AdminView-DRidJtkq.js.map} +1 -1
- package/bizar-dash/dist/assets/{AgentActivity-lSPkOr-O.js → AgentActivity-79l4SBMC.js} +1 -1
- package/bizar-dash/dist/assets/{AgentActivity-lSPkOr-O.js.map → AgentActivity-79l4SBMC.js.map} +1 -1
- package/bizar-dash/dist/assets/{AgentsView-BIWGkz7w.js → AgentsView-0vT4uPSc.js} +1 -1
- package/bizar-dash/dist/assets/{AgentsView-BIWGkz7w.js.map → AgentsView-0vT4uPSc.js.map} +1 -1
- package/bizar-dash/dist/assets/{Alert-DN0UysMg.js → Alert-CG5q_s75.js} +1 -1
- package/bizar-dash/dist/assets/{Alert-DN0UysMg.js.map → Alert-CG5q_s75.js.map} +1 -1
- package/bizar-dash/dist/assets/ArtifactsView-DM8ZmpR9.js +21 -0
- package/bizar-dash/dist/assets/ArtifactsView-DM8ZmpR9.js.map +1 -0
- package/bizar-dash/dist/assets/{AuthView-DW5cs7ma.js → AuthView-BveN94MC.js} +1 -1
- package/bizar-dash/dist/assets/{AuthView-DW5cs7ma.js.map → AuthView-BveN94MC.js.map} +1 -1
- package/bizar-dash/dist/assets/{Avatar-DZPv9pvd.js → Avatar-ulUPIYTd.js} +1 -1
- package/bizar-dash/dist/assets/{Avatar-DZPv9pvd.js.map → Avatar-ulUPIYTd.js.map} +1 -1
- package/bizar-dash/dist/assets/{BackgroundJobsView-D8O-EyMD.js → BackgroundJobsView-BWErhqFo.js} +1 -1
- package/bizar-dash/dist/assets/{BackgroundJobsView-D8O-EyMD.js.map → BackgroundJobsView-BWErhqFo.js.map} +1 -1
- package/bizar-dash/dist/assets/{BackupView-uwXkhxOV.js → BackupView-CnBjzG9N.js} +1 -1
- package/bizar-dash/dist/assets/{BackupView-uwXkhxOV.js.map → BackupView-CnBjzG9N.js.map} +1 -1
- package/bizar-dash/dist/assets/{Card-Tyboihu7.js → Card-B0imhxMk.js} +1 -1
- package/bizar-dash/dist/assets/{Card-Tyboihu7.js.map → Card-B0imhxMk.js.map} +1 -1
- package/bizar-dash/dist/assets/{ChatView-DRBHzgay.js → ChatView-D6zGytzx.js} +1 -1
- package/bizar-dash/dist/assets/{ChatView-DRBHzgay.js.map → ChatView-D6zGytzx.js.map} +1 -1
- package/bizar-dash/dist/assets/{Chip-BrXIlR9w.js → Chip-wwZSGwLE.js} +1 -1
- package/bizar-dash/dist/assets/{Chip-BrXIlR9w.js.map → Chip-wwZSGwLE.js.map} +1 -1
- package/bizar-dash/dist/assets/{ClaudeSessionsView-Ck62jaOr.js → ClaudeSessionsView-DK1sOLhi.js} +1 -1
- package/bizar-dash/dist/assets/{ClaudeSessionsView-Ck62jaOr.js.map → ClaudeSessionsView-DK1sOLhi.js.map} +1 -1
- package/bizar-dash/dist/assets/{ClipboardView-DfND6xL6.js → ClipboardView-CZ2lYPr0.js} +1 -1
- package/bizar-dash/dist/assets/{ClipboardView-DfND6xL6.js.map → ClipboardView-CZ2lYPr0.js.map} +1 -1
- package/bizar-dash/dist/assets/{ConfigView-DjJmSrOJ.js → ConfigView-D-zT9eNA.js} +1 -1
- package/bizar-dash/dist/assets/{ConfigView-DjJmSrOJ.js.map → ConfigView-D-zT9eNA.js.map} +1 -1
- package/bizar-dash/dist/assets/{DiagnosticsView-CKgItf0J.js → DiagnosticsView-BocV68Lb.js} +1 -1
- package/bizar-dash/dist/assets/{DiagnosticsView-CKgItf0J.js.map → DiagnosticsView-BocV68Lb.js.map} +1 -1
- package/bizar-dash/dist/assets/{DialogsView-DHYF8dE1.js → DialogsView-BVjPI5x7.js} +1 -1
- package/bizar-dash/dist/assets/{DialogsView-DHYF8dE1.js.map → DialogsView-BVjPI5x7.js.map} +1 -1
- package/bizar-dash/dist/assets/{DoctorView-DDLHrSKk.js → DoctorView-84gJcZwM.js} +1 -1
- package/bizar-dash/dist/assets/{DoctorView-DDLHrSKk.js.map → DoctorView-84gJcZwM.js.map} +1 -1
- package/bizar-dash/dist/assets/{EmptyState-DZ2ouKwd.js → EmptyState-BjgVOrcO.js} +1 -1
- package/bizar-dash/dist/assets/{EmptyState-DZ2ouKwd.js.map → EmptyState-BjgVOrcO.js.map} +1 -1
- package/bizar-dash/dist/assets/{EnvVarsView-D1cx83mB.js → EnvVarsView-BJ2TEzF7.js} +1 -1
- package/bizar-dash/dist/assets/{EnvVarsView-D1cx83mB.js.map → EnvVarsView-BJ2TEzF7.js.map} +1 -1
- package/bizar-dash/dist/assets/{ErrorState-DwwJ2-Jq.js → ErrorState-BE3WBFqF.js} +1 -1
- package/bizar-dash/dist/assets/{ErrorState-DwwJ2-Jq.js.map → ErrorState-BE3WBFqF.js.map} +1 -1
- package/bizar-dash/dist/assets/{EvalView-C9aYixXW.js → EvalView-BJJ2P-kK.js} +1 -1
- package/bizar-dash/dist/assets/{EvalView-C9aYixXW.js.map → EvalView-BJJ2P-kK.js.map} +1 -1
- package/bizar-dash/dist/assets/{GoalsView-DZa_mGmh.js → GoalsView-pwROHApi.js} +1 -1
- package/bizar-dash/dist/assets/{GoalsView-DZa_mGmh.js.map → GoalsView-pwROHApi.js.map} +1 -1
- package/bizar-dash/dist/assets/{Grid-CiRyhAgR.js → Grid-qcvbhx6e.js} +1 -1
- package/bizar-dash/dist/assets/{Grid-CiRyhAgR.js.map → Grid-qcvbhx6e.js.map} +1 -1
- package/bizar-dash/dist/assets/{HeadroomView-CNc-TI7u.js → HeadroomView-D1UiTYbt.js} +1 -1
- package/bizar-dash/dist/assets/{HeadroomView-CNc-TI7u.js.map → HeadroomView-D1UiTYbt.js.map} +1 -1
- package/bizar-dash/dist/assets/{HistoryView-Dv7aPxJE.js → HistoryView-C7SSOLJA.js} +1 -1
- package/bizar-dash/dist/assets/{HistoryView-Dv7aPxJE.js.map → HistoryView-C7SSOLJA.js.map} +1 -1
- package/bizar-dash/dist/assets/{Input-BbFUDQnS.js → Input-Bgzxf_Hu.js} +1 -1
- package/bizar-dash/dist/assets/{Input-BbFUDQnS.js.map → Input-Bgzxf_Hu.js.map} +1 -1
- package/bizar-dash/dist/assets/{LibrariesView-DBQ83TNa.js → LibrariesView-CbFFksox.js} +1 -1
- package/bizar-dash/dist/assets/{LibrariesView-DBQ83TNa.js.map → LibrariesView-CbFFksox.js.map} +1 -1
- package/bizar-dash/dist/assets/{LightRAGView-JYpE5u9p.js → LightRAGView-BqDrJbRq.js} +1 -1
- package/bizar-dash/dist/assets/{LightRAGView-JYpE5u9p.js.map → LightRAGView-BqDrJbRq.js.map} +1 -1
- package/bizar-dash/dist/assets/{ListHeader-BGjm11Z0.js → ListHeader-TjPJrUfY.js} +1 -1
- package/bizar-dash/dist/assets/{ListHeader-BGjm11Z0.js.map → ListHeader-TjPJrUfY.js.map} +1 -1
- package/bizar-dash/dist/assets/{MemoryView-CnOP2MvM.js → MemoryView-BlTeHLhX.js} +1 -1
- package/bizar-dash/dist/assets/{MemoryView-CnOP2MvM.js.map → MemoryView-BlTeHLhX.js.map} +1 -1
- package/bizar-dash/dist/assets/{MiscView-BBcpUbZZ.js → MiscView-B8QY82Pm.js} +1 -1
- package/bizar-dash/dist/assets/{MiscView-BBcpUbZZ.js.map → MiscView-B8QY82Pm.js.map} +1 -1
- package/bizar-dash/dist/assets/{ModsView-CU4cAmIz.js → ModsView-CvW1OWts.js} +1 -1
- package/bizar-dash/dist/assets/{ModsView-CU4cAmIz.js.map → ModsView-CvW1OWts.js.map} +1 -1
- package/bizar-dash/dist/assets/{NotificationsView-BYffvthq.js → NotificationsView-CsBt5U7x.js} +1 -1
- package/bizar-dash/dist/assets/{NotificationsView-BYffvthq.js.map → NotificationsView-CsBt5U7x.js.map} +1 -1
- package/bizar-dash/dist/assets/{ObsidianView-D3bI0row.js → ObsidianView-BCTejEj2.js} +1 -1
- package/bizar-dash/dist/assets/{ObsidianView-D3bI0row.js.map → ObsidianView-BCTejEj2.js.map} +1 -1
- package/bizar-dash/dist/assets/{OverviewView-0tq8f-Y0.js → OverviewView-BsuPxwnB.js} +1 -1
- package/bizar-dash/dist/assets/{OverviewView-0tq8f-Y0.js.map → OverviewView-BsuPxwnB.js.map} +1 -1
- package/bizar-dash/dist/assets/{ProgressBar-B-h60LKA.js → ProgressBar-DlZTETEn.js} +1 -1
- package/bizar-dash/dist/assets/{ProgressBar-B-h60LKA.js.map → ProgressBar-DlZTETEn.js.map} +1 -1
- package/bizar-dash/dist/assets/{ProjectsView-DhX5lzHS.js → ProjectsView-Soxtmpet.js} +1 -1
- package/bizar-dash/dist/assets/{ProjectsView-DhX5lzHS.js.map → ProjectsView-Soxtmpet.js.map} +1 -1
- package/bizar-dash/dist/assets/{ProvidersView-CJMX7jm9.js → ProvidersView-DaCEV9pM.js} +1 -1
- package/bizar-dash/dist/assets/{ProvidersView-CJMX7jm9.js.map → ProvidersView-DaCEV9pM.js.map} +1 -1
- package/bizar-dash/dist/assets/{SchedulesView-CcwDQ9Nm.js → SchedulesView-DY2ByBj_.js} +1 -1
- package/bizar-dash/dist/assets/{SchedulesView-CcwDQ9Nm.js.map → SchedulesView-DY2ByBj_.js.map} +1 -1
- package/bizar-dash/dist/assets/{Select-Cw7Qtz2M.js → Select-Dbd88_n8.js} +1 -1
- package/bizar-dash/dist/assets/{Select-Cw7Qtz2M.js.map → Select-Dbd88_n8.js.map} +1 -1
- package/bizar-dash/dist/assets/{SettingsRow-DgsgbPSI.js → SettingsRow-CLMNd434.js} +1 -1
- package/bizar-dash/dist/assets/{SettingsRow-DgsgbPSI.js.map → SettingsRow-CLMNd434.js.map} +1 -1
- package/bizar-dash/dist/assets/{SettingsView-DTAFdKyK.js → SettingsView-b24oFq9x.js} +1 -1
- package/bizar-dash/dist/assets/{SettingsView-DTAFdKyK.js.map → SettingsView-b24oFq9x.js.map} +1 -1
- package/bizar-dash/dist/assets/{Sheet-DH-WNJ3Y.js → Sheet-DnDid58j.js} +1 -1
- package/bizar-dash/dist/assets/{Sheet-DH-WNJ3Y.js.map → Sheet-DnDid58j.js.map} +1 -1
- package/bizar-dash/dist/assets/{Slider-DXbJuQ7y.js → Slider-BJCvKI1I.js} +1 -1
- package/bizar-dash/dist/assets/{Slider-DXbJuQ7y.js.map → Slider-BJCvKI1I.js.map} +1 -1
- package/bizar-dash/dist/assets/{Sparkline-CYtAn82j.js → Sparkline-ktqQLdyb.js} +1 -1
- package/bizar-dash/dist/assets/{Sparkline-CYtAn82j.js.map → Sparkline-ktqQLdyb.js.map} +1 -1
- package/bizar-dash/dist/assets/{Switch-BItGrPqr.js → Switch-CwXfsDii.js} +1 -1
- package/bizar-dash/dist/assets/{Switch-BItGrPqr.js.map → Switch-CwXfsDii.js.map} +1 -1
- package/bizar-dash/dist/assets/{TasksView-i5tm6CkX.js → TasksView-BHmpzFCf.js} +1 -1
- package/bizar-dash/dist/assets/{TasksView-i5tm6CkX.js.map → TasksView-BHmpzFCf.js.map} +1 -1
- package/bizar-dash/dist/assets/{Textarea-qYDf3yds.js → Textarea-BzIAwSlp.js} +1 -1
- package/bizar-dash/dist/assets/{Textarea-qYDf3yds.js.map → Textarea-BzIAwSlp.js.map} +1 -1
- package/bizar-dash/dist/assets/{UpdateView-Xln-ej26.js → UpdateView-CSmX9NN0.js} +1 -1
- package/bizar-dash/dist/assets/{UpdateView-Xln-ej26.js.map → UpdateView-CSmX9NN0.js.map} +1 -1
- package/bizar-dash/dist/assets/{UsageView-BGuZSUZ6.js → UsageView-COyr_Gfi.js} +1 -1
- package/bizar-dash/dist/assets/{UsageView-BGuZSUZ6.js.map → UsageView-COyr_Gfi.js.map} +1 -1
- package/bizar-dash/dist/assets/{ViewHeader-BqIPVKuG.js → ViewHeader-BzYZ3m-e.js} +1 -1
- package/bizar-dash/dist/assets/{ViewHeader-BqIPVKuG.js.map → ViewHeader-BzYZ3m-e.js.map} +1 -1
- package/bizar-dash/dist/assets/{VoiceView-CWUOCQs_.js → VoiceView-D33_xG5t.js} +1 -1
- package/bizar-dash/dist/assets/{VoiceView-CWUOCQs_.js.map → VoiceView-D33_xG5t.js.map} +1 -1
- package/bizar-dash/dist/assets/{brain-fsBSDl2g.js → brain-CM-Vls8r.js} +1 -1
- package/bizar-dash/dist/assets/{brain-fsBSDl2g.js.map → brain-CM-Vls8r.js.map} +1 -1
- package/bizar-dash/dist/assets/{calendar-DsHTZoCU.js → calendar-C0lgZk69.js} +1 -1
- package/bizar-dash/dist/assets/{calendar-DsHTZoCU.js.map → calendar-C0lgZk69.js.map} +1 -1
- package/bizar-dash/dist/assets/{circle-CqSxqqeJ.js → circle-Bx_hQEBC.js} +1 -1
- package/bizar-dash/dist/assets/{circle-CqSxqqeJ.js.map → circle-Bx_hQEBC.js.map} +1 -1
- package/bizar-dash/dist/assets/{circle-alert-BsLl001l.js → circle-alert-DHvsqpw5.js} +1 -1
- package/bizar-dash/dist/assets/{circle-alert-BsLl001l.js.map → circle-alert-DHvsqpw5.js.map} +1 -1
- package/bizar-dash/dist/assets/{circle-check-rVKMQmZp.js → circle-check-Bx8wmXR9.js} +1 -1
- package/bizar-dash/dist/assets/{circle-check-rVKMQmZp.js.map → circle-check-Bx8wmXR9.js.map} +1 -1
- package/bizar-dash/dist/assets/{clock-OuficfgH.js → clock-CejGoecX.js} +1 -1
- package/bizar-dash/dist/assets/{clock-OuficfgH.js.map → clock-CejGoecX.js.map} +1 -1
- package/bizar-dash/dist/assets/{download-BdHlstUY.js → download-4cEUWue1.js} +1 -1
- package/bizar-dash/dist/assets/{download-BdHlstUY.js.map → download-4cEUWue1.js.map} +1 -1
- package/bizar-dash/dist/assets/{external-link-DA3_VGm-.js → external-link-0NqUhI4i.js} +1 -1
- package/bizar-dash/dist/assets/{external-link-DA3_VGm-.js.map → external-link-0NqUhI4i.js.map} +1 -1
- package/bizar-dash/dist/assets/{file-text-B-g8H227.js → file-text-yEMkrSoZ.js} +1 -1
- package/bizar-dash/dist/assets/{file-text-B-g8H227.js.map → file-text-yEMkrSoZ.js.map} +1 -1
- package/bizar-dash/dist/assets/{filter-D9B8s5CG.js → filter-BXt60zAS.js} +1 -1
- package/bizar-dash/dist/assets/{filter-D9B8s5CG.js.map → filter-BXt60zAS.js.map} +1 -1
- package/bizar-dash/dist/assets/{git-pull-request-ByH0YuVz.js → git-pull-request-BjG5lNtR.js} +1 -1
- package/bizar-dash/dist/assets/{git-pull-request-ByH0YuVz.js.map → git-pull-request-BjG5lNtR.js.map} +1 -1
- package/bizar-dash/dist/assets/{main-CdOm1Pj9.js → main-CokHAXWM.js} +2 -2
- package/bizar-dash/dist/assets/{main-CdOm1Pj9.js.map → main-CokHAXWM.js.map} +1 -1
- package/bizar-dash/dist/assets/{pause-C9hgd8wX.js → pause-CAmulU0F.js} +1 -1
- package/bizar-dash/dist/assets/{pause-C9hgd8wX.js.map → pause-CAmulU0F.js.map} +1 -1
- package/bizar-dash/dist/assets/{pencil-BanqaMw7.js → pencil-eq5IqBmU.js} +1 -1
- package/bizar-dash/dist/assets/{pencil-BanqaMw7.js.map → pencil-eq5IqBmU.js.map} +1 -1
- package/bizar-dash/dist/assets/{play-CawUZ02_.js → play-BoLrrcBA.js} +1 -1
- package/bizar-dash/dist/assets/{play-CawUZ02_.js.map → play-BoLrrcBA.js.map} +1 -1
- package/bizar-dash/dist/assets/{plug-Cmv0-T_e.js → plug-DBrVM9lK.js} +1 -1
- package/bizar-dash/dist/assets/{plug-Cmv0-T_e.js.map → plug-DBrVM9lK.js.map} +1 -1
- package/bizar-dash/dist/assets/{plus-M6LYiKX8.js → plus-DENm0hqV.js} +1 -1
- package/bizar-dash/dist/assets/{plus-M6LYiKX8.js.map → plus-DENm0hqV.js.map} +1 -1
- package/bizar-dash/dist/assets/{power-DP5x_SRF.js → power-DLjXF7zE.js} +1 -1
- package/bizar-dash/dist/assets/{power-DP5x_SRF.js.map → power-DLjXF7zE.js.map} +1 -1
- package/bizar-dash/dist/assets/{power-off-NOJRLAIs.js → power-off-D47cOnG_.js} +1 -1
- package/bizar-dash/dist/assets/{power-off-NOJRLAIs.js.map → power-off-D47cOnG_.js.map} +1 -1
- package/bizar-dash/dist/assets/{refresh-ccw-CHMprsm5.js → refresh-ccw-DNrXX9gB.js} +1 -1
- package/bizar-dash/dist/assets/{refresh-ccw-CHMprsm5.js.map → refresh-ccw-DNrXX9gB.js.map} +1 -1
- package/bizar-dash/dist/assets/{refresh-cw-1RBqDKgJ.js → refresh-cw-CoAHDpeS.js} +1 -1
- package/bizar-dash/dist/assets/{refresh-cw-1RBqDKgJ.js.map → refresh-cw-CoAHDpeS.js.map} +1 -1
- package/bizar-dash/dist/assets/{rotate-ccw-DJRekBbi.js → rotate-ccw-ObX5awaG.js} +1 -1
- package/bizar-dash/dist/assets/{rotate-ccw-DJRekBbi.js.map → rotate-ccw-ObX5awaG.js.map} +1 -1
- package/bizar-dash/dist/assets/{rotate-cw-DHooc0Tx.js → rotate-cw-xpPJw9LP.js} +1 -1
- package/bizar-dash/dist/assets/{rotate-cw-DHooc0Tx.js.map → rotate-cw-xpPJw9LP.js.map} +1 -1
- package/bizar-dash/dist/assets/{save-DQKCKyZK.js → save-D1iKiJJl.js} +1 -1
- package/bizar-dash/dist/assets/{save-DQKCKyZK.js.map → save-D1iKiJJl.js.map} +1 -1
- package/bizar-dash/dist/assets/{send-xgy3RAi7.js → send-BRcAhZUm.js} +1 -1
- package/bizar-dash/dist/assets/{send-xgy3RAi7.js.map → send-BRcAhZUm.js.map} +1 -1
- package/bizar-dash/dist/assets/{shield-alert-CWV2F5ab.js → shield-alert-C6vEcK-g.js} +1 -1
- package/bizar-dash/dist/assets/{shield-alert-CWV2F5ab.js.map → shield-alert-C6vEcK-g.js.map} +1 -1
- package/bizar-dash/dist/assets/{trash-2-CL4khSRv.js → trash-2-DZDjEnNh.js} +1 -1
- package/bizar-dash/dist/assets/{trash-2-CL4khSRv.js.map → trash-2-DZDjEnNh.js.map} +1 -1
- package/bizar-dash/dist/assets/{trending-up-CSJJhWTS.js → trending-up-DMj-AJVf.js} +1 -1
- package/bizar-dash/dist/assets/{trending-up-CSJJhWTS.js.map → trending-up-DMj-AJVf.js.map} +1 -1
- package/bizar-dash/dist/assets/{triangle-alert-BPLb7uLt.js → triangle-alert-_2xe_tl4.js} +1 -1
- package/bizar-dash/dist/assets/{triangle-alert-BPLb7uLt.js.map → triangle-alert-_2xe_tl4.js.map} +1 -1
- package/bizar-dash/dist/assets/{upload-Cww6MvFq.js → upload-DdDljAeV.js} +1 -1
- package/bizar-dash/dist/assets/{upload-Cww6MvFq.js.map → upload-DdDljAeV.js.map} +1 -1
- package/bizar-dash/dist/assets/{zap-DmhVUzRA.js → zap-mHsgh0FP.js} +1 -1
- package/bizar-dash/dist/assets/{zap-DmhVUzRA.js.map → zap-mHsgh0FP.js.map} +1 -1
- package/bizar-dash/dist/index.html +1 -1
- package/bizar-dash/skills/headroom/SKILL.md +94 -0
- package/bizar-dash/src/server/api.mjs +11 -0
- package/bizar-dash/src/server/artifacts-store.mjs +120 -14
- package/bizar-dash/src/server/claude-artifacts.mjs +281 -0
- package/bizar-dash/src/server/headroom.mjs +649 -0
- package/bizar-dash/src/server/routes/artifacts.mjs +42 -6
- package/bizar-dash/src/server/routes/audit.mjs +78 -0
- package/bizar-dash/src/server/routes/decisions.mjs +78 -0
- package/bizar-dash/src/server/routes/headroom.mjs +126 -0
- package/bizar-dash/src/server/routes/model-router.mjs +55 -0
- package/bizar-dash/src/server/routes/model-router.test.mjs +28 -0
- package/bizar-dash/src/server/routes/sessions.mjs +80 -0
- package/bizar-dash/src/server/sprint.test.mjs +163 -0
- package/bizar-dash/src/web/v8/views/Artifacts/ArtifactsView.tsx +232 -21
- package/bizar-dash/src/web/v8/views/Artifacts/ClaudeArtifactView.tsx +179 -0
- package/bizar-dash/tests/headroom-install.test.mjs +173 -0
- package/bizar-dash/tests/headroom-settings.test.mjs +126 -0
- package/bizar-dash/tests/headroom-status.test.mjs +117 -0
- package/cli/commands/headroom.mjs +204 -0
- package/package.json +1 -1
- package/packages/sdk/dist/agent/cron.d.ts +44 -0
- package/packages/sdk/dist/agent/cron.js +79 -0
- package/packages/sdk/dist/agent/heartbeat.d.ts +43 -0
- package/packages/sdk/dist/agent/heartbeat.js +116 -0
- package/packages/sdk/dist/index.d.ts +3 -0
- package/packages/sdk/dist/index.js +5 -0
- package/packages/sdk/dist/learning/decisions.d.ts +46 -0
- package/packages/sdk/dist/learning/decisions.js +132 -0
- package/packages/sdk/dist/learning/index.d.ts +8 -0
- package/packages/sdk/dist/learning/index.js +8 -0
- package/packages/sdk/dist/learning/instincts.d.ts +38 -0
- package/packages/sdk/dist/learning/instincts.js +139 -0
- package/packages/sdk/dist/mcp/server.js +119 -1
- package/packages/sdk/dist/router/agent-model-registry.d.ts +89 -0
- package/packages/sdk/dist/router/agent-model-registry.js +144 -0
- package/packages/sdk/dist/router/index.d.ts +1 -0
- package/packages/sdk/dist/router/index.js +1 -0
- package/packages/sdk/package.json +3 -2
- package/scripts/audit.mjs +388 -0
- package/scripts/audit.test.mjs +149 -0
- package/scripts/eval-gate.mjs +134 -0
- package/scripts/eval-gate.test.mjs +138 -0
- package/scripts/feature-state-machine.mjs +178 -0
- package/scripts/feature-state-machine.test.mjs +135 -0
- package/scripts/spec-dedup.test.mjs +124 -0
- package/scripts/sprint.mjs +213 -0
- package/scripts/worktree-setup.sh +79 -0
- package/bizar-dash/dist/assets/ArtifactsView-CDIhPBAx.js +0 -6
- package/bizar-dash/dist/assets/ArtifactsView-CDIhPBAx.js.map +0 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scripts/audit.test.mjs
|
|
3
|
+
*
|
|
4
|
+
* Pillar B — Tests for scripts/audit.mjs
|
|
5
|
+
* 5 test groups: each dimension scoring, total score, missing-state tolerance, evidence captured.
|
|
6
|
+
*/
|
|
7
|
+
import { test } from 'node:test';
|
|
8
|
+
import assert from 'node:assert/strict';
|
|
9
|
+
import { execSync } from 'node:child_process';
|
|
10
|
+
import { existsSync, readFileSync, rmSync, renameSync } from 'node:fs';
|
|
11
|
+
import { join, resolve } from 'node:path';
|
|
12
|
+
|
|
13
|
+
const PROJECT_ROOT = resolve(import.meta.filename, '..', '..');
|
|
14
|
+
const AUDIT_SCRIPT = join(PROJECT_ROOT, 'scripts', 'audit.mjs');
|
|
15
|
+
|
|
16
|
+
// Load the module as a script (audit.mjs writes to stdout, so we invoke it)
|
|
17
|
+
// Always runs from PROJECT_ROOT so that ROOT resolution is correct.
|
|
18
|
+
function runAudit() {
|
|
19
|
+
try {
|
|
20
|
+
const stdout = execSync(`node "${AUDIT_SCRIPT}" 2>&1`, {
|
|
21
|
+
cwd: PROJECT_ROOT,
|
|
22
|
+
encoding: 'utf8',
|
|
23
|
+
});
|
|
24
|
+
return { stdout, stderr: '', status: 0 };
|
|
25
|
+
} catch (err) {
|
|
26
|
+
return { stdout: err.stdout ?? '', stderr: err.stderr ?? '', status: err.status ?? 1 };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseAuditOutput(stdout) {
|
|
31
|
+
try {
|
|
32
|
+
if (!stdout || typeof stdout !== 'string') return null;
|
|
33
|
+
// Try last non-empty line first (handles single-line JSON)
|
|
34
|
+
const trimmed = stdout.trim();
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(trimmed);
|
|
37
|
+
} catch {
|
|
38
|
+
// Multi-line JSON: find the last line that starts a valid object/array
|
|
39
|
+
const lines = trimmed.split('\n');
|
|
40
|
+
// Find the first '{' or '[' and parse from there to end
|
|
41
|
+
const startIdx = lines.findIndex((l) => l.trim().startsWith('{') || l.trim().startsWith('['));
|
|
42
|
+
if (startIdx >= 0) {
|
|
43
|
+
return JSON.parse(lines.slice(startIdx).join('\n'));
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
// ── Tests ─────────────────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
test('each dimension returns a score 0-10 and an evidence string', () => {
|
|
57
|
+
const out = runAudit();
|
|
58
|
+
if (out.status !== 0) return; // audit.mjs may fail in broken environments
|
|
59
|
+
const result = parseAuditOutput(out.stdout);
|
|
60
|
+
const dims = [
|
|
61
|
+
'typecheck', 'tests', 'e2e', 'archBoundaries', 'securityPatterns',
|
|
62
|
+
'docSync', 'featureListState', 'cleanState', 'perfBudget',
|
|
63
|
+
'coverage', 'observability', 'drift',
|
|
64
|
+
];
|
|
65
|
+
for (const dim of dims) {
|
|
66
|
+
assert.ok(dim in result.categories, `Missing category: ${dim}`);
|
|
67
|
+
const { score, evidence } = result.categories[dim];
|
|
68
|
+
assert.ok(typeof score === 'number', `${dim}.score must be number`);
|
|
69
|
+
assert.ok(score >= 0 && score <= 10, `${dim}.score must be 0-10, got ${score}`);
|
|
70
|
+
assert.ok(typeof evidence === 'string', `${dim}.evidence must be string`);
|
|
71
|
+
assert.ok(evidence.length > 0, `${dim}.evidence must be non-empty`);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('total score is the weighted sum of all dimensions', () => {
|
|
76
|
+
const out = runAudit();
|
|
77
|
+
if (out.status !== 0) return;
|
|
78
|
+
const result = parseAuditOutput(out.stdout);
|
|
79
|
+
|
|
80
|
+
const weights = result.weights ?? {};
|
|
81
|
+
let expected = 0;
|
|
82
|
+
for (const [key, weight] of Object.entries(weights)) {
|
|
83
|
+
expected += (result.scores[key] ?? 0) * weight;
|
|
84
|
+
}
|
|
85
|
+
expected = Math.round(expected * 10) / 10;
|
|
86
|
+
|
|
87
|
+
assert.equal(result.total, expected, `total ${result.total} should equal weighted sum ${expected}`);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('missing feature_list.json yields score 0 for feature-list-state (tolerates missing state)', () => {
|
|
91
|
+
// Rename the real feature_list.json out of the way, run audit, restore it
|
|
92
|
+
const flPath = join(PROJECT_ROOT, 'feature_list.json');
|
|
93
|
+
const backupPath = flPath + '.audit-test-backup';
|
|
94
|
+
const hadBackup = existsSync(backupPath);
|
|
95
|
+
if (existsSync(flPath)) {
|
|
96
|
+
renameSync(flPath, backupPath);
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const { stdout } = execSync(`node "${AUDIT_SCRIPT}" 2>&1`, {
|
|
100
|
+
cwd: PROJECT_ROOT,
|
|
101
|
+
encoding: 'utf8',
|
|
102
|
+
});
|
|
103
|
+
const result = parseAuditOutput(stdout);
|
|
104
|
+
assert.ok(result !== null, `audit output should be valid JSON. stdout: ${(stdout ?? '').slice(0, 200)}`);
|
|
105
|
+
assert.equal(result.scores.featureListState, 0, 'missing feature_list should give featureListState=0');
|
|
106
|
+
} finally {
|
|
107
|
+
if (existsSync(backupPath)) {
|
|
108
|
+
renameSync(backupPath, flPath);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('evidence strings are human-readable (non-empty, contain useful context)', () => {
|
|
114
|
+
const out = runAudit();
|
|
115
|
+
if (out.status !== 0) return;
|
|
116
|
+
const result = parseAuditOutput(out.stdout);
|
|
117
|
+
|
|
118
|
+
for (const [dim, { evidence }] of Object.entries(result.categories)) {
|
|
119
|
+
assert.ok(
|
|
120
|
+
evidence.length > 5 && typeof evidence === 'string',
|
|
121
|
+
`${dim} evidence too short or not a string: "${evidence}"`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('--write creates .harness/audit/latest.json', () => {
|
|
127
|
+
const latestPath = join(PROJECT_ROOT, '.harness', 'audit', 'latest.json');
|
|
128
|
+
const hadLatest = existsSync(latestPath);
|
|
129
|
+
const backupPath = latestPath + '.audit-test-backup';
|
|
130
|
+
if (hadLatest) {
|
|
131
|
+
renameSync(latestPath, backupPath);
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
execSync(`node "${AUDIT_SCRIPT}" --write 2>&1`, {
|
|
135
|
+
cwd: PROJECT_ROOT,
|
|
136
|
+
encoding: 'utf8',
|
|
137
|
+
});
|
|
138
|
+
assert.ok(existsSync(latestPath), '.harness/audit/latest.json should exist after --write');
|
|
139
|
+
const written = JSON.parse(readFileSync(latestPath, 'utf8'));
|
|
140
|
+
assert.ok('total' in written, 'written JSON should have total');
|
|
141
|
+
assert.ok('categories' in written, 'written JSON should have categories');
|
|
142
|
+
assert.ok('scores' in written, 'written JSON should have scores');
|
|
143
|
+
} finally {
|
|
144
|
+
if (existsSync(latestPath)) rmSync(latestPath);
|
|
145
|
+
if (hadLatest) {
|
|
146
|
+
renameSync(backupPath, latestPath);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* eval-gate.mjs — Verify passing features have satisfying eval files.
|
|
3
|
+
*
|
|
4
|
+
* For each feature with state === "passing", checks that:
|
|
5
|
+
* 1. .harness/evals/<id>.jsonl exists
|
|
6
|
+
* 2. The file has at least one parseable JSON line
|
|
7
|
+
* 3. The pass-rate (passed / total) >= threshold (default 0.9)
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* bun run scripts/eval-gate.mjs [--threshold=0.9] [--dry-run] [--json]
|
|
11
|
+
* make eval-gate
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
15
|
+
import { join, dirname } from "node:path";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
|
|
18
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const ROOT = join(__dirname, "..");
|
|
20
|
+
const FEATURE_LIST_PATH = join(ROOT, "feature_list.json");
|
|
21
|
+
const EVALS_DIR = join(ROOT, ".harness", "evals");
|
|
22
|
+
|
|
23
|
+
const DEFAULT_THRESHOLD = 0.9;
|
|
24
|
+
|
|
25
|
+
function readJson(/** @type {string} */ path) {
|
|
26
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {{ id: string, behavior: string, state: string }} feature
|
|
31
|
+
* @param {number} threshold
|
|
32
|
+
* @returns {{ ok: boolean, reason?: string, detail?: string }}
|
|
33
|
+
*/
|
|
34
|
+
function checkFeature(feature, threshold) {
|
|
35
|
+
if (feature.state !== "passing") {
|
|
36
|
+
return { ok: true };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const evalPath = join(EVALS_DIR, `${feature.id}.jsonl`);
|
|
40
|
+
|
|
41
|
+
if (!existsSync(evalPath)) {
|
|
42
|
+
return { ok: false, reason: "eval file not found", detail: evalPath };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let content;
|
|
46
|
+
try {
|
|
47
|
+
content = readFileSync(evalPath, "utf-8");
|
|
48
|
+
} catch {
|
|
49
|
+
return { ok: false, reason: "eval file unreadable", detail: evalPath };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!content.trim()) {
|
|
53
|
+
return { ok: false, reason: "eval file is empty", detail: evalPath };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const lines = content.split("\n").filter((l) => l.trim() !== "");
|
|
57
|
+
let passed = 0;
|
|
58
|
+
let total = 0;
|
|
59
|
+
for (const line of lines) {
|
|
60
|
+
try {
|
|
61
|
+
const record = JSON.parse(line);
|
|
62
|
+
total++;
|
|
63
|
+
if (record.passed === true || record.pass === true || record.status === "pass") {
|
|
64
|
+
passed++;
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// Malformed line — skip (lenient)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (total === 0) {
|
|
72
|
+
return { ok: false, reason: "eval file has no parseable records", detail: evalPath };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const rate = passed / total;
|
|
76
|
+
if (rate < threshold) {
|
|
77
|
+
return {
|
|
78
|
+
ok: false,
|
|
79
|
+
reason: `pass-rate ${rate.toFixed(3)} < threshold ${threshold}`,
|
|
80
|
+
detail: evalPath,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return { ok: true };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// CLI
|
|
88
|
+
const args = process.argv.slice(2);
|
|
89
|
+
const thresholdArg = args.find((a) => a.startsWith("--threshold="));
|
|
90
|
+
const dryRun = args.includes("--dry-run");
|
|
91
|
+
const jsonOutput = args.includes("--json");
|
|
92
|
+
const threshold = thresholdArg
|
|
93
|
+
? parseFloat(thresholdArg.split("=")[1])
|
|
94
|
+
: DEFAULT_THRESHOLD;
|
|
95
|
+
|
|
96
|
+
const featureList = readJson(FEATURE_LIST_PATH);
|
|
97
|
+
const features = /** @type {Array<{id: string, behavior: string, state: string}>} */ (featureList.features ?? []);
|
|
98
|
+
|
|
99
|
+
const results = [];
|
|
100
|
+
let passCount = 0;
|
|
101
|
+
let failCount = 0;
|
|
102
|
+
|
|
103
|
+
for (const feature of features) {
|
|
104
|
+
const result = checkFeature(feature, threshold);
|
|
105
|
+
results.push({ featureId: feature.id, behavior: feature.behavior, ...result });
|
|
106
|
+
if (result.ok) {
|
|
107
|
+
passCount++;
|
|
108
|
+
} else {
|
|
109
|
+
failCount++;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (jsonOutput) {
|
|
114
|
+
console.log(
|
|
115
|
+
JSON.stringify({ threshold, total: results.length, passing: passCount, failing: failCount, results }, null, 2)
|
|
116
|
+
);
|
|
117
|
+
} else {
|
|
118
|
+
console.log(`\n▶ Eval gate — threshold: ${threshold}`);
|
|
119
|
+
console.log("─".repeat(60));
|
|
120
|
+
for (const r of results) {
|
|
121
|
+
if (r.ok) {
|
|
122
|
+
console.log(` PASS ${r.featureId}`);
|
|
123
|
+
} else {
|
|
124
|
+
console.log(` FAIL ${r.featureId} — ${r.reason}`);
|
|
125
|
+
if (r.detail) console.log(` (${r.detail})`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
console.log("─".repeat(60));
|
|
129
|
+
console.log(` ${passCount} passing, ${failCount} failing\n`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (failCount > 0 && !dryRun) {
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* eval-gate.test.mjs — 5 tests for eval-gate.mjs
|
|
3
|
+
*
|
|
4
|
+
* Tests:
|
|
5
|
+
* 1. passing-feature eval present → pass
|
|
6
|
+
* 2. fail-rate threshold → fail
|
|
7
|
+
* 3. missing eval flagged → fail
|
|
8
|
+
* 4. malformed JSONL tolerated → pass
|
|
9
|
+
* 5. dry-run mode → no exit failure
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { writeFileSync, mkdirSync, rmSync, readFileSync } from "node:fs";
|
|
13
|
+
import { join, dirname } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const ROOT = join(__dirname, "..");
|
|
18
|
+
const EVALS_DIR = join(ROOT, ".harness", "evals");
|
|
19
|
+
const FEATURE_LIST_PATH = join(ROOT, "feature_list.json");
|
|
20
|
+
|
|
21
|
+
await mkdirSync(EVALS_DIR, { recursive: true });
|
|
22
|
+
|
|
23
|
+
/** Run eval-gate and return { stdout, stderr, exitCode } */
|
|
24
|
+
async function runGate(args = []) {
|
|
25
|
+
const proc = Bun.spawn({
|
|
26
|
+
cmd: ["/home/drb0rk/.bun/bin/bun", "run", join(__dirname, "eval-gate.mjs"), ...args],
|
|
27
|
+
cwd: ROOT,
|
|
28
|
+
env: { PATH: "/home/drb0rk/.bun/bin:/usr/bin:/bin" },
|
|
29
|
+
});
|
|
30
|
+
const [stdout, exitCode] = await Promise.all([
|
|
31
|
+
new Response(proc.stdout).text(),
|
|
32
|
+
proc.exited,
|
|
33
|
+
]);
|
|
34
|
+
const stderr = ""; // not captured
|
|
35
|
+
return { stdout, stderr, exitCode };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
function withFeatureList(/** @type {object[]} */ features, /** @type {() => Promise<void>} */ fn) {
|
|
41
|
+
const orig = readFileSync(FEATURE_LIST_PATH, "utf-8");
|
|
42
|
+
writeFileSync(FEATURE_LIST_PATH, JSON.stringify({ features }, null, 2), "utf-8");
|
|
43
|
+
return fn().finally(() => writeFileSync(FEATURE_LIST_PATH, orig, "utf-8"));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── test 1: passing-feature eval present → pass ──────────────────────────────
|
|
47
|
+
|
|
48
|
+
await writeFileSync(join(EVALS_DIR, "F-001.jsonl"), JSON.stringify({ passed: true }) + "\n");
|
|
49
|
+
|
|
50
|
+
await withFeatureList(
|
|
51
|
+
[{ id: "F-001", behavior: "test feature", state: "passing", evidence: "commit abc" }],
|
|
52
|
+
async () => {
|
|
53
|
+
const r = await runGate(["--threshold=0.9"]);
|
|
54
|
+
if (r.exitCode !== 0) {
|
|
55
|
+
console.error("FAIL test_1: expected exit 0, got", r.exitCode);
|
|
56
|
+
console.error(r.stdout);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
console.log("PASS test_1: passing-feature with eval present → pass");
|
|
60
|
+
}
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// ── test 2: fail-rate threshold → fail ───────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
await writeFileSync(join(EVALS_DIR, "F-002.jsonl"), [
|
|
66
|
+
JSON.stringify({ passed: true }),
|
|
67
|
+
JSON.stringify({ passed: true }),
|
|
68
|
+
JSON.stringify({ passed: false }),
|
|
69
|
+
].join("\n"));
|
|
70
|
+
|
|
71
|
+
await withFeatureList(
|
|
72
|
+
[{ id: "F-002", behavior: "test", state: "passing" }],
|
|
73
|
+
async () => {
|
|
74
|
+
const r = await runGate(["--threshold=0.9"]);
|
|
75
|
+
if (r.exitCode === 0) {
|
|
76
|
+
console.error("FAIL test_2: expected non-zero exit for failing threshold");
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
console.log("PASS test_2: fail-rate below threshold → fail");
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// ── test 3: missing eval flagged → fail ──────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
await withFeatureList(
|
|
86
|
+
[{ id: "F-999", behavior: "test", state: "passing" }],
|
|
87
|
+
async () => {
|
|
88
|
+
const r = await runGate();
|
|
89
|
+
if (r.exitCode === 0) {
|
|
90
|
+
console.error("FAIL test_3: expected non-zero exit for missing eval");
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
console.log("PASS test_3: missing eval file → fail");
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// ── test 4: malformed JSONL tolerated → pass ─────────────────────────────────
|
|
98
|
+
|
|
99
|
+
await writeFileSync(join(EVALS_DIR, "F-003.jsonl"), [
|
|
100
|
+
JSON.stringify({ passed: true }),
|
|
101
|
+
"NOT JSON",
|
|
102
|
+
JSON.stringify({ passed: true }),
|
|
103
|
+
"",
|
|
104
|
+
].join("\n"));
|
|
105
|
+
|
|
106
|
+
await withFeatureList(
|
|
107
|
+
[{ id: "F-003", behavior: "test", state: "passing" }],
|
|
108
|
+
async () => {
|
|
109
|
+
const r = await runGate();
|
|
110
|
+
if (r.exitCode !== 0) {
|
|
111
|
+
console.error("FAIL test_4: malformed JSONL should be tolerated");
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
console.log("PASS test_4: malformed JSONL lines tolerated → pass");
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// ── test 5: dry-run mode ─────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
await withFeatureList(
|
|
121
|
+
[{ id: "F-001", behavior: "test", state: "passing" }],
|
|
122
|
+
async () => {
|
|
123
|
+
const r = await runGate(["--dry-run"]);
|
|
124
|
+
if (r.exitCode !== 0) {
|
|
125
|
+
console.error("FAIL test_5: dry-run should always exit 0");
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
console.log("PASS test_5: dry-run exits 0 regardless of gate state");
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// ── cleanup ───────────────────────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
rmSync(join(EVALS_DIR, "F-001.jsonl"), { force: true });
|
|
135
|
+
rmSync(join(EVALS_DIR, "F-002.jsonl"), { force: true });
|
|
136
|
+
rmSync(join(EVALS_DIR, "F-003.jsonl"), { force: true });
|
|
137
|
+
|
|
138
|
+
console.log("\nAll 5 tests passed.");
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* feature-state-machine.mjs — Enforce plan→exec→verify→audit state machine
|
|
3
|
+
* per feature in feature_list.json.
|
|
4
|
+
*
|
|
5
|
+
* For each `passing` feature, verifies:
|
|
6
|
+
* 1. evidence field is populated (non-empty string)
|
|
7
|
+
* 2. commit field is populated (non-empty string)
|
|
8
|
+
* 3. make audit score >= 80 (run from feature branch, lenient on missing audit script)
|
|
9
|
+
* 4. eval gate satisfied (delegates to eval-gate.mjs logic)
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* node scripts/feature-state-machine.mjs [--json] [--dry-run]
|
|
13
|
+
* make feature-state-machine
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
17
|
+
import { join, dirname } from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
import { execSync } from "node:child_process";
|
|
20
|
+
|
|
21
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const ROOT = join(__dirname, "..");
|
|
23
|
+
const FEATURE_LIST_PATH = join(ROOT, "feature_list.json");
|
|
24
|
+
const EVALS_DIR = join(ROOT, ".harness", "evals");
|
|
25
|
+
const AUDIT_SCRIPT = join(ROOT, "tools", "audit-harness.sh");
|
|
26
|
+
|
|
27
|
+
const args = process.argv.slice(2);
|
|
28
|
+
const jsonOutput = args.includes("--json");
|
|
29
|
+
const dryRun = args.includes("--dry-run");
|
|
30
|
+
|
|
31
|
+
function readJson(path) {
|
|
32
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {string} featureId
|
|
37
|
+
* @returns {{score: number, raw: string} | null}
|
|
38
|
+
*/
|
|
39
|
+
function runAudit(featureId) {
|
|
40
|
+
if (!existsSync(AUDIT_SCRIPT)) return null;
|
|
41
|
+
try {
|
|
42
|
+
const raw = execSync(`bash "${AUDIT_SCRIPT}" . 2>&1 || true`, {
|
|
43
|
+
cwd: ROOT,
|
|
44
|
+
timeout: 60_000,
|
|
45
|
+
}).toString("utf-8");
|
|
46
|
+
|
|
47
|
+
// Extract score: look for "Score: XX" or "AUDIT SCORE: XX"
|
|
48
|
+
const scoreMatch = raw.match(/score[:\s]+(\d+)/i)
|
|
49
|
+
|| raw.match(/(\d+)\s*(?:\/\s*100|%)/);
|
|
50
|
+
const score = scoreMatch ? parseInt(scoreMatch[1], 10) : null;
|
|
51
|
+
return { score: score ?? 0, raw };
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {string} featureId
|
|
59
|
+
* @returns {{passed: number, total: number, rate: number} | null}
|
|
60
|
+
*/
|
|
61
|
+
function evalPassRate(featureId) {
|
|
62
|
+
const evalPath = join(EVALS_DIR, `${featureId}.jsonl`);
|
|
63
|
+
if (!existsSync(evalPath)) return null;
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const content = readFileSync(evalPath, "utf-8");
|
|
67
|
+
const lines = content.split("\n").filter((l) => l.trim() !== "");
|
|
68
|
+
let passed = 0;
|
|
69
|
+
let total = 0;
|
|
70
|
+
for (const line of lines) {
|
|
71
|
+
try {
|
|
72
|
+
const record = JSON.parse(line);
|
|
73
|
+
total++;
|
|
74
|
+
if (record.passed === true || record.pass === true || record.status === "pass") {
|
|
75
|
+
passed++;
|
|
76
|
+
}
|
|
77
|
+
} catch {
|
|
78
|
+
// Malformed line — skip (lenient)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (total === 0) return null;
|
|
82
|
+
return { passed, total, rate: passed / total };
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @param {{id: string, behavior: string, state: string, evidence?: string, commit?: string, auditScore?: number}} feature
|
|
90
|
+
* @returns {{ok: boolean, checks: Record<string, boolean | string>, reasons: string[]}}
|
|
91
|
+
*/
|
|
92
|
+
function checkFeature(feature) {
|
|
93
|
+
if (feature.state !== "passing") {
|
|
94
|
+
return { ok: true, checks: {}, reasons: [] };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const checks = {};
|
|
98
|
+
const reasons = [];
|
|
99
|
+
|
|
100
|
+
// Check 1: evidence field
|
|
101
|
+
const hasEvidence = Boolean(feature.evidence && feature.evidence.trim().length > 0);
|
|
102
|
+
checks.evidence = hasEvidence;
|
|
103
|
+
if (!hasEvidence) reasons.push("evidence field is empty");
|
|
104
|
+
|
|
105
|
+
// Check 2: commit hash
|
|
106
|
+
const hasCommit = Boolean(feature.commit && feature.commit.trim().length > 0);
|
|
107
|
+
checks.commit = hasCommit;
|
|
108
|
+
if (!hasCommit) reasons.push("commit hash is empty");
|
|
109
|
+
|
|
110
|
+
// Check 3: audit score >= 80
|
|
111
|
+
let auditScore = null;
|
|
112
|
+
if (existsSync(AUDIT_SCRIPT)) {
|
|
113
|
+
const result = runAudit(feature.id);
|
|
114
|
+
if (result) auditScore = result.score;
|
|
115
|
+
}
|
|
116
|
+
const auditPass = auditScore !== null && auditScore >= 80;
|
|
117
|
+
checks.auditScore = auditPass;
|
|
118
|
+
if (auditScore === null) {
|
|
119
|
+
// Lenient: audit script may not exist in all environments
|
|
120
|
+
checks.auditScore = true;
|
|
121
|
+
} else if (!auditPass) {
|
|
122
|
+
reasons.push(`audit score ${auditScore} < 80`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Check 4: eval gate
|
|
126
|
+
const evalResult = evalPassRate(feature.id);
|
|
127
|
+
const evalPass = evalResult !== null && evalResult.rate >= 0.9;
|
|
128
|
+
checks.evalGate = evalPass;
|
|
129
|
+
if (evalResult === null) {
|
|
130
|
+
// Lenient: no eval file is not a failure
|
|
131
|
+
checks.evalGate = true;
|
|
132
|
+
} else if (!evalPass) {
|
|
133
|
+
reasons.push(`eval pass-rate ${(evalResult.rate * 100).toFixed(1)}% < 90%`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const ok = hasEvidence && hasCommit && (auditPass || auditScore === null) && (evalPass || evalResult === null);
|
|
137
|
+
return { ok, checks, reasons };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const featureList = readJson(FEATURE_LIST_PATH);
|
|
141
|
+
const features = featureList.features ?? [];
|
|
142
|
+
|
|
143
|
+
const results = [];
|
|
144
|
+
let passCount = 0;
|
|
145
|
+
let failCount = 0;
|
|
146
|
+
|
|
147
|
+
for (const feature of features) {
|
|
148
|
+
const { ok, checks, reasons } = checkFeature(feature);
|
|
149
|
+
results.push({ featureId: feature.id, behavior: feature.behavior, state: feature.state, ok, checks, reasons });
|
|
150
|
+
if (ok) passCount++;
|
|
151
|
+
else failCount++;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (jsonOutput) {
|
|
155
|
+
console.log(
|
|
156
|
+
JSON.stringify({ total: results.length, passing: passCount, failing: failCount, results }, null, 2)
|
|
157
|
+
);
|
|
158
|
+
} else {
|
|
159
|
+
console.log("\n▶ Feature state machine — passing features\n");
|
|
160
|
+
console.log("─".repeat(60));
|
|
161
|
+
for (const r of results) {
|
|
162
|
+
if (r.state !== "passing") continue;
|
|
163
|
+
if (r.ok) {
|
|
164
|
+
console.log(` PASS ${r.featureId}`);
|
|
165
|
+
} else {
|
|
166
|
+
console.log(` FAIL ${r.featureId}`);
|
|
167
|
+
for (const reason of r.reasons) {
|
|
168
|
+
console.log(` - ${reason}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
console.log("─".repeat(60));
|
|
173
|
+
console.log(` ${passCount} passing, ${failCount} failing\n`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (failCount > 0 && !dryRun) {
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|