@polderlabs/bizar 10.1.0 → 10.3.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 +31 -10
- package/bizar-dash/CONTROL_SURFACES.md +0 -1
- package/bizar-dash/dist/assets/{ActivityFeed-Da-JCrE6.js → ActivityFeed-xXiUCZLB.js} +1 -1
- package/bizar-dash/dist/assets/{ActivityFeed-Da-JCrE6.js.map → ActivityFeed-xXiUCZLB.js.map} +1 -1
- package/bizar-dash/dist/assets/{ActivityView-nIRsSETb.js → ActivityView-nOYhCp3h.js} +1 -1
- package/bizar-dash/dist/assets/{ActivityView-nIRsSETb.js.map → ActivityView-nOYhCp3h.js.map} +1 -1
- package/bizar-dash/dist/assets/{AdminView-DP1NMxmG.js → AdminView-Coi4V3sq.js} +1 -1
- package/bizar-dash/dist/assets/{AdminView-DP1NMxmG.js.map → AdminView-Coi4V3sq.js.map} +1 -1
- package/bizar-dash/dist/assets/{AgentActivity-lSPkOr-O.js → AgentActivity-nEri4lbx.js} +1 -1
- package/bizar-dash/dist/assets/{AgentActivity-lSPkOr-O.js.map → AgentActivity-nEri4lbx.js.map} +1 -1
- package/bizar-dash/dist/assets/{AgentsView-BIWGkz7w.js → AgentsView-ByiIJ0ej.js} +3 -3
- package/bizar-dash/dist/assets/{AgentsView-BIWGkz7w.js.map → AgentsView-ByiIJ0ej.js.map} +1 -1
- package/bizar-dash/dist/assets/{Alert-DN0UysMg.js → Alert-Dg_Vwxh5.js} +1 -1
- package/bizar-dash/dist/assets/{Alert-DN0UysMg.js.map → Alert-Dg_Vwxh5.js.map} +1 -1
- package/bizar-dash/dist/assets/ArtifactsView-CGclRNnz.js +21 -0
- package/bizar-dash/dist/assets/ArtifactsView-CGclRNnz.js.map +1 -0
- package/bizar-dash/dist/assets/{AuthView-DW5cs7ma.js → AuthView-utVm6M3D.js} +1 -1
- package/bizar-dash/dist/assets/{AuthView-DW5cs7ma.js.map → AuthView-utVm6M3D.js.map} +1 -1
- package/bizar-dash/dist/assets/{Avatar-DZPv9pvd.js → Avatar-CXDZdKy4.js} +1 -1
- package/bizar-dash/dist/assets/{Avatar-DZPv9pvd.js.map → Avatar-CXDZdKy4.js.map} +1 -1
- package/bizar-dash/dist/assets/{BackgroundJobsView-D8O-EyMD.js → BackgroundJobsView-j603pz6o.js} +1 -1
- package/bizar-dash/dist/assets/{BackgroundJobsView-D8O-EyMD.js.map → BackgroundJobsView-j603pz6o.js.map} +1 -1
- package/bizar-dash/dist/assets/{BackupView-uwXkhxOV.js → BackupView-DgvnntTE.js} +1 -1
- package/bizar-dash/dist/assets/{BackupView-uwXkhxOV.js.map → BackupView-DgvnntTE.js.map} +1 -1
- package/bizar-dash/dist/assets/{Card-Tyboihu7.js → Card-D9019JOD.js} +1 -1
- package/bizar-dash/dist/assets/{Card-Tyboihu7.js.map → Card-D9019JOD.js.map} +1 -1
- package/bizar-dash/dist/assets/{ChatView-DRBHzgay.js → ChatView-C0cTgj5d.js} +1 -1
- package/bizar-dash/dist/assets/{ChatView-DRBHzgay.js.map → ChatView-C0cTgj5d.js.map} +1 -1
- package/bizar-dash/dist/assets/{Chip-BrXIlR9w.js → Chip-DQtalZ4y.js} +1 -1
- package/bizar-dash/dist/assets/{Chip-BrXIlR9w.js.map → Chip-DQtalZ4y.js.map} +1 -1
- package/bizar-dash/dist/assets/{ClaudeSessionsView-Ck62jaOr.js → ClaudeSessionsView-H3Bqjo6b.js} +1 -1
- package/bizar-dash/dist/assets/{ClaudeSessionsView-Ck62jaOr.js.map → ClaudeSessionsView-H3Bqjo6b.js.map} +1 -1
- package/bizar-dash/dist/assets/{ClipboardView-DfND6xL6.js → ClipboardView-BAguaKbx.js} +1 -1
- package/bizar-dash/dist/assets/{ClipboardView-DfND6xL6.js.map → ClipboardView-BAguaKbx.js.map} +1 -1
- package/bizar-dash/dist/assets/{ConfigView-DjJmSrOJ.js → ConfigView-DWHJgoe8.js} +1 -1
- package/bizar-dash/dist/assets/{ConfigView-DjJmSrOJ.js.map → ConfigView-DWHJgoe8.js.map} +1 -1
- package/bizar-dash/dist/assets/{DiagnosticsView-CKgItf0J.js → DiagnosticsView-oCT624hk.js} +1 -1
- package/bizar-dash/dist/assets/{DiagnosticsView-CKgItf0J.js.map → DiagnosticsView-oCT624hk.js.map} +1 -1
- package/bizar-dash/dist/assets/{DialogsView-DHYF8dE1.js → DialogsView-CqHNals_.js} +1 -1
- package/bizar-dash/dist/assets/{DialogsView-DHYF8dE1.js.map → DialogsView-CqHNals_.js.map} +1 -1
- package/bizar-dash/dist/assets/{DoctorView-DDLHrSKk.js → DoctorView-6kfQq_Mx.js} +1 -1
- package/bizar-dash/dist/assets/{DoctorView-DDLHrSKk.js.map → DoctorView-6kfQq_Mx.js.map} +1 -1
- package/bizar-dash/dist/assets/{EmptyState-DZ2ouKwd.js → EmptyState-DLmtBX_6.js} +1 -1
- package/bizar-dash/dist/assets/{EmptyState-DZ2ouKwd.js.map → EmptyState-DLmtBX_6.js.map} +1 -1
- package/bizar-dash/dist/assets/{EnvVarsView-D1cx83mB.js → EnvVarsView-DySOXlP1.js} +2 -2
- package/bizar-dash/dist/assets/{EnvVarsView-D1cx83mB.js.map → EnvVarsView-DySOXlP1.js.map} +1 -1
- package/bizar-dash/dist/assets/{ErrorState-DwwJ2-Jq.js → ErrorState-Bump8diE.js} +1 -1
- package/bizar-dash/dist/assets/{ErrorState-DwwJ2-Jq.js.map → ErrorState-Bump8diE.js.map} +1 -1
- package/bizar-dash/dist/assets/{EvalView-C9aYixXW.js → EvalView-FnZ5d93z.js} +1 -1
- package/bizar-dash/dist/assets/{EvalView-C9aYixXW.js.map → EvalView-FnZ5d93z.js.map} +1 -1
- package/bizar-dash/dist/assets/{GoalsView-DZa_mGmh.js → GoalsView-C4m1WnVv.js} +1 -1
- package/bizar-dash/dist/assets/{GoalsView-DZa_mGmh.js.map → GoalsView-C4m1WnVv.js.map} +1 -1
- package/bizar-dash/dist/assets/{Grid-CiRyhAgR.js → Grid-dEnoGGws.js} +1 -1
- package/bizar-dash/dist/assets/{Grid-CiRyhAgR.js.map → Grid-dEnoGGws.js.map} +1 -1
- package/bizar-dash/dist/assets/{HistoryView-Dv7aPxJE.js → HistoryView-xdRybjZ2.js} +1 -1
- package/bizar-dash/dist/assets/{HistoryView-Dv7aPxJE.js.map → HistoryView-xdRybjZ2.js.map} +1 -1
- package/bizar-dash/dist/assets/{Input-BbFUDQnS.js → Input-BSa3lh_-.js} +1 -1
- package/bizar-dash/dist/assets/{Input-BbFUDQnS.js.map → Input-BSa3lh_-.js.map} +1 -1
- package/bizar-dash/dist/assets/{LibrariesView-DBQ83TNa.js → LibrariesView-1zR04Eci.js} +1 -1
- package/bizar-dash/dist/assets/{LibrariesView-DBQ83TNa.js.map → LibrariesView-1zR04Eci.js.map} +1 -1
- package/bizar-dash/dist/assets/{LightRAGView-JYpE5u9p.js → LightRAGView-PubmA9oL.js} +1 -1
- package/bizar-dash/dist/assets/{LightRAGView-JYpE5u9p.js.map → LightRAGView-PubmA9oL.js.map} +1 -1
- package/bizar-dash/dist/assets/{ListHeader-BGjm11Z0.js → ListHeader-Ak9WJd39.js} +1 -1
- package/bizar-dash/dist/assets/{ListHeader-BGjm11Z0.js.map → ListHeader-Ak9WJd39.js.map} +1 -1
- package/bizar-dash/dist/assets/{MemoryView-CnOP2MvM.js → MemoryView-DBGpuETw.js} +1 -1
- package/bizar-dash/dist/assets/{MemoryView-CnOP2MvM.js.map → MemoryView-DBGpuETw.js.map} +1 -1
- package/bizar-dash/dist/assets/MiscView-DufqBHkX.js +1 -0
- package/bizar-dash/dist/assets/{MiscView-BBcpUbZZ.js.map → MiscView-DufqBHkX.js.map} +1 -1
- package/bizar-dash/dist/assets/{ModsView-CU4cAmIz.js → ModsView-DcX6eVU2.js} +2 -2
- package/bizar-dash/dist/assets/{ModsView-CU4cAmIz.js.map → ModsView-DcX6eVU2.js.map} +1 -1
- package/bizar-dash/dist/assets/{NotificationsView-BYffvthq.js → NotificationsView-B8IhHRQC.js} +1 -1
- package/bizar-dash/dist/assets/{NotificationsView-BYffvthq.js.map → NotificationsView-B8IhHRQC.js.map} +1 -1
- package/bizar-dash/dist/assets/ObsidianView-DEuk65zS.js +1 -0
- package/bizar-dash/dist/assets/{ObsidianView-D3bI0row.js.map → ObsidianView-DEuk65zS.js.map} +1 -1
- package/bizar-dash/dist/assets/{OverviewView-0tq8f-Y0.js → OverviewView-CTI2spTd.js} +1 -1
- package/bizar-dash/dist/assets/{OverviewView-0tq8f-Y0.js.map → OverviewView-CTI2spTd.js.map} +1 -1
- package/bizar-dash/dist/assets/{ProgressBar-B-h60LKA.js → ProgressBar-D3lkz42E.js} +1 -1
- package/bizar-dash/dist/assets/{ProgressBar-B-h60LKA.js.map → ProgressBar-D3lkz42E.js.map} +1 -1
- package/bizar-dash/dist/assets/{ProjectsView-DhX5lzHS.js → ProjectsView-Du0LzE7A.js} +1 -1
- package/bizar-dash/dist/assets/{ProjectsView-DhX5lzHS.js.map → ProjectsView-Du0LzE7A.js.map} +1 -1
- package/bizar-dash/dist/assets/{ProvidersView-CJMX7jm9.js → ProvidersView-5bCgXwBo.js} +1 -1
- package/bizar-dash/dist/assets/{ProvidersView-CJMX7jm9.js.map → ProvidersView-5bCgXwBo.js.map} +1 -1
- package/bizar-dash/dist/assets/{SchedulesView-CcwDQ9Nm.js → SchedulesView-DFHdIz5P.js} +1 -1
- package/bizar-dash/dist/assets/{SchedulesView-CcwDQ9Nm.js.map → SchedulesView-DFHdIz5P.js.map} +1 -1
- package/bizar-dash/dist/assets/{Select-Cw7Qtz2M.js → Select-C8EJj4NM.js} +1 -1
- package/bizar-dash/dist/assets/{Select-Cw7Qtz2M.js.map → Select-C8EJj4NM.js.map} +1 -1
- package/bizar-dash/dist/assets/{SettingsRow-DgsgbPSI.js → SettingsRow-BdOOmYMD.js} +1 -1
- package/bizar-dash/dist/assets/{SettingsRow-DgsgbPSI.js.map → SettingsRow-BdOOmYMD.js.map} +1 -1
- package/bizar-dash/dist/assets/{SettingsView-DTAFdKyK.js → SettingsView-CqwQ6Knv.js} +1 -1
- package/bizar-dash/dist/assets/{SettingsView-DTAFdKyK.js.map → SettingsView-CqwQ6Knv.js.map} +1 -1
- package/bizar-dash/dist/assets/{Sheet-DH-WNJ3Y.js → Sheet-Cztsvgse.js} +1 -1
- package/bizar-dash/dist/assets/{Sheet-DH-WNJ3Y.js.map → Sheet-Cztsvgse.js.map} +1 -1
- package/bizar-dash/dist/assets/{Slider-DXbJuQ7y.js → Slider-BexNfF6K.js} +1 -1
- package/bizar-dash/dist/assets/{Slider-DXbJuQ7y.js.map → Slider-BexNfF6K.js.map} +1 -1
- package/bizar-dash/dist/assets/{Sparkline-CYtAn82j.js → Sparkline-CLZBtvsN.js} +1 -1
- package/bizar-dash/dist/assets/{Sparkline-CYtAn82j.js.map → Sparkline-CLZBtvsN.js.map} +1 -1
- package/bizar-dash/dist/assets/{Switch-BItGrPqr.js → Switch-B1tiRwNK.js} +1 -1
- package/bizar-dash/dist/assets/{Switch-BItGrPqr.js.map → Switch-B1tiRwNK.js.map} +1 -1
- package/bizar-dash/dist/assets/{TasksView-i5tm6CkX.js → TasksView-CT0fPlos.js} +1 -1
- package/bizar-dash/dist/assets/{TasksView-i5tm6CkX.js.map → TasksView-CT0fPlos.js.map} +1 -1
- package/bizar-dash/dist/assets/{Textarea-qYDf3yds.js → Textarea-D_TPpVMy.js} +1 -1
- package/bizar-dash/dist/assets/{Textarea-qYDf3yds.js.map → Textarea-D_TPpVMy.js.map} +1 -1
- package/bizar-dash/dist/assets/{UpdateView-Xln-ej26.js → UpdateView-tD2q2OhW.js} +1 -1
- package/bizar-dash/dist/assets/{UpdateView-Xln-ej26.js.map → UpdateView-tD2q2OhW.js.map} +1 -1
- package/bizar-dash/dist/assets/{UsageView-BGuZSUZ6.js → UsageView-B4BuAFKA.js} +1 -1
- package/bizar-dash/dist/assets/{UsageView-BGuZSUZ6.js.map → UsageView-B4BuAFKA.js.map} +1 -1
- package/bizar-dash/dist/assets/{ViewHeader-BqIPVKuG.js → ViewHeader-V_SzbUpw.js} +1 -1
- package/bizar-dash/dist/assets/{ViewHeader-BqIPVKuG.js.map → ViewHeader-V_SzbUpw.js.map} +1 -1
- package/bizar-dash/dist/assets/{VoiceView-CWUOCQs_.js → VoiceView-D3vb5OM2.js} +1 -1
- package/bizar-dash/dist/assets/{VoiceView-CWUOCQs_.js.map → VoiceView-D3vb5OM2.js.map} +1 -1
- package/bizar-dash/dist/assets/{brain-fsBSDl2g.js → brain-BmfWcitQ.js} +1 -1
- package/bizar-dash/dist/assets/{brain-fsBSDl2g.js.map → brain-BmfWcitQ.js.map} +1 -1
- package/bizar-dash/dist/assets/{calendar-DsHTZoCU.js → calendar-VntJUzFB.js} +1 -1
- package/bizar-dash/dist/assets/{calendar-DsHTZoCU.js.map → calendar-VntJUzFB.js.map} +1 -1
- package/bizar-dash/dist/assets/{circle-CqSxqqeJ.js → circle-KrQQySeK.js} +1 -1
- package/bizar-dash/dist/assets/{circle-CqSxqqeJ.js.map → circle-KrQQySeK.js.map} +1 -1
- package/bizar-dash/dist/assets/{circle-alert-BsLl001l.js → circle-alert-CQ9MLygI.js} +1 -1
- package/bizar-dash/dist/assets/{circle-alert-BsLl001l.js.map → circle-alert-CQ9MLygI.js.map} +1 -1
- package/bizar-dash/dist/assets/{circle-check-rVKMQmZp.js → circle-check-COtVgsWw.js} +1 -1
- package/bizar-dash/dist/assets/{circle-check-rVKMQmZp.js.map → circle-check-COtVgsWw.js.map} +1 -1
- package/bizar-dash/dist/assets/{clock-OuficfgH.js → clock-C0GlM4qp.js} +1 -1
- package/bizar-dash/dist/assets/{clock-OuficfgH.js.map → clock-C0GlM4qp.js.map} +1 -1
- package/bizar-dash/dist/assets/{download-BdHlstUY.js → download-CW4DmtRe.js} +1 -1
- package/bizar-dash/dist/assets/{download-BdHlstUY.js.map → download-CW4DmtRe.js.map} +1 -1
- package/bizar-dash/dist/assets/{external-link-DA3_VGm-.js → external-link-CAOnh_DH.js} +1 -1
- package/bizar-dash/dist/assets/{external-link-DA3_VGm-.js.map → external-link-CAOnh_DH.js.map} +1 -1
- package/bizar-dash/dist/assets/{file-text-B-g8H227.js → file-text-CS1jB_oV.js} +1 -1
- package/bizar-dash/dist/assets/{file-text-B-g8H227.js.map → file-text-CS1jB_oV.js.map} +1 -1
- package/bizar-dash/dist/assets/{filter-D9B8s5CG.js → filter-DyQbml3L.js} +1 -1
- package/bizar-dash/dist/assets/{filter-D9B8s5CG.js.map → filter-DyQbml3L.js.map} +1 -1
- package/bizar-dash/dist/assets/{git-pull-request-ByH0YuVz.js → git-pull-request-Co6gYfba.js} +1 -1
- package/bizar-dash/dist/assets/{git-pull-request-ByH0YuVz.js.map → git-pull-request-Co6gYfba.js.map} +1 -1
- package/bizar-dash/dist/assets/{main-CdOm1Pj9.js → main-CyAIirBB.js} +28 -33
- package/bizar-dash/dist/assets/main-CyAIirBB.js.map +1 -0
- package/bizar-dash/dist/assets/network-CDn43Gsj.js +6 -0
- package/bizar-dash/dist/assets/network-CDn43Gsj.js.map +1 -0
- package/bizar-dash/dist/assets/{pause-C9hgd8wX.js → pause-CKYjSTGX.js} +1 -1
- package/bizar-dash/dist/assets/{pause-C9hgd8wX.js.map → pause-CKYjSTGX.js.map} +1 -1
- package/bizar-dash/dist/assets/{pencil-BanqaMw7.js → pencil-BUJ2DpWs.js} +1 -1
- package/bizar-dash/dist/assets/{pencil-BanqaMw7.js.map → pencil-BUJ2DpWs.js.map} +1 -1
- package/bizar-dash/dist/assets/{play-CawUZ02_.js → play-CGxZWqtW.js} +1 -1
- package/bizar-dash/dist/assets/{play-CawUZ02_.js.map → play-CGxZWqtW.js.map} +1 -1
- package/bizar-dash/dist/assets/{plug-Cmv0-T_e.js → plug-CQYGgGSy.js} +1 -1
- package/bizar-dash/dist/assets/{plug-Cmv0-T_e.js.map → plug-CQYGgGSy.js.map} +1 -1
- package/bizar-dash/dist/assets/{plus-M6LYiKX8.js → plus-fFxt-8z5.js} +1 -1
- package/bizar-dash/dist/assets/{plus-M6LYiKX8.js.map → plus-fFxt-8z5.js.map} +1 -1
- package/bizar-dash/dist/assets/{power-DP5x_SRF.js → power-Cjgy2KsI.js} +1 -1
- package/bizar-dash/dist/assets/{power-DP5x_SRF.js.map → power-Cjgy2KsI.js.map} +1 -1
- package/bizar-dash/dist/assets/{power-off-NOJRLAIs.js → power-off-B81hLRah.js} +1 -1
- package/bizar-dash/dist/assets/{power-off-NOJRLAIs.js.map → power-off-B81hLRah.js.map} +1 -1
- package/bizar-dash/dist/assets/{refresh-ccw-CHMprsm5.js → refresh-ccw-BCXx86xp.js} +1 -1
- package/bizar-dash/dist/assets/{refresh-ccw-CHMprsm5.js.map → refresh-ccw-BCXx86xp.js.map} +1 -1
- package/bizar-dash/dist/assets/{refresh-cw-1RBqDKgJ.js → refresh-cw-COTg1nZo.js} +1 -1
- package/bizar-dash/dist/assets/{refresh-cw-1RBqDKgJ.js.map → refresh-cw-COTg1nZo.js.map} +1 -1
- package/bizar-dash/dist/assets/{rotate-ccw-DJRekBbi.js → rotate-ccw-BZrEVc0Z.js} +1 -1
- package/bizar-dash/dist/assets/{rotate-ccw-DJRekBbi.js.map → rotate-ccw-BZrEVc0Z.js.map} +1 -1
- package/bizar-dash/dist/assets/{rotate-cw-DHooc0Tx.js → rotate-cw-CsTtaLV2.js} +1 -1
- package/bizar-dash/dist/assets/{rotate-cw-DHooc0Tx.js.map → rotate-cw-CsTtaLV2.js.map} +1 -1
- package/bizar-dash/dist/assets/{save-DQKCKyZK.js → save-DtMTge1F.js} +1 -1
- package/bizar-dash/dist/assets/{save-DQKCKyZK.js.map → save-DtMTge1F.js.map} +1 -1
- package/bizar-dash/dist/assets/{send-xgy3RAi7.js → send-DyBhyk8t.js} +1 -1
- package/bizar-dash/dist/assets/{send-xgy3RAi7.js.map → send-DyBhyk8t.js.map} +1 -1
- package/bizar-dash/dist/assets/{shield-alert-CWV2F5ab.js → shield-alert-BPGJlRF8.js} +1 -1
- package/bizar-dash/dist/assets/{shield-alert-CWV2F5ab.js.map → shield-alert-BPGJlRF8.js.map} +1 -1
- package/bizar-dash/dist/assets/{trash-2-CL4khSRv.js → trash-2-2NsyTU8c.js} +1 -1
- package/bizar-dash/dist/assets/{trash-2-CL4khSRv.js.map → trash-2-2NsyTU8c.js.map} +1 -1
- package/bizar-dash/dist/assets/{trending-up-CSJJhWTS.js → trending-up-BXwP23VK.js} +1 -1
- package/bizar-dash/dist/assets/{trending-up-CSJJhWTS.js.map → trending-up-BXwP23VK.js.map} +1 -1
- package/bizar-dash/dist/assets/{triangle-alert-BPLb7uLt.js → triangle-alert-DFmQ_RXN.js} +1 -1
- package/bizar-dash/dist/assets/{triangle-alert-BPLb7uLt.js.map → triangle-alert-DFmQ_RXN.js.map} +1 -1
- package/bizar-dash/dist/assets/{upload-Cww6MvFq.js → upload-Cj-HYRYV.js} +1 -1
- package/bizar-dash/dist/assets/{upload-Cww6MvFq.js.map → upload-Cj-HYRYV.js.map} +1 -1
- package/bizar-dash/dist/assets/{zap-DmhVUzRA.js → zap-BaOiybcl.js} +1 -1
- package/bizar-dash/dist/assets/{zap-DmhVUzRA.js.map → zap-BaOiybcl.js.map} +1 -1
- package/bizar-dash/dist/index.html +1 -1
- package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- 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/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/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/routes/update.mjs +48 -21
- package/bizar-dash/src/server/sprint.test.mjs +163 -0
- package/bizar-dash/src/web/v8/App.tsx +0 -4
- package/bizar-dash/src/web/v8/__tests__/new-views.test.tsx +0 -17
- package/bizar-dash/src/web/v8/shell/Sidebar.tsx +0 -1
- 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/src/web/v8/views/Router.tsx +0 -5
- package/bizar-dash/tests/_update-test-helpers.mjs +13 -0
- package/bizar-dash/tests/update-check.test.mjs +18 -13
- package/bizar-dash/tests/update-packages.test.mjs +154 -0
- package/bizar-dash/tests/update-run.test.mjs +17 -80
- package/cli/banner.mjs +1 -1
- package/cli/bin.mjs +8 -1
- package/cli/bootstrap.mjs +1 -1
- package/cli/commands/claude-cmd.mjs +1 -1
- package/cli/commands/deploy/docker.mjs +0 -3
- package/cli/install/banner.mjs +67 -0
- package/cli/install/banner.test.mjs +39 -0
- package/cli/install/index.mjs +32 -0
- package/cli/install/index.test.mjs +61 -0
- package/cli/install/paths.mjs +80 -0
- package/cli/install/paths.test.mjs +77 -0
- package/cli/install/plugin.mjs +161 -0
- package/cli/install/postinstall.mjs +111 -0
- package/cli/install.mjs +13 -600
- package/cli/migrate.mjs +157 -0
- package/cli/migrate.test.mjs +85 -0
- package/cli/provision.mjs +615 -1918
- package/cli/provision.test.mjs +1 -1
- package/cli/service-controller.test.mjs +0 -1
- package/cli/service-env.mjs +0 -1
- package/cli/utils.mjs +0 -1
- package/install.sh +54 -466
- 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/install-curl.sh +17 -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
- package/bizar-dash/dist/assets/HeadroomView-CNc-TI7u.js +0 -11
- package/bizar-dash/dist/assets/HeadroomView-CNc-TI7u.js.map +0 -1
- package/bizar-dash/dist/assets/MiscView-BBcpUbZZ.js +0 -1
- package/bizar-dash/dist/assets/ObsidianView-D3bI0row.js +0 -1
- package/bizar-dash/dist/assets/main-CdOm1Pj9.js.map +0 -1
- package/bizar-dash/src/web/v8/views/Headroom/HeadroomView.tsx +0 -201
- package/bizar-dash/tests/graphify-mod-spawn.node.test.mjs +0 -90
- package/bizar-dash/tests/v7-archive/mobile-overview.test.tsx +0 -214
- package/bizar-dash/tests/v7-archive/mobile-settings.test.tsx +0 -181
- package/bizar-dash/tests/v7-archive/navigation/CommandPalette.test.tsx +0 -105
- package/bizar-dash/tests/v7-archive/settings-nav.test.tsx +0 -128
- package/bizar-dash/tests/v7-archive/views/Overview.test.tsx +0 -413
- package/cli/provision-claude.mjs +0 -1176
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scripts/audit.mjs
|
|
3
|
+
*
|
|
4
|
+
* Pillar B — Self-auditing: harness score 0-100 across 12 categories.
|
|
5
|
+
*
|
|
6
|
+
* Each category scores 0-10. Weighted sum gives 0-100.
|
|
7
|
+
* Deterministic — same repo state always yields same score.
|
|
8
|
+
* Evidence string captured per category for transparency.
|
|
9
|
+
*
|
|
10
|
+
* Categories:
|
|
11
|
+
* typecheck tests e2e arch-boundaries security-patterns
|
|
12
|
+
* doc-sync feature-list-state clean-state perf-budget
|
|
13
|
+
* coverage observability drift
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* node scripts/audit.mjs → JSON to stdout
|
|
17
|
+
* node scripts/audit.mjs --write → also write .harness/audit/latest.json
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { execSync } from 'node:child_process';
|
|
21
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';
|
|
22
|
+
import { join, dirname, resolve } from 'node:path';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
24
|
+
|
|
25
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const ROOT = resolve(__dirname, '..');
|
|
27
|
+
const HARNESS_AUDIT_DIR = join(ROOT, '.harness', 'audit');
|
|
28
|
+
const LATEST_PATH = join(HARNESS_AUDIT_DIR, 'latest.json');
|
|
29
|
+
const FEATURE_LIST = join(ROOT, 'feature_list.json');
|
|
30
|
+
const PROGRESS_MD = join(ROOT, '.bizar', 'PROGRESS.md');
|
|
31
|
+
const ARCH_RULES = join(ROOT, '.harness', 'arch-rules.json');
|
|
32
|
+
const CLAUDE_MD = join(ROOT, 'CLAUDE.md');
|
|
33
|
+
const AGENTS_MD = join(ROOT, 'AGENTS.md');
|
|
34
|
+
|
|
35
|
+
const WRITE = process.argv.includes('--write');
|
|
36
|
+
|
|
37
|
+
// ── Category definitions ──────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {{ score: number, evidence: string }} CategoryResult
|
|
41
|
+
* @typedef {Record<string, CategoryResult>} AuditResult
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/** Run a shell command, return {ok, stdout, stderr, exitCode}. */
|
|
45
|
+
function run(cmd, cwd = ROOT) {
|
|
46
|
+
try {
|
|
47
|
+
const stdout = execSync(cmd, { cwd, encoding: 'utf8', timeout: 120_000 });
|
|
48
|
+
return { ok: true, stdout: stdout.trim(), stderr: '', exitCode: 0 };
|
|
49
|
+
} catch (err) {
|
|
50
|
+
return {
|
|
51
|
+
ok: false,
|
|
52
|
+
stdout: err.stdout?.trim() ?? '',
|
|
53
|
+
stderr: err.stderr?.trim() ?? '',
|
|
54
|
+
exitCode: err.status ?? 1,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Score 10 if ok, 0 if not. */
|
|
60
|
+
function scoreOk(r) {
|
|
61
|
+
return r.exitCode === 0 ? 10 : 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Score 10 if pass in last line, 0 otherwise. */
|
|
65
|
+
function scorePassInLastLine(r) {
|
|
66
|
+
if (r.exitCode !== 0) return 0;
|
|
67
|
+
const lines = r.stdout.split('\n');
|
|
68
|
+
const last = lines[lines.length - 1]?.toLowerCase() ?? '';
|
|
69
|
+
return last.includes('pass') || last.includes('ok') || last.includes('0 errors') ? 10 : 5;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ── Individual category checks ────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
function checkTypecheck() {
|
|
75
|
+
const r = run('bunx tsc --noEmit 2>&1');
|
|
76
|
+
const score = r.exitCode === 0 ? 10 : 0;
|
|
77
|
+
const evidence = score === 10
|
|
78
|
+
? 'bunx tsc --noEmit exited 0'
|
|
79
|
+
: `bunx tsc --noEmit failed:\n${r.stdout.split('\n').slice(-3).join('\n')}`;
|
|
80
|
+
return { score, evidence };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function checkTests() {
|
|
84
|
+
// Run make test and look for pass/fail summary in last 5 lines
|
|
85
|
+
const r = run('make test 2>&1');
|
|
86
|
+
const lines = r.stdout.split('\n').slice(-5).join(' ');
|
|
87
|
+
const lower = lines.toLowerCase();
|
|
88
|
+
let score = 0;
|
|
89
|
+
if (lower.includes('pass') || lower.includes('ok')) score = 10;
|
|
90
|
+
else if (r.exitCode === 0) score = 10;
|
|
91
|
+
else score = 0;
|
|
92
|
+
const evidence = `make test exit ${r.exitCode}: ${lines.slice(-100)}`;
|
|
93
|
+
return { score, evidence };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function checkE2e() {
|
|
97
|
+
const r = run('bun run scripts/bh-full-e2e.mjs 2>&1');
|
|
98
|
+
const lastLine = r.stdout.split('\n').slice(-2).join(' ').toLowerCase();
|
|
99
|
+
const score = (r.exitCode === 0 || lastLine.includes('pass') || lastLine.includes('13/13'))
|
|
100
|
+
? 10
|
|
101
|
+
: lastLine.includes('14/14') ? 10
|
|
102
|
+
: lastLine.includes('22/22') ? 10
|
|
103
|
+
: 0;
|
|
104
|
+
const evidence = `bh-full-e2e.mjs exit ${r.exitCode}: ${r.stdout.split('\n').slice(-2).join(' ')}`;
|
|
105
|
+
return { score, evidence };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function checkArchBoundaries() {
|
|
109
|
+
const r = run('bash scripts/check-arch.sh . 2>&1');
|
|
110
|
+
const score = r.exitCode === 0 ? 10 : 0;
|
|
111
|
+
const evidence = score === 10
|
|
112
|
+
? 'check-arch.sh passed'
|
|
113
|
+
: `check-arch.sh failed:\n${r.stdout.split('\n').slice(-3).join('\n')}`;
|
|
114
|
+
return { score, evidence };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function checkSecurityPatterns() {
|
|
118
|
+
// Scan for common security issues in config files
|
|
119
|
+
let score = 10;
|
|
120
|
+
const issues = [];
|
|
121
|
+
|
|
122
|
+
// Check settings.json for dangerous patterns
|
|
123
|
+
const settingsPath = join(ROOT, '.claude', 'settings.json');
|
|
124
|
+
if (existsSync(settingsPath)) {
|
|
125
|
+
try {
|
|
126
|
+
const content = readFileSync(settingsPath, 'utf8');
|
|
127
|
+
// Check for hardcoded API keys (simple heuristic)
|
|
128
|
+
if (/api[_-]?key["\s:]+["'][a-zA-Z0-9_-]{20,}/.test(content)) {
|
|
129
|
+
score -= 2;
|
|
130
|
+
issues.push('possible hardcoded API key in settings.json');
|
|
131
|
+
}
|
|
132
|
+
// Check for dangerous shell exec patterns
|
|
133
|
+
if (/shell:\s*true/.test(content)) {
|
|
134
|
+
score -= 1;
|
|
135
|
+
issues.push('shell:true found in settings');
|
|
136
|
+
}
|
|
137
|
+
} catch {
|
|
138
|
+
// ignore
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Check .claude/agents for unsafe tool grants
|
|
143
|
+
const agentsDir = join(ROOT, '.claude', 'agents');
|
|
144
|
+
if (existsSync(agentsDir)) {
|
|
145
|
+
for (const file of readdirSync(agentsDir)) {
|
|
146
|
+
if (!file.endsWith('.md')) continue;
|
|
147
|
+
const content = readFileSync(join(agentsDir, file), 'utf8');
|
|
148
|
+
// Flag commands that allow arbitrary bash without scoping
|
|
149
|
+
if (/allowed-tools:.*\*\*/.test(content)) {
|
|
150
|
+
score -= 2;
|
|
151
|
+
issues.push(`${file}: overly broad allowed-tools:*`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
score = Math.max(0, score);
|
|
157
|
+
const evidence = issues.length === 0
|
|
158
|
+
? 'No security pattern issues found'
|
|
159
|
+
: `Issues: ${issues.join('; ')}`;
|
|
160
|
+
return { score, evidence };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function checkDocSync() {
|
|
164
|
+
// Check AGENTS.md and CLAUDE.md are in sync (mirror check)
|
|
165
|
+
const r = run('bash scripts/mirror-claude-md.sh --check 2>&1');
|
|
166
|
+
const score = r.exitCode === 0 ? 10 : 0;
|
|
167
|
+
const evidence = score === 10
|
|
168
|
+
? 'AGENTS.md and CLAUDE.md in sync'
|
|
169
|
+
: 'Mirror check failed — run make mirror-claude-md';
|
|
170
|
+
return { score, evidence };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function checkFeatureListState() {
|
|
174
|
+
if (!existsSync(FEATURE_LIST)) {
|
|
175
|
+
return { score: 0, evidence: 'feature_list.json not found' };
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const raw = JSON.parse(readFileSync(FEATURE_LIST, 'utf8'));
|
|
179
|
+
const features = raw.features ?? [];
|
|
180
|
+
if (features.length === 0) return { score: 0, evidence: 'feature_list.json has 0 features' };
|
|
181
|
+
|
|
182
|
+
const notStarted = features.filter((f) => f.state === 'not_started').length;
|
|
183
|
+
const passing = features.filter((f) => f.state === 'passing').length;
|
|
184
|
+
const active = features.filter((f) => f.state === 'active').length;
|
|
185
|
+
const total = features.length;
|
|
186
|
+
|
|
187
|
+
// Penalise not_started features (WIP=1 violation) and missing evidence
|
|
188
|
+
const noEvidence = features.filter((f) => f.state === 'passing' && !f.evidence).length;
|
|
189
|
+
let score = 10;
|
|
190
|
+
if (notStarted > 1) score -= 2;
|
|
191
|
+
if (noEvidence > 0) score -= 1;
|
|
192
|
+
score = Math.max(0, score);
|
|
193
|
+
|
|
194
|
+
const evidence = `features=${total} passing=${passing} active=${active} not_started=${notStarted} missing_evidence=${noEvidence}`;
|
|
195
|
+
return { score, evidence };
|
|
196
|
+
} catch (err) {
|
|
197
|
+
return { score: 0, evidence: `feature_list.json parse error: ${err.message}` };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function checkCleanState() {
|
|
202
|
+
// Check for console.log / debugger / .only() in production code
|
|
203
|
+
const r = run(
|
|
204
|
+
"grep -rEn '(console\\.log|debugger|\\.only\\()' packages/sdk/src plugins/bizar/index.ts plugins/bizar/src --include='*.ts' --include='*.mjs' 2>/dev/null | grep -v test | grep -v '\\.test\\.' || true"
|
|
205
|
+
);
|
|
206
|
+
const lines = r.stdout.trim().split('\n').filter(Boolean);
|
|
207
|
+
const score = lines.length === 0 ? 10 : 0;
|
|
208
|
+
const evidence = lines.length === 0
|
|
209
|
+
? 'No debug artifacts found'
|
|
210
|
+
: `Found ${lines.length} debug artifacts:\n${lines.slice(0, 3).join('\n')}`;
|
|
211
|
+
return { score, evidence };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function checkPerfBudget() {
|
|
215
|
+
// Check that no single file exceeds reasonable line count (simple proxy)
|
|
216
|
+
const TOO_BIG = 2000;
|
|
217
|
+
let violations = [];
|
|
218
|
+
for (const dir of [join(ROOT, 'packages', 'sdk', 'src'), join(ROOT, 'plugins', 'bizar', 'src')]) {
|
|
219
|
+
if (!existsSync(dir)) continue;
|
|
220
|
+
for (const file of readdirSync(dir, { recursive: true })) {
|
|
221
|
+
if (!file.endsWith('.ts') && !file.endsWith('.mjs')) continue;
|
|
222
|
+
const full = join(dir, file);
|
|
223
|
+
try {
|
|
224
|
+
const lines = readFileSync(full, 'utf8').split('\n');
|
|
225
|
+
if (lines.length > TOO_BIG) {
|
|
226
|
+
violations.push(`${file}:${lines.length} lines`);
|
|
227
|
+
}
|
|
228
|
+
} catch {
|
|
229
|
+
// ignore unreadable
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const score = violations.length === 0 ? 10 : Math.max(0, 10 - violations.length);
|
|
234
|
+
const evidence = violations.length === 0
|
|
235
|
+
? 'No file exceeds 2000 lines'
|
|
236
|
+
: `Files over 2000 lines: ${violations.slice(0, 3).join(', ')}`;
|
|
237
|
+
return { score, evidence };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function checkCoverage() {
|
|
241
|
+
// ponytail: coverage is not currently measured. When coverage tooling
|
|
242
|
+
// (c8, vitest coverage) is added, replace this stub with actual coverage check.
|
|
243
|
+
// Heuristic: check if any coverage-related config exists
|
|
244
|
+
const hasCoverageConfig = existsSync(join(ROOT, 'coverage'))
|
|
245
|
+
|| existsSync(join(ROOT, '.nycrc'))
|
|
246
|
+
|| existsSync(join(ROOT, 'vitest.config.ts'));
|
|
247
|
+
const score = hasCoverageConfig ? 7 : 3;
|
|
248
|
+
const evidence = hasCoverageConfig
|
|
249
|
+
? 'Coverage tooling config found (actual coverage not measured)'
|
|
250
|
+
: 'No coverage tooling configured — add c8/vitest coverage to measure';
|
|
251
|
+
return { score, evidence };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function checkObservability() {
|
|
255
|
+
// Check session traces directory exists and is writable
|
|
256
|
+
const tracesDir = join(ROOT, '.harness', 'traces');
|
|
257
|
+
const hasSessionsFile = existsSync(join(tracesDir, 'sessions.jsonl'));
|
|
258
|
+
const hasArchRules = existsSync(ARCH_RULES);
|
|
259
|
+
const score = hasSessionsFile && hasArchRules ? 10 : hasArchRules ? 7 : 3;
|
|
260
|
+
const evidence = `sessions.jsonl=${hasSessionsFile} arch-rules.json=${hasArchRules}`;
|
|
261
|
+
return { score, evidence };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function checkDrift() {
|
|
265
|
+
// Compare latest audit score vs previous
|
|
266
|
+
if (!existsSync(LATEST_PATH)) {
|
|
267
|
+
return { score: 10, evidence: 'No prior audit — drift check skipped (first run)' };
|
|
268
|
+
}
|
|
269
|
+
try {
|
|
270
|
+
const prev = JSON.parse(readFileSync(LATEST_PATH, 'utf8'));
|
|
271
|
+
const prevTotal = prev.total ?? 0;
|
|
272
|
+
const categories = prev.categories ?? {};
|
|
273
|
+
const prevDiffs = Object.entries(categories)
|
|
274
|
+
.map(([k, v]) => `${k}:${v.score}`)
|
|
275
|
+
.sort()
|
|
276
|
+
.join(',');
|
|
277
|
+
|
|
278
|
+
// Compute current scores for comparison
|
|
279
|
+
const currentScores = {
|
|
280
|
+
typecheck: checkTypecheck().score,
|
|
281
|
+
tests: checkTests().score,
|
|
282
|
+
e2e: checkE2e().score,
|
|
283
|
+
archBoundaries: checkArchBoundaries().score,
|
|
284
|
+
securityPatterns: checkSecurityPatterns().score,
|
|
285
|
+
docSync: checkDocSync().score,
|
|
286
|
+
featureListState: checkFeatureListState().score,
|
|
287
|
+
cleanState: checkCleanState().score,
|
|
288
|
+
perfBudget: checkPerfBudget().score,
|
|
289
|
+
coverage: checkCoverage().score,
|
|
290
|
+
observability: checkObservability().score,
|
|
291
|
+
};
|
|
292
|
+
const currentTotal = computeTotal(currentScores);
|
|
293
|
+
const currentDiffs = Object.entries(currentScores)
|
|
294
|
+
.map(([k, v]) => `${k}:${v}`)
|
|
295
|
+
.sort()
|
|
296
|
+
.join(',');
|
|
297
|
+
|
|
298
|
+
const changed = prevDiffs !== currentDiffs;
|
|
299
|
+
const delta = currentTotal - prevTotal;
|
|
300
|
+
const score = changed ? 10 : 0; // Any change = good (we're tracking)
|
|
301
|
+
const evidence = changed
|
|
302
|
+
? `Score changed: ${prevTotal}→${currentTotal} (Δ${delta >= 0 ? '+' : ''}${delta})`
|
|
303
|
+
: `Score unchanged: ${prevTotal} (no drift since last audit)`;
|
|
304
|
+
return { score, evidence };
|
|
305
|
+
} catch (err) {
|
|
306
|
+
return { score: 5, evidence: `Could not read prior audit: ${err.message}` };
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ── Weights and aggregation ───────────────────────────────────────────────────
|
|
311
|
+
|
|
312
|
+
/** @type {Record<string, number>} */
|
|
313
|
+
const WEIGHTS = {
|
|
314
|
+
typecheck: 0.15,
|
|
315
|
+
tests: 0.12,
|
|
316
|
+
e2e: 0.12,
|
|
317
|
+
archBoundaries: 0.08,
|
|
318
|
+
securityPatterns: 0.10,
|
|
319
|
+
docSync: 0.05,
|
|
320
|
+
featureListState: 0.10,
|
|
321
|
+
cleanState: 0.08,
|
|
322
|
+
perfBudget: 0.05,
|
|
323
|
+
coverage: 0.05,
|
|
324
|
+
observability: 0.05,
|
|
325
|
+
drift: 0.05,
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* @param {Record<string, number>} scores
|
|
330
|
+
* @returns {number}
|
|
331
|
+
*/
|
|
332
|
+
function computeTotal(scores) {
|
|
333
|
+
let total = 0;
|
|
334
|
+
for (const [key, weight] of Object.entries(WEIGHTS)) {
|
|
335
|
+
total += (scores[key] ?? 0) * weight;
|
|
336
|
+
}
|
|
337
|
+
return Math.round(total * 10) / 10; // round to 1 decimal
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
341
|
+
|
|
342
|
+
function main() {
|
|
343
|
+
const startedAt = new Date().toISOString();
|
|
344
|
+
|
|
345
|
+
const categories = {
|
|
346
|
+
typecheck: checkTypecheck(),
|
|
347
|
+
tests: checkTests(),
|
|
348
|
+
e2e: checkE2e(),
|
|
349
|
+
archBoundaries: checkArchBoundaries(),
|
|
350
|
+
securityPatterns: checkSecurityPatterns(),
|
|
351
|
+
docSync: checkDocSync(),
|
|
352
|
+
featureListState: checkFeatureListState(),
|
|
353
|
+
cleanState: checkCleanState(),
|
|
354
|
+
perfBudget: checkPerfBudget(),
|
|
355
|
+
coverage: checkCoverage(),
|
|
356
|
+
observability: checkObservability(),
|
|
357
|
+
drift: checkDrift(),
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
/** @type {Record<string, number>} */
|
|
361
|
+
const scores = {};
|
|
362
|
+
for (const [key, result] of Object.entries(categories)) {
|
|
363
|
+
scores[key] = result.score;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const total = computeTotal(scores);
|
|
367
|
+
|
|
368
|
+
/** @type {AuditResult} */
|
|
369
|
+
const result = {
|
|
370
|
+
version: '1.0.0',
|
|
371
|
+
startedAt,
|
|
372
|
+
completedAt: new Date().toISOString(),
|
|
373
|
+
total,
|
|
374
|
+
categories,
|
|
375
|
+
scores,
|
|
376
|
+
weights: WEIGHTS,
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
if (WRITE) {
|
|
380
|
+
mkdirSync(HARNESS_AUDIT_DIR, { recursive: true });
|
|
381
|
+
writeFileSync(LATEST_PATH, JSON.stringify(result, null, 2) + '\n', 'utf8');
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Always print JSON to stdout
|
|
385
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
main();
|
|
@@ -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
|
+
}
|