@maycombhq/atticus 0.0.0-stage → 2.0.0-beta.10
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/LICENSE +177 -0
- package/README.md +17 -2
- package/THIRD-PARTY-LICENSES.txt +2607 -0
- package/dist/bin.js +47 -0
- package/dist/chunk-23M2LUYM.js +55 -0
- package/dist/chunk-26PLPSQW.js +48 -0
- package/dist/chunk-2G4DFUWJ.js +43 -0
- package/dist/chunk-2HMWX4T6.js +226 -0
- package/dist/chunk-2KDSXJFJ.js +294 -0
- package/dist/chunk-2M6WXO73.js +38 -0
- package/dist/chunk-2RX7PI2Y.js +166 -0
- package/dist/chunk-2XM6PYQQ.js +199 -0
- package/dist/chunk-3G4YI4ZU.js +16 -0
- package/dist/chunk-3GTZ2OON.js +362 -0
- package/dist/chunk-3QAKDIEU.js +108 -0
- package/dist/chunk-47QE246J.js +6115 -0
- package/dist/chunk-4GZXS6S2.js +5401 -0
- package/dist/chunk-4PJADZRR.js +545 -0
- package/dist/chunk-4Q3NN7VF.js +32 -0
- package/dist/chunk-4QUQEBFT.js +143 -0
- package/dist/chunk-4SKZDI62.js +22 -0
- package/dist/chunk-4SVJNVEE.js +80 -0
- package/dist/chunk-52R2YXP5.js +80 -0
- package/dist/chunk-577AJVRS.js +44694 -0
- package/dist/chunk-5F3RJCHB.js +45 -0
- package/dist/chunk-5IALSMZ2.js +34 -0
- package/dist/chunk-5KTOPOEP.js +174 -0
- package/dist/chunk-5MI2SEV5.js +242 -0
- package/dist/chunk-5XDNUJ4P.js +2743 -0
- package/dist/chunk-5XN76FTF.js +22 -0
- package/dist/chunk-5YIBJXGB.js +2007 -0
- package/dist/chunk-62YMDXY6.js +38 -0
- package/dist/chunk-65C57N7P.js +116 -0
- package/dist/chunk-67J3VZBA.js +120 -0
- package/dist/chunk-6EDQE7GO.js +27809 -0
- package/dist/chunk-6GNS32SI.js +285 -0
- package/dist/chunk-6GWHKMQR.js +405 -0
- package/dist/chunk-6JLPXDWN.js +7923 -0
- package/dist/chunk-6TJCUYC5.js +22 -0
- package/dist/chunk-6VBXAGJX.js +234 -0
- package/dist/chunk-73VPWF5R.js +415 -0
- package/dist/chunk-76WKMA4J.js +14 -0
- package/dist/chunk-7DUDLVFJ.js +52 -0
- package/dist/chunk-7EFILTCI.js +67 -0
- package/dist/chunk-7EKESFWV.js +130 -0
- package/dist/chunk-7F2RUWPU.js +101 -0
- package/dist/chunk-7GB4ZRE2.js +378 -0
- package/dist/chunk-7TGYNOXD.js +189 -0
- package/dist/chunk-7UKVYFQ2.js +2078 -0
- package/dist/chunk-7URGP4DD.js +102 -0
- package/dist/chunk-7YYEKTYF.js +2522 -0
- package/dist/chunk-AJJK7GLO.js +252 -0
- package/dist/chunk-AKJTKMXU.js +25 -0
- package/dist/chunk-APSVL2AI.js +344 -0
- package/dist/chunk-AWECWW5Y.js +3206 -0
- package/dist/chunk-AY2IUF4K.js +5071 -0
- package/dist/chunk-B2VSVN4P.js +34 -0
- package/dist/chunk-B3O52AUQ.js +275 -0
- package/dist/chunk-B7434AE2.js +104 -0
- package/dist/chunk-BDEKREZH.js +155 -0
- package/dist/chunk-BJPMKJTL.js +1428 -0
- package/dist/chunk-BMCKL3VQ.js +324 -0
- package/dist/chunk-BMHGA7WD.js +25 -0
- package/dist/chunk-BUYJL3GJ.js +287 -0
- package/dist/chunk-BXIX7S7O.js +32 -0
- package/dist/chunk-CHC5FKTU.js +26 -0
- package/dist/chunk-CITDNDIZ.js +53 -0
- package/dist/chunk-CIUHAHM6.js +70 -0
- package/dist/chunk-CNKGYDMG.js +334 -0
- package/dist/chunk-CRE4BJHM.js +408 -0
- package/dist/chunk-D3PO4DOZ.js +261 -0
- package/dist/chunk-DAQBIK5G.js +47 -0
- package/dist/chunk-DDAHCPGA.js +212 -0
- package/dist/chunk-DPHCFZ7W.js +374 -0
- package/dist/chunk-DZRQDNPT.js +34 -0
- package/dist/chunk-E3JYO547.js +46 -0
- package/dist/chunk-E3Z67HRK.js +104 -0
- package/dist/chunk-EADP3PEN.js +450 -0
- package/dist/chunk-EX6ZXS5K.js +169 -0
- package/dist/chunk-EZIUFEN6.js +27 -0
- package/dist/chunk-F2FOQLJ7.js +430 -0
- package/dist/chunk-F7UQR4ZX.js +149 -0
- package/dist/chunk-F7YL2RZ4.js +94 -0
- package/dist/chunk-FEZE6NIA.js +69 -0
- package/dist/chunk-FJCFYKVO.js +1014 -0
- package/dist/chunk-FLOEZA6K.js +29 -0
- package/dist/chunk-FMKKVWZD.js +133 -0
- package/dist/chunk-FNHSVBRA.js +48 -0
- package/dist/chunk-FOGG4AUR.js +114 -0
- package/dist/chunk-FPGVOSOG.js +41 -0
- package/dist/chunk-FQSRHTTC.js +49 -0
- package/dist/chunk-FR7JPYBD.js +182 -0
- package/dist/chunk-FTV6I76U.js +211 -0
- package/dist/chunk-FV5YFR6I.js +540 -0
- package/dist/chunk-FZ2E5LGK.js +54 -0
- package/dist/chunk-FZL2HNWP.js +531 -0
- package/dist/chunk-G2E5MNYP.js +549 -0
- package/dist/chunk-G5SFOH7P.js +256 -0
- package/dist/chunk-GFXLPJWY.js +53 -0
- package/dist/chunk-GR6YKM55.js +480 -0
- package/dist/chunk-GXJVY6WT.js +250 -0
- package/dist/chunk-GZ4TKR3Q.js +928 -0
- package/dist/chunk-HB6XSGI6.js +131 -0
- package/dist/chunk-HE4ZQXZM.js +396 -0
- package/dist/chunk-HHUFVJF7.js +1019 -0
- package/dist/chunk-HKTUIMH4.js +76 -0
- package/dist/chunk-HUZQDKQK.js +48 -0
- package/dist/chunk-I7IWQIEB.js +58 -0
- package/dist/chunk-IDGSU7JF.js +1496 -0
- package/dist/chunk-ILFKECGI.js +69 -0
- package/dist/chunk-ISNZYKAS.js +53 -0
- package/dist/chunk-IUD3ME4E.js +251 -0
- package/dist/chunk-IV7BCWWN.js +101 -0
- package/dist/chunk-IZNSAEBX.js +164 -0
- package/dist/chunk-J5LHDJU4.js +400 -0
- package/dist/chunk-JAA53T23.js +172 -0
- package/dist/chunk-JF2NSTFO.js +47 -0
- package/dist/chunk-JMSEEBN4.js +41 -0
- package/dist/chunk-JQ7XQPQS.js +273 -0
- package/dist/chunk-KC5DROFC.js +16667 -0
- package/dist/chunk-KIAI6PWL.js +36 -0
- package/dist/chunk-KIHOIVST.js +18 -0
- package/dist/chunk-KM2FBM3T.js +659 -0
- package/dist/chunk-KQDZXGNR.js +14 -0
- package/dist/chunk-L4VJ4ETL.js +41 -0
- package/dist/chunk-LDKUC2QJ.js +49 -0
- package/dist/chunk-LK44ULGI.js +2043 -0
- package/dist/chunk-LRS573EX.js +104 -0
- package/dist/chunk-LTQANUDH.js +119 -0
- package/dist/chunk-LVGBKKSM.js +282 -0
- package/dist/chunk-MF7QH4AX.js +162 -0
- package/dist/chunk-MGVYVW7Q.js +915 -0
- package/dist/chunk-MH5OCRUM.js +240 -0
- package/dist/chunk-MKFYQQ5Q.js +36 -0
- package/dist/chunk-MKRBG5EF.js +1142 -0
- package/dist/chunk-MTAUZW2Z.js +2504 -0
- package/dist/chunk-MTEBM4Y4.js +39 -0
- package/dist/chunk-N3NBWIYQ.js +48 -0
- package/dist/chunk-N4QZJJVB.js +3851 -0
- package/dist/chunk-NGPU2ARF.js +1726 -0
- package/dist/chunk-NIXVDQVO.js +2929 -0
- package/dist/chunk-NNUASOAA.js +34 -0
- package/dist/chunk-NPCD54I7.js +226 -0
- package/dist/chunk-OEI7LVRP.js +856 -0
- package/dist/chunk-OGRWQ562.js +18 -0
- package/dist/chunk-OIU657QQ.js +95 -0
- package/dist/chunk-OJLRFGBP.js +128 -0
- package/dist/chunk-OS7PQ35Z.js +391 -0
- package/dist/chunk-OSGQW2Z6.js +104 -0
- package/dist/chunk-P3TKKBAH.js +131 -0
- package/dist/chunk-PEF5OEMB.js +163 -0
- package/dist/chunk-PGKHJYOB.js +706 -0
- package/dist/chunk-PIVKU64W.js +688 -0
- package/dist/chunk-PKCMRPPB.js +109 -0
- package/dist/chunk-QICFHVRW.js +15 -0
- package/dist/chunk-QKEFZRHS.js +166 -0
- package/dist/chunk-QWVBEK4P.js +113 -0
- package/dist/chunk-R4TBNKKC.js +3217 -0
- package/dist/chunk-R64GLVBO.js +2919 -0
- package/dist/chunk-R6B6XAKK.js +56 -0
- package/dist/chunk-R6RY4Q5T.js +51 -0
- package/dist/chunk-RK6MKKKP.js +471 -0
- package/dist/chunk-ROTY5J2L.js +25 -0
- package/dist/chunk-RRVT4MPS.js +150 -0
- package/dist/chunk-SS66Q4VE.js +231 -0
- package/dist/chunk-T4RTWMJD.js +310 -0
- package/dist/chunk-T6USVBEJ.js +105 -0
- package/dist/chunk-TM7LC37P.js +4044 -0
- package/dist/chunk-TV45EUGM.js +51 -0
- package/dist/chunk-TZDSDDSV.js +233 -0
- package/dist/chunk-UBWCUP3G.js +55 -0
- package/dist/chunk-UF7TAFJM.js +906 -0
- package/dist/chunk-UR7RMZ3R.js +19943 -0
- package/dist/chunk-UYYN2IUZ.js +1611 -0
- package/dist/chunk-V2TH46ZY.js +66 -0
- package/dist/chunk-VKMTFIWS.js +315 -0
- package/dist/chunk-VOGCSYOI.js +16 -0
- package/dist/chunk-W5OYJMBT.js +41 -0
- package/dist/chunk-WARBF2KY.js +296 -0
- package/dist/chunk-WGZWR4RF.js +130 -0
- package/dist/chunk-WLIIQ2A4.js +18 -0
- package/dist/chunk-X4W4XXWP.js +134 -0
- package/dist/chunk-XB5RFZHH.js +1217 -0
- package/dist/chunk-XEG3FE2R.js +766 -0
- package/dist/chunk-XIWYUQZQ.js +583 -0
- package/dist/chunk-XKS6THI5.js +31 -0
- package/dist/chunk-XKZHAD6Y.js +184 -0
- package/dist/chunk-XNYNEEBR.js +214 -0
- package/dist/chunk-XPCWQXTY.js +37 -0
- package/dist/chunk-XPEEICAO.js +325 -0
- package/dist/chunk-XQ3YIV5R.js +3507 -0
- package/dist/chunk-XRO65C7V.js +1079 -0
- package/dist/chunk-XTVDAHH2.js +203 -0
- package/dist/chunk-XZRON6IV.js +131 -0
- package/dist/chunk-Y4BRGOJR.js +523 -0
- package/dist/chunk-Y5PZHS4K.js +3898 -0
- package/dist/chunk-Y7KBQ4BD.js +230 -0
- package/dist/chunk-YFSSNGLR.js +277 -0
- package/dist/chunk-YGKYFMSL.js +60 -0
- package/dist/chunk-YGR6OCHQ.js +345 -0
- package/dist/chunk-YIEOFHOL.js +30 -0
- package/dist/chunk-YLAJAI2W.js +180 -0
- package/dist/chunk-YMOJD5BO.js +50 -0
- package/dist/chunk-YPBQNJHA.js +33 -0
- package/dist/chunk-YPIEYQF4.js +307 -0
- package/dist/chunk-YVLMBAYE.js +221 -0
- package/dist/chunk-YXJ4XOHF.js +376 -0
- package/dist/chunk-ZE3P35DA.js +1247 -0
- package/dist/chunk-ZONE6E3Z.js +32 -0
- package/dist/chunk-ZP4FJHR4.js +95 -0
- package/dist/chunk-ZZXF6OOJ.js +119 -0
- package/dist/files.json +1976 -0
- package/dist/fixtures/forge/README.md +19 -0
- package/dist/fixtures/forge/expected/discrepancies.yaml +54 -0
- package/dist/fixtures/forge/expected/inputs.yaml +38 -0
- package/dist/fixtures/forge/expected/scan.yaml +342 -0
- package/dist/fixtures/forge/inputs/diagrams/forge-context.drawio +23 -0
- package/dist/fixtures/forge/inputs/partner-specs/northwind-pay-openapi.yaml +11 -0
- package/dist/fixtures/forge/inputs/tickets/ado-stories.json +4 -0
- package/dist/fixtures/forge/inputs/tickets/closed-service-requests.json +5 -0
- package/dist/fixtures/forge/inputs/wiki/integration-patterns.md +11 -0
- package/dist/fixtures/forge/inputs/wiki/ledger-solution-spec.md +9 -0
- package/dist/fixtures/forge/inputs/wiki/snow-firewall-request.md +4 -0
- package/dist/fixtures/forge/metarepo/judgments/reaches/84af5b6af24f7ca8c23013f3.yaml +17 -0
- package/dist/fixtures/forge/metarepo/judgments/reaches/c3917555edebddce8cecb647.yaml +17 -0
- package/dist/fixtures/forge/metarepo/judgments/reaches/c5fafd6601f8ca9e34e0bbd0.yaml +17 -0
- package/dist/fixtures/forge/metarepo/org-pack/agents/architecture-critic.yaml +9 -0
- package/dist/fixtures/forge/metarepo/org-pack/agents/story-writer.yaml +10 -0
- package/dist/fixtures/forge/metarepo/org-pack/checklists/architecture.yaml +115 -0
- package/dist/fixtures/forge/metarepo/org-pack/dialects/forge.yaml +111 -0
- package/dist/fixtures/forge/metarepo/org-pack/gates.yaml +22 -0
- package/dist/fixtures/forge/metarepo/org-pack/pack.yaml +11 -0
- package/dist/fixtures/forge/metarepo/org-pack/patterns/A.EB.PUB.yaml +12 -0
- package/dist/fixtures/forge/metarepo/org-pack/patterns/D.JDBC.yaml +12 -0
- package/dist/fixtures/forge/metarepo/org-pack/patterns/S.DIR.BFF.yaml +12 -0
- package/dist/fixtures/forge/metarepo/org-pack/patterns/S.GW.SOR.yaml +13 -0
- package/dist/fixtures/forge/metarepo/org-pack/patterns/S.GW.VND.yaml +14 -0
- package/dist/fixtures/forge/metarepo/org-pack/process/change-templates/feature.md +37 -0
- package/dist/fixtures/forge/metarepo/org-pack/process/skills/add-forge-endpoint/SKILL.md +17 -0
- package/dist/fixtures/forge/metarepo/org-pack/process/snow/firewall-rule.yaml +14 -0
- package/dist/fixtures/forge/metarepo/org-pack/process/snow/gateway-route.yaml +13 -0
- package/dist/fixtures/forge/metarepo/org-pack/profiles/forge/profile.yaml +12 -0
- package/dist/fixtures/forge/metarepo/org-pack/profiles/forge/repos.manifest.yaml +44 -0
- package/dist/fixtures/forge/metarepo/org-pack/roles.yaml +14 -0
- package/dist/fixtures/forge/metarepo/org-pack/runtime.yaml +14 -0
- package/dist/fixtures/forge/metarepo/org-pack/sources.yaml +3 -0
- package/dist/fixtures/forge/repos/forge-events/topics/payments.initiated.yaml +4 -0
- package/dist/fixtures/forge/repos/forge-events/topics/transfers.completed.yaml +4 -0
- package/dist/fixtures/forge/repos/forge-gateway/proxies/ledger-v1/apiproxy/ledger-v1.xml +3 -0
- package/dist/fixtures/forge/repos/forge-gateway/proxies/ledger-v1/apiproxy/policies/SpikeArrest.xml +3 -0
- package/dist/fixtures/forge/repos/forge-gateway/proxies/ledger-v1/apiproxy/policies/VerifyOAuth.xml +4 -0
- package/dist/fixtures/forge/repos/forge-gateway/proxies/ledger-v1/apiproxy/proxies/default.xml +14 -0
- package/dist/fixtures/forge/repos/forge-gateway/proxies/ledger-v1/apiproxy/targets/ledger.xml +5 -0
- package/dist/fixtures/forge/repos/forge-infra/alerts/ledger-api.yaml +5 -0
- package/dist/fixtures/forge/repos/forge-infra/k8s/ledger-api.yaml +15 -0
- package/dist/fixtures/forge/repos/forge-infra/k8s/portal-bff.yaml +7 -0
- package/dist/fixtures/forge/repos/forge-infra/terraform/ledger-db.tf +9 -0
- package/dist/fixtures/forge/repos/forge-ledger/api/ledger-openapi.yaml +31 -0
- package/dist/fixtures/forge/repos/forge-ledger/forge.yaml +4 -0
- package/dist/fixtures/forge/repos/forge-ledger/src/main/java/forge/ledger/LedgerEndpoints.java +13 -0
- package/dist/fixtures/forge/repos/forge-ledger/src/main/java/forge/ledger/LedgerEvents.java +11 -0
- package/dist/fixtures/forge/repos/forge-ledger/src/main/java/forge/ledger/NorthwindClient.java +10 -0
- package/dist/fixtures/forge/repos/forge-ledger/src/main/resources/application.yml +3 -0
- package/dist/fixtures/forge/repos/forge-ledger/src/main/resources/db/migration/V1__create_account.sql +5 -0
- package/dist/fixtures/forge/repos/forge-ledger/src/main/resources/db/migration/V2__create_entry.sql +7 -0
- package/dist/fixtures/forge/repos/forge-portal-bff/pom.xml +1 -0
- package/dist/fixtures/forge/repos/forge-portal-bff/src/main/java/forge/portal/AuditPublisher.java +12 -0
- package/dist/fixtures/forge/repos/forge-portal-bff/src/main/java/forge/portal/BalanceController.java +16 -0
- package/dist/fixtures/forge/repos/forge-portal-bff/src/main/java/forge/portal/LedgerClient.java +13 -0
- package/dist/fixtures/forge/repos/forge-portal-bff/src/main/java/forge/portal/PaymentEvents.java +13 -0
- package/dist/fixtures/forge/repos/forge-portal-bff/src/main/resources/application.yml +8 -0
- package/dist/fixtures/forge/repos/forge-portal-ui/package.json +1 -0
- package/dist/fixtures/forge/repos/forge-portal-ui/src/api/balance.ts +5 -0
- package/dist/fixtures/forge/repos/forge-reports-job/src/main/java/forge/reports/DailyEntriesJob.java +19 -0
- package/dist/fixtures/forge/repos/forge-reports-job/src/main/resources/application.yml +3 -0
- package/dist/fixtures/forge-change/README.md +41 -0
- package/dist/fixtures/forge-change/expected/findings.yaml +301 -0
- package/dist/fixtures/forge-change/expected/stories.yaml +101 -0
- package/dist/fixtures/forge-change/judgments/personal-data/0b5f3dbf5257a22275d8c74b.yaml +18 -0
- package/dist/fixtures/forge-change/judgments/personal-data/2759a66770134c417440bf2d.yaml +18 -0
- package/dist/fixtures/forge-change/judgments/personal-data/31b1ea2f14d0737f1aca79fd.yaml +18 -0
- package/dist/fixtures/forge-change/judgments/personal-data/49afd70c08228e65ad6ca1ed.yaml +18 -0
- package/dist/fixtures/forge-change/judgments/personal-data/761cabcb8a39f5e5866d2dd6.yaml +18 -0
- package/dist/fixtures/forge-change/judgments/personal-data/adae902b90cc8ffa0c765145.yaml +18 -0
- package/dist/fixtures/forge-change/judgments/personal-data/ba11bb933105ec9493e065dd.yaml +18 -0
- package/dist/fixtures/forge-change/judgments/personal-data/bdbcd1cdac3021c68063c3e7.yaml +18 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/brief.md +35 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/change.yaml +120 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/data.md +18 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/design.md +29 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/interfaces.md +17 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/mappings.md +26 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/proposal.md +35 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/snow.md +12 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/specs/transfer-limits/spec.md +19 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/stories/s1-refuse-over-limit.md +17 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/stories/s2-show-remaining.md +16 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/stories/s3-audit-refusals.md +18 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/stories/s4-velocity-check.md +16 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/stories.md +8 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/tasks.md +12 -0
- package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/ux.md +7 -0
- package/dist/fixtures/forge-change/org-pack/agents/code-and-test-critic.yaml +13 -0
- package/dist/fixtures/forge-change/org-pack/agents/complete-critic.yaml +12 -0
- package/dist/fixtures/forge-change/org-pack/agents/safe-critic.yaml +12 -0
- package/dist/fixtures/forge-change/org-pack/agents/ship-critic.yaml +11 -0
- package/dist/fixtures/forge-change/org-pack/agents/true-critic.yaml +11 -0
- package/dist/fixtures/forge-change/org-pack/checklists/review.yaml +54 -0
- package/dist/fixtures/forge-change/org-pack/gates.yaml +25 -0
- package/dist/fixtures/forge-change/org-pack/teams/team-ledger/checklists/ledger-team.yaml +227 -0
- package/dist/fixtures/forge-change/recordings/complete-critic/2ad84f84bf39c1d5412697e69fe23034d6983ec45a00314bf0975e627589a3c5.json +12 -0
- package/dist/fixtures/forge-change/recordings/complete-critic/417c98d8e54a4387d0fd93441c49f44c2868958739070d855c1dce0c41e24db3.json +12 -0
- package/dist/fixtures/forge-change/recordings/complete-critic/50203ee22e9cb3ef047295edb6538fe7d5695e1e4b139f3c929013dc6ce47021.json +12 -0
- package/dist/fixtures/forge-change/recordings/complete-critic/89f18d67f5d9c325bf35e2a9e020f9ddb6ee13b8523315037f0905cc57c040b1.json +12 -0
- package/dist/fixtures/forge-change/recordings/complete-critic/b4097867d26ae8eadc8b9efa8cdd86897013358035f0b40086c299a59c4ba121.json +12 -0
- package/dist/fixtures/forge-change/recordings/safe-critic/302d06f49664dd67ad2e21ab9cf74c1b0f7d3f750709c6ed8eff2b70983274e2.json +12 -0
- package/dist/fixtures/forge-change/recordings/safe-critic/32f1049c9a5ab4a759543290db4644ec6e221cc45fdfdcf06193837c6a8ce0b9.json +12 -0
- package/dist/fixtures/forge-change/recordings/safe-critic/4e4a84bfc04b9f26ca4250319a879901ff7a1c081c71e46a117675fee05026b0.json +12 -0
- package/dist/fixtures/forge-change/recordings/safe-critic/604af0fcdd6df6b76208fa88305fdc15c37606c2a4d1ae816477e46ac8c2dd04.json +12 -0
- package/dist/fixtures/forge-change/recordings/safe-critic/f0bf50af11f14c50d07904f0dffe1c8c20fb0cd89770645a8824c1d30a443955.json +12 -0
- package/dist/fixtures/forge-change/recordings/ship-critic/350954b58833b2c04880c67db45a1b328b7fd541d9f0d943b63905b8875488a6.json +12 -0
- package/dist/fixtures/forge-change/recordings/ship-critic/4af95c6c96a206777cec9de1f9c2d8c55a36ddc9b8db2f81d6eb83217b835efa.json +12 -0
- package/dist/fixtures/forge-change/recordings/ship-critic/76c34522a49cf7587a4e5689a5030d384603fa780b8aba97d27815cbc829683e.json +12 -0
- package/dist/fixtures/forge-change/recordings/ship-critic/a3b2cd1ace48587e5994333db03b34c3a0f9dec4f1932f1730ddfb687f6918c8.json +12 -0
- package/dist/fixtures/forge-change/recordings/ship-critic/de288a4fa64d0fc20890f9a787080ce6eac5203b7b9b72d884df11e8cf111835.json +12 -0
- package/dist/fixtures/forge-change/recordings/story-writer/1ddb00eac2aaf91a5ec5d9dfd220116be9f75aec6546a4a3a3b050ed89af2864.json +12 -0
- package/dist/packages/assistant-kit/frameworks-kit/README.md +86 -0
- package/dist/packages/assistant-kit/frameworks-kit/checklist.md +11 -0
- package/dist/packages/assistant-kit/frameworks-kit/examples/tide-lantern.yaml +56 -0
- package/dist/packages/assistant-kit/frameworks-kit/examples/tide-platform.yaml +43 -0
- package/dist/packages/assistant-kit/frameworks-kit/prompts/write-framework-model.md +12 -0
- package/dist/packages/assistant-kit/kit/README.md +98 -0
- package/dist/packages/assistant-kit/kit/c2-reference.md +334 -0
- package/dist/packages/assistant-kit/kit/checklist.md +39 -0
- package/dist/packages/assistant-kit/kit/examples/house-http-client.yaml +102 -0
- package/dist/packages/assistant-kit/kit/examples/tide-apigee.yaml +56 -0
- package/dist/packages/assistant-kit/kit/examples/tide-apim.yaml +32 -0
- package/dist/packages/assistant-kit/kit/examples/tide-kong.yaml +44 -0
- package/dist/packages/assistant-kit/kit/prompts/write-dialect.md +34 -0
- package/dist/packages/assistant-kit/kit/worked-example.md +198 -0
- package/dist/packages/conformance/sample/README.md +4 -0
- package/dist/packages/conformance/sample/inputs/notes.md +3 -0
- package/dist/packages/conformance/sample/inputs/systems.csv +3 -0
- package/dist/packages/conformance/sample/repo/api/greeter.proto +6 -0
- package/dist/packages/conformance/sample/repo/api/openapi.yaml +10 -0
- package/dist/packages/conformance/sample/repo/api/schema.graphql +3 -0
- package/dist/packages/conformance/sample/repo/db/V1__create_greeting.sql +4 -0
- package/dist/packages/conformance/sample/repo/package.json +7 -0
- package/dist/packages/conformance/sample/repo/src/main/java/sample/HelloController.java +10 -0
- package/dist/packages/conformance/sample/repo/web/app.ts +4 -0
- package/dist/packages/evals/datasets/critic-lenses.yaml +62 -0
- package/dist/packages/evals/datasets/dialect-writer.yaml +14 -0
- package/dist/packages/evals/datasets/feature-namer.yaml +34 -0
- package/dist/packages/evals/datasets/glossary-definer.yaml +25 -0
- package/dist/packages/evals/datasets/story-writer.yaml +13 -0
- package/dist/packages/evals/datasets/template-writer.yaml +16 -0
- package/dist/packages/evals/datasets/test-writer.yaml +18 -0
- package/dist/packages/executors/reference/reference-executor.mjs +233 -0
- package/dist/packages/extractors/dialects/argocd.yaml +45 -0
- package/dist/packages/extractors/dialects/fixtures/jaxrs/accounts-service/src/main/java/forge/accounts/AccountResource.java +47 -0
- package/dist/packages/extractors/dialects/fixtures/jaxrs/accounts-service/src/main/java/forge/accounts/AuditClient.java +12 -0
- package/dist/packages/extractors/dialects/fixtures/jaxrs/accounts-service/src/main/java/forge/accounts/LedgerClient.java +17 -0
- package/dist/packages/extractors/dialects/fixtures/micronaut/statements-service/src/main/java/forge/statements/LedgerClient.java +14 -0
- package/dist/packages/extractors/dialects/fixtures/micronaut/statements-service/src/main/java/forge/statements/RatesClient.java +11 -0
- package/dist/packages/extractors/dialects/fixtures/micronaut/statements-service/src/main/java/forge/statements/StatementController.java +36 -0
- package/dist/packages/extractors/dialects/fixtures/micronaut/statements-service/src/main/resources/application.yml +7 -0
- package/dist/packages/extractors/dialects/fixtures/quarkus-reactive-messaging/fraud-service/src/main/java/forge/fraud/FraudScorer.java +27 -0
- package/dist/packages/extractors/dialects/fixtures/quarkus-reactive-messaging/fraud-service/src/main/resources/application.properties +4 -0
- package/dist/packages/extractors/dialects/fixtures/terraform-aws/forge-aws-infra/envs/prod/api.tf +38 -0
- package/dist/packages/extractors/dialects/fixtures/terraform-aws/forge-aws-infra/envs/prod/main.tf +95 -0
- package/dist/packages/extractors/dialects/fixtures/terraform-aws/forge-aws-infra/envs/prod/terraform.tfvars +1 -0
- package/dist/packages/extractors/dialects/fixtures/terraform-aws/forge-aws-infra/envs/prod/variables.tf +4 -0
- package/dist/packages/extractors/dialects/fixtures/terraform-azure/forge-azure-infra/main.tf +102 -0
- package/dist/packages/extractors/dialects/fixtures/terraform-gcp/forge-gcp-infra/main.tf +51 -0
- package/dist/packages/extractors/dialects/harness.yaml +101 -0
- package/dist/packages/extractors/dialects/jaxrs.yaml +155 -0
- package/dist/packages/extractors/dialects/micronaut.yaml +207 -0
- package/dist/packages/extractors/dialects/quarkus-reactive-messaging.yaml +158 -0
- package/dist/packages/extractors/dialects/tekton.yaml +71 -0
- package/dist/packages/extractors/dialects/terraform-aws.yaml +267 -0
- package/dist/packages/extractors/dialects/terraform-azure.yaml +212 -0
- package/dist/packages/extractors/dialects/terraform-gcp.yaml +109 -0
- package/dist/packages/extractors/grammars/README.md +11 -0
- package/dist/packages/extractors/grammars/tree-sitter-groovy.LICENSE +21 -0
- package/dist/packages/extractors/grammars/tree-sitter-groovy.wasm +0 -0
- package/dist/packages/extractors/grammars/vendored.json +11 -0
- package/dist/packages/learn/lessons/architect-diagrams-and-code.yaml +69 -0
- package/dist/packages/learn/lessons/business-analyst-story-bar.yaml +94 -0
- package/dist/packages/learn/lessons/delivery-lead-release-readiness.yaml +44 -0
- package/dist/packages/learn/lessons/designer-features-in-user-words.yaml +57 -0
- package/dist/packages/learn/lessons/developer-find-your-way.yaml +60 -0
- package/dist/packages/learn/lessons/everyone-first-hour.yaml +74 -0
- package/dist/packages/learn/lessons/platform-lead-coverage-and-defences.yaml +58 -0
- package/dist/packages/learn/lessons/product-owner-first-change.yaml +167 -0
- package/dist/packages/learn/lessons/qa-tests-and-trace.yaml +53 -0
- package/dist/packages/learn/lessons/team-lead-initiative.yaml +146 -0
- package/dist/packages/naysayer/constitution/constitution.yaml +305 -0
- package/dist/packages/naysayer/starter/review.yaml +256 -0
- package/dist/packages/sdk/npm/index.js +9780 -0
- package/dist/packages/speckit/rules/drift.yaml +78 -0
- package/dist/packages/taskrun/rules/conform.yaml +38 -0
- package/dist/packages/testgen/rules/trace.yaml +49 -0
- package/dist/packages/ui/dist/assets/elk-api-Mo5w5xCa.js +2 -0
- package/dist/packages/ui/dist/assets/elk-worker.min--dAJroGl.js +6401 -0
- package/dist/packages/ui/dist/assets/elk-worker.min-DdYmS5kP.js +2 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-400-normal-BTotfTJu.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-400-normal-DZqxrq2p.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-500-normal-ByOcLdNv.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-500-normal-CocWQlwt.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-600-normal-71GNu3SW.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-600-normal-BGq0mW3O.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-ext-400-normal-Dsrv2Tcn.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-ext-400-normal-g30qAdWV.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-ext-500-normal-Cs5J6C77.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-ext-500-normal-DB5PtV2g.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-ext-600-normal-Bz0x94Yp.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-ext-600-normal-DUMzJB7m.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-greek-400-normal-D9ESIMu3.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-greek-400-normal-_efipK4i.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-greek-500-normal-CuWXN6rf.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-greek-500-normal-JMMifIXV.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-greek-600-normal-D-CqTdkO.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-greek-600-normal-DzTrcv_p.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-ext-400-normal-C5H60-Va.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-ext-400-normal-RBey6euL.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-ext-500-normal-D0aIdm-b.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-ext-500-normal-DakdToA3.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-ext-600-normal-DIrixKbi.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-ext-600-normal-DOrvGEcy.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-vietnamese-400-normal-DG4YqDda.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-vietnamese-400-normal-fK1oJ5dG.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-vietnamese-500-normal-BEb3_waV.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-vietnamese-500-normal-e4dixQRQ.woff2 +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-vietnamese-600-normal-DgdngZtN.woff +0 -0
- package/dist/packages/ui/dist/assets/ibm-plex-sans-vietnamese-600-normal-DpPYBSTl.woff2 +0 -0
- package/dist/packages/ui/dist/assets/index-Bp3XhPHX.css +1 -0
- package/dist/packages/ui/dist/assets/index-DM212ZbV.js +29 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/packages/ui/dist/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
- package/dist/packages/ui/dist/assets/rajdhani-devanagari-600-normal-BtY48IV_.woff +0 -0
- package/dist/packages/ui/dist/assets/rajdhani-devanagari-600-normal-DhS7ScYx.woff2 +0 -0
- package/dist/packages/ui/dist/assets/rajdhani-devanagari-700-normal-BQQOj9BB.woff2 +0 -0
- package/dist/packages/ui/dist/assets/rajdhani-devanagari-700-normal-DBRwWuxA.woff +0 -0
- package/dist/packages/ui/dist/assets/rajdhani-latin-600-normal-CXCVEoA9.woff2 +0 -0
- package/dist/packages/ui/dist/assets/rajdhani-latin-600-normal-Dx02qI2_.woff +0 -0
- package/dist/packages/ui/dist/assets/rajdhani-latin-700-normal-BPDUZo87.woff2 +0 -0
- package/dist/packages/ui/dist/assets/rajdhani-latin-700-normal-e0Uj8ZlN.woff +0 -0
- package/dist/packages/ui/dist/assets/rajdhani-latin-ext-600-normal-BleIgkPp.woff +0 -0
- package/dist/packages/ui/dist/assets/rajdhani-latin-ext-600-normal-C9ZjJQAt.woff2 +0 -0
- package/dist/packages/ui/dist/assets/rajdhani-latin-ext-700-normal-BPaQ7tlD.woff +0 -0
- package/dist/packages/ui/dist/assets/rajdhani-latin-ext-700-normal-S6aYESQK.woff2 +0 -0
- package/dist/packages/ui/dist/favicon.svg +1 -0
- package/dist/packages/ui/dist/index.html +15 -0
- package/dist/third-party.json +575 -0
- package/dist/vendor/@fission-ai/openspec/bin/openspec.js +85986 -0
- package/dist/vendor/@fission-ai/openspec/package.json +82 -0
- package/dist/vendor/@fission-ai/openspec/schemas/spec-driven/schema.yaml +248 -0
- package/dist/vendor/@fission-ai/openspec/schemas/spec-driven/templates/design.md +21 -0
- package/dist/vendor/@fission-ai/openspec/schemas/spec-driven/templates/proposal.md +34 -0
- package/dist/vendor/@fission-ai/openspec/schemas/spec-driven/templates/spec.md +13 -0
- package/dist/vendor/@fission-ai/openspec/schemas/spec-driven/templates/tasks.md +11 -0
- package/dist/vendor/tree-sitter-wasms/out/tree-sitter-c_sharp.wasm +0 -0
- package/dist/vendor/tree-sitter-wasms/out/tree-sitter-go.wasm +0 -0
- package/dist/vendor/tree-sitter-wasms/out/tree-sitter-java.wasm +0 -0
- package/dist/vendor/tree-sitter-wasms/out/tree-sitter-javascript.wasm +0 -0
- package/dist/vendor/tree-sitter-wasms/out/tree-sitter-kotlin.wasm +0 -0
- package/dist/vendor/tree-sitter-wasms/out/tree-sitter-php.wasm +0 -0
- package/dist/vendor/tree-sitter-wasms/out/tree-sitter-python.wasm +0 -0
- package/dist/vendor/tree-sitter-wasms/out/tree-sitter-ruby.wasm +0 -0
- package/dist/vendor/tree-sitter-wasms/out/tree-sitter-rust.wasm +0 -0
- package/dist/vendor/tree-sitter-wasms/out/tree-sitter-scala.wasm +0 -0
- package/dist/vendor/tree-sitter-wasms/out/tree-sitter-tsx.wasm +0 -0
- package/dist/vendor/tree-sitter-wasms/out/tree-sitter-typescript.wasm +0 -0
- package/dist/vendor/web-tree-sitter/tree-sitter.wasm +0 -0
- package/package.json +36 -3
|
@@ -0,0 +1,4044 @@
|
|
|
1
|
+
import { createRequire as __atticusCreateRequire } from "node:module";
|
|
2
|
+
import { fileURLToPath as __atticusFileURLToPath } from "node:url";
|
|
3
|
+
var require = __atticusCreateRequire(import.meta.url);
|
|
4
|
+
var __atticusUrl = (p) => new URL("./" + p, import.meta.url).href;
|
|
5
|
+
var __atticusPath = (p) => __atticusFileURLToPath(__atticusUrl(p));
|
|
6
|
+
import {
|
|
7
|
+
listRuns,
|
|
8
|
+
runTranscript
|
|
9
|
+
} from "./chunk-IZNSAEBX.js";
|
|
10
|
+
import {
|
|
11
|
+
historyEntry,
|
|
12
|
+
listHistory
|
|
13
|
+
} from "./chunk-MH5OCRUM.js";
|
|
14
|
+
import {
|
|
15
|
+
uiFactsRoutes
|
|
16
|
+
} from "./chunk-NPCD54I7.js";
|
|
17
|
+
import {
|
|
18
|
+
answerAudit,
|
|
19
|
+
auditLines,
|
|
20
|
+
defaultAuditSeed,
|
|
21
|
+
precisionOf,
|
|
22
|
+
prepareAudit,
|
|
23
|
+
writeAudit
|
|
24
|
+
} from "./chunk-6GWHKMQR.js";
|
|
25
|
+
import "./chunk-3GTZ2OON.js";
|
|
26
|
+
import {
|
|
27
|
+
uiCiteRoutes
|
|
28
|
+
} from "./chunk-YVLMBAYE.js";
|
|
29
|
+
import {
|
|
30
|
+
HttpError,
|
|
31
|
+
badRequest,
|
|
32
|
+
conflict,
|
|
33
|
+
notFound,
|
|
34
|
+
startServer,
|
|
35
|
+
strParam
|
|
36
|
+
} from "./chunk-HHUFVJF7.js";
|
|
37
|
+
import "./chunk-IUD3ME4E.js";
|
|
38
|
+
import {
|
|
39
|
+
isHandoffStep
|
|
40
|
+
} from "./chunk-4Q3NN7VF.js";
|
|
41
|
+
import {
|
|
42
|
+
estimateOf
|
|
43
|
+
} from "./chunk-X4W4XXWP.js";
|
|
44
|
+
import {
|
|
45
|
+
changeMapBody,
|
|
46
|
+
changeMapOf
|
|
47
|
+
} from "./chunk-67J3VZBA.js";
|
|
48
|
+
import {
|
|
49
|
+
changeArchive,
|
|
50
|
+
changeHandoff,
|
|
51
|
+
changeNew,
|
|
52
|
+
changeSync
|
|
53
|
+
} from "./chunk-4PJADZRR.js";
|
|
54
|
+
import {
|
|
55
|
+
conformanceCommand
|
|
56
|
+
} from "./chunk-XRO65C7V.js";
|
|
57
|
+
import {
|
|
58
|
+
outcomeList,
|
|
59
|
+
outcomeRecord,
|
|
60
|
+
releaseEvidenceCommand,
|
|
61
|
+
releaseForecastCommand,
|
|
62
|
+
releaseNotesCommand,
|
|
63
|
+
releasePlanCommand,
|
|
64
|
+
releaseSecurityCommand
|
|
65
|
+
} from "./chunk-5MI2SEV5.js";
|
|
66
|
+
import {
|
|
67
|
+
taskRun
|
|
68
|
+
} from "./chunk-YPIEYQF4.js";
|
|
69
|
+
import "./chunk-XB5RFZHH.js";
|
|
70
|
+
import {
|
|
71
|
+
executorsCheck,
|
|
72
|
+
todoDone,
|
|
73
|
+
todoRun,
|
|
74
|
+
todoShow
|
|
75
|
+
} from "./chunk-TZDSDDSV.js";
|
|
76
|
+
import {
|
|
77
|
+
inputsSha256,
|
|
78
|
+
loadExecutors
|
|
79
|
+
} from "./chunk-PIVKU64W.js";
|
|
80
|
+
import {
|
|
81
|
+
isSandbox,
|
|
82
|
+
learnCheck,
|
|
83
|
+
learnStart,
|
|
84
|
+
lessonsFor,
|
|
85
|
+
readProgress,
|
|
86
|
+
sandboxCreate
|
|
87
|
+
} from "./chunk-7UKVYFQ2.js";
|
|
88
|
+
import {
|
|
89
|
+
defencesScan,
|
|
90
|
+
evalRun
|
|
91
|
+
} from "./chunk-6GNS32SI.js";
|
|
92
|
+
import {
|
|
93
|
+
listReports,
|
|
94
|
+
readQuality
|
|
95
|
+
} from "./chunk-AWECWW5Y.js";
|
|
96
|
+
import {
|
|
97
|
+
readServiceRequests
|
|
98
|
+
} from "./chunk-5YIBJXGB.js";
|
|
99
|
+
import {
|
|
100
|
+
initiativeStatus,
|
|
101
|
+
readView
|
|
102
|
+
} from "./chunk-UYYN2IUZ.js";
|
|
103
|
+
import {
|
|
104
|
+
reconcileCommand
|
|
105
|
+
} from "./chunk-F7UQR4ZX.js";
|
|
106
|
+
import {
|
|
107
|
+
sourcesAdd,
|
|
108
|
+
sourcesSync
|
|
109
|
+
} from "./chunk-F2FOQLJ7.js";
|
|
110
|
+
import {
|
|
111
|
+
INPUTS_SOURCE_ID
|
|
112
|
+
} from "./chunk-IV7BCWWN.js";
|
|
113
|
+
import "./chunk-P3TKKBAH.js";
|
|
114
|
+
import {
|
|
115
|
+
intakeContext
|
|
116
|
+
} from "./chunk-WARBF2KY.js";
|
|
117
|
+
import {
|
|
118
|
+
baselineCommand,
|
|
119
|
+
setupSteps
|
|
120
|
+
} from "./chunk-XTVDAHH2.js";
|
|
121
|
+
import {
|
|
122
|
+
uiOnboardingRoutes
|
|
123
|
+
} from "./chunk-2RX7PI2Y.js";
|
|
124
|
+
import "./chunk-HE4ZQXZM.js";
|
|
125
|
+
import {
|
|
126
|
+
uiTrustRoutes
|
|
127
|
+
} from "./chunk-2KDSXJFJ.js";
|
|
128
|
+
import "./chunk-B3O52AUQ.js";
|
|
129
|
+
import "./chunk-XKS6THI5.js";
|
|
130
|
+
import {
|
|
131
|
+
readMe,
|
|
132
|
+
writeMe
|
|
133
|
+
} from "./chunk-UBWCUP3G.js";
|
|
134
|
+
import {
|
|
135
|
+
digestCommand
|
|
136
|
+
} from "./chunk-4QUQEBFT.js";
|
|
137
|
+
import {
|
|
138
|
+
scanCommand
|
|
139
|
+
} from "./chunk-7TGYNOXD.js";
|
|
140
|
+
import "./chunk-LRS573EX.js";
|
|
141
|
+
import {
|
|
142
|
+
catalogRecords,
|
|
143
|
+
openChanges,
|
|
144
|
+
systemGuide
|
|
145
|
+
} from "./chunk-DPHCFZ7W.js";
|
|
146
|
+
import {
|
|
147
|
+
doctorChecks
|
|
148
|
+
} from "./chunk-FV5YFR6I.js";
|
|
149
|
+
import "./chunk-3G4YI4ZU.js";
|
|
150
|
+
import "./chunk-Y4BRGOJR.js";
|
|
151
|
+
import "./chunk-R64GLVBO.js";
|
|
152
|
+
import {
|
|
153
|
+
handoffDone,
|
|
154
|
+
workspaceOpen,
|
|
155
|
+
workspaceSync
|
|
156
|
+
} from "./chunk-APSVL2AI.js";
|
|
157
|
+
import "./chunk-23M2LUYM.js";
|
|
158
|
+
import "./chunk-Y5PZHS4K.js";
|
|
159
|
+
import {
|
|
160
|
+
readChangeTasks,
|
|
161
|
+
readStory
|
|
162
|
+
} from "./chunk-IDGSU7JF.js";
|
|
163
|
+
import {
|
|
164
|
+
ASSISTANT_FORMATS,
|
|
165
|
+
readProbes,
|
|
166
|
+
whatRunsWhere
|
|
167
|
+
} from "./chunk-LK44ULGI.js";
|
|
168
|
+
import {
|
|
169
|
+
critic,
|
|
170
|
+
criticStats,
|
|
171
|
+
findingAction
|
|
172
|
+
} from "./chunk-73VPWF5R.js";
|
|
173
|
+
import "./chunk-J5LHDJU4.js";
|
|
174
|
+
import {
|
|
175
|
+
planStories,
|
|
176
|
+
snowDraft,
|
|
177
|
+
snowSubmit
|
|
178
|
+
} from "./chunk-YXJ4XOHF.js";
|
|
179
|
+
import "./chunk-ROTY5J2L.js";
|
|
180
|
+
import {
|
|
181
|
+
archiveGate,
|
|
182
|
+
ciArchiveGate
|
|
183
|
+
} from "./chunk-ZE3P35DA.js";
|
|
184
|
+
import {
|
|
185
|
+
traceMatrix
|
|
186
|
+
} from "./chunk-7YYEKTYF.js";
|
|
187
|
+
import "./chunk-XEG3FE2R.js";
|
|
188
|
+
import {
|
|
189
|
+
NEEDS_FILE,
|
|
190
|
+
PlannerError,
|
|
191
|
+
SYNC_FILE,
|
|
192
|
+
TARGET_DIR,
|
|
193
|
+
codeFactRecords,
|
|
194
|
+
commentOnFinding,
|
|
195
|
+
conflictTouches,
|
|
196
|
+
markNeed,
|
|
197
|
+
mergeDelta,
|
|
198
|
+
plainWhy,
|
|
199
|
+
readDeltas,
|
|
200
|
+
readKnowledgeApplies,
|
|
201
|
+
readNeeds,
|
|
202
|
+
readStageLoop,
|
|
203
|
+
setKnowledgeStatus,
|
|
204
|
+
stageComments,
|
|
205
|
+
stageGate
|
|
206
|
+
} from "./chunk-KC5DROFC.js";
|
|
207
|
+
import {
|
|
208
|
+
STAGE_PHASE,
|
|
209
|
+
fieldNames,
|
|
210
|
+
rerunOf
|
|
211
|
+
} from "./chunk-RK6MKKKP.js";
|
|
212
|
+
import "./chunk-VOGCSYOI.js";
|
|
213
|
+
import {
|
|
214
|
+
layerRules,
|
|
215
|
+
loadConstitution,
|
|
216
|
+
overrideStats,
|
|
217
|
+
readChangeFiles,
|
|
218
|
+
readPackFiles,
|
|
219
|
+
readProposals,
|
|
220
|
+
readRound,
|
|
221
|
+
readSnowDrafts,
|
|
222
|
+
readStoryIndex,
|
|
223
|
+
reviewedGates,
|
|
224
|
+
roundsOf
|
|
225
|
+
} from "./chunk-47QE246J.js";
|
|
226
|
+
import {
|
|
227
|
+
artifactFiles,
|
|
228
|
+
artifactPhase,
|
|
229
|
+
changeFolder,
|
|
230
|
+
changeReport,
|
|
231
|
+
changeSize,
|
|
232
|
+
changeStore,
|
|
233
|
+
detectConflicts,
|
|
234
|
+
liveHandoff,
|
|
235
|
+
loadChangeContext,
|
|
236
|
+
requiredArtifacts
|
|
237
|
+
} from "./chunk-XQ3YIV5R.js";
|
|
238
|
+
import "./chunk-MGVYVW7Q.js";
|
|
239
|
+
import {
|
|
240
|
+
fileStepStore,
|
|
241
|
+
manualAdapter,
|
|
242
|
+
stepOfProvisional
|
|
243
|
+
} from "./chunk-NGPU2ARF.js";
|
|
244
|
+
import {
|
|
245
|
+
componentRenames,
|
|
246
|
+
estateFacts,
|
|
247
|
+
estateJoinOf,
|
|
248
|
+
forgetCatalogs,
|
|
249
|
+
hostAliases,
|
|
250
|
+
joinReposOf,
|
|
251
|
+
keepCatalogsInMemory,
|
|
252
|
+
outsideProfileSystems,
|
|
253
|
+
readCatalog
|
|
254
|
+
} from "./chunk-AY2IUF4K.js";
|
|
255
|
+
import {
|
|
256
|
+
OrgPackProposal,
|
|
257
|
+
SNAPSHOT_FILE,
|
|
258
|
+
SnapshotManifest,
|
|
259
|
+
acceptOrgPackDraft,
|
|
260
|
+
correctableFields,
|
|
261
|
+
draftOrgPack,
|
|
262
|
+
filterItems,
|
|
263
|
+
itemTitle,
|
|
264
|
+
keepCandidate,
|
|
265
|
+
readConflicts,
|
|
266
|
+
readFind,
|
|
267
|
+
readFinds,
|
|
268
|
+
readKnowledge,
|
|
269
|
+
readReviews,
|
|
270
|
+
readSnapshot,
|
|
271
|
+
readSourceSettings,
|
|
272
|
+
reviewItem,
|
|
273
|
+
scopeWords,
|
|
274
|
+
searchPages,
|
|
275
|
+
snapshotDates,
|
|
276
|
+
snapshotSources,
|
|
277
|
+
writeReview
|
|
278
|
+
} from "./chunk-6JLPXDWN.js";
|
|
279
|
+
import {
|
|
280
|
+
NoSuchNode,
|
|
281
|
+
buildEstate,
|
|
282
|
+
focusView,
|
|
283
|
+
nodeView,
|
|
284
|
+
viewKey
|
|
285
|
+
} from "./chunk-4GZXS6S2.js";
|
|
286
|
+
import {
|
|
287
|
+
readServiceRequestExports,
|
|
288
|
+
readVendorSpec,
|
|
289
|
+
readWorkItemExports
|
|
290
|
+
} from "./chunk-R4TBNKKC.js";
|
|
291
|
+
import "./chunk-NIXVDQVO.js";
|
|
292
|
+
import "./chunk-UR7RMZ3R.js";
|
|
293
|
+
import "./chunk-N4QZJJVB.js";
|
|
294
|
+
import "./chunk-CHC5FKTU.js";
|
|
295
|
+
import "./chunk-7F2RUWPU.js";
|
|
296
|
+
import {
|
|
297
|
+
readChangeRepos
|
|
298
|
+
} from "./chunk-PGKHJYOB.js";
|
|
299
|
+
import "./chunk-MKRBG5EF.js";
|
|
300
|
+
import {
|
|
301
|
+
COMMON_SETTINGS,
|
|
302
|
+
PROVIDER_SETTINGS,
|
|
303
|
+
defaultKeyStore,
|
|
304
|
+
looksLikeKey
|
|
305
|
+
} from "./chunk-6EDQE7GO.js";
|
|
306
|
+
import {
|
|
307
|
+
providerFactoryFor
|
|
308
|
+
} from "./chunk-5XDNUJ4P.js";
|
|
309
|
+
import {
|
|
310
|
+
ASSISTANTS,
|
|
311
|
+
ChangeArtifact,
|
|
312
|
+
DEFAULT_PORT,
|
|
313
|
+
DEFAULT_RUNTIME,
|
|
314
|
+
DiagramLayout,
|
|
315
|
+
KNOWLEDGE_KINDS,
|
|
316
|
+
KNOWLEDGE_RECORD_KIND,
|
|
317
|
+
LAYOUT_SCHEMA_VERSION,
|
|
318
|
+
MANUAL_SYSTEMS,
|
|
319
|
+
QuestionError,
|
|
320
|
+
QuestionState,
|
|
321
|
+
RUN_SCHEMA_VERSION,
|
|
322
|
+
RecordId,
|
|
323
|
+
Runtime,
|
|
324
|
+
SOURCE_KIND_SUGGESTIONS,
|
|
325
|
+
ViewKey,
|
|
326
|
+
acceptProposal,
|
|
327
|
+
answerQuestion,
|
|
328
|
+
api_exports,
|
|
329
|
+
catalogDirOf,
|
|
330
|
+
changesDirOf,
|
|
331
|
+
changesRelOf,
|
|
332
|
+
external_exports,
|
|
333
|
+
factIdPart,
|
|
334
|
+
factsRelOf,
|
|
335
|
+
gateRoundLimit,
|
|
336
|
+
inputsDirOf,
|
|
337
|
+
joinConventions,
|
|
338
|
+
joinSharedMaps,
|
|
339
|
+
joinVendorOperations,
|
|
340
|
+
knowledgeDirOf,
|
|
341
|
+
knowledgeKindOf,
|
|
342
|
+
knowledgeRelOf,
|
|
343
|
+
layoutPath,
|
|
344
|
+
listQuestions,
|
|
345
|
+
loadOrgPack,
|
|
346
|
+
orgPackDirOf,
|
|
347
|
+
pageStanding,
|
|
348
|
+
proposalsDirOf,
|
|
349
|
+
proposalsRelOf,
|
|
350
|
+
readEstateJoins,
|
|
351
|
+
readQuestion,
|
|
352
|
+
readRepoSpec,
|
|
353
|
+
readSharedMaps,
|
|
354
|
+
require_dist,
|
|
355
|
+
runsDirOf,
|
|
356
|
+
runsRelOf,
|
|
357
|
+
specRelOf,
|
|
358
|
+
specRepos
|
|
359
|
+
} from "./chunk-577AJVRS.js";
|
|
360
|
+
import {
|
|
361
|
+
createLog,
|
|
362
|
+
writeFileAtomic
|
|
363
|
+
} from "./chunk-D3PO4DOZ.js";
|
|
364
|
+
import {
|
|
365
|
+
__toESM
|
|
366
|
+
} from "./chunk-TV45EUGM.js";
|
|
367
|
+
|
|
368
|
+
// packages/cli/dist/ui-commands.js
|
|
369
|
+
import { spawn } from "node:child_process";
|
|
370
|
+
import { isAbsolute, join as join11, relative as relative2, sep as sep2 } from "node:path";
|
|
371
|
+
|
|
372
|
+
// packages/ui/dist-lib/index.js
|
|
373
|
+
import { dirname, join } from "node:path";
|
|
374
|
+
import { fileURLToPath } from "node:url";
|
|
375
|
+
var __atticus_import_meta_url = __atticusUrl("packages/ui/dist-lib/index.js");
|
|
376
|
+
function uiDistDir() {
|
|
377
|
+
return join(dirname(fileURLToPath(__atticus_import_meta_url)), "..", "dist");
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// packages/cli/dist/ui-api.js
|
|
381
|
+
import { createHash } from "node:crypto";
|
|
382
|
+
import { readdir, readFile, writeFile } from "node:fs/promises";
|
|
383
|
+
import { join as join2 } from "node:path";
|
|
384
|
+
async function run(f) {
|
|
385
|
+
const out = [];
|
|
386
|
+
const err = [];
|
|
387
|
+
const code = await f({ out: (l) => out.push(l), err: (l) => err.push(l) });
|
|
388
|
+
return { code, out, err };
|
|
389
|
+
}
|
|
390
|
+
function refused(r) {
|
|
391
|
+
const detail = (r.err.length ? r.err : r.out).join("\n") || "The command refused.";
|
|
392
|
+
return r.code === 2 ? new HttpError(400, "The request isn't valid", detail) : new HttpError(422, "Atticus can't do that yet", detail, { lines: [...r.out, ...r.err] });
|
|
393
|
+
}
|
|
394
|
+
function parseBody(schema, body) {
|
|
395
|
+
const r = schema.safeParse(body ?? {});
|
|
396
|
+
if (!r.success)
|
|
397
|
+
throw badRequest("Some fields aren't right.", {
|
|
398
|
+
errors: r.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
|
|
399
|
+
});
|
|
400
|
+
return r.data;
|
|
401
|
+
}
|
|
402
|
+
var sha = (s) => createHash("sha256").update(s).digest("hex");
|
|
403
|
+
var MAX_ATTACH = 15 * 1024 * 1024;
|
|
404
|
+
var NewChange = api_exports.NewChangeRequest;
|
|
405
|
+
async function latestFindings(root, change) {
|
|
406
|
+
const out = [];
|
|
407
|
+
for (const gate of await reviewedGates(root, change)) {
|
|
408
|
+
const rounds = await roundsOf(root, change, gate);
|
|
409
|
+
if (rounds.length)
|
|
410
|
+
out.push(...(await readRound(root, change, gate, rounds.at(-1))).findings.map((f) => f.finding));
|
|
411
|
+
}
|
|
412
|
+
return out;
|
|
413
|
+
}
|
|
414
|
+
async function readChange(root, id) {
|
|
415
|
+
try {
|
|
416
|
+
return await changeStore(root).read(id);
|
|
417
|
+
} catch {
|
|
418
|
+
throw notFound(`There is no change ${id}.`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
async function changeDetail(root, id) {
|
|
422
|
+
const c = await readChange(root, id);
|
|
423
|
+
const report = await changeReport(root, id);
|
|
424
|
+
const findings = await latestFindings(root, id);
|
|
425
|
+
const open = { blocking: 0, gap: 0, risk: 0, idea: 0 };
|
|
426
|
+
for (const f of findings)
|
|
427
|
+
if (f.state === "open")
|
|
428
|
+
open[f.severity] += 1;
|
|
429
|
+
const dir = join2(changesDirOf(root), id);
|
|
430
|
+
const phases = report.type.phases;
|
|
431
|
+
const current = report.phase ? phases.indexOf(report.phase) : phases.length;
|
|
432
|
+
const artifacts = [];
|
|
433
|
+
const blockedArtifacts = new Set(report.blockers.map((b) => b.artifact).filter(Boolean));
|
|
434
|
+
const cctx = await loadChangeContext(root);
|
|
435
|
+
const onStages = (await Promise.all(cctx.policy.stages.map((s) => readFile(join2(dir, ...s.loop.split("/"))).then(() => true, () => false)))).some(Boolean);
|
|
436
|
+
const type = report.type.artifacts;
|
|
437
|
+
const ofStages = cctx.policy.stages.flatMap((s) => s.artifacts).filter((a, i, all) => type.includes(a) && all.indexOf(a) === i);
|
|
438
|
+
const due = /* @__PURE__ */ new Set();
|
|
439
|
+
for (const p of phases)
|
|
440
|
+
for (const a of requiredArtifacts(cctx, report.type, p, report.footprint))
|
|
441
|
+
due.add(a);
|
|
442
|
+
const order = onStages ? [...ofStages, ...type.filter((a) => !ofStages.includes(a))] : type;
|
|
443
|
+
for (const a of order) {
|
|
444
|
+
const files = await artifactFiles(dir, a);
|
|
445
|
+
if (onStages && !files.length && !(ofStages.includes(a) && due.has(a)))
|
|
446
|
+
continue;
|
|
447
|
+
const at = phases.indexOf(artifactPhase(cctx, report.type, a));
|
|
448
|
+
const done = files.length > 0 && !blockedArtifacts.has(a);
|
|
449
|
+
artifacts.push({
|
|
450
|
+
artifact: a,
|
|
451
|
+
path: `${changesRelOf(root)}/${id}/${files[0] ?? `${a}.md`}`,
|
|
452
|
+
status: done ? "done" : at <= current ? "ready" : "blocked",
|
|
453
|
+
openFindings: findings.filter((f) => f.state === "open" && f.artifact.startsWith(a)).length
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
const h = liveHandoff(c) ?? c.handoffs.at(-1);
|
|
457
|
+
const fast = (await changeSize(cctx, c)).fast;
|
|
458
|
+
return {
|
|
459
|
+
id: c.id,
|
|
460
|
+
title: c.title,
|
|
461
|
+
type: c.type,
|
|
462
|
+
state: c.state,
|
|
463
|
+
repos: c.touches.repos,
|
|
464
|
+
openFindings: open,
|
|
465
|
+
updatedAt: c.history.at(-1)?.at ?? c.createdAt,
|
|
466
|
+
...fast ? { fastPath: true } : {},
|
|
467
|
+
artifacts,
|
|
468
|
+
...h ? {
|
|
469
|
+
handoff: {
|
|
470
|
+
phase: h.phase,
|
|
471
|
+
...h.pr?.url ? { pr: h.pr.url } : {},
|
|
472
|
+
state: h.state === "merged" ? "merged" : h.state === "closed" ? "closed" : "open"
|
|
473
|
+
}
|
|
474
|
+
} : {}
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
async function runView(root, change, runId) {
|
|
478
|
+
const dir = join2(runsDirOf(root), change, runId);
|
|
479
|
+
const text = await readFile(join2(dir, "run.json"), "utf8").catch(() => void 0);
|
|
480
|
+
if (!text)
|
|
481
|
+
return void 0;
|
|
482
|
+
const r = JSON.parse(text);
|
|
483
|
+
const manifest = await readFile(join2(dir, "context-manifest.json"), "utf8").catch(() => "");
|
|
484
|
+
return {
|
|
485
|
+
schemaVersion: RUN_SCHEMA_VERSION,
|
|
486
|
+
runId: r.runId,
|
|
487
|
+
change: r.request.change,
|
|
488
|
+
// A run records the whole agent definition; the view names it.
|
|
489
|
+
agent: typeof r.request.agent === "string" ? r.request.agent : r.request.agent.id,
|
|
490
|
+
runtime: r.runtime,
|
|
491
|
+
state: r.state,
|
|
492
|
+
startedAt: r.startedAt,
|
|
493
|
+
...r.finishedAt ? { finishedAt: r.finishedAt } : {},
|
|
494
|
+
...r.request.outputPath ? { output: r.request.outputPath } : {},
|
|
495
|
+
contextManifestSha256: sha(manifest),
|
|
496
|
+
errors: r.errors ?? [],
|
|
497
|
+
attempt: r.attempt ?? 1,
|
|
498
|
+
...r.stepClass ? { stepClass: r.stepClass } : {},
|
|
499
|
+
...r.model ? { model: r.model } : {},
|
|
500
|
+
promptPath: `${runsRelOf(root)}/${change}/${runId}/prompt.md`
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
async function findRun(root, runId) {
|
|
504
|
+
if (!/^run-[0-9a-z-]+$/.test(runId))
|
|
505
|
+
throw notFound(`There is no run ${runId}.`);
|
|
506
|
+
for (const change of await readdir(runsDirOf(root)).catch(() => [])) {
|
|
507
|
+
const v = await runView(root, change, runId);
|
|
508
|
+
if (v)
|
|
509
|
+
return v;
|
|
510
|
+
}
|
|
511
|
+
throw notFound(`There is no run ${runId}.`);
|
|
512
|
+
}
|
|
513
|
+
async function artifactPath(root, id, artifact) {
|
|
514
|
+
const a = ChangeArtifact.safeParse(artifact);
|
|
515
|
+
if (!a.success)
|
|
516
|
+
throw badRequest(`${artifact} is not an artifact. Use one of ${ChangeArtifact.options.join(", ")}.`);
|
|
517
|
+
const dir = join2(changesDirOf(root), id);
|
|
518
|
+
const files = await artifactFiles(dir, a.data);
|
|
519
|
+
if (files.length === 0)
|
|
520
|
+
throw notFound(`Change ${id} has no ${artifact} yet.`);
|
|
521
|
+
return files[0];
|
|
522
|
+
}
|
|
523
|
+
function uiRoutes(r, ctx, deps = {}) {
|
|
524
|
+
const { root, user, jobs } = ctx;
|
|
525
|
+
const changeDeps = {
|
|
526
|
+
...deps.gitHost ? { gitHost: deps.gitHost } : {},
|
|
527
|
+
...deps.now ? { now: deps.now } : {}
|
|
528
|
+
};
|
|
529
|
+
return r.add("POST", "/api/changes", async ({ body }) => {
|
|
530
|
+
const b = parseBody(NewChange, body);
|
|
531
|
+
const owners = Object.entries(b.owners ?? {}).map(([role, who]) => `${role}=${who.join(",")}`);
|
|
532
|
+
const res = await run((io) => changeNew(root, {
|
|
533
|
+
id: b.id,
|
|
534
|
+
title: b.title,
|
|
535
|
+
type: b.type,
|
|
536
|
+
by: user,
|
|
537
|
+
owners,
|
|
538
|
+
...b.systems ? { systems: b.systems } : {},
|
|
539
|
+
...b.template ? { template: b.template } : {},
|
|
540
|
+
...b.like ? { like: b.like } : {}
|
|
541
|
+
}, io, changeDeps));
|
|
542
|
+
if (res.code !== 0) {
|
|
543
|
+
if (res.err.some((l) => /already exists/i.test(l)))
|
|
544
|
+
throw conflict(res.err.join("\n"));
|
|
545
|
+
throw refused(res);
|
|
546
|
+
}
|
|
547
|
+
return { status: 201, body: await changeDetail(root, b.id) };
|
|
548
|
+
}).add("GET", "/api/changes/{changeId}", async ({ params }) => ({
|
|
549
|
+
status: 200,
|
|
550
|
+
body: await changeDetail(root, params.changeId)
|
|
551
|
+
})).add("GET", "/api/changes/{changeId}/status", async ({ params }) => {
|
|
552
|
+
await readChange(root, params.changeId);
|
|
553
|
+
const rep = await changeReport(root, params.changeId);
|
|
554
|
+
return {
|
|
555
|
+
status: 200,
|
|
556
|
+
body: {
|
|
557
|
+
state: rep.state,
|
|
558
|
+
...rep.phase ? { phase: rep.phase } : {},
|
|
559
|
+
...rep.next ? { next: rep.next } : {},
|
|
560
|
+
...rep.progress ? { progress: rep.progress } : {},
|
|
561
|
+
nextStep: rep.nextStep,
|
|
562
|
+
risk: rep.risk,
|
|
563
|
+
tests: rep.tests,
|
|
564
|
+
...rep.handoff ? {
|
|
565
|
+
handoff: {
|
|
566
|
+
pr: rep.handoff.pr,
|
|
567
|
+
state: rep.handoff.state,
|
|
568
|
+
phase: rep.handoff.phase
|
|
569
|
+
}
|
|
570
|
+
} : {},
|
|
571
|
+
blockers: rep.blockers,
|
|
572
|
+
reasons: rep.reasons
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
}).add("GET", "/api/changes/{changeId}/artifacts/{artifact}", async ({ params }) => {
|
|
576
|
+
await readChange(root, params.changeId);
|
|
577
|
+
const rel = await artifactPath(root, params.changeId, params.artifact);
|
|
578
|
+
const content = await readFile(join2(changesDirOf(root), params.changeId, rel), "utf8");
|
|
579
|
+
return {
|
|
580
|
+
status: 200,
|
|
581
|
+
body: {
|
|
582
|
+
artifact: params.artifact,
|
|
583
|
+
path: `${changesRelOf(root)}/${params.changeId}/${rel}`,
|
|
584
|
+
content,
|
|
585
|
+
sha256: sha(content)
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
}).add("PUT", "/api/changes/{changeId}/artifacts/{artifact}", async ({ params, body }) => {
|
|
589
|
+
await readChange(root, params.changeId);
|
|
590
|
+
const b = parseBody(api_exports.SaveArtifactRequest, body);
|
|
591
|
+
const rel = await artifactPath(root, params.changeId, params.artifact);
|
|
592
|
+
const file = join2(changesDirOf(root), params.changeId, rel);
|
|
593
|
+
const now = await readFile(file, "utf8");
|
|
594
|
+
if (sha(now) !== b.baseSha256)
|
|
595
|
+
throw conflict(`${rel} changed since you opened it. Reload it, then make your edit again.`);
|
|
596
|
+
await writeFile(file, b.content, "utf8");
|
|
597
|
+
ctx.events.publish({
|
|
598
|
+
type: "file.changed",
|
|
599
|
+
path: `${changesRelOf(root)}/${params.changeId}/${rel}`
|
|
600
|
+
});
|
|
601
|
+
return {
|
|
602
|
+
status: 200,
|
|
603
|
+
body: {
|
|
604
|
+
artifact: params.artifact,
|
|
605
|
+
path: `${changesRelOf(root)}/${params.changeId}/${rel}`,
|
|
606
|
+
content: b.content,
|
|
607
|
+
sha256: sha(b.content)
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
}).add("GET", "/api/changes/{changeId}/tasks", async ({ params }) => {
|
|
611
|
+
await readChange(root, params.changeId);
|
|
612
|
+
const files = await readChangeFiles(join2(changesDirOf(root), params.changeId));
|
|
613
|
+
return {
|
|
614
|
+
status: 200,
|
|
615
|
+
body: readChangeTasks(files).map((t) => ({
|
|
616
|
+
id: t.id,
|
|
617
|
+
repo: t.repo,
|
|
618
|
+
description: t.title,
|
|
619
|
+
done: t.done,
|
|
620
|
+
...t.stories[0] ? { story: t.stories[0] } : {},
|
|
621
|
+
branch: `task/${params.changeId}/${t.id}`
|
|
622
|
+
}))
|
|
623
|
+
};
|
|
624
|
+
}).add("POST", "/api/changes/{changeId}/workspace", async ({ params, body }) => {
|
|
625
|
+
await readChange(root, params.changeId);
|
|
626
|
+
const b = parseBody(api_exports.OpenWorkspaceRequest, body);
|
|
627
|
+
const res = await run((io) => workspaceOpen(root, params.changeId, b.repos, io));
|
|
628
|
+
if (res.code !== 0)
|
|
629
|
+
throw refused(res);
|
|
630
|
+
const repos = await readFile(join2(changesDirOf(root), params.changeId, "repos.yaml"), "utf8").catch(() => "");
|
|
631
|
+
return { status: 200, body: { lines: res.out, reposYaml: repos } };
|
|
632
|
+
}).add("POST", "/api/changes/{changeId}/handoff", async ({ params, body }) => {
|
|
633
|
+
await readChange(root, params.changeId);
|
|
634
|
+
const b = parseBody(api_exports.HandoffRequest, body);
|
|
635
|
+
const res = await run((io) => changeHandoff(root, params.changeId, { by: user, approve: true, ...b.repo ? { repo: b.repo } : {} }, io, changeDeps));
|
|
636
|
+
if (res.code !== 0)
|
|
637
|
+
throw refused(res);
|
|
638
|
+
return { status: 201, body: await changeDetail(root, params.changeId) };
|
|
639
|
+
}).add("POST", "/api/changes/{changeId}/sync", async ({ params, body }) => {
|
|
640
|
+
await readChange(root, params.changeId);
|
|
641
|
+
const b = parseBody(api_exports.SyncRequest, body);
|
|
642
|
+
const res = await run((io) => changeSync(root, params.changeId, { by: user, approve: true, ...b.repo ? { repo: b.repo } : {} }, io, changeDeps));
|
|
643
|
+
if (res.code === 2)
|
|
644
|
+
throw refused(res);
|
|
645
|
+
return {
|
|
646
|
+
status: 200,
|
|
647
|
+
body: {
|
|
648
|
+
...await changeDetail(root, params.changeId),
|
|
649
|
+
lines: [...res.out, ...res.err]
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
}).add("GET", "/api/changes/{changeId}/inputs", async ({ params }) => {
|
|
653
|
+
const c = await readChange(root, params.changeId);
|
|
654
|
+
const { readChangeInputs } = await import("./chunk-FOGG4AUR.js");
|
|
655
|
+
return {
|
|
656
|
+
status: 200,
|
|
657
|
+
body: api_exports.ChangeInputList.parse(await readChangeInputs(join2(changesDirOf(root), c.id)))
|
|
658
|
+
};
|
|
659
|
+
}).add("GET", "/api/changes/{changeId}/prototypes", async ({ params }) => {
|
|
660
|
+
const c = await readChange(root, params.changeId);
|
|
661
|
+
const { changePrototypes } = await import("./chunk-LVGBKKSM.js");
|
|
662
|
+
return {
|
|
663
|
+
status: 200,
|
|
664
|
+
body: api_exports.ChangePrototypeList.parse(await changePrototypes(root, c.id))
|
|
665
|
+
};
|
|
666
|
+
}).add("POST", "/api/changes/{changeId}/inputs", async ({ params, body }) => {
|
|
667
|
+
const c = await readChange(root, params.changeId);
|
|
668
|
+
const b = parseBody(api_exports.AttachFileRequest, body);
|
|
669
|
+
const bytes = new Uint8Array(Buffer.from(b.content, "base64"));
|
|
670
|
+
if (bytes.length > MAX_ATTACH)
|
|
671
|
+
throw new HttpError(413, "The file is too large", "Attach a file of 15 MB or less.");
|
|
672
|
+
const { attachFiles } = await import("./chunk-Y7KBQ4BD.js");
|
|
673
|
+
const r2 = await attachFiles(root, c.id, [{ name: b.name, bytes }], user);
|
|
674
|
+
if (!r2.added.length)
|
|
675
|
+
throw new HttpError(422, "Atticus can't attach that", r2.refused[0]?.why ?? "", {
|
|
676
|
+
lines: r2.refused.map((x) => `${x.name}: ${x.why}`)
|
|
677
|
+
});
|
|
678
|
+
return { status: 201, body: api_exports.AttachResult.parse(r2) };
|
|
679
|
+
}, { maxBody: Math.ceil(MAX_ATTACH * 4 / 3) + 64 * 1024 }).add("GET", "/api/changes/{changeId}/runs", async ({ params }) => {
|
|
680
|
+
await readChange(root, params.changeId);
|
|
681
|
+
const dir = join2(runsDirOf(root), params.changeId);
|
|
682
|
+
const out = [];
|
|
683
|
+
for (const id of (await readdir(dir).catch(() => [])).sort()) {
|
|
684
|
+
const v = await runView(root, params.changeId, id);
|
|
685
|
+
if (v)
|
|
686
|
+
out.push(v);
|
|
687
|
+
}
|
|
688
|
+
return { status: 200, body: out };
|
|
689
|
+
}).add("POST", "/api/changes/{changeId}/runs", async ({ params, body }) => {
|
|
690
|
+
await readChange(root, params.changeId);
|
|
691
|
+
const b = parseBody(api_exports.StartRunRequest, body);
|
|
692
|
+
const before = new Set(await readdir(join2(runsDirOf(root), params.changeId)).catch(() => []));
|
|
693
|
+
let res;
|
|
694
|
+
if (b.task)
|
|
695
|
+
res = await run((io) => taskRun(root, params.changeId, b.task, {
|
|
696
|
+
mode: b.runtime,
|
|
697
|
+
by: user,
|
|
698
|
+
...b.autonomy ? {
|
|
699
|
+
level: b.autonomy === "draft-and-pr" ? "pr" : b.autonomy === "plan-only" ? "plan" : "draft"
|
|
700
|
+
} : {}
|
|
701
|
+
}, io));
|
|
702
|
+
else if (b.agent === "story-writer")
|
|
703
|
+
res = await run((io) => planStories(root, params.changeId, { mode: b.runtime }, io));
|
|
704
|
+
else if (b.agent.endsWith("-critic"))
|
|
705
|
+
res = await run((io) => critic(root, params.changeId, { mode: b.runtime }, io));
|
|
706
|
+
else
|
|
707
|
+
throw badRequest(`Start ${b.agent} from its screen; this call starts task runs, the story writer and the critic.`);
|
|
708
|
+
if (res.code === 2)
|
|
709
|
+
throw refused(res);
|
|
710
|
+
const after = (await readdir(join2(runsDirOf(root), params.changeId)).catch(() => [])).filter((x) => !before.has(x)).sort();
|
|
711
|
+
if (after.length === 0)
|
|
712
|
+
throw refused(res);
|
|
713
|
+
const v = await runView(root, params.changeId, after.at(-1));
|
|
714
|
+
for (const id of after)
|
|
715
|
+
ctx.events.publish({
|
|
716
|
+
type: "run.changed",
|
|
717
|
+
runId: id,
|
|
718
|
+
state: (await runView(root, params.changeId, id))?.state ?? "prepared"
|
|
719
|
+
});
|
|
720
|
+
return { status: 201, body: v };
|
|
721
|
+
}).add("POST", "/api/changes/{changeId}/critic", async ({ params, body }) => {
|
|
722
|
+
await readChange(root, params.changeId);
|
|
723
|
+
const b = parseBody(api_exports.RunCriticRequest, body);
|
|
724
|
+
const op = await loadOrgPack(orgPackDirOf(root));
|
|
725
|
+
const mode = op.runtime.ai === "off" ? void 0 : "hand-off";
|
|
726
|
+
const once = (gate) => run((io) => critic(root, params.changeId, { ...gate ? { gate } : {}, ...mode ? { mode } : {}, json: true }, io));
|
|
727
|
+
let res = await once(b.gate);
|
|
728
|
+
if (!b.gate && res.code === 2 && res.err.some((l) => /has no gate/.test(l))) {
|
|
729
|
+
const next = (await changeReport(root, params.changeId)).next;
|
|
730
|
+
if (next)
|
|
731
|
+
res = await once(next);
|
|
732
|
+
}
|
|
733
|
+
if (res.code === 2)
|
|
734
|
+
throw refused(res);
|
|
735
|
+
ctx.events.publish({
|
|
736
|
+
type: "file.changed",
|
|
737
|
+
path: `${changesRelOf(root)}/${params.changeId}/review`
|
|
738
|
+
});
|
|
739
|
+
let round;
|
|
740
|
+
try {
|
|
741
|
+
round = JSON.parse(res.out.join("\n"));
|
|
742
|
+
} catch {
|
|
743
|
+
throw refused(res);
|
|
744
|
+
}
|
|
745
|
+
return { status: 200, body: { ai: op.runtime.ai, round } };
|
|
746
|
+
}).add("GET", "/api/runs/{runId}", async ({ params }) => ({
|
|
747
|
+
status: 200,
|
|
748
|
+
body: await findRun(root, params.runId)
|
|
749
|
+
})).add("POST", "/api/runs/{runId}/done", async ({ params }) => {
|
|
750
|
+
const before = await findRun(root, params.runId);
|
|
751
|
+
if (before.state === "validated" || before.state === "cancelled")
|
|
752
|
+
throw conflict(`Run ${params.runId} is already ${before.state}.`);
|
|
753
|
+
const res = await run((io) => handoffDone(root, params.runId, io));
|
|
754
|
+
const after = await findRun(root, params.runId);
|
|
755
|
+
ctx.events.publish({ type: "run.changed", runId: after.runId, state: after.state });
|
|
756
|
+
return { status: 200, body: { ...after, lines: [...res.out, ...res.err] } };
|
|
757
|
+
}).add("POST", "/api/runs/{runId}/retry", async ({ params }) => {
|
|
758
|
+
const v = await findRun(root, params.runId);
|
|
759
|
+
if (v.state !== "failed")
|
|
760
|
+
throw conflict(`Only a failed run can be retried; ${v.runId} is ${v.state}.`);
|
|
761
|
+
const file = join2(runsDirOf(root), v.change, v.runId, "run.json");
|
|
762
|
+
const rec = JSON.parse(await readFile(file, "utf8"));
|
|
763
|
+
rec.state = "prepared";
|
|
764
|
+
rec.attempt = (rec.attempt ?? 1) + 1;
|
|
765
|
+
rec.errors = [];
|
|
766
|
+
await writeFile(file, JSON.stringify(rec, null, 2) + "\n");
|
|
767
|
+
const after = await findRun(root, params.runId);
|
|
768
|
+
ctx.events.publish({ type: "run.changed", runId: after.runId, state: after.state });
|
|
769
|
+
return { status: 200, body: after };
|
|
770
|
+
}).add("POST", "/api/runs/{runId}/cancel", async ({ params }) => {
|
|
771
|
+
const v = await findRun(root, params.runId);
|
|
772
|
+
if (["validated", "cancelled"].includes(v.state))
|
|
773
|
+
throw conflict(`Run ${v.runId} is already ${v.state}.`);
|
|
774
|
+
const file = join2(runsDirOf(root), v.change, v.runId, "run.json");
|
|
775
|
+
const rec = JSON.parse(await readFile(file, "utf8"));
|
|
776
|
+
rec.state = "cancelled";
|
|
777
|
+
rec.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
778
|
+
await writeFile(file, JSON.stringify(rec, null, 2) + "\n");
|
|
779
|
+
const after = await findRun(root, params.runId);
|
|
780
|
+
ctx.events.publish({ type: "run.changed", runId: after.runId, state: after.state });
|
|
781
|
+
return { status: 200, body: after };
|
|
782
|
+
}).add("POST", "/api/findings/{changeId}/{findingId}/action", async ({ params, body }) => {
|
|
783
|
+
await readChange(root, params.changeId);
|
|
784
|
+
const b = parseBody(api_exports.FindingActionRequest, body);
|
|
785
|
+
const res = await run((io) => findingAction(root, params.changeId, params.findingId, b.action, {
|
|
786
|
+
by: user,
|
|
787
|
+
...b.reason ? { reason: b.reason } : {},
|
|
788
|
+
...b.askedOf ? { askedOf: b.askedOf } : {},
|
|
789
|
+
...b.patch ? { edit: b.patch } : {},
|
|
790
|
+
...b.gate ? { gate: b.gate } : {}
|
|
791
|
+
}, io));
|
|
792
|
+
if (res.code !== 0) {
|
|
793
|
+
if (res.err.some((l) => /no finding|not found/i.test(l)))
|
|
794
|
+
throw notFound(res.err.join("\n"));
|
|
795
|
+
if (res.err.some((l) => /can't be waived|not allowed|needs/i.test(l)))
|
|
796
|
+
throw new HttpError(403, "Not allowed", res.err.join("\n"));
|
|
797
|
+
throw refused(res);
|
|
798
|
+
}
|
|
799
|
+
const f = (await latestFindings(root, params.changeId)).find((x) => x.id === params.findingId);
|
|
800
|
+
return { status: 200, body: f };
|
|
801
|
+
}).add("POST", "/api/scan", ({ body }) => {
|
|
802
|
+
const b = parseBody(api_exports.ScanRequest, body);
|
|
803
|
+
if (jobs.runningOf("scan"))
|
|
804
|
+
throw conflict("A scan is already running.");
|
|
805
|
+
const job = jobs.start("scan", async (c) => {
|
|
806
|
+
c.progress(0, 1, "Scanning");
|
|
807
|
+
const res = await run((io) => scanCommand(root, {
|
|
808
|
+
...b.repos ? { repos: b.repos } : {},
|
|
809
|
+
full: b.full,
|
|
810
|
+
json: true,
|
|
811
|
+
// ADR 0123: another branch, scanned beside the baseline.
|
|
812
|
+
...b.ref ? { ref: b.ref } : {}
|
|
813
|
+
}, io));
|
|
814
|
+
c.progress(1, 1, "Scanned");
|
|
815
|
+
const parsed = (() => {
|
|
816
|
+
try {
|
|
817
|
+
return JSON.parse(res.out.join("\n"));
|
|
818
|
+
} catch {
|
|
819
|
+
return {};
|
|
820
|
+
}
|
|
821
|
+
})();
|
|
822
|
+
const problems = parsed.problems?.length ?? 0;
|
|
823
|
+
ctx.events.publish({
|
|
824
|
+
type: "index.rebuilt",
|
|
825
|
+
records: Number(parsed.stats?.records ?? 0)
|
|
826
|
+
});
|
|
827
|
+
return {
|
|
828
|
+
result: { stats: parsed.stats, problems },
|
|
829
|
+
errors: res.code === 2 ? res.err : []
|
|
830
|
+
};
|
|
831
|
+
});
|
|
832
|
+
return { status: 202, body: { jobId: job.id } };
|
|
833
|
+
}).add("POST", "/api/workspace/sync", ({ body }) => {
|
|
834
|
+
const b = parseBody(api_exports.ScanRequest.pick({ repos: true }), body);
|
|
835
|
+
const job = jobs.start("sync", async (c) => {
|
|
836
|
+
c.progress(0, 1, "Syncing base clones");
|
|
837
|
+
const res = await run((io) => workspaceSync(root, b.repos, io));
|
|
838
|
+
c.progress(1, 1, "Synced");
|
|
839
|
+
return { result: { lines: res.out }, errors: res.code === 0 ? [] : res.err };
|
|
840
|
+
});
|
|
841
|
+
return { status: 202, body: { jobId: job.id } };
|
|
842
|
+
}).add("POST", "/api/reconcile", ({ body }) => {
|
|
843
|
+
const b = parseBody(api_exports.ReconcileRequest, body);
|
|
844
|
+
const job = jobs.start("reconcile", async (c) => {
|
|
845
|
+
c.progress(0, 1, `Reconciling ${b.diagram}`);
|
|
846
|
+
const res = await run((io) => reconcileCommand(root, [b.diagram], { json: true }, io));
|
|
847
|
+
c.progress(1, 1, "Reconciled");
|
|
848
|
+
let result;
|
|
849
|
+
try {
|
|
850
|
+
result = JSON.parse(res.out.join("\n"));
|
|
851
|
+
} catch {
|
|
852
|
+
result = { lines: res.out };
|
|
853
|
+
}
|
|
854
|
+
return { result, errors: res.code === 2 ? res.err : [] };
|
|
855
|
+
});
|
|
856
|
+
return { status: 202, body: { jobId: job.id } };
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// packages/cli/dist/ui-api-run.js
|
|
861
|
+
var import_yaml = __toESM(require_dist(), 1);
|
|
862
|
+
import { readdir as readdir2, readFile as readFile2, stat } from "node:fs/promises";
|
|
863
|
+
import { join as join3, resolve } from "node:path";
|
|
864
|
+
async function run2(f) {
|
|
865
|
+
const out = [];
|
|
866
|
+
const err = [];
|
|
867
|
+
const code = await f({ out: (l) => out.push(l), err: (l) => err.push(l) });
|
|
868
|
+
return { code, out, err };
|
|
869
|
+
}
|
|
870
|
+
function refused2(r) {
|
|
871
|
+
const detail = (r.err.length ? r.err : r.out).join("\n") || "The command refused.";
|
|
872
|
+
return r.code === 2 ? new HttpError(400, "The request isn't valid", detail) : new HttpError(422, "Atticus can't do that yet", detail, { lines: [...r.out, ...r.err] });
|
|
873
|
+
}
|
|
874
|
+
function parseBody2(schema, body) {
|
|
875
|
+
const r = schema.safeParse(body ?? {});
|
|
876
|
+
if (!r.success)
|
|
877
|
+
throw badRequest("Some fields aren't right.", {
|
|
878
|
+
errors: r.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
|
|
879
|
+
});
|
|
880
|
+
return r.data;
|
|
881
|
+
}
|
|
882
|
+
function jsonOf(r) {
|
|
883
|
+
const text = r.out.join("\n").trim();
|
|
884
|
+
if (!text.startsWith("{") && !text.startsWith("["))
|
|
885
|
+
return void 0;
|
|
886
|
+
try {
|
|
887
|
+
return JSON.parse(text);
|
|
888
|
+
} catch {
|
|
889
|
+
return void 0;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
function viewOf(r) {
|
|
893
|
+
const out = r.code === 0 ? jsonOf(r) : void 0;
|
|
894
|
+
if (out !== void 0)
|
|
895
|
+
return out;
|
|
896
|
+
if (r.err.some((l) => /^There is no change/.test(l)))
|
|
897
|
+
throw notFound(r.err.join("\n"));
|
|
898
|
+
throw refused2(r.code === 0 ? { ...r, code: 1 } : r);
|
|
899
|
+
}
|
|
900
|
+
async function readChange2(root, id) {
|
|
901
|
+
try {
|
|
902
|
+
return await changeStore(root).read(id);
|
|
903
|
+
} catch {
|
|
904
|
+
throw notFound(`There is no change ${id}.`);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
async function openChanges2(root) {
|
|
908
|
+
return (await changeStore(root).list()).filter((c) => c.state !== "archived").sort((a, b) => a.id.localeCompare(b.id));
|
|
909
|
+
}
|
|
910
|
+
async function latestFindings2(root, change) {
|
|
911
|
+
const out = [];
|
|
912
|
+
for (const gate of await reviewedGates(root, change)) {
|
|
913
|
+
const rounds = await roundsOf(root, change, gate);
|
|
914
|
+
if (rounds.length)
|
|
915
|
+
out.push(...(await readRound(root, change, gate, rounds.at(-1))).findings.map((f) => f.finding));
|
|
916
|
+
}
|
|
917
|
+
return out;
|
|
918
|
+
}
|
|
919
|
+
async function filesUnder(dir, prefix = "") {
|
|
920
|
+
const out = [];
|
|
921
|
+
let entries;
|
|
922
|
+
try {
|
|
923
|
+
entries = await readdir2(dir, { withFileTypes: true });
|
|
924
|
+
} catch {
|
|
925
|
+
return out;
|
|
926
|
+
}
|
|
927
|
+
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
928
|
+
const rel = prefix ? `${prefix}/${e.name}` : e.name;
|
|
929
|
+
if (e.isDirectory())
|
|
930
|
+
out.push(...await filesUnder(join3(dir, e.name), rel));
|
|
931
|
+
else
|
|
932
|
+
out.push({ path: rel, bytes: (await stat(join3(dir, e.name))).size });
|
|
933
|
+
}
|
|
934
|
+
return out;
|
|
935
|
+
}
|
|
936
|
+
var RUNTIME_KIND = {
|
|
937
|
+
"work-tracker": "workTracker",
|
|
938
|
+
"service-desk": "serviceDesk",
|
|
939
|
+
"git-host": "gitHost",
|
|
940
|
+
wiki: "wiki",
|
|
941
|
+
notify: "notify",
|
|
942
|
+
design: "design"
|
|
943
|
+
};
|
|
944
|
+
async function executorsOf(root, deps) {
|
|
945
|
+
return deps.todo?.executors ? { executors: deps.todo.executors, problems: [] } : await loadExecutors(root);
|
|
946
|
+
}
|
|
947
|
+
var stepRef = (id, change) => change ? `${change}/${id}` : id;
|
|
948
|
+
function stepView(s, all, pack, configured) {
|
|
949
|
+
const proc = s.procedure ? pack.procedures.find((p) => p.id === s.procedure) : void 0;
|
|
950
|
+
return {
|
|
951
|
+
id: s.id,
|
|
952
|
+
...s.change ? { change: s.change } : {},
|
|
953
|
+
system: s.system,
|
|
954
|
+
action: s.action,
|
|
955
|
+
state: s.state,
|
|
956
|
+
title: s.title,
|
|
957
|
+
where: s.where,
|
|
958
|
+
...s.procedure ? { procedure: s.procedure } : {},
|
|
959
|
+
...s.procedureStep ? { procedureStep: s.procedureStep } : {},
|
|
960
|
+
fields: s.fields.map((f) => ({
|
|
961
|
+
name: f.name,
|
|
962
|
+
value: f.value,
|
|
963
|
+
...f.help ? { help: f.help } : {}
|
|
964
|
+
})),
|
|
965
|
+
instructions: s.instructions,
|
|
966
|
+
result: s.result,
|
|
967
|
+
waitsFor: s.dependsOn.filter((d) => all.find((x) => x.id === d)?.state === "open"),
|
|
968
|
+
...s.reason ? { reason: s.reason } : {},
|
|
969
|
+
...s.recorded ? { recorded: s.recorded } : {},
|
|
970
|
+
runs: s.runs.map((r) => ({
|
|
971
|
+
runId: r.runId,
|
|
972
|
+
executor: r.executor,
|
|
973
|
+
at: r.at,
|
|
974
|
+
status: r.status,
|
|
975
|
+
mode: r.mode,
|
|
976
|
+
by: r.approval.by,
|
|
977
|
+
...r.message ? { message: r.message } : {}
|
|
978
|
+
})),
|
|
979
|
+
/** Executors that may do this step: the procedure allows them and this person has them. */
|
|
980
|
+
executors: (proc?.executors.allowed ?? []).filter((n) => configured.has(n)),
|
|
981
|
+
createdAt: s.createdAt,
|
|
982
|
+
createdBy: s.createdBy
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
async function findStep(root, id, change) {
|
|
986
|
+
const all = await fileStepStore(root).list(change ? { change } : {});
|
|
987
|
+
const s = all.find((x) => x.id === id);
|
|
988
|
+
if (!s)
|
|
989
|
+
throw notFound(`There is no step ${stepRef(id, change)}.`);
|
|
990
|
+
return s;
|
|
991
|
+
}
|
|
992
|
+
var SyncEntries = external_exports.object({
|
|
993
|
+
requests: external_exports.array(external_exports.object({ key: external_exports.string(), id: external_exports.string() })).default([])
|
|
994
|
+
});
|
|
995
|
+
async function changeRequests(root, change, templates, steps) {
|
|
996
|
+
const dir = changeFolder(root, change);
|
|
997
|
+
const drafts = readSnowDrafts(await readChangeFiles(dir));
|
|
998
|
+
if (drafts.length === 0)
|
|
999
|
+
return [];
|
|
1000
|
+
let synced = [];
|
|
1001
|
+
try {
|
|
1002
|
+
synced = SyncEntries.parse((0, import_yaml.parse)(await readFile2(join3(dir, SYNC_FILE), "utf8"))).requests;
|
|
1003
|
+
} catch {
|
|
1004
|
+
}
|
|
1005
|
+
return drafts.map((d) => {
|
|
1006
|
+
const h = d.heading.toLowerCase();
|
|
1007
|
+
const t = templates.find((x) => [x.requestType, x.name, x.id].some((n) => h.includes(n.toLowerCase())));
|
|
1008
|
+
const record = /:\s*(\S+)\s*$/.exec(d.heading)?.[1] ?? "";
|
|
1009
|
+
const fields = t ? t.fields.map((f) => ({
|
|
1010
|
+
name: f.name,
|
|
1011
|
+
value: d.fields.get(f.name.toLowerCase())?.value ?? "",
|
|
1012
|
+
required: f.required
|
|
1013
|
+
})) : [...d.fields].map(([name, v2]) => ({ name, value: v2.value, required: false }));
|
|
1014
|
+
const base = {
|
|
1015
|
+
change,
|
|
1016
|
+
heading: d.heading,
|
|
1017
|
+
...t ? { template: t.id, leadTimeDays: t.leadTimeDays } : {},
|
|
1018
|
+
requestType: t?.requestType ?? d.heading,
|
|
1019
|
+
record,
|
|
1020
|
+
approvers: t?.approvers ?? [],
|
|
1021
|
+
fields,
|
|
1022
|
+
missing: fields.filter((f) => f.required && !f.value.trim()).map((f) => f.name)
|
|
1023
|
+
};
|
|
1024
|
+
const entry = t ? synced.find((e) => e.key === `${t.id}|${record}`) : void 0;
|
|
1025
|
+
if (!entry)
|
|
1026
|
+
return { ...base, state: "to-file" };
|
|
1027
|
+
const stepId = stepOfProvisional(entry.id);
|
|
1028
|
+
if (!stepId)
|
|
1029
|
+
return { ...base, state: "filed", number: entry.id };
|
|
1030
|
+
const s = steps.find((x) => x.id === stepId);
|
|
1031
|
+
const v = s?.state === "done" ? s.recorded?.values : void 0;
|
|
1032
|
+
return typeof v?.requestNumber === "string" ? {
|
|
1033
|
+
...base,
|
|
1034
|
+
state: "filed",
|
|
1035
|
+
step: stepId,
|
|
1036
|
+
number: v.requestNumber,
|
|
1037
|
+
...typeof v.url === "string" ? { url: v.url } : {}
|
|
1038
|
+
} : { ...base, state: "waiting", step: stepId };
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
function questionOf(check) {
|
|
1042
|
+
if (check.type === "answer")
|
|
1043
|
+
return check.question;
|
|
1044
|
+
if (check.type === "all") {
|
|
1045
|
+
for (const c of check.checks)
|
|
1046
|
+
if (c.type === "answer")
|
|
1047
|
+
return c.question;
|
|
1048
|
+
}
|
|
1049
|
+
return void 0;
|
|
1050
|
+
}
|
|
1051
|
+
var commandLine = (argv) => `atticus ${argv.map((a) => /[\s"']/.test(a) ? `"${a.replace(/"/g, '\\"')}"` : a).join(" ")}`;
|
|
1052
|
+
function stepOfLesson(l, id) {
|
|
1053
|
+
const s = l.steps.find((x) => x.id === id);
|
|
1054
|
+
if (!s)
|
|
1055
|
+
return void 0;
|
|
1056
|
+
const q = questionOf(s.check);
|
|
1057
|
+
return {
|
|
1058
|
+
id: s.id,
|
|
1059
|
+
n: l.steps.indexOf(s) + 1,
|
|
1060
|
+
title: s.title,
|
|
1061
|
+
body: s.body,
|
|
1062
|
+
commands: s.do.map(commandLine),
|
|
1063
|
+
...q ? { question: q } : {},
|
|
1064
|
+
hint: s.hint
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
var ALL = /* @__PURE__ */ new Set(["1", "true", "yes"]);
|
|
1068
|
+
function uiRunRoutes(r, ctx, deps = {}) {
|
|
1069
|
+
const { root, user } = ctx;
|
|
1070
|
+
const nowDate = deps.now ? () => new Date(deps.now) : void 0;
|
|
1071
|
+
const pack = () => loadOrgPack(orgPackDirOf(root));
|
|
1072
|
+
const rel = deps.now ? { now: () => new Date(deps.now) } : {};
|
|
1073
|
+
return r.add("GET", "/api/todo", async ({ query }) => {
|
|
1074
|
+
const change = strParam(query, "change");
|
|
1075
|
+
const op = await pack();
|
|
1076
|
+
let steps;
|
|
1077
|
+
try {
|
|
1078
|
+
steps = await fileStepStore(root).list(change ? { change } : {});
|
|
1079
|
+
} catch (e) {
|
|
1080
|
+
throw new HttpError(422, "A step file can't be read", e.message);
|
|
1081
|
+
}
|
|
1082
|
+
const all = ALL.has(strParam(query, "all") ?? "");
|
|
1083
|
+
const ex = await executorsOf(root, deps);
|
|
1084
|
+
const configured = new Set(ex.executors.map((e) => e.name));
|
|
1085
|
+
const inputs = { root, inputsDir: inputsDirOf(root) };
|
|
1086
|
+
const [items, requests] = await Promise.all([
|
|
1087
|
+
readWorkItemExports(inputs),
|
|
1088
|
+
readServiceRequestExports(inputs)
|
|
1089
|
+
]);
|
|
1090
|
+
return {
|
|
1091
|
+
status: 200,
|
|
1092
|
+
body: {
|
|
1093
|
+
steps: steps.filter((s) => all || s.state === "open").map((s) => stepView(s, steps, op, configured)),
|
|
1094
|
+
sources: MANUAL_SYSTEMS.map((system) => ({
|
|
1095
|
+
system,
|
|
1096
|
+
configured: op.runtime.adapters[RUNTIME_KIND[system]]?.type ?? null,
|
|
1097
|
+
open: steps.filter((s) => s.system === system && s.state === "open").length
|
|
1098
|
+
})),
|
|
1099
|
+
exports: {
|
|
1100
|
+
workItems: items.items.length,
|
|
1101
|
+
requests: requests.requests.length,
|
|
1102
|
+
files: [.../* @__PURE__ */ new Set([...items.files, ...requests.files])].sort()
|
|
1103
|
+
},
|
|
1104
|
+
procedures: op.procedures.map((p) => {
|
|
1105
|
+
const runs = steps.filter((s) => s.procedure === p.id).flatMap((s) => s.runs).sort((a, b) => a.at < b.at ? -1 : 1);
|
|
1106
|
+
const last = runs.at(-1);
|
|
1107
|
+
return {
|
|
1108
|
+
id: p.id,
|
|
1109
|
+
name: p.name,
|
|
1110
|
+
system: p.system,
|
|
1111
|
+
status: p.status,
|
|
1112
|
+
steps: p.steps.length,
|
|
1113
|
+
writes: p.steps.filter((s) => s.writes).length,
|
|
1114
|
+
allowed: p.executors.allowed,
|
|
1115
|
+
configured: p.executors.allowed.filter((n) => configured.has(n)),
|
|
1116
|
+
runs: runs.length,
|
|
1117
|
+
...last ? { lastRun: { at: last.at, status: last.status, executor: last.executor } } : {}
|
|
1118
|
+
};
|
|
1119
|
+
}),
|
|
1120
|
+
executors: { configured: [...configured].sort(), problems: ex.problems }
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
}).add("GET", "/api/todo/{stepId}/export", async ({ params, query }) => {
|
|
1124
|
+
const format = strParam(query, "format") ?? "text";
|
|
1125
|
+
const res = await run2((io) => todoShow(root, stepRef(params.stepId, strParam(query, "change")), { export: format }, io));
|
|
1126
|
+
if (res.code === 0)
|
|
1127
|
+
return { status: 200, body: { format, text: res.out.join("\n") } };
|
|
1128
|
+
if (res.err.some((l) => /^There is no step/.test(l)))
|
|
1129
|
+
throw notFound(res.err.join("\n"));
|
|
1130
|
+
throw refused2(res);
|
|
1131
|
+
}).add("POST", "/api/todo/{stepId}/done", async ({ params, body }) => {
|
|
1132
|
+
const b = parseBody2(api_exports.StepDoneRequest, body);
|
|
1133
|
+
const ref = stepRef(params.stepId, b.change);
|
|
1134
|
+
const res = await run2((io) => todoDone(root, ref, {
|
|
1135
|
+
by: user,
|
|
1136
|
+
result: b.values,
|
|
1137
|
+
...b.skip ? { skip: true } : {},
|
|
1138
|
+
...b.note ? { note: b.note } : {}
|
|
1139
|
+
}, io, deps.todo ?? {}));
|
|
1140
|
+
if (res.code !== 0) {
|
|
1141
|
+
if (res.err.some((l) => /^There is no step/.test(l)))
|
|
1142
|
+
throw notFound(res.err.join("\n"));
|
|
1143
|
+
throw refused2(res);
|
|
1144
|
+
}
|
|
1145
|
+
ctx.events.publish({ type: "file.changed", path: `steps/${params.stepId}` });
|
|
1146
|
+
const s = await findStep(root, params.stepId, b.change);
|
|
1147
|
+
return { status: 200, body: { ...s, lines: res.out } };
|
|
1148
|
+
}).add("GET", "/api/todo/{stepId}/run", async ({ params, query }) => {
|
|
1149
|
+
const s = await findStep(root, params.stepId, strParam(query, "change"));
|
|
1150
|
+
const op = await pack();
|
|
1151
|
+
const proc = s.procedure ? op.procedures.find((p) => p.id === s.procedure) : void 0;
|
|
1152
|
+
const configured = new Set((await executorsOf(root, deps)).executors.map((e) => e.name));
|
|
1153
|
+
const allowed = (proc?.executors.allowed ?? []).filter((n) => configured.has(n));
|
|
1154
|
+
const want = strParam(query, "with");
|
|
1155
|
+
const step = proc?.steps.find((x) => x.id === s.procedureStep);
|
|
1156
|
+
return {
|
|
1157
|
+
status: 200,
|
|
1158
|
+
body: {
|
|
1159
|
+
step: s.id,
|
|
1160
|
+
inputs: s.inputs,
|
|
1161
|
+
inputsSha256: inputsSha256(s.inputs),
|
|
1162
|
+
executors: allowed,
|
|
1163
|
+
...want ? { allowed: allowed.includes(want) } : {},
|
|
1164
|
+
writes: step?.writes ?? false,
|
|
1165
|
+
...proc ? { procedure: proc.id } : {}
|
|
1166
|
+
}
|
|
1167
|
+
};
|
|
1168
|
+
}).add("POST", "/api/todo/{stepId}/run", async ({ params, body }) => {
|
|
1169
|
+
const b = parseBody2(api_exports.StepRunRequest, body);
|
|
1170
|
+
const res = await run2((io) => todoRun(root, stepRef(params.stepId, b.change), {
|
|
1171
|
+
with: b.with,
|
|
1172
|
+
approve: user,
|
|
1173
|
+
inputs: b.inputsSha256,
|
|
1174
|
+
...b.fillOnly ? { fillOnly: true } : {}
|
|
1175
|
+
}, io, deps.todo ?? {}));
|
|
1176
|
+
if (res.code !== 0) {
|
|
1177
|
+
if (res.err.some((l) => /^There is no step/.test(l)))
|
|
1178
|
+
throw notFound(res.err.join("\n"));
|
|
1179
|
+
throw refused2(res);
|
|
1180
|
+
}
|
|
1181
|
+
ctx.events.publish({ type: "file.changed", path: `steps/${params.stepId}` });
|
|
1182
|
+
return {
|
|
1183
|
+
status: 200,
|
|
1184
|
+
body: { step: await findStep(root, params.stepId, b.change), lines: res.out }
|
|
1185
|
+
};
|
|
1186
|
+
}).add("GET", "/api/service-desk", async ({ query }) => {
|
|
1187
|
+
const only = strParam(query, "change");
|
|
1188
|
+
const op = await pack();
|
|
1189
|
+
const steps = await fileStepStore(root).list({ system: "service-desk" }).catch(() => []);
|
|
1190
|
+
const requests = [];
|
|
1191
|
+
for (const c of await openChanges2(root))
|
|
1192
|
+
if (!only || c.id === only)
|
|
1193
|
+
requests.push(...await changeRequests(root, c.id, op.serviceRequests, steps));
|
|
1194
|
+
const exported = await readServiceRequestExports({ root, inputsDir: inputsDirOf(root) });
|
|
1195
|
+
const ex = await executorsOf(root, deps);
|
|
1196
|
+
const configured = new Set(ex.executors.map((e) => e.name));
|
|
1197
|
+
return {
|
|
1198
|
+
status: 200,
|
|
1199
|
+
body: {
|
|
1200
|
+
adapter: { configured: op.runtime.adapters.serviceDesk?.type ?? null },
|
|
1201
|
+
exports: { requests: exported.requests.length, files: exported.files },
|
|
1202
|
+
templates: op.serviceRequests.map((t) => ({
|
|
1203
|
+
id: t.id,
|
|
1204
|
+
name: t.name,
|
|
1205
|
+
requestType: t.requestType,
|
|
1206
|
+
leadTimeDays: t.leadTimeDays
|
|
1207
|
+
})),
|
|
1208
|
+
executors: op.procedures.filter((p) => p.system === "service-desk").flatMap((p) => p.executors.allowed.filter((n) => configured.has(n))).filter((n, i, a) => a.indexOf(n) === i),
|
|
1209
|
+
requests
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
1212
|
+
}).add("POST", "/api/service-desk/{changeId}/draft", async ({ params, body }) => {
|
|
1213
|
+
await readChange2(root, params.changeId);
|
|
1214
|
+
const b = parseBody2(api_exports.DraftRequestsRequest, body);
|
|
1215
|
+
const res = await run2((io) => snowDraft(root, params.changeId, b.force ? { force: true } : {}, io));
|
|
1216
|
+
if (res.code !== 0 && (res.code !== 1 || res.err.length))
|
|
1217
|
+
throw refused2(res);
|
|
1218
|
+
ctx.events.publish({ type: "file.changed", path: `${params.changeId}/snow.md` });
|
|
1219
|
+
return { status: 200, body: { lines: res.out, toFill: res.code === 1 } };
|
|
1220
|
+
}).add("POST", "/api/service-desk/{changeId}/submit", async ({ params }) => {
|
|
1221
|
+
await readChange2(root, params.changeId);
|
|
1222
|
+
const op = await pack();
|
|
1223
|
+
const desk = deps.serviceDesk ?? manualAdapter("serviceDesk", {
|
|
1224
|
+
store: fileStepStore(root),
|
|
1225
|
+
root,
|
|
1226
|
+
change: params.changeId,
|
|
1227
|
+
procedures: op.procedures,
|
|
1228
|
+
...deps.now ? { now: () => deps.now } : {}
|
|
1229
|
+
}, op.runtime.adapters.serviceDesk?.settings ?? {});
|
|
1230
|
+
const res = await run2((io) => snowSubmit(root, params.changeId, { approve: user }, io, {
|
|
1231
|
+
serviceDesk: desk,
|
|
1232
|
+
...nowDate ? { now: nowDate } : {}
|
|
1233
|
+
}));
|
|
1234
|
+
if (res.code !== 0)
|
|
1235
|
+
throw refused2(res);
|
|
1236
|
+
ctx.events.publish({ type: "file.changed", path: `${params.changeId}/sync.yaml` });
|
|
1237
|
+
return { status: 200, body: { lines: res.out } };
|
|
1238
|
+
}).add("GET", "/api/release", async () => {
|
|
1239
|
+
const at = /* @__PURE__ */ new Set(["release-ready", "released", "post-release-review"]);
|
|
1240
|
+
const list = (await openChanges2(root)).map((c) => ({
|
|
1241
|
+
id: c.id,
|
|
1242
|
+
title: c.title,
|
|
1243
|
+
type: c.type,
|
|
1244
|
+
state: c.state,
|
|
1245
|
+
atRelease: at.has(c.state)
|
|
1246
|
+
}));
|
|
1247
|
+
list.sort((a, b) => Number(b.atRelease) - Number(a.atRelease));
|
|
1248
|
+
return { status: 200, body: list };
|
|
1249
|
+
}).add("GET", "/api/release/{changeId}", async ({ params }) => {
|
|
1250
|
+
const id = params.changeId;
|
|
1251
|
+
const c = await readChange2(root, id);
|
|
1252
|
+
const op = await pack();
|
|
1253
|
+
const rep = await changeReport(root, id);
|
|
1254
|
+
const findings = await latestFindings2(root, id);
|
|
1255
|
+
const open = { blocking: 0, gap: 0, risk: 0, idea: 0 };
|
|
1256
|
+
for (const f of findings)
|
|
1257
|
+
if (f.state === "open")
|
|
1258
|
+
open[f.severity] += 1;
|
|
1259
|
+
const steps = await fileStepStore(root).list({ change: id }).catch(() => []);
|
|
1260
|
+
const requests = await changeRequests(root, id, op.serviceRequests, steps);
|
|
1261
|
+
const design = (await readChangeFiles(changeFolder(root, id))).docs.get("design.md");
|
|
1262
|
+
const sec = design?.sections.find((s) => /rollout|rollback|release|deploy/i.test(s.heading));
|
|
1263
|
+
const repos = (await readChangeRepos(root, id).catch(() => void 0))?.repos ?? [];
|
|
1264
|
+
const conflicts = (await detectConflicts(root).catch(() => [])).filter((p) => p.changes.includes(id)).map((p) => ({ with: p.changes.find((x) => x !== id), overlap: p.overlap }));
|
|
1265
|
+
const archive = await archiveGate(root, id).catch((e) => ({
|
|
1266
|
+
pass: false,
|
|
1267
|
+
blockers: [{ code: "error", message: e.message }],
|
|
1268
|
+
notes: []
|
|
1269
|
+
}));
|
|
1270
|
+
const handoff = c.handoffs.filter((h) => h.phase === "release").at(-1);
|
|
1271
|
+
return {
|
|
1272
|
+
status: 200,
|
|
1273
|
+
body: {
|
|
1274
|
+
change: { id: c.id, title: c.title, type: c.type, state: c.state, owners: c.owners },
|
|
1275
|
+
status: {
|
|
1276
|
+
state: rep.state,
|
|
1277
|
+
...rep.phase ? { phase: rep.phase } : {},
|
|
1278
|
+
...rep.next ? { next: rep.next } : {},
|
|
1279
|
+
nextStep: rep.nextStep,
|
|
1280
|
+
risk: rep.risk,
|
|
1281
|
+
blockers: rep.blockers,
|
|
1282
|
+
reasons: rep.reasons
|
|
1283
|
+
},
|
|
1284
|
+
// Go / no-go is the release hand-off PR: merging it is the go (ADR 0073).
|
|
1285
|
+
...handoff ? {
|
|
1286
|
+
handoff: {
|
|
1287
|
+
state: handoff.state,
|
|
1288
|
+
pr: handoff.pr.url,
|
|
1289
|
+
approvals: handoff.approvals,
|
|
1290
|
+
reviewers: handoff.reviewers
|
|
1291
|
+
}
|
|
1292
|
+
} : {},
|
|
1293
|
+
openFindings: open,
|
|
1294
|
+
requests: {
|
|
1295
|
+
total: requests.length,
|
|
1296
|
+
filed: requests.filter((x) => x.state !== "to-file").length,
|
|
1297
|
+
toFile: requests.filter((x) => x.state === "to-file").length,
|
|
1298
|
+
list: requests
|
|
1299
|
+
},
|
|
1300
|
+
repos: repos.map((x) => ({
|
|
1301
|
+
id: x.id,
|
|
1302
|
+
branch: x.branch,
|
|
1303
|
+
...x.pr ? { pr: x.pr } : {},
|
|
1304
|
+
build: x.build
|
|
1305
|
+
})),
|
|
1306
|
+
...sec && design ? {
|
|
1307
|
+
rollout: {
|
|
1308
|
+
heading: sec.heading,
|
|
1309
|
+
text: design.lines.slice(sec.line, sec.end).join("\n").trim()
|
|
1310
|
+
}
|
|
1311
|
+
} : {},
|
|
1312
|
+
conflicts,
|
|
1313
|
+
archive: { pass: archive.pass, blockers: archive.blockers, notes: archive.notes },
|
|
1314
|
+
evidence: await filesUnder(changeFolder(root, id))
|
|
1315
|
+
}
|
|
1316
|
+
};
|
|
1317
|
+
}).add("POST", "/api/release/{changeId}/archive", async ({ params, body }) => {
|
|
1318
|
+
const id = params.changeId;
|
|
1319
|
+
await readChange2(root, id);
|
|
1320
|
+
const b = parseBody2(api_exports.ArchiveRequest, body);
|
|
1321
|
+
const gate = await run2((io) => ciArchiveGate(root, id, {}, io));
|
|
1322
|
+
if (gate.code !== 0)
|
|
1323
|
+
throw refused2(gate);
|
|
1324
|
+
const res = await run2((io) => changeArchive(root, id, { by: user, ...b.rescan === false ? { rescan: false } : {} }, io));
|
|
1325
|
+
if (res.code !== 0)
|
|
1326
|
+
throw refused2(res);
|
|
1327
|
+
ctx.events.publish({ type: "file.changed", path: id });
|
|
1328
|
+
return { status: 200, body: { lines: [...gate.out, ...res.out] } };
|
|
1329
|
+
}).add("GET", "/api/release/{changeId}/plan", async ({ params }) => ({
|
|
1330
|
+
status: 200,
|
|
1331
|
+
body: viewOf(await run2((io) => releasePlanCommand(root, params.changeId, { json: true }, io, rel)))
|
|
1332
|
+
})).add("GET", "/api/release/{changeId}/forecast", async ({ params }) => ({
|
|
1333
|
+
status: 200,
|
|
1334
|
+
body: viewOf(await run2((io) => releaseForecastCommand(root, params.changeId, { json: true }, io, rel)))
|
|
1335
|
+
})).add("GET", "/api/release/{changeId}/evidence", async ({ params }) => ({
|
|
1336
|
+
status: 200,
|
|
1337
|
+
body: viewOf(await run2((io) => releaseEvidenceCommand(root, params.changeId, { json: true }, io, rel)))
|
|
1338
|
+
})).add("GET", "/api/release/{changeId}/notes", async ({ params, query }) => {
|
|
1339
|
+
const audience = strParam(query, "audience") ?? "business";
|
|
1340
|
+
return {
|
|
1341
|
+
status: 200,
|
|
1342
|
+
body: viewOf(await run2((io) => releaseNotesCommand(root, params.changeId, { audience, json: true }, io, rel)))
|
|
1343
|
+
};
|
|
1344
|
+
}).add("GET", "/api/release/{changeId}/security", async ({ params }) => ({
|
|
1345
|
+
status: 200,
|
|
1346
|
+
body: viewOf(await run2((io) => releaseSecurityCommand(root, params.changeId, { json: true }, io, rel)))
|
|
1347
|
+
})).add("GET", "/api/outcomes", async ({ query }) => {
|
|
1348
|
+
const change = strParam(query, "change");
|
|
1349
|
+
return {
|
|
1350
|
+
status: 200,
|
|
1351
|
+
body: viewOf(await run2((io) => outcomeList(root, { ...change ? { change } : {}, json: true }, io, rel)))
|
|
1352
|
+
};
|
|
1353
|
+
}).add("POST", "/api/release/{changeId}/outcomes", async ({ params, body }) => {
|
|
1354
|
+
const id = params.changeId;
|
|
1355
|
+
await readChange2(root, id);
|
|
1356
|
+
const b = parseBody2(api_exports.RecordOutcomeRequest, body);
|
|
1357
|
+
const res = await run2((io) => outcomeRecord(root, id, b.measure, { value: b.value, by: user, ...b.note ? { note: b.note } : {}, json: true }, io, rel));
|
|
1358
|
+
if (res.code !== 0)
|
|
1359
|
+
throw refused2(res);
|
|
1360
|
+
ctx.events.publish({ type: "file.changed", path: `${id}/review/outcomes.yaml` });
|
|
1361
|
+
return { status: 200, body: jsonOf(res) ?? { lines: res.out } };
|
|
1362
|
+
}).add("GET", "/api/governance", async () => {
|
|
1363
|
+
const conf = jsonOf(await run2((io) => conformanceCommand(orgPackDirOf(root), { kind: "org-pack", json: true }, io)));
|
|
1364
|
+
const constitution = await loadConstitution();
|
|
1365
|
+
const org = await readPackFiles(root, join3(orgPackDirOf(root), "checklists"));
|
|
1366
|
+
const team = await readPackFiles(root, join3(orgPackDirOf(root), "teams"));
|
|
1367
|
+
const layered = layerRules([
|
|
1368
|
+
{ pack: constitution, file: "built-in", origin: "constitution" },
|
|
1369
|
+
...org.packs.map((p) => ({ ...p, origin: "org" })),
|
|
1370
|
+
...team.packs.map((p) => ({ ...p, origin: "team" }))
|
|
1371
|
+
]);
|
|
1372
|
+
const changes = await openChanges2(root);
|
|
1373
|
+
const waivers = [];
|
|
1374
|
+
for (const c of changes) {
|
|
1375
|
+
for (const f of await latestFindings2(root, c.id))
|
|
1376
|
+
if (f.state === "waived")
|
|
1377
|
+
waivers.push({
|
|
1378
|
+
change: c.id,
|
|
1379
|
+
gate: f.gate,
|
|
1380
|
+
rule: f.rule,
|
|
1381
|
+
summary: f.summary,
|
|
1382
|
+
...f.resolution ? { by: f.resolution.by, at: f.resolution.at, reason: f.resolution.reason } : {}
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
const stats = jsonOf(await run2((io) => criticStats(root, { json: true }, io)));
|
|
1386
|
+
const count = (packs) => packs.reduce((a, p) => a + p.pack.rules.length, 0);
|
|
1387
|
+
return {
|
|
1388
|
+
status: 200,
|
|
1389
|
+
body: {
|
|
1390
|
+
conformance: conf ? { ...conf, target: "org-pack" } : {
|
|
1391
|
+
kind: "org-pack",
|
|
1392
|
+
passed: false,
|
|
1393
|
+
results: [],
|
|
1394
|
+
summary: { pass: 0, fail: 0, warn: 0 }
|
|
1395
|
+
},
|
|
1396
|
+
rules: {
|
|
1397
|
+
constitution: constitution.rules.length,
|
|
1398
|
+
org: count(org.packs),
|
|
1399
|
+
team: count(team.packs),
|
|
1400
|
+
inForce: layered.rules.length,
|
|
1401
|
+
waivable: layered.rules.filter((x) => x.waivable).length,
|
|
1402
|
+
refused: layered.refused,
|
|
1403
|
+
problems: [...org.problems, ...team.problems, ...layered.problems]
|
|
1404
|
+
},
|
|
1405
|
+
waivers: waivers.filter((w, i, a) => a.findIndex((x) => x.change === w.change && x.rule === w.rule && x.gate === w.gate) === i),
|
|
1406
|
+
overrides: stats ?? { changes: 0, rules: [], suggestions: [] }
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
}).add("GET", "/api/evals", async () => {
|
|
1410
|
+
const op = await pack();
|
|
1411
|
+
const stats = await overrideStats(root);
|
|
1412
|
+
const t = { total: 0, applied: 0, dismissed: 0, waived: 0, asked: 0, open: 0 };
|
|
1413
|
+
for (const x of stats.rules) {
|
|
1414
|
+
t.total += x.total;
|
|
1415
|
+
t.applied += x.applied;
|
|
1416
|
+
t.dismissed += x.dismissed;
|
|
1417
|
+
t.waived += x.waived;
|
|
1418
|
+
t.asked += x.asked;
|
|
1419
|
+
t.open += x.open;
|
|
1420
|
+
}
|
|
1421
|
+
return {
|
|
1422
|
+
status: 200,
|
|
1423
|
+
body: {
|
|
1424
|
+
quality: await readQuality(root) ?? null,
|
|
1425
|
+
runs: (await listReports(root)).map((x) => ({
|
|
1426
|
+
runId: x.runId,
|
|
1427
|
+
mode: x.mode,
|
|
1428
|
+
startedAt: x.startedAt,
|
|
1429
|
+
finishedAt: x.finishedAt,
|
|
1430
|
+
runs: x.runs,
|
|
1431
|
+
agents: x.agents,
|
|
1432
|
+
failed: x.results.filter((c) => !c.pass).length,
|
|
1433
|
+
skipped: x.skipped.length,
|
|
1434
|
+
...x.liveBar ? { liveBar: x.liveBar.pass } : {}
|
|
1435
|
+
})),
|
|
1436
|
+
findings: t,
|
|
1437
|
+
modes: op.runtime.modes,
|
|
1438
|
+
models: op.runtime.models.allowed
|
|
1439
|
+
}
|
|
1440
|
+
};
|
|
1441
|
+
}).add("POST", "/api/evals/run", async ({ body }) => {
|
|
1442
|
+
const b = parseBody2(api_exports.EvalRunRequest, body);
|
|
1443
|
+
if (b.mode === "live")
|
|
1444
|
+
throw badRequest("Live evals call a real model, so the app doesn't start them. Run atticus eval run --mode live in a terminal.");
|
|
1445
|
+
const res = await run2((io) => evalRun(root, {
|
|
1446
|
+
mode: b.mode,
|
|
1447
|
+
json: true,
|
|
1448
|
+
...b.agents ? { agent: b.agents } : {},
|
|
1449
|
+
...b.runs ? { runs: b.runs } : {}
|
|
1450
|
+
}, io, nowDate ? { now: nowDate } : {}));
|
|
1451
|
+
const out = jsonOf(res);
|
|
1452
|
+
if (res.code === 2 || !out)
|
|
1453
|
+
throw refused2(res.code === 2 ? res : { ...res, code: 1 });
|
|
1454
|
+
ctx.events.publish({ type: "file.changed", path: ".evals/quality.json" });
|
|
1455
|
+
return { status: 200, body: { ...out, problems: res.err } };
|
|
1456
|
+
}).add("GET", "/api/evals/defences", async () => {
|
|
1457
|
+
const res = await run2((io) => defencesScan(root, void 0, { json: true }, io));
|
|
1458
|
+
const scan = jsonOf(res) ?? {
|
|
1459
|
+
files: [],
|
|
1460
|
+
scanned: 0,
|
|
1461
|
+
skipped: 0
|
|
1462
|
+
};
|
|
1463
|
+
const items = [];
|
|
1464
|
+
for (const c of await openChanges2(root)) {
|
|
1465
|
+
const dir = join3(changeFolder(root, c.id), "review", "defences");
|
|
1466
|
+
for (const f of (await readdir2(dir).catch(() => [])).sort()) {
|
|
1467
|
+
if (!f.endsWith(".json"))
|
|
1468
|
+
continue;
|
|
1469
|
+
try {
|
|
1470
|
+
const list = JSON.parse(await readFile2(join3(dir, f), "utf8"));
|
|
1471
|
+
for (const q of list)
|
|
1472
|
+
items.push({
|
|
1473
|
+
change: c.id,
|
|
1474
|
+
file: `review/defences/${f}`,
|
|
1475
|
+
source: q.source,
|
|
1476
|
+
origin: q.origin,
|
|
1477
|
+
summary: q.summary,
|
|
1478
|
+
fix: q.fix,
|
|
1479
|
+
question: q.question,
|
|
1480
|
+
signals: q.signals.length
|
|
1481
|
+
});
|
|
1482
|
+
} catch {
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
return { status: 200, body: { ...scan, items, problems: res.err } };
|
|
1487
|
+
}).add("GET", "/api/learning", async () => {
|
|
1488
|
+
const { lessons, problems } = await lessonsFor(root);
|
|
1489
|
+
const sandbox = await isSandbox(root);
|
|
1490
|
+
const progress = sandbox ? await readProgress(root) : void 0;
|
|
1491
|
+
const view2 = lessons.map((l) => {
|
|
1492
|
+
const p = progress?.lessons[l.id];
|
|
1493
|
+
const done = p ? l.steps.filter((s) => p.steps[s.id]).length : 0;
|
|
1494
|
+
return {
|
|
1495
|
+
id: l.id,
|
|
1496
|
+
role: l.role,
|
|
1497
|
+
title: l.title,
|
|
1498
|
+
summary: l.summary,
|
|
1499
|
+
minutes: l.minutes,
|
|
1500
|
+
steps: l.steps.length,
|
|
1501
|
+
done,
|
|
1502
|
+
state: !p ? "not-started" : p.completedAt ? "done" : "in-progress"
|
|
1503
|
+
};
|
|
1504
|
+
});
|
|
1505
|
+
const roles = [...new Set(lessons.map((l) => l.role))].map((role) => ({
|
|
1506
|
+
role,
|
|
1507
|
+
lessons: view2.filter((v) => v.role === role).length,
|
|
1508
|
+
done: view2.filter((v) => v.role === role && v.state === "done").length
|
|
1509
|
+
}));
|
|
1510
|
+
const cur = progress?.current ? lessons.find((l) => l.id === progress.current) : void 0;
|
|
1511
|
+
const lp = cur ? progress.lessons[cur.id] : void 0;
|
|
1512
|
+
const next = cur?.steps.find((s) => !lp?.steps[s.id]);
|
|
1513
|
+
return {
|
|
1514
|
+
status: 200,
|
|
1515
|
+
body: {
|
|
1516
|
+
sandbox,
|
|
1517
|
+
roles,
|
|
1518
|
+
lessons: view2,
|
|
1519
|
+
...cur ? {
|
|
1520
|
+
current: {
|
|
1521
|
+
lesson: cur.id,
|
|
1522
|
+
title: cur.title,
|
|
1523
|
+
intro: cur.intro,
|
|
1524
|
+
steps: cur.steps.length,
|
|
1525
|
+
completed: !next,
|
|
1526
|
+
...next ? { step: stepOfLesson(cur, next.id) } : { outro: cur.outro }
|
|
1527
|
+
}
|
|
1528
|
+
} : {},
|
|
1529
|
+
problems
|
|
1530
|
+
}
|
|
1531
|
+
};
|
|
1532
|
+
}).add("POST", "/api/learning/start", async ({ body }) => {
|
|
1533
|
+
const b = parseBody2(api_exports.StartLessonRequest, body);
|
|
1534
|
+
const res = await run2((io) => learnStart(root, b.lesson, { json: true }, io, nowDate ? { now: nowDate() } : {}));
|
|
1535
|
+
const out = jsonOf(res);
|
|
1536
|
+
if (res.code !== 0 || !out)
|
|
1537
|
+
throw refused2(res);
|
|
1538
|
+
return { status: 200, body: out };
|
|
1539
|
+
}).add("POST", "/api/learning/check", async ({ body }) => {
|
|
1540
|
+
const b = parseBody2(api_exports.CheckLessonRequest, body);
|
|
1541
|
+
const res = await run2((io) => learnCheck(root, {
|
|
1542
|
+
json: true,
|
|
1543
|
+
...b.answer !== void 0 && b.answer !== "" ? { answer: b.answer } : {},
|
|
1544
|
+
...b.lesson ? { lesson: b.lesson } : {},
|
|
1545
|
+
...b.step ? { step: b.step } : {}
|
|
1546
|
+
}, io, nowDate ? { now: nowDate() } : {}));
|
|
1547
|
+
const out = jsonOf(res);
|
|
1548
|
+
if (!out)
|
|
1549
|
+
throw refused2(res.code === 0 ? { ...res, code: 1 } : res);
|
|
1550
|
+
let hint;
|
|
1551
|
+
if (!out.pass) {
|
|
1552
|
+
const l = (await lessonsFor(root)).lessons.find((x) => x.id === out.lesson);
|
|
1553
|
+
hint = l?.steps.find((s) => s.id === out.step)?.hint;
|
|
1554
|
+
}
|
|
1555
|
+
return { status: 200, body: { ...out, ...hint ? { hint } : {} } };
|
|
1556
|
+
}).add("POST", "/api/learning/sandbox", async ({ body }) => {
|
|
1557
|
+
const b = parseBody2(api_exports.SandboxRequest, body);
|
|
1558
|
+
const res = await run2((io) => sandboxCreate(resolve(b.dir), { json: true }, io, nowDate ? { now: nowDate() } : {}));
|
|
1559
|
+
const out = jsonOf(res);
|
|
1560
|
+
if (res.code !== 0 || !out)
|
|
1561
|
+
throw refused2(res);
|
|
1562
|
+
return { status: 201, body: out };
|
|
1563
|
+
}).add("GET", "/api/settings", async () => {
|
|
1564
|
+
const op = await pack();
|
|
1565
|
+
const rt = op.runtime;
|
|
1566
|
+
const ex = await executorsOf(root, deps);
|
|
1567
|
+
const names = new Set(ex.executors.map((e) => e.name));
|
|
1568
|
+
const kinds = [
|
|
1569
|
+
"workTracker",
|
|
1570
|
+
"serviceDesk",
|
|
1571
|
+
"gitHost",
|
|
1572
|
+
"wiki",
|
|
1573
|
+
"notify",
|
|
1574
|
+
"design",
|
|
1575
|
+
"model"
|
|
1576
|
+
];
|
|
1577
|
+
return {
|
|
1578
|
+
status: 200,
|
|
1579
|
+
body: {
|
|
1580
|
+
runtime: {
|
|
1581
|
+
assistants: rt.assistants,
|
|
1582
|
+
editors: rt.editors,
|
|
1583
|
+
modes: rt.modes,
|
|
1584
|
+
models: rt.models,
|
|
1585
|
+
autonomy: rt.autonomy,
|
|
1586
|
+
budgets: rt.budgets,
|
|
1587
|
+
retention: rt.retention,
|
|
1588
|
+
telemetry: rt.telemetry,
|
|
1589
|
+
buildEngine: rt.buildEngine ?? { default: "speckit", perRepo: {} }
|
|
1590
|
+
},
|
|
1591
|
+
adapters: kinds.map((kind) => {
|
|
1592
|
+
const cfg = rt.adapters[kind];
|
|
1593
|
+
return {
|
|
1594
|
+
kind,
|
|
1595
|
+
type: cfg?.type ?? null,
|
|
1596
|
+
mode: !cfg ? "not-set" : cfg.type === "manual" ? "manual" : cfg.type === "mock" ? "mock" : "live"
|
|
1597
|
+
};
|
|
1598
|
+
}),
|
|
1599
|
+
executors: {
|
|
1600
|
+
configured: ex.executors.map((e) => ({
|
|
1601
|
+
name: e.name,
|
|
1602
|
+
transport: e.transport,
|
|
1603
|
+
where: e.transport === "http" ? e.url : [e.command, ...e.args].join(" "),
|
|
1604
|
+
fillOnly: e.fillOnly,
|
|
1605
|
+
covers: [...e.procedures, ...e.stepKinds],
|
|
1606
|
+
allowedFor: op.procedures.filter((p) => p.executors.allowed.includes(e.name)).map((p) => p.id)
|
|
1607
|
+
})),
|
|
1608
|
+
missing: op.procedures.flatMap((p) => p.executors.allowed.filter((n) => !names.has(n)).map((n) => ({ procedure: p.id, executor: n }))),
|
|
1609
|
+
problems: ex.problems
|
|
1610
|
+
},
|
|
1611
|
+
orgPack: {
|
|
1612
|
+
platforms: op.profiles.map((p) => p.platform),
|
|
1613
|
+
...op.pack ? { pack: op.pack } : {},
|
|
1614
|
+
counts: {
|
|
1615
|
+
patterns: op.patterns.length,
|
|
1616
|
+
dialects: op.dialects.length,
|
|
1617
|
+
checklists: op.checklists.length,
|
|
1618
|
+
agents: op.agents.length,
|
|
1619
|
+
procedures: op.procedures.length,
|
|
1620
|
+
serviceRequests: op.serviceRequests.length,
|
|
1621
|
+
profiles: op.profiles.length,
|
|
1622
|
+
skills: op.skills.length
|
|
1623
|
+
},
|
|
1624
|
+
defaulted: op.defaulted,
|
|
1625
|
+
problems: op.problems
|
|
1626
|
+
},
|
|
1627
|
+
gates: op.gates.gates.map((g) => ({
|
|
1628
|
+
id: g.id,
|
|
1629
|
+
phase: g.phase,
|
|
1630
|
+
checklists: g.checklists,
|
|
1631
|
+
checks: g.checks.map((c) => c.check),
|
|
1632
|
+
// ADR 0087: null when the gate has no round limit.
|
|
1633
|
+
maxAuthorRounds: gateRoundLimit(op.gates, g.id) ?? null
|
|
1634
|
+
}))
|
|
1635
|
+
}
|
|
1636
|
+
};
|
|
1637
|
+
}).add("GET", "/api/settings/executors/check", async () => {
|
|
1638
|
+
const res = await run2((io) => executorsCheck(root, { json: true }, io, deps.todo ?? {}));
|
|
1639
|
+
const out = jsonOf(res);
|
|
1640
|
+
if (!out)
|
|
1641
|
+
throw refused2({ ...res, code: 1 });
|
|
1642
|
+
return { status: 200, body: out };
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
// packages/cli/dist/ui-api-me.js
|
|
1647
|
+
async function meView(ctx) {
|
|
1648
|
+
const op = await loadOrgPack(orgPackDirOf(ctx.root));
|
|
1649
|
+
const me = await readMe(ctx.root);
|
|
1650
|
+
const p = ctx.person;
|
|
1651
|
+
return {
|
|
1652
|
+
user: ctx.user,
|
|
1653
|
+
...p?.name ? { name: p.name } : {},
|
|
1654
|
+
...p?.email ? { email: p.email } : {},
|
|
1655
|
+
source: p?.source ?? "given",
|
|
1656
|
+
...p?.warning ? { warning: p.warning } : {},
|
|
1657
|
+
ai: op.runtime.ai,
|
|
1658
|
+
notify: {
|
|
1659
|
+
digest: me.notify?.digest ?? "off",
|
|
1660
|
+
...me.notify?.channel ? { channel: me.notify.channel } : {}
|
|
1661
|
+
},
|
|
1662
|
+
// WP7.5: the changes the person follows.
|
|
1663
|
+
watching: me.watch
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
function uiMeRoutes(r, ctx, deps = {}) {
|
|
1667
|
+
return r.add("GET", "/api/me", async () => ({ status: 200, body: await meView(ctx) })).add("PUT", "/api/me/notify", async ({ body }) => {
|
|
1668
|
+
const b = api_exports.SaveNotifyRequest.safeParse(body ?? {});
|
|
1669
|
+
if (!b.success)
|
|
1670
|
+
throw badRequest("Say how often: { digest: off | daily | weekly, channel?: <channel> }.");
|
|
1671
|
+
const me = await readMe(ctx.root);
|
|
1672
|
+
await writeMe(ctx.root, {
|
|
1673
|
+
...me,
|
|
1674
|
+
notify: { digest: b.data.digest, ...b.data.channel ? { channel: b.data.channel } : {} }
|
|
1675
|
+
});
|
|
1676
|
+
return { status: 200, body: await meView(ctx) };
|
|
1677
|
+
}).add("PUT", "/api/me/watch", async ({ body }) => {
|
|
1678
|
+
const b = api_exports.WatchRequest.safeParse(body ?? {});
|
|
1679
|
+
if (!b.success)
|
|
1680
|
+
throw badRequest("Say which change and whether: { change, watch: true }.");
|
|
1681
|
+
const me = await readMe(ctx.root);
|
|
1682
|
+
const watch = b.data.watch ? [...me.watch, b.data.change] : me.watch.filter((c) => c !== b.data.change);
|
|
1683
|
+
await writeMe(ctx.root, { ...me, watch });
|
|
1684
|
+
return { status: 200, body: await meView(ctx) };
|
|
1685
|
+
}).add("POST", "/api/me/digest", async ({ body }) => {
|
|
1686
|
+
const b = api_exports.SendDigestRequest.safeParse(body ?? {});
|
|
1687
|
+
if (!b.success)
|
|
1688
|
+
throw badRequest("Send { send: true } to post it, or {} to see it.");
|
|
1689
|
+
const me = await readMe(ctx.root);
|
|
1690
|
+
const out = [];
|
|
1691
|
+
const err = [];
|
|
1692
|
+
const code = await digestCommand(ctx.root, {
|
|
1693
|
+
as: ctx.user,
|
|
1694
|
+
...me.notify?.channel ? { channel: me.notify.channel } : {},
|
|
1695
|
+
send: b.data.send,
|
|
1696
|
+
json: true
|
|
1697
|
+
}, { out: (l) => out.push(l), err: (l) => err.push(l) }, deps);
|
|
1698
|
+
if (code === 2)
|
|
1699
|
+
throw new HttpError(400, "The request isn't valid", err.join("\n"));
|
|
1700
|
+
return { status: 200, body: JSON.parse(out.join("\n")) };
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
// packages/cli/dist/ui-api-know.js
|
|
1705
|
+
var ID = /^[a-z0-9][a-z0-9._-]*$/;
|
|
1706
|
+
var VIEWS = {
|
|
1707
|
+
coverage: () => ["coverage"],
|
|
1708
|
+
questions: () => ["questions"],
|
|
1709
|
+
intake: () => ["intake", "list"],
|
|
1710
|
+
triage: () => ["triage"],
|
|
1711
|
+
features: (id) => id ? ["feature", "show", id] : ["feature", "list"],
|
|
1712
|
+
glossary: () => ["glossary", "list"],
|
|
1713
|
+
"critic-stats": () => ["critic", "stats"],
|
|
1714
|
+
conflicts: () => ["change", "conflicts"],
|
|
1715
|
+
findings: (id) => id ? ["findings", id] : void 0,
|
|
1716
|
+
drift: (id) => id ? ["drift", id] : void 0,
|
|
1717
|
+
trace: (id) => id ? ["trace", id, "--no-write"] : void 0,
|
|
1718
|
+
"story-bar": (id) => id ? ["plan", "bar", id] : void 0,
|
|
1719
|
+
sources: () => ["sources", "sync"],
|
|
1720
|
+
initiatives: (id) => id ? ["initiative", "status", id] : void 0,
|
|
1721
|
+
reconcile: () => ["reconcile"]
|
|
1722
|
+
};
|
|
1723
|
+
var DIAGRAM_VIEWS = ["context", "container", "data", "event", "sequence"];
|
|
1724
|
+
var scanFirst = (words) => /catalog is empty|scan first/i.test(words);
|
|
1725
|
+
function uiKnowRoutes(r, ctx, o = {}) {
|
|
1726
|
+
const doctor = o.doctor ?? ((root) => doctorChecks(root, { portFree: async () => true }));
|
|
1727
|
+
let checked;
|
|
1728
|
+
const machine = (fresh = false) => {
|
|
1729
|
+
if (fresh || !checked || Date.now() - checked.at > 3e5) {
|
|
1730
|
+
const run3 = { at: Date.now(), checks: doctor(ctx.root) };
|
|
1731
|
+
checked = run3;
|
|
1732
|
+
run3.checks.catch(() => {
|
|
1733
|
+
if (checked === run3)
|
|
1734
|
+
checked = void 0;
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
return checked.checks;
|
|
1738
|
+
};
|
|
1739
|
+
if (o.warm)
|
|
1740
|
+
void machine().catch(() => void 0);
|
|
1741
|
+
const view2 = async (name, arg) => {
|
|
1742
|
+
const build = VIEWS[name];
|
|
1743
|
+
if (!build)
|
|
1744
|
+
throw notFound(`There is no view ${name}.`);
|
|
1745
|
+
if (arg !== void 0 && !ID.test(arg))
|
|
1746
|
+
throw badRequest(`${arg} isn't an id.`);
|
|
1747
|
+
const args = build(arg);
|
|
1748
|
+
if (!args)
|
|
1749
|
+
throw badRequest(`The ${name} view needs an id: /api/view/${name}/<id>.`);
|
|
1750
|
+
const { main } = await import("./chunk-MTAUZW2Z.js");
|
|
1751
|
+
const out = [];
|
|
1752
|
+
const err = [];
|
|
1753
|
+
const code = await main([...args, "--json", "--root", ctx.root], { out: (l) => out.push(l), err: (l) => err.push(l) }, { quiet: true });
|
|
1754
|
+
const text2 = out.join("\n").trim();
|
|
1755
|
+
if (code === 2) {
|
|
1756
|
+
const words = err.join("\n") || "The command refused the request.";
|
|
1757
|
+
if (scanFirst(words))
|
|
1758
|
+
throw new HttpError(422, "Atticus can't show that yet", words, { notYet: true });
|
|
1759
|
+
throw badRequest(words);
|
|
1760
|
+
}
|
|
1761
|
+
let body;
|
|
1762
|
+
try {
|
|
1763
|
+
body = text2 ? JSON.parse(text2) : null;
|
|
1764
|
+
} catch {
|
|
1765
|
+
body = void 0;
|
|
1766
|
+
}
|
|
1767
|
+
if (body === void 0) {
|
|
1768
|
+
const words = [...err, ...out].join("\n") || "No output.";
|
|
1769
|
+
throw new HttpError(422, "Atticus can't show that yet", words, scanFirst(words) ? { notYet: true } : void 0);
|
|
1770
|
+
}
|
|
1771
|
+
return { status: 200, body: { code, data: body, problems: err } };
|
|
1772
|
+
};
|
|
1773
|
+
const text = async (args) => {
|
|
1774
|
+
const { main } = await import("./chunk-MTAUZW2Z.js");
|
|
1775
|
+
const out = [];
|
|
1776
|
+
const err = [];
|
|
1777
|
+
const code = await main([...args, "--root", ctx.root], { out: (l) => out.push(l), err: (l) => err.push(l) }, { quiet: true });
|
|
1778
|
+
if (code === 2) {
|
|
1779
|
+
const words = err.join("\n") || "The command refused the request.";
|
|
1780
|
+
if (scanFirst(words))
|
|
1781
|
+
throw new HttpError(422, "Atticus can't show that yet", words, { notYet: true });
|
|
1782
|
+
throw badRequest(words);
|
|
1783
|
+
}
|
|
1784
|
+
return { code, text: out.join("\n"), problems: err };
|
|
1785
|
+
};
|
|
1786
|
+
return r.add("POST", "/api/triage/{itemId}/decide", async ({ params, body }) => {
|
|
1787
|
+
const b = api_exports.TriageDecideRequest.safeParse(body ?? {});
|
|
1788
|
+
if (!b.success)
|
|
1789
|
+
throw badRequest("Some fields aren't right.", {
|
|
1790
|
+
errors: b.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
|
|
1791
|
+
});
|
|
1792
|
+
const { triageDecide } = await import("./chunk-NNUASOAA.js");
|
|
1793
|
+
const out = [];
|
|
1794
|
+
const err = [];
|
|
1795
|
+
const code = await triageDecide(ctx.root, params.itemId, b.data.option, { by: ctx.user, ...b.data.reason ? { reason: b.data.reason } : {} }, { out: (l) => out.push(l), err: (l) => err.push(l) });
|
|
1796
|
+
if (code !== 0) {
|
|
1797
|
+
const detail = [...err, ...out].join("\n") || "Atticus refused.";
|
|
1798
|
+
if (/There is no open triage item/.test(detail))
|
|
1799
|
+
throw notFound(detail);
|
|
1800
|
+
throw code === 2 ? badRequest(detail) : new HttpError(422, "Atticus refused", detail);
|
|
1801
|
+
}
|
|
1802
|
+
ctx.events.publish({ type: "file.changed", path: "catalog/curated" });
|
|
1803
|
+
return { status: 200, body: { item: params.itemId, option: b.data.option, lines: out } };
|
|
1804
|
+
}).add("GET", "/api/setup", async () => ({
|
|
1805
|
+
status: 200,
|
|
1806
|
+
body: await setupSteps(ctx.root, { doctor: machine() })
|
|
1807
|
+
})).add("GET", "/api/doctor", async ({ query }) => {
|
|
1808
|
+
const checks = await machine(query.get("fresh") === "1");
|
|
1809
|
+
return { status: 200, body: { checks, ok: !checks.some((c) => c.status === "fail") } };
|
|
1810
|
+
}).add("POST", "/api/baseline", async ({ body }) => {
|
|
1811
|
+
const b = api_exports.BaselineRequest.safeParse(body ?? {});
|
|
1812
|
+
if (!b.success)
|
|
1813
|
+
throw badRequest("Some fields aren't right.");
|
|
1814
|
+
const out = [];
|
|
1815
|
+
const err = [];
|
|
1816
|
+
const code = await baselineCommand(ctx.root, { by: ctx.user, ...b.data.note ? { note: b.data.note } : {} }, { out: (l) => out.push(l), err: (l) => err.push(l) });
|
|
1817
|
+
if (code !== 0)
|
|
1818
|
+
throw new HttpError(422, "Atticus refused", [...err, ...out].join("\n"));
|
|
1819
|
+
ctx.events.publish({ type: "file.changed", path: "catalog/baseline.yaml" });
|
|
1820
|
+
return {
|
|
1821
|
+
status: 200,
|
|
1822
|
+
body: { lines: out, ...await setupSteps(ctx.root, { doctor: await machine() }) }
|
|
1823
|
+
};
|
|
1824
|
+
}).add("GET", "/api/guide/{system}", async ({ params }) => {
|
|
1825
|
+
const id = params.system;
|
|
1826
|
+
if (!ID.test(id))
|
|
1827
|
+
throw badRequest(`${id} isn't an id.`);
|
|
1828
|
+
try {
|
|
1829
|
+
return {
|
|
1830
|
+
status: 200,
|
|
1831
|
+
body: systemGuide(await catalogRecords(ctx.root), id, {
|
|
1832
|
+
changes: await openChanges(ctx.root)
|
|
1833
|
+
})
|
|
1834
|
+
};
|
|
1835
|
+
} catch (e) {
|
|
1836
|
+
throw notFound(e.message);
|
|
1837
|
+
}
|
|
1838
|
+
}).add("GET", "/api/initiatives", async () => {
|
|
1839
|
+
const v = await readView(ctx.root);
|
|
1840
|
+
const body = [];
|
|
1841
|
+
for (const i of v.all("Initiative").sort((a, b) => a.id.localeCompare(b.id)))
|
|
1842
|
+
body.push(await initiativeStatus(ctx.root, i.id));
|
|
1843
|
+
return { status: 200, body };
|
|
1844
|
+
}).add("GET", "/api/diagram/{view}", async ({ params, query }) => {
|
|
1845
|
+
const name = params.view;
|
|
1846
|
+
if (!DIAGRAM_VIEWS.includes(name))
|
|
1847
|
+
throw badRequest(`${name} isn't a view. Views: ${DIAGRAM_VIEWS.join(", ")}.`);
|
|
1848
|
+
const focus = query.get("focus") ?? void 0;
|
|
1849
|
+
if (focus !== void 0 && !ID.test(focus))
|
|
1850
|
+
throw badRequest(`${focus} isn't an id.`);
|
|
1851
|
+
return {
|
|
1852
|
+
status: 200,
|
|
1853
|
+
body: await text(["diagram", "render", name, ...focus ? ["--focus", focus] : []])
|
|
1854
|
+
};
|
|
1855
|
+
}).add("GET", "/api/diagram/{view}/graph", async ({ params, query }) => {
|
|
1856
|
+
const name = params.view;
|
|
1857
|
+
if (!DIAGRAM_VIEWS.includes(name) || name === "sequence")
|
|
1858
|
+
throw badRequest(`${name} has no canvas. Views: ${DIAGRAM_VIEWS.filter((v) => v !== "sequence").join(", ")}.`);
|
|
1859
|
+
const focus = query.get("focus") ?? void 0;
|
|
1860
|
+
if (focus !== void 0 && !ID.test(focus))
|
|
1861
|
+
throw badRequest(`${focus} isn't an id.`);
|
|
1862
|
+
const r2 = await text([
|
|
1863
|
+
"diagram",
|
|
1864
|
+
"render",
|
|
1865
|
+
name,
|
|
1866
|
+
"--format",
|
|
1867
|
+
"json",
|
|
1868
|
+
...focus ? ["--focus", focus] : []
|
|
1869
|
+
]);
|
|
1870
|
+
const out = r2.text.trim();
|
|
1871
|
+
if (!out.startsWith("{")) {
|
|
1872
|
+
const words = r2.problems.join("\n") || "Nothing to draw.";
|
|
1873
|
+
throw new HttpError(422, "Atticus can't show that yet", words, scanFirst(words) ? { notYet: true } : void 0);
|
|
1874
|
+
}
|
|
1875
|
+
return { status: 200, body: { view: JSON.parse(out) } };
|
|
1876
|
+
}).add("GET", "/api/view/{name}", ({ params }) => view2(params.name)).add("GET", "/api/view/{name}/{id}", ({ params }) => view2(params.name, params.id));
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
// packages/cli/dist/ui-api-questions.js
|
|
1880
|
+
function uiQuestionRoutes(r, ctx, deps = {}) {
|
|
1881
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
1882
|
+
return r.add("GET", "/api/questions", async ({ query }) => {
|
|
1883
|
+
const state = strParam(query, "state");
|
|
1884
|
+
if (state !== void 0 && !QuestionState.safeParse(state).success)
|
|
1885
|
+
throw badRequest("state is open, answered or retired.");
|
|
1886
|
+
const mine = strParam(query, "mine");
|
|
1887
|
+
if (mine !== void 0 && mine !== "true")
|
|
1888
|
+
throw badRequest("mine is true, or left out.");
|
|
1889
|
+
const listed = await listQuestions(ctx.root, state ? { state } : {});
|
|
1890
|
+
const { routeQuestions } = await import("./chunk-QICFHVRW.js");
|
|
1891
|
+
const routed = await routeQuestions(ctx.root, listed.questions, mine ? { mine: ctx.user } : {});
|
|
1892
|
+
const body = {
|
|
1893
|
+
questions: routed.questions,
|
|
1894
|
+
problems: [...listed.problems, ...routed.problems],
|
|
1895
|
+
about: routed.about,
|
|
1896
|
+
...routed.me ? { me: routed.me } : {}
|
|
1897
|
+
};
|
|
1898
|
+
return { status: 200, body };
|
|
1899
|
+
}).add("GET", "/api/questions/{id}", async ({ params }) => {
|
|
1900
|
+
const q = await readQuestion(ctx.root, params.id).catch(() => {
|
|
1901
|
+
throw new HttpError(422, "Atticus can't read that question", `questions/${params.id}.yaml isn't a valid question.`);
|
|
1902
|
+
});
|
|
1903
|
+
if (!q)
|
|
1904
|
+
throw notFound(`There is no question ${params.id}.`);
|
|
1905
|
+
return { status: 200, body: q };
|
|
1906
|
+
}).add("GET", "/api/questions/{id}/evidence", async ({ params }) => {
|
|
1907
|
+
const q = await readQuestion(ctx.root, params.id).catch(() => void 0);
|
|
1908
|
+
if (!q)
|
|
1909
|
+
throw notFound(`There is no question ${params.id}.`);
|
|
1910
|
+
const { evidenceExcerpts } = await import("./chunk-R6B6XAKK.js");
|
|
1911
|
+
return {
|
|
1912
|
+
status: 200,
|
|
1913
|
+
body: {
|
|
1914
|
+
excerpts: await evidenceExcerpts(ctx.root, { repo: q.repo, evidence: q.evidence })
|
|
1915
|
+
}
|
|
1916
|
+
};
|
|
1917
|
+
}).add("POST", "/api/questions/{id}/answer", async ({ params, body }) => {
|
|
1918
|
+
const b = api_exports.AnswerQuestionRequest.safeParse(body ?? {});
|
|
1919
|
+
if (!b.success)
|
|
1920
|
+
throw badRequest('Answer with { option: <n> } or { text: "your own words" }.', {
|
|
1921
|
+
errors: b.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
|
|
1922
|
+
});
|
|
1923
|
+
try {
|
|
1924
|
+
const { acceptProposal: acceptProposal2, ...given } = b.data;
|
|
1925
|
+
const r2 = acceptProposal2 ? await acceptProposal(ctx.root, params.id, {
|
|
1926
|
+
...given.scope ? { scope: given.scope } : {},
|
|
1927
|
+
...given.repoType ? { repoType: given.repoType } : {},
|
|
1928
|
+
...given.match ? { match: given.match } : {},
|
|
1929
|
+
by: ctx.user,
|
|
1930
|
+
at: now().toISOString()
|
|
1931
|
+
}) : await answerQuestion(ctx.root, params.id, {
|
|
1932
|
+
...given,
|
|
1933
|
+
by: ctx.user,
|
|
1934
|
+
at: now().toISOString()
|
|
1935
|
+
});
|
|
1936
|
+
return {
|
|
1937
|
+
status: 200,
|
|
1938
|
+
body: {
|
|
1939
|
+
question: r2.question,
|
|
1940
|
+
rerun: r2.question.rerun,
|
|
1941
|
+
...r2.skill ? { skill: r2.skill } : {}
|
|
1942
|
+
}
|
|
1943
|
+
};
|
|
1944
|
+
} catch (e) {
|
|
1945
|
+
if (!(e instanceof QuestionError))
|
|
1946
|
+
throw e;
|
|
1947
|
+
if (e.code === "not-found")
|
|
1948
|
+
throw notFound(e.message);
|
|
1949
|
+
if (e.code === "bad-answer")
|
|
1950
|
+
throw badRequest(e.message);
|
|
1951
|
+
throw new HttpError(422, "Atticus can't do that yet", e.message);
|
|
1952
|
+
}
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
// packages/cli/dist/ui-api-across.js
|
|
1957
|
+
var ACROSS_VIEWS = [
|
|
1958
|
+
"findings",
|
|
1959
|
+
"tasks",
|
|
1960
|
+
"stories",
|
|
1961
|
+
"tests",
|
|
1962
|
+
"requests",
|
|
1963
|
+
"releases"
|
|
1964
|
+
];
|
|
1965
|
+
async function openChanges3(root) {
|
|
1966
|
+
return (await changeStore(root).list()).filter((c) => c.state !== "archived").sort((a, b) => a.id.localeCompare(b.id));
|
|
1967
|
+
}
|
|
1968
|
+
async function latestFindings3(root, change) {
|
|
1969
|
+
const out = [];
|
|
1970
|
+
for (const gate of await reviewedGates(root, change)) {
|
|
1971
|
+
const rounds = await roundsOf(root, change, gate);
|
|
1972
|
+
if (rounds.length)
|
|
1973
|
+
out.push(...(await readRound(root, change, gate, rounds.at(-1))).findings.map((f) => f.finding));
|
|
1974
|
+
}
|
|
1975
|
+
return out;
|
|
1976
|
+
}
|
|
1977
|
+
var SEVERITY_ORDER = ["blocking", "gap", "risk", "idea"];
|
|
1978
|
+
var AT_RELEASE = /* @__PURE__ */ new Set(["release-ready", "released", "post-release-review"]);
|
|
1979
|
+
async function acrossRows(root, view2) {
|
|
1980
|
+
const changes = await openChanges3(root);
|
|
1981
|
+
const rows = [];
|
|
1982
|
+
const op = view2 === "requests" ? await loadOrgPack(orgPackDirOf(root)) : void 0;
|
|
1983
|
+
const steps = view2 === "requests" ? await fileStepStore(root).list({ system: "service-desk" }).catch(() => []) : [];
|
|
1984
|
+
for (const c of changes) {
|
|
1985
|
+
const base = { change: c.id, changeTitle: c.title, changeState: c.state };
|
|
1986
|
+
const dir = changeFolder(root, c.id);
|
|
1987
|
+
switch (view2) {
|
|
1988
|
+
case "findings":
|
|
1989
|
+
for (const f of await latestFindings3(root, c.id))
|
|
1990
|
+
rows.push({
|
|
1991
|
+
...base,
|
|
1992
|
+
id: f.id,
|
|
1993
|
+
gate: f.gate,
|
|
1994
|
+
lens: f.lens,
|
|
1995
|
+
severity: f.severity,
|
|
1996
|
+
state: f.state,
|
|
1997
|
+
summary: f.summary,
|
|
1998
|
+
artifact: f.artifact
|
|
1999
|
+
});
|
|
2000
|
+
break;
|
|
2001
|
+
case "tasks":
|
|
2002
|
+
for (const t of readChangeTasks(await readChangeFiles(dir)))
|
|
2003
|
+
rows.push({
|
|
2004
|
+
...base,
|
|
2005
|
+
id: t.id,
|
|
2006
|
+
repo: t.repo,
|
|
2007
|
+
description: t.title,
|
|
2008
|
+
done: t.done,
|
|
2009
|
+
...t.stories[0] ? { story: t.stories[0] } : {}
|
|
2010
|
+
});
|
|
2011
|
+
break;
|
|
2012
|
+
case "stories": {
|
|
2013
|
+
const files = await readChangeFiles(dir);
|
|
2014
|
+
for (const s of readStoryIndex(files)) {
|
|
2015
|
+
const story = readStory(files, s.id);
|
|
2016
|
+
rows.push({
|
|
2017
|
+
...base,
|
|
2018
|
+
id: s.id,
|
|
2019
|
+
title: s.title,
|
|
2020
|
+
repos: s.repos,
|
|
2021
|
+
dependsOn: s.dependsOn,
|
|
2022
|
+
written: !!story,
|
|
2023
|
+
criteria: story?.criteria.length ?? 0
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
2026
|
+
break;
|
|
2027
|
+
}
|
|
2028
|
+
case "tests": {
|
|
2029
|
+
const m = await traceMatrix(root, c.id).catch(() => void 0);
|
|
2030
|
+
if (!m)
|
|
2031
|
+
break;
|
|
2032
|
+
const reqRows = m.rows.filter((r) => r.requirement);
|
|
2033
|
+
rows.push({
|
|
2034
|
+
...base,
|
|
2035
|
+
requirements: m.requirements.length,
|
|
2036
|
+
tested: reqRows.filter((r) => r.tests.length > 0).length,
|
|
2037
|
+
rows: reqRows.length,
|
|
2038
|
+
stories: m.stories.length,
|
|
2039
|
+
gaps: m.gaps.length,
|
|
2040
|
+
topGaps: m.gaps.slice(0, 3).map((g) => g.summary)
|
|
2041
|
+
});
|
|
2042
|
+
break;
|
|
2043
|
+
}
|
|
2044
|
+
case "requests":
|
|
2045
|
+
for (const r of await readServiceRequests(dir, await readChangeFiles(dir), op.serviceRequests, steps.filter((s) => s.change === c.id), []))
|
|
2046
|
+
rows.push({
|
|
2047
|
+
...base,
|
|
2048
|
+
heading: r.heading,
|
|
2049
|
+
requestType: r.requestType,
|
|
2050
|
+
record: r.record,
|
|
2051
|
+
state: r.state,
|
|
2052
|
+
missing: r.missing,
|
|
2053
|
+
...r.leadTimeDays !== void 0 ? { leadTimeDays: r.leadTimeDays } : {},
|
|
2054
|
+
...r.number ? { number: r.number } : {}
|
|
2055
|
+
});
|
|
2056
|
+
break;
|
|
2057
|
+
case "releases": {
|
|
2058
|
+
const rep = await changeReport(root, c.id);
|
|
2059
|
+
const h = liveHandoff(c);
|
|
2060
|
+
rows.push({
|
|
2061
|
+
...base,
|
|
2062
|
+
atRelease: AT_RELEASE.has(c.state),
|
|
2063
|
+
...rep.phase ? { phase: rep.phase } : {},
|
|
2064
|
+
...rep.next ? { next: rep.next } : {},
|
|
2065
|
+
nextStep: rep.nextStep,
|
|
2066
|
+
risk: rep.risk.level,
|
|
2067
|
+
blockers: rep.blockers.length,
|
|
2068
|
+
...h ? { handoff: { phase: h.phase, pr: h.pr.url, state: h.state } } : {}
|
|
2069
|
+
});
|
|
2070
|
+
break;
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
if (view2 === "findings")
|
|
2075
|
+
rows.sort((a, b) => Number(a.state !== "open") - Number(b.state !== "open") || SEVERITY_ORDER.indexOf(String(a.severity)) - SEVERITY_ORDER.indexOf(String(b.severity)) || a.change.localeCompare(b.change));
|
|
2076
|
+
if (view2 === "releases")
|
|
2077
|
+
rows.sort((a, b) => Number(b.atRelease) - Number(a.atRelease));
|
|
2078
|
+
return rows;
|
|
2079
|
+
}
|
|
2080
|
+
var pathKey = (s) => s.toLowerCase().replace(/\{[^}]*\}|:[a-z0-9_]+/g, "{}").replace(/\/+$/, "").trim();
|
|
2081
|
+
function hopKey(heading) {
|
|
2082
|
+
const m = /^\s*(get|post|put|patch|delete|head|options)\s+(\S+)/i.exec(heading);
|
|
2083
|
+
return m ? { method: m[1].toUpperCase(), path: pathKey(m[2]) } : { path: pathKey(heading) };
|
|
2084
|
+
}
|
|
2085
|
+
async function mappingGrid(root, change, records) {
|
|
2086
|
+
const files = await readChangeFiles(changeFolder(root, change));
|
|
2087
|
+
const doc = files.docs.get("mappings.md");
|
|
2088
|
+
const all = records ?? await catalogRecords(root);
|
|
2089
|
+
const mappings = all.filter((r) => r.kind === "FieldMapping");
|
|
2090
|
+
const interfaces = all.filter((r) => r.kind === "Interface");
|
|
2091
|
+
const hops = [];
|
|
2092
|
+
for (const s of doc?.sections.filter((x) => x.level === 2) ?? []) {
|
|
2093
|
+
const body = doc.lines.slice(s.line, s.end).join("\n");
|
|
2094
|
+
const table = doc.tables.find((t) => t.line > s.line && t.line <= s.end);
|
|
2095
|
+
const cell = (r, ...names) => names.map((n) => r[n]).find((v) => v !== void 0) ?? "";
|
|
2096
|
+
const rows = (table?.rows ?? []).map((r) => ({
|
|
2097
|
+
line: r.line,
|
|
2098
|
+
source: cell(r.cells, "source field", "source"),
|
|
2099
|
+
target: cell(r.cells, "target field", "target"),
|
|
2100
|
+
rule: cell(r.cells, "rule or transformation", "transform", "rule"),
|
|
2101
|
+
whenMissing: cell(r.cells, "when missing or invalid", "when missing")
|
|
2102
|
+
}));
|
|
2103
|
+
const k = hopKey(s.heading);
|
|
2104
|
+
const ifIds = new Set(interfaces.filter((i) => pathKey(i.normalizedPath) === k.path && (!k.method || !i.method || i.method.toUpperCase() === k.method)).map((i) => i.id));
|
|
2105
|
+
const noChange = /no mapping change/i.test(body);
|
|
2106
|
+
const note = noChange ? body.replace(/\s+/g, " ").replace(/^.*?no mapping change\.?\s*/i, "").trim() : void 0;
|
|
2107
|
+
hops.push({
|
|
2108
|
+
heading: s.heading,
|
|
2109
|
+
line: s.line,
|
|
2110
|
+
noChange,
|
|
2111
|
+
...note ? { note } : {},
|
|
2112
|
+
rows,
|
|
2113
|
+
asIs: asIsFor(mappings, ifIds),
|
|
2114
|
+
from: "mappings.md"
|
|
2115
|
+
});
|
|
2116
|
+
}
|
|
2117
|
+
if (!hops.length)
|
|
2118
|
+
hops.push(...await hopsFromTargetFlows(changeFolder(root, change), mappings, interfaces));
|
|
2119
|
+
return { change, hops, catalogMappings: mappings.length };
|
|
2120
|
+
}
|
|
2121
|
+
var asIsFor = (mappings, ifIds) => mappings.filter((m) => ifIds.has(m.interface)).map((m) => ({
|
|
2122
|
+
id: m.id,
|
|
2123
|
+
interface: m.interface,
|
|
2124
|
+
source: m.sourceField,
|
|
2125
|
+
target: m.targetField,
|
|
2126
|
+
...m.transform ? { transform: m.transform } : {}
|
|
2127
|
+
}));
|
|
2128
|
+
async function hopsFromTargetFlows(dir, mappings, interfaces) {
|
|
2129
|
+
const { deltas } = await readDeltas(dir);
|
|
2130
|
+
const hops = [];
|
|
2131
|
+
for (const d of deltas) {
|
|
2132
|
+
let at = 0;
|
|
2133
|
+
for (const s of d.steps) {
|
|
2134
|
+
at += 1;
|
|
2135
|
+
const heading = s.to ? s.to.entry : s.store ? s.store.table : void 0;
|
|
2136
|
+
if (!heading)
|
|
2137
|
+
continue;
|
|
2138
|
+
const as = s.fields?.as ?? {};
|
|
2139
|
+
const derived = s.fields?.derived ?? {};
|
|
2140
|
+
const names = fieldNames(s.fields);
|
|
2141
|
+
if (!names.length)
|
|
2142
|
+
continue;
|
|
2143
|
+
const k = hopKey(heading);
|
|
2144
|
+
const ifIds = new Set(interfaces.filter((i) => pathKey(i.normalizedPath) === k.path && (!k.method || !i.method || i.method.toUpperCase() === k.method)).map((i) => i.id));
|
|
2145
|
+
hops.push({
|
|
2146
|
+
heading,
|
|
2147
|
+
line: at,
|
|
2148
|
+
noChange: false,
|
|
2149
|
+
rows: names.map((name, i) => ({
|
|
2150
|
+
line: i + 1,
|
|
2151
|
+
source: name,
|
|
2152
|
+
target: as[name] ?? name,
|
|
2153
|
+
rule: derived[name] ?? "",
|
|
2154
|
+
whenMissing: ""
|
|
2155
|
+
})),
|
|
2156
|
+
asIs: asIsFor(mappings, ifIds),
|
|
2157
|
+
from: `target-flows/${d.flow}.yaml`
|
|
2158
|
+
});
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
return hops;
|
|
2162
|
+
}
|
|
2163
|
+
function uiAcrossRoutes(r, ctx) {
|
|
2164
|
+
const { root } = ctx;
|
|
2165
|
+
return r.add("GET", "/api/across", async ({ query }) => {
|
|
2166
|
+
const view2 = strParam(query, "view") ?? "";
|
|
2167
|
+
if (!ACROSS_VIEWS.includes(view2))
|
|
2168
|
+
throw badRequest(`Say which view: ?view= one of ${ACROSS_VIEWS.join(", ")}.`);
|
|
2169
|
+
const rows = await acrossRows(root, view2);
|
|
2170
|
+
return {
|
|
2171
|
+
status: 200,
|
|
2172
|
+
body: { view: view2, changes: (await openChanges3(root)).length, rows }
|
|
2173
|
+
};
|
|
2174
|
+
}).add("GET", "/api/changes/{changeId}/mappings", async ({ params }) => {
|
|
2175
|
+
try {
|
|
2176
|
+
await changeStore(root).read(params.changeId);
|
|
2177
|
+
} catch {
|
|
2178
|
+
throw notFound(`There is no change ${params.changeId}.`);
|
|
2179
|
+
}
|
|
2180
|
+
return { status: 200, body: await mappingGrid(root, params.changeId) };
|
|
2181
|
+
});
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
// packages/cli/dist/ui-api-graph.js
|
|
2185
|
+
var import_yaml2 = __toESM(require_dist(), 1);
|
|
2186
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
2187
|
+
import { join as join4 } from "node:path";
|
|
2188
|
+
var lensOf = (v, fallback) => {
|
|
2189
|
+
if (v === null)
|
|
2190
|
+
return fallback;
|
|
2191
|
+
const l = api_exports.GraphLens.safeParse(v);
|
|
2192
|
+
if (!l.success)
|
|
2193
|
+
throw badRequest(`${v} isn't a lens. Lenses: ${api_exports.GRAPH_LENSES.join(", ")}.`);
|
|
2194
|
+
return l.data;
|
|
2195
|
+
};
|
|
2196
|
+
var idOf = (id) => {
|
|
2197
|
+
if (!api_exports.HierId.safeParse(id).success)
|
|
2198
|
+
throw badRequest(`${id} isn't a node id. Use estate, or <kind>:<record id>, e.g. system:ledger.`);
|
|
2199
|
+
return id;
|
|
2200
|
+
};
|
|
2201
|
+
var keyOf = (k) => {
|
|
2202
|
+
if (!ViewKey.safeParse(k).success)
|
|
2203
|
+
throw badRequest(`${k} isn't a view key. Use <lens>.<node>, e.g. architecture.estate.`);
|
|
2204
|
+
return k;
|
|
2205
|
+
};
|
|
2206
|
+
function uiGraphRoutes(r, ctx) {
|
|
2207
|
+
const estate = async () => {
|
|
2208
|
+
const records = await catalogRecords(ctx.root);
|
|
2209
|
+
if (!records.length)
|
|
2210
|
+
throw new HttpError(422, "Atticus can't show that yet", "The catalog is empty: read the repos first (atticus map <repo>, then atticus verify <repo> --write).", { notYet: true });
|
|
2211
|
+
return buildEstate(records);
|
|
2212
|
+
};
|
|
2213
|
+
const guard = (f) => {
|
|
2214
|
+
try {
|
|
2215
|
+
return f();
|
|
2216
|
+
} catch (e) {
|
|
2217
|
+
if (e instanceof NoSuchNode)
|
|
2218
|
+
throw notFound(e.message);
|
|
2219
|
+
throw e;
|
|
2220
|
+
}
|
|
2221
|
+
};
|
|
2222
|
+
const readLayout = async (view2) => {
|
|
2223
|
+
try {
|
|
2224
|
+
const f = DiagramLayout.safeParse((0, import_yaml2.parse)(await readFile3(join4(ctx.root, layoutPath(view2)), "utf8")));
|
|
2225
|
+
if (!f.success)
|
|
2226
|
+
throw new HttpError(422, "Atticus refused", `${layoutPath(view2)} isn't a layout file.`);
|
|
2227
|
+
return f.data;
|
|
2228
|
+
} catch (e) {
|
|
2229
|
+
if (e.code === "ENOENT")
|
|
2230
|
+
return void 0;
|
|
2231
|
+
throw e;
|
|
2232
|
+
}
|
|
2233
|
+
};
|
|
2234
|
+
return r.add("GET", "/api/graph/node/{id}", async ({ params, query }) => {
|
|
2235
|
+
const id = idOf(params.id);
|
|
2236
|
+
const lens = lensOf(query.get("lens"), "architecture");
|
|
2237
|
+
const d = query.get("depth");
|
|
2238
|
+
if (d !== null && !/^[1-3]$/.test(d))
|
|
2239
|
+
throw badRequest("depth is 1, 2 or 3.");
|
|
2240
|
+
const est = await estate();
|
|
2241
|
+
return {
|
|
2242
|
+
status: 200,
|
|
2243
|
+
body: guard(() => nodeView(est, id, { lens, depth: d ? Number(d) : 1, all: query.get("all") === "1" }))
|
|
2244
|
+
};
|
|
2245
|
+
}).add("GET", "/api/graph/focus/{id}", async ({ params, query }) => {
|
|
2246
|
+
const id = idOf(params.id);
|
|
2247
|
+
const lens = lensOf(query.get("lens"), "data");
|
|
2248
|
+
const est = await estate();
|
|
2249
|
+
return { status: 200, body: guard(() => focusView(est, id, { lens })) };
|
|
2250
|
+
}).add("GET", "/api/graph/layout/{viewKey}", async ({ params }) => {
|
|
2251
|
+
const view2 = keyOf(params.viewKey);
|
|
2252
|
+
const f = await readLayout(view2);
|
|
2253
|
+
return {
|
|
2254
|
+
status: 200,
|
|
2255
|
+
body: { view: view2, saved: !!f, path: layoutPath(view2), positions: f?.positions ?? {} }
|
|
2256
|
+
};
|
|
2257
|
+
}).add("PUT", "/api/graph/layout/{viewKey}", async ({ params, body }) => {
|
|
2258
|
+
const view2 = keyOf(params.viewKey);
|
|
2259
|
+
const b = api_exports.PutLayoutRequest.safeParse(body ?? {});
|
|
2260
|
+
if (!b.success)
|
|
2261
|
+
throw badRequest("Some fields aren't right.", {
|
|
2262
|
+
errors: b.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
|
|
2263
|
+
});
|
|
2264
|
+
const { lens, node, positions } = b.data;
|
|
2265
|
+
if (view2 !== viewKey(lens, node) && view2 !== viewKey(lens, node, "focus"))
|
|
2266
|
+
throw badRequest(`${view2} isn't the view key of ${node} in the ${lens} lens.`);
|
|
2267
|
+
if (Object.keys(positions).length > 5e3)
|
|
2268
|
+
throw badRequest("At most 5000 boxes per view.");
|
|
2269
|
+
const sorted = Object.fromEntries(Object.entries(positions).sort(([a], [b2]) => a < b2 ? -1 : 1).map(([k, p]) => [k, { x: Math.round(p.x), y: Math.round(p.y) }]));
|
|
2270
|
+
const file = DiagramLayout.parse({
|
|
2271
|
+
schemaVersion: LAYOUT_SCHEMA_VERSION,
|
|
2272
|
+
view: view2,
|
|
2273
|
+
lens,
|
|
2274
|
+
node,
|
|
2275
|
+
by: ctx.user,
|
|
2276
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2277
|
+
positions: sorted
|
|
2278
|
+
});
|
|
2279
|
+
await writeFileAtomic(join4(ctx.root, layoutPath(view2)), (0, import_yaml2.stringify)(file));
|
|
2280
|
+
ctx.events.publish({ type: "file.changed", path: layoutPath(view2) });
|
|
2281
|
+
return {
|
|
2282
|
+
status: 200,
|
|
2283
|
+
body: { view: view2, saved: true, path: layoutPath(view2), positions: file.positions }
|
|
2284
|
+
};
|
|
2285
|
+
});
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
// packages/cli/dist/ui-api-specs.js
|
|
2289
|
+
import { stat as stat2 } from "node:fs/promises";
|
|
2290
|
+
import { join as join5 } from "node:path";
|
|
2291
|
+
var idOf2 = (v, what) => {
|
|
2292
|
+
if (!RecordId.safeParse(v).success)
|
|
2293
|
+
throw badRequest(`${v} isn't a ${what} id. Ids are lowercase: a-z, 0-9, '.', '_' and '-'.`);
|
|
2294
|
+
return v;
|
|
2295
|
+
};
|
|
2296
|
+
var confirmed = (f) => f.steps.filter((s) => s.confirmedBy).length;
|
|
2297
|
+
function uiSpecRoutes(r, ctx) {
|
|
2298
|
+
const root = ctx.root;
|
|
2299
|
+
const specOf = async (repo) => {
|
|
2300
|
+
if (!(await specRepos(root)).includes(repo))
|
|
2301
|
+
throw notFound(`${repo} has no spec. Draft one with atticus spec draft ${repo}.`);
|
|
2302
|
+
return readRepoSpec(root, repo, { cacheRoot: root });
|
|
2303
|
+
};
|
|
2304
|
+
const namesIn = async (repos) => {
|
|
2305
|
+
const names = /* @__PURE__ */ new Map();
|
|
2306
|
+
const have = new Set(await specRepos(root));
|
|
2307
|
+
for (const repo of [...new Set(repos)].filter((x) => have.has(x)))
|
|
2308
|
+
for (const f of (await readRepoSpec(root, repo, { cacheRoot: root })).flows)
|
|
2309
|
+
names.set(f.id, f.name);
|
|
2310
|
+
return names;
|
|
2311
|
+
};
|
|
2312
|
+
const linksOf = (joins, flow) => ({
|
|
2313
|
+
out: (joins?.joins ?? []).filter((j) => j.from.flow === flow),
|
|
2314
|
+
in: (joins?.joins ?? []).filter((j) => j.to.flow === flow),
|
|
2315
|
+
unmatched: (joins?.unmatched ?? []).filter((u) => u.from.flow === flow)
|
|
2316
|
+
});
|
|
2317
|
+
r.add("GET", "/api/specs", async () => {
|
|
2318
|
+
const repos = [];
|
|
2319
|
+
for (const repo of await specRepos(root))
|
|
2320
|
+
repos.push({
|
|
2321
|
+
repo,
|
|
2322
|
+
flows: (await readRepoSpec(root, repo, { cacheRoot: root })).flows.length
|
|
2323
|
+
});
|
|
2324
|
+
return { status: 200, body: { repos } };
|
|
2325
|
+
});
|
|
2326
|
+
r.add("GET", "/api/specs/{repo}", async ({ params }) => {
|
|
2327
|
+
const repo = idOf2(params.repo, "repo");
|
|
2328
|
+
const spec = await specOf(repo);
|
|
2329
|
+
const joins = await readEstateJoins(root);
|
|
2330
|
+
const body = {
|
|
2331
|
+
repo,
|
|
2332
|
+
path: specRelOf(root, repo),
|
|
2333
|
+
...spec.header?.commit ? { commit: spec.header.commit } : {},
|
|
2334
|
+
...spec.header?.template ? { template: spec.header.template } : {},
|
|
2335
|
+
flows: spec.flows.map((f) => {
|
|
2336
|
+
const l = linksOf(joins, f.id);
|
|
2337
|
+
return {
|
|
2338
|
+
id: f.id,
|
|
2339
|
+
name: f.name,
|
|
2340
|
+
component: f.component,
|
|
2341
|
+
trigger: { kind: f.trigger.kind, what: f.trigger.what },
|
|
2342
|
+
steps: f.steps.length,
|
|
2343
|
+
confirmed: confirmed(f),
|
|
2344
|
+
errors: f.errors.length,
|
|
2345
|
+
calls: l.out.length,
|
|
2346
|
+
calledBy: l.in.length
|
|
2347
|
+
};
|
|
2348
|
+
}),
|
|
2349
|
+
problems: spec.problems
|
|
2350
|
+
};
|
|
2351
|
+
return { status: 200, body };
|
|
2352
|
+
});
|
|
2353
|
+
r.add("GET", "/api/specs/{repo}/flows/{flowId}", async ({ params }) => {
|
|
2354
|
+
const repo = idOf2(params.repo, "repo");
|
|
2355
|
+
const id = idOf2(params.flowId, "flow");
|
|
2356
|
+
const flow = (await specOf(repo)).flows.find((f) => f.id === id);
|
|
2357
|
+
if (!flow)
|
|
2358
|
+
throw notFound(`${repo}'s spec has no flow ${id}.`);
|
|
2359
|
+
const joins = await readEstateJoins(root);
|
|
2360
|
+
const l = linksOf(joins, id);
|
|
2361
|
+
const names = await namesIn([...l.out.map((j) => j.to.repo), ...l.in.map((j) => j.from.repo)]);
|
|
2362
|
+
const named = (flowId) => {
|
|
2363
|
+
const n = names.get(flowId);
|
|
2364
|
+
return n ? { name: n } : {};
|
|
2365
|
+
};
|
|
2366
|
+
const body = {
|
|
2367
|
+
repo,
|
|
2368
|
+
file: `${specRelOf(root, repo)}/${id}.yaml`,
|
|
2369
|
+
flow,
|
|
2370
|
+
joined: joins !== void 0,
|
|
2371
|
+
calls: l.out.map((j) => ({
|
|
2372
|
+
step: j.from.step,
|
|
2373
|
+
repo: j.to.repo,
|
|
2374
|
+
flow: j.to.flow,
|
|
2375
|
+
...named(j.to.flow),
|
|
2376
|
+
how: j.how
|
|
2377
|
+
})),
|
|
2378
|
+
calledBy: l.in.map((j) => ({
|
|
2379
|
+
step: j.from.step,
|
|
2380
|
+
repo: j.from.repo,
|
|
2381
|
+
flow: j.from.flow,
|
|
2382
|
+
...named(j.from.flow),
|
|
2383
|
+
how: j.how
|
|
2384
|
+
})),
|
|
2385
|
+
unmatched: l.unmatched.map((u) => ({
|
|
2386
|
+
step: u.from.step,
|
|
2387
|
+
entry: u.entry,
|
|
2388
|
+
reason: u.reason,
|
|
2389
|
+
message: u.message
|
|
2390
|
+
}))
|
|
2391
|
+
};
|
|
2392
|
+
return { status: 200, body };
|
|
2393
|
+
});
|
|
2394
|
+
const deltasOf = async (change) => {
|
|
2395
|
+
const dir = join5(changesDirOf(root), change);
|
|
2396
|
+
if (!await stat2(join5(dir, "change.yaml")).catch(() => void 0))
|
|
2397
|
+
throw notFound(`There is no change ${change}.`);
|
|
2398
|
+
return readDeltas(dir);
|
|
2399
|
+
};
|
|
2400
|
+
const todayOf = async (repo, flow) => (await specRepos(root)).includes(repo) ? (await readRepoSpec(root, repo, { cacheRoot: root })).flows.find((f) => f.id === flow) : void 0;
|
|
2401
|
+
r.add("GET", "/api/changes/{changeId}/target-flows", async ({ params }) => {
|
|
2402
|
+
const change = idOf2(params.changeId, "change");
|
|
2403
|
+
const { deltas, problems } = await deltasOf(change);
|
|
2404
|
+
const flows = [];
|
|
2405
|
+
for (const d of deltas) {
|
|
2406
|
+
const m = mergeDelta(d, await todayOf(d.repo, d.flow));
|
|
2407
|
+
flows.push({
|
|
2408
|
+
flow: d.flow,
|
|
2409
|
+
repo: d.repo,
|
|
2410
|
+
change: d.change,
|
|
2411
|
+
name: m.name,
|
|
2412
|
+
why: d.why,
|
|
2413
|
+
...m.counts
|
|
2414
|
+
});
|
|
2415
|
+
}
|
|
2416
|
+
return { status: 200, body: { change, flows, problems } };
|
|
2417
|
+
});
|
|
2418
|
+
r.add("GET", "/api/changes/{changeId}/conformance", async ({ params }) => {
|
|
2419
|
+
const change = idOf2(params.changeId, "change");
|
|
2420
|
+
const { conformanceOf } = await import("./chunk-F7YL2RZ4.js");
|
|
2421
|
+
const c = await conformanceOf(root, change);
|
|
2422
|
+
if (!c)
|
|
2423
|
+
throw notFound(`There is no change ${change}.`);
|
|
2424
|
+
return { status: 200, body: c };
|
|
2425
|
+
});
|
|
2426
|
+
r.add("GET", "/api/changes/{changeId}/estimate", async ({ params }) => {
|
|
2427
|
+
const change = idOf2(params.changeId, "change");
|
|
2428
|
+
const e = await estimateOf(root, change);
|
|
2429
|
+
if (!e)
|
|
2430
|
+
throw notFound(`There is no change ${change}.`);
|
|
2431
|
+
return { status: 200, body: e };
|
|
2432
|
+
});
|
|
2433
|
+
r.add("GET", "/api/changes/{changeId}/impact", async ({ params }) => {
|
|
2434
|
+
const change = idOf2(params.changeId, "change");
|
|
2435
|
+
const { changeImpactOf } = await import("./chunk-JAA53T23.js");
|
|
2436
|
+
const i = await changeImpactOf(root, change);
|
|
2437
|
+
if (!i)
|
|
2438
|
+
throw notFound(`There is no change ${change}.`);
|
|
2439
|
+
return { status: 200, body: i };
|
|
2440
|
+
});
|
|
2441
|
+
r.add("GET", "/api/changes/{changeId}/map", async ({ params }) => {
|
|
2442
|
+
const change = idOf2(params.changeId, "change");
|
|
2443
|
+
const m = await changeMapOf(root, change);
|
|
2444
|
+
if (!m)
|
|
2445
|
+
throw notFound(`There is no change ${change}.`);
|
|
2446
|
+
return { status: 200, body: changeMapBody(change, m) };
|
|
2447
|
+
});
|
|
2448
|
+
r.add("GET", "/api/changes/{changeId}/target-flows/{flowId}", async ({ params }) => {
|
|
2449
|
+
const change = idOf2(params.changeId, "change");
|
|
2450
|
+
const flow = idOf2(params.flowId, "flow");
|
|
2451
|
+
const d = (await deltasOf(change)).deltas.find((x) => x.flow === flow);
|
|
2452
|
+
if (!d)
|
|
2453
|
+
throw notFound(`Change ${change} has no target flow ${flow}.`);
|
|
2454
|
+
const { flow: _f, repo: _r, change: delta, ...m } = mergeDelta(d, await todayOf(d.repo, flow));
|
|
2455
|
+
const body = {
|
|
2456
|
+
change,
|
|
2457
|
+
file: `${changesRelOf(root)}/${change}/${TARGET_DIR}/${flow}.yaml`,
|
|
2458
|
+
flow,
|
|
2459
|
+
repo: d.repo,
|
|
2460
|
+
delta,
|
|
2461
|
+
...m
|
|
2462
|
+
};
|
|
2463
|
+
return { status: 200, body };
|
|
2464
|
+
});
|
|
2465
|
+
return r;
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
// packages/cli/dist/ui-api-knowledge.js
|
|
2469
|
+
var import_yaml3 = __toESM(require_dist(), 1);
|
|
2470
|
+
import { readFile as readFile4, stat as stat3 } from "node:fs/promises";
|
|
2471
|
+
import { join as join6 } from "node:path";
|
|
2472
|
+
var STALE_DAYS = 14;
|
|
2473
|
+
var SLUG = /^[a-z0-9][a-z0-9-]*$/;
|
|
2474
|
+
var DAY = 864e5;
|
|
2475
|
+
var stamp = (d) => d.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
2476
|
+
var idOf3 = (v, what) => {
|
|
2477
|
+
if (!RecordId.safeParse(v).success)
|
|
2478
|
+
throw badRequest(`${v} isn't a ${what} id. Ids are lowercase: a-z, 0-9, '.', '_' and '-'.`);
|
|
2479
|
+
return v;
|
|
2480
|
+
};
|
|
2481
|
+
var invalid = (what, e) => badRequest(what, {
|
|
2482
|
+
errors: e.issues.map((i) => ({ path: i.path.map(String).join("."), message: i.message }))
|
|
2483
|
+
});
|
|
2484
|
+
function asSet(set) {
|
|
2485
|
+
if (!set)
|
|
2486
|
+
return void 0;
|
|
2487
|
+
const out = {};
|
|
2488
|
+
for (const [k, v] of Object.entries(set))
|
|
2489
|
+
out[k] = typeof v === "string" ? v : v.map((x) => typeof x === "string" ? x : x.label?.trim() ? `${x.label.trim()}=${x.url}` : x.url);
|
|
2490
|
+
return out;
|
|
2491
|
+
}
|
|
2492
|
+
var keepAs = (recordKind) => KNOWLEDGE_KINDS.filter((k) => KNOWLEDGE_RECORD_KIND[k] === recordKind);
|
|
2493
|
+
function conflictView(v) {
|
|
2494
|
+
const c = v.conflict;
|
|
2495
|
+
return {
|
|
2496
|
+
key: v.key,
|
|
2497
|
+
about: c.about,
|
|
2498
|
+
...c.summary ? { summary: c.summary } : {},
|
|
2499
|
+
pages: c.pages.map((p, k) => ({ source: p.source, page: p.page, says: c.says[k] ?? "" })),
|
|
2500
|
+
applies: v.applies.map((p) => ({ source: p.source, page: p.page })),
|
|
2501
|
+
...v.decided ? { decided: v.decided } : {},
|
|
2502
|
+
...v.suggested ? {
|
|
2503
|
+
suggested: {
|
|
2504
|
+
source: v.suggested.source,
|
|
2505
|
+
page: v.suggested.page,
|
|
2506
|
+
signal: v.suggested.signal,
|
|
2507
|
+
detail: v.suggested.detail
|
|
2508
|
+
}
|
|
2509
|
+
} : {},
|
|
2510
|
+
...v.question ? { question: v.question } : {}
|
|
2511
|
+
};
|
|
2512
|
+
}
|
|
2513
|
+
function itemView(r, review, conflicts = []) {
|
|
2514
|
+
const mine = conflicts.filter((v) => v.conflict.about.includes(r.id));
|
|
2515
|
+
return {
|
|
2516
|
+
id: r.id,
|
|
2517
|
+
kind: knowledgeKindOf(r) ?? keepAs(r.kind)[0],
|
|
2518
|
+
recordKind: r.kind,
|
|
2519
|
+
title: itemTitle(r),
|
|
2520
|
+
record: r,
|
|
2521
|
+
...review ? { review } : {},
|
|
2522
|
+
fields: correctableFields(r),
|
|
2523
|
+
conflict: !!r.flags?.some((f) => f.startsWith("conflict:")) || mine.length > 0,
|
|
2524
|
+
...mine.length ? { conflicts: mine.map(conflictView) } : {}
|
|
2525
|
+
};
|
|
2526
|
+
}
|
|
2527
|
+
function candidateView(c) {
|
|
2528
|
+
const r = c.record;
|
|
2529
|
+
return {
|
|
2530
|
+
id: r.id,
|
|
2531
|
+
kind: knowledgeKindOf(r) ?? keepAs(r.kind)[0],
|
|
2532
|
+
recordKind: r.kind,
|
|
2533
|
+
title: itemTitle(r),
|
|
2534
|
+
record: r,
|
|
2535
|
+
...c.known ? { known: c.known } : {},
|
|
2536
|
+
...c.kept ? { kept: c.kept } : {},
|
|
2537
|
+
keepAs: keepAs(r.kind)
|
|
2538
|
+
};
|
|
2539
|
+
}
|
|
2540
|
+
var exists = (p) => stat3(p).then(() => true, () => false);
|
|
2541
|
+
function capture() {
|
|
2542
|
+
const lines = [];
|
|
2543
|
+
const problems = [];
|
|
2544
|
+
return {
|
|
2545
|
+
io: { out: (l) => lines.push(l), err: (l) => problems.push(l) },
|
|
2546
|
+
lines,
|
|
2547
|
+
problems
|
|
2548
|
+
};
|
|
2549
|
+
}
|
|
2550
|
+
function uiKnowledgeRoutes(r, ctx, deps = {}) {
|
|
2551
|
+
const root = ctx.root;
|
|
2552
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
2553
|
+
const changed = (path) => ctx.events.publish({ type: "file.changed", path });
|
|
2554
|
+
const appliesView = async (change) => {
|
|
2555
|
+
const file = `${changesRelOf(root)}/${change}/knowledge.yaml`;
|
|
2556
|
+
const present = await exists(join6(changeFolder(root, change), "knowledge.yaml"));
|
|
2557
|
+
const doc = present ? await readKnowledgeApplies(root, change) : void 0;
|
|
2558
|
+
if (present && !doc)
|
|
2559
|
+
throw new HttpError(422, "Atticus can't read that file", `${file} isn't a valid knowledge list.`);
|
|
2560
|
+
const titles = /* @__PURE__ */ new Map();
|
|
2561
|
+
if (doc?.items.some((i) => !i.title) || doc?.nearMisses.some((n) => !n.title))
|
|
2562
|
+
for (const r2 of await readKnowledge(root, { cacheRoot: root }).then((k) => [
|
|
2563
|
+
...k.items,
|
|
2564
|
+
...k.rejected
|
|
2565
|
+
]))
|
|
2566
|
+
titles.set(r2.id, itemTitle(r2));
|
|
2567
|
+
const titled = (x) => {
|
|
2568
|
+
const title = x.title ?? titles.get(x.id);
|
|
2569
|
+
return title ? { ...x, title } : x;
|
|
2570
|
+
};
|
|
2571
|
+
const used = new Set((doc?.items ?? []).filter((i) => i.status !== "excluded").map((i) => i.id));
|
|
2572
|
+
const conflicts = used.size ? (await readConflicts(root, { records: await codeFactRecords(root) }).catch(() => [])).filter((v) => conflictTouches(v, used)) : [];
|
|
2573
|
+
const settings = readSourceSettings(root);
|
|
2574
|
+
const quoted = /* @__PURE__ */ new Map();
|
|
2575
|
+
if (settings.size && doc?.items.length) {
|
|
2576
|
+
for (const r2 of (await readKnowledge(root, { cacheRoot: root })).items)
|
|
2577
|
+
if (r2.quote)
|
|
2578
|
+
quoted.set(r2.id, { source: r2.quote.source, page: r2.quote.page });
|
|
2579
|
+
}
|
|
2580
|
+
const sourceOf = (id) => {
|
|
2581
|
+
const q = quoted.get(id);
|
|
2582
|
+
if (q)
|
|
2583
|
+
return q;
|
|
2584
|
+
const hash = id.indexOf("#");
|
|
2585
|
+
const slash = id.indexOf("/");
|
|
2586
|
+
return hash > 0 && slash > 0 && slash < hash ? { source: id.slice(0, slash), page: id.slice(slash + 1, hash) } : void 0;
|
|
2587
|
+
};
|
|
2588
|
+
return {
|
|
2589
|
+
change,
|
|
2590
|
+
file,
|
|
2591
|
+
present,
|
|
2592
|
+
items: (doc?.items ?? []).map((i) => {
|
|
2593
|
+
const reason = plainWhy(i);
|
|
2594
|
+
const at = sourceOf(i.id);
|
|
2595
|
+
const w = at ? settings.get(at.source) : void 0;
|
|
2596
|
+
const authority = w && (w.authority || w.byPage?.length) ? pageStanding(w, at.page, {}).authority : void 0;
|
|
2597
|
+
return {
|
|
2598
|
+
...titled(i),
|
|
2599
|
+
...reason ? { reason } : {},
|
|
2600
|
+
...at && settings.has(at.source) ? { source: at.source } : {},
|
|
2601
|
+
...authority ? { authority } : {}
|
|
2602
|
+
};
|
|
2603
|
+
}),
|
|
2604
|
+
nearMisses: (doc?.nearMisses ?? []).map(titled),
|
|
2605
|
+
...conflicts.length ? { conflicts: conflicts.map(conflictView) } : {},
|
|
2606
|
+
command: `atticus change knowledge ${change}`
|
|
2607
|
+
};
|
|
2608
|
+
};
|
|
2609
|
+
const reviewed = async (id) => {
|
|
2610
|
+
const k = await readKnowledge(root, { cacheRoot: root });
|
|
2611
|
+
const item = [...k.items, ...k.rejected].find((i) => i.id === id);
|
|
2612
|
+
const conflicts = await readConflicts(root, {
|
|
2613
|
+
conflicts: k.report?.conflicts ?? [],
|
|
2614
|
+
records: await codeFactRecords(root)
|
|
2615
|
+
}).catch(() => []);
|
|
2616
|
+
return item ? itemView(item, k.reviews[id], conflicts) : void 0;
|
|
2617
|
+
};
|
|
2618
|
+
const findView = (f) => ({
|
|
2619
|
+
slug: f.slug,
|
|
2620
|
+
phrase: f.phrase,
|
|
2621
|
+
at: f.at,
|
|
2622
|
+
file: `${knowledgeRelOf(root)}/finds/${f.slug}.yaml`,
|
|
2623
|
+
matches: f.matches,
|
|
2624
|
+
candidates: f.candidates.map(candidateView)
|
|
2625
|
+
});
|
|
2626
|
+
const draftView = async () => {
|
|
2627
|
+
const op = await loadOrgPack(orgPackDirOf(root));
|
|
2628
|
+
const platforms = [];
|
|
2629
|
+
for (const { platform } of op.profiles) {
|
|
2630
|
+
const text = await readFile4(join6(proposalsDirOf(root), "orgpack", `${platform}.yaml`), "utf8").catch(() => void 0);
|
|
2631
|
+
const data = text === void 0 ? void 0 : (0, import_yaml3.parse)(text);
|
|
2632
|
+
const proposals = Array.isArray(data?.proposals) ? data.proposals.flatMap((p) => {
|
|
2633
|
+
const x = OrgPackProposal.safeParse(p);
|
|
2634
|
+
return x.success ? [x.data] : [];
|
|
2635
|
+
}) : [];
|
|
2636
|
+
platforms.push({
|
|
2637
|
+
platform,
|
|
2638
|
+
file: `${proposalsRelOf(root)}/orgpack/${platform}.yaml`,
|
|
2639
|
+
present: text !== void 0,
|
|
2640
|
+
...typeof data?.at === "string" ? { at: data.at } : {},
|
|
2641
|
+
proposals,
|
|
2642
|
+
command: `atticus orgpack draft --platform ${platform}`
|
|
2643
|
+
});
|
|
2644
|
+
}
|
|
2645
|
+
return { platforms };
|
|
2646
|
+
};
|
|
2647
|
+
return r.add("GET", "/api/knowledge/sources", async () => {
|
|
2648
|
+
const op = await loadOrgPack(orgPackDirOf(root));
|
|
2649
|
+
const problems = op.problems.filter((p) => p.file === "sources.yaml").map((p) => ({
|
|
2650
|
+
file: `org-pack/${p.file}`,
|
|
2651
|
+
message: `${p.path ? `${p.path}: ` : ""}${p.message}`
|
|
2652
|
+
}));
|
|
2653
|
+
const k = await readKnowledge(root, { raw: true, cacheRoot: root });
|
|
2654
|
+
const scanned = new Map((k.report?.snapshots ?? []).map((s) => [s.source, s]));
|
|
2655
|
+
const today = now().getTime();
|
|
2656
|
+
const sources = [];
|
|
2657
|
+
for (const w of op.sources.wikis) {
|
|
2658
|
+
const date = (await snapshotDates(root, w.id))[0];
|
|
2659
|
+
let snapshot;
|
|
2660
|
+
if (date) {
|
|
2661
|
+
const file = join6(knowledgeDirOf(root), "snapshots", w.id, date, SNAPSHOT_FILE);
|
|
2662
|
+
const text = await readFile4(file, "utf8").catch(() => void 0);
|
|
2663
|
+
const m = text === void 0 ? void 0 : SnapshotManifest.safeParse((0, import_yaml3.parse)(text));
|
|
2664
|
+
if (m?.success)
|
|
2665
|
+
snapshot = {
|
|
2666
|
+
date,
|
|
2667
|
+
takenAt: m.data.takenAt,
|
|
2668
|
+
...m.data.commit ? { commit: m.data.commit } : {},
|
|
2669
|
+
pages: m.data.pages.length,
|
|
2670
|
+
skipped: m.data.skipped.length,
|
|
2671
|
+
ageDays: Math.max(0, Math.floor((today - Date.parse(`${date}T00:00:00Z`)) / DAY))
|
|
2672
|
+
};
|
|
2673
|
+
else
|
|
2674
|
+
problems.push({
|
|
2675
|
+
file: `${knowledgeRelOf(root)}/snapshots/${w.id}/${date}/${SNAPSHOT_FILE}`,
|
|
2676
|
+
message: "not a snapshot manifest"
|
|
2677
|
+
});
|
|
2678
|
+
}
|
|
2679
|
+
const s = scanned.get(w.id);
|
|
2680
|
+
sources.push({
|
|
2681
|
+
id: w.id,
|
|
2682
|
+
type: w.type,
|
|
2683
|
+
location: w.location,
|
|
2684
|
+
...w.ref ? { ref: w.ref } : {},
|
|
2685
|
+
...w.path ? { path: w.path } : {},
|
|
2686
|
+
...w.checkout ? { checkout: w.checkout } : {},
|
|
2687
|
+
...w.owner ? { owner: w.owner } : {},
|
|
2688
|
+
tags: { kinds: w.tags?.kinds ?? [], frameworks: w.tags?.frameworks ?? [] },
|
|
2689
|
+
// 2.30.0 (ADR 0132): how much its word counts and where it applies.
|
|
2690
|
+
...w.authority ? { authority: w.authority } : {},
|
|
2691
|
+
...w.appliesTo ? { appliesTo: w.appliesTo, scope: scopeWords(w.appliesTo) } : {},
|
|
2692
|
+
...w.byPage?.length ? { byPage: w.byPage.length } : {},
|
|
2693
|
+
...w.readRestricted !== void 0 ? { readRestricted: w.readRestricted } : {},
|
|
2694
|
+
...w.maxPages ? { maxPages: w.maxPages } : {},
|
|
2695
|
+
...snapshot ? { snapshot } : {},
|
|
2696
|
+
...s ? { scanned: { date: s.date, pages: s.pages } } : {},
|
|
2697
|
+
stale: !snapshot || snapshot.ageDays > STALE_DAYS,
|
|
2698
|
+
commands: {
|
|
2699
|
+
sync: `atticus sources sync --source ${w.id}`,
|
|
2700
|
+
scan: `atticus knowledge scan --source ${w.id}`,
|
|
2701
|
+
slash: `/atticus-knowledge ${w.id}`
|
|
2702
|
+
}
|
|
2703
|
+
});
|
|
2704
|
+
}
|
|
2705
|
+
const body = {
|
|
2706
|
+
file: "org-pack/sources.yaml",
|
|
2707
|
+
types: [...api_exports.WIKI_TYPES],
|
|
2708
|
+
kindSuggestions: [...SOURCE_KIND_SUGGESTIONS],
|
|
2709
|
+
staleDays: STALE_DAYS,
|
|
2710
|
+
sources,
|
|
2711
|
+
problems
|
|
2712
|
+
};
|
|
2713
|
+
return { status: 200, body };
|
|
2714
|
+
}).add("POST", "/api/knowledge/sources", async ({ body }) => {
|
|
2715
|
+
const b = api_exports.AddSourceRequest.safeParse(body ?? {});
|
|
2716
|
+
if (!b.success)
|
|
2717
|
+
throw invalid("Some fields aren't right.", b.error);
|
|
2718
|
+
const { type, location, kinds, frameworks, ...o } = b.data;
|
|
2719
|
+
const c = capture();
|
|
2720
|
+
const code = await sourcesAdd(root, type, location, c.io, {
|
|
2721
|
+
...o.id ? { id: o.id } : {},
|
|
2722
|
+
...o.path ? { path: o.path } : {},
|
|
2723
|
+
...o.ref ? { ref: o.ref } : {},
|
|
2724
|
+
...o.submodule ? { submodule: true } : {},
|
|
2725
|
+
...kinds?.length ? { kinds } : {},
|
|
2726
|
+
...frameworks?.length ? { frameworks } : {},
|
|
2727
|
+
...o.owner ? { owner: o.owner } : {},
|
|
2728
|
+
save: o.save
|
|
2729
|
+
});
|
|
2730
|
+
const words = [...c.problems, ...c.lines].join("\n") || "Atticus refused.";
|
|
2731
|
+
if (code === 2)
|
|
2732
|
+
throw badRequest(words);
|
|
2733
|
+
if (code !== 0)
|
|
2734
|
+
throw new HttpError(422, "Atticus refused", words);
|
|
2735
|
+
if (o.save)
|
|
2736
|
+
changed("org-pack/sources.yaml");
|
|
2737
|
+
return { status: 200, body: { code, lines: c.lines, problems: c.problems } };
|
|
2738
|
+
}).add("POST", "/api/knowledge/sources/{sourceId}/sync", async ({ params }) => {
|
|
2739
|
+
const id = idOf3(params.sourceId, "source");
|
|
2740
|
+
const op = await loadOrgPack(orgPackDirOf(root));
|
|
2741
|
+
if (id !== INPUTS_SOURCE_ID && !op.sources.wikis.some((w) => w.id === id))
|
|
2742
|
+
throw notFound(`There is no source ${id} in org-pack/sources.yaml.`);
|
|
2743
|
+
const c = capture();
|
|
2744
|
+
const code = await sourcesSync(root, c.io, { source: id, now: stamp(now()) });
|
|
2745
|
+
if (code === 2)
|
|
2746
|
+
throw new HttpError(422, "Atticus refused", c.problems.join("\n") || "Nothing to sync.");
|
|
2747
|
+
changed(`${knowledgeRelOf(root)}/snapshots/${id}`);
|
|
2748
|
+
return { status: 200, body: { code, lines: c.lines, problems: c.problems } };
|
|
2749
|
+
}).add("GET", "/api/knowledge/scan", async () => {
|
|
2750
|
+
const k = await readKnowledge(root, { cacheRoot: root });
|
|
2751
|
+
const r2 = k.report;
|
|
2752
|
+
const body = {
|
|
2753
|
+
...r2 ? { scannedAt: r2.scannedAt } : {},
|
|
2754
|
+
snapshots: (r2?.snapshots ?? []).map((s) => ({
|
|
2755
|
+
source: s.source,
|
|
2756
|
+
date: s.date,
|
|
2757
|
+
pages: s.pages
|
|
2758
|
+
})),
|
|
2759
|
+
...r2?.changes ? { changes: r2.changes } : {},
|
|
2760
|
+
checkAgain: [...k.items, ...k.rejected].filter((i) => k.reviews[i.id]?.state === "stale").sort((a, b) => a.id.localeCompare(b.id)).map((i) => ({
|
|
2761
|
+
id: i.id,
|
|
2762
|
+
kind: knowledgeKindOf(i) ?? keepAs(i.kind)[0],
|
|
2763
|
+
title: itemTitle(i),
|
|
2764
|
+
by: k.reviews[i.id].by
|
|
2765
|
+
})),
|
|
2766
|
+
conflicts: r2?.conflicts.length ?? 0,
|
|
2767
|
+
injections: r2?.injections.length ?? 0,
|
|
2768
|
+
failed: r2?.failed.length ?? 0,
|
|
2769
|
+
command: "atticus knowledge scan",
|
|
2770
|
+
slash: "/atticus-knowledge"
|
|
2771
|
+
};
|
|
2772
|
+
return { status: 200, body };
|
|
2773
|
+
}).add("GET", "/api/knowledge/items", async ({ query }) => {
|
|
2774
|
+
const kind = strParam(query, "kind");
|
|
2775
|
+
if (kind !== void 0 && !KNOWLEDGE_KINDS.includes(kind))
|
|
2776
|
+
throw badRequest(`There is no knowledge kind ${kind}. Use one of: ${KNOWLEDGE_KINDS.join(", ")}.`);
|
|
2777
|
+
const tag = strParam(query, "tag");
|
|
2778
|
+
const k = await readKnowledge(root, { cacheRoot: root });
|
|
2779
|
+
const items = filterItems([...k.items, ...k.rejected], {
|
|
2780
|
+
...kind ? { kind } : {},
|
|
2781
|
+
...tag ? { tag } : {}
|
|
2782
|
+
}).sort((a, b) => a.id.localeCompare(b.id));
|
|
2783
|
+
const conflicts = await readConflicts(root, {
|
|
2784
|
+
conflicts: k.report?.conflicts ?? [],
|
|
2785
|
+
records: await codeFactRecords(root)
|
|
2786
|
+
}).catch(() => []);
|
|
2787
|
+
const body = {
|
|
2788
|
+
items: items.map((i) => itemView(i, k.reviews[i.id], conflicts)),
|
|
2789
|
+
problems: k.problems.map((p) => ({
|
|
2790
|
+
file: `${knowledgeRelOf(root)}/${p.file.startsWith("reviews/") || p.file === "scan.yaml" ? "" : "items/"}${p.file}`,
|
|
2791
|
+
message: p.message
|
|
2792
|
+
}))
|
|
2793
|
+
};
|
|
2794
|
+
return { status: 200, body };
|
|
2795
|
+
}).add("POST", "/api/knowledge/items/{itemId}/review", async ({ params, body }) => {
|
|
2796
|
+
const id = idOf3(params.itemId, "knowledge item");
|
|
2797
|
+
const b = api_exports.ReviewItemRequest.safeParse(body ?? {});
|
|
2798
|
+
if (!b.success)
|
|
2799
|
+
throw invalid("Say verified, corrected or rejected.", b.error);
|
|
2800
|
+
const raw = await readKnowledge(root, { raw: true, cacheRoot: root });
|
|
2801
|
+
const item = raw.items.find((i) => i.id === id);
|
|
2802
|
+
if (!item)
|
|
2803
|
+
throw notFound(`There is no knowledge item ${id}.`);
|
|
2804
|
+
let set = asSet(b.data.set);
|
|
2805
|
+
if (b.data.verdict === "corrected") {
|
|
2806
|
+
const before = (await readReviews(root)).reviews.get(id);
|
|
2807
|
+
if (before?.corrections)
|
|
2808
|
+
set = { ...asSet(before.corrections), ...set };
|
|
2809
|
+
}
|
|
2810
|
+
const res = reviewItem(item, {
|
|
2811
|
+
verdict: b.data.verdict,
|
|
2812
|
+
by: ctx.user,
|
|
2813
|
+
at: stamp(now()),
|
|
2814
|
+
...b.data.note ? { note: b.data.note } : {},
|
|
2815
|
+
...set && Object.keys(set).length ? { set } : {}
|
|
2816
|
+
});
|
|
2817
|
+
if ("error" in res)
|
|
2818
|
+
throw badRequest(res.error);
|
|
2819
|
+
const file = await writeReview(root, res.review);
|
|
2820
|
+
changed(`${knowledgeRelOf(root)}/${file}`);
|
|
2821
|
+
return {
|
|
2822
|
+
status: 200,
|
|
2823
|
+
body: { item: await reviewed(id), file: `${knowledgeRelOf(root)}/${file}` }
|
|
2824
|
+
};
|
|
2825
|
+
}).add("GET", "/api/knowledge/search", async ({ query }) => {
|
|
2826
|
+
const phrase = strParam(query, "q")?.trim();
|
|
2827
|
+
if (!phrase)
|
|
2828
|
+
throw badRequest("Say what to look for: ?q=<a term or phrase>.");
|
|
2829
|
+
if (phrase.length > 200)
|
|
2830
|
+
throw badRequest("Search for a term or phrase of up to 200 characters.");
|
|
2831
|
+
const source = strParam(query, "source");
|
|
2832
|
+
if (source !== void 0)
|
|
2833
|
+
idOf3(source, "source");
|
|
2834
|
+
const ids = await snapshotSources(root);
|
|
2835
|
+
if (source && !ids.includes(source))
|
|
2836
|
+
throw notFound(`${source} has no snapshot yet: atticus sources sync --source ${source}.`);
|
|
2837
|
+
const snapshots = [];
|
|
2838
|
+
for (const s of source ? [source] : ids) {
|
|
2839
|
+
const snap = await readSnapshot(root, s).catch(() => void 0);
|
|
2840
|
+
if (snap)
|
|
2841
|
+
snapshots.push(snap);
|
|
2842
|
+
}
|
|
2843
|
+
const matches = searchPages(snapshots, phrase);
|
|
2844
|
+
const body = {
|
|
2845
|
+
phrase,
|
|
2846
|
+
sources: snapshots.length,
|
|
2847
|
+
matches,
|
|
2848
|
+
pages: [...new Set(matches.map((m) => `${m.source}/${m.page}`))],
|
|
2849
|
+
command: `atticus knowledge find ${JSON.stringify(phrase)}${source ? ` --source ${source}` : ""}`,
|
|
2850
|
+
slash: `/atticus-find ${phrase}`
|
|
2851
|
+
};
|
|
2852
|
+
return { status: 200, body };
|
|
2853
|
+
}).add("GET", "/api/knowledge/finds", async () => {
|
|
2854
|
+
const finds = [];
|
|
2855
|
+
for (const f of await readFinds(root)) {
|
|
2856
|
+
const s = await readFind(root, f.slug);
|
|
2857
|
+
if (s)
|
|
2858
|
+
finds.push(findView(s));
|
|
2859
|
+
}
|
|
2860
|
+
return { status: 200, body: { finds } };
|
|
2861
|
+
}).add("GET", "/api/knowledge/finds/{slug}", async ({ params }) => {
|
|
2862
|
+
const slug = params.slug;
|
|
2863
|
+
if (!SLUG.test(slug))
|
|
2864
|
+
throw badRequest(`${slug} isn't a saved search.`);
|
|
2865
|
+
const f = await readFind(root, slug);
|
|
2866
|
+
if (!f)
|
|
2867
|
+
throw notFound(`There is no saved search ${slug}.`);
|
|
2868
|
+
return { status: 200, body: findView(f) };
|
|
2869
|
+
}).add("POST", "/api/knowledge/finds/{slug}/keep", async ({ params, body }) => {
|
|
2870
|
+
const slug = params.slug;
|
|
2871
|
+
if (!SLUG.test(slug))
|
|
2872
|
+
throw badRequest(`${slug} isn't a saved search.`);
|
|
2873
|
+
const b = api_exports.KeepCandidateRequest.safeParse(body ?? {});
|
|
2874
|
+
if (!b.success)
|
|
2875
|
+
throw invalid("Say which candidate to keep: { item }.", b.error);
|
|
2876
|
+
const f = await readFind(root, slug);
|
|
2877
|
+
if (!f)
|
|
2878
|
+
throw notFound(`There is no saved search ${slug}.`);
|
|
2879
|
+
if (!f.candidates.some((c) => c.record.id === b.data.item))
|
|
2880
|
+
throw notFound(`The search ${slug} has no candidate ${b.data.item}.`);
|
|
2881
|
+
const set = asSet(b.data.set);
|
|
2882
|
+
const res = await keepCandidate(root, slug, b.data.item, {
|
|
2883
|
+
...b.data.as ? { as: b.data.as } : {},
|
|
2884
|
+
by: ctx.user,
|
|
2885
|
+
at: stamp(now()),
|
|
2886
|
+
...b.data.note ? { note: b.data.note } : {},
|
|
2887
|
+
...set && Object.keys(set).length ? { set } : {}
|
|
2888
|
+
});
|
|
2889
|
+
if ("error" in res)
|
|
2890
|
+
throw new HttpError(422, "Atticus refused", res.error);
|
|
2891
|
+
changed(`${knowledgeRelOf(root)}/items`);
|
|
2892
|
+
const item = await reviewed(b.data.item) ?? itemView(res.item, void 0);
|
|
2893
|
+
return { status: 200, body: { item, as: res.as } };
|
|
2894
|
+
}).add("GET", "/api/changes/{changeId}/needs", async ({ params }) => {
|
|
2895
|
+
const change = idOf3(params.changeId, "change");
|
|
2896
|
+
if (!await exists(changeFolder(root, change)))
|
|
2897
|
+
throw notFound(`There is no change ${change}.`);
|
|
2898
|
+
const fileExists = await exists(join6(changeFolder(root, change), NEEDS_FILE));
|
|
2899
|
+
const f = await readNeeds(root, change);
|
|
2900
|
+
if (fileExists && !f)
|
|
2901
|
+
throw new HttpError(422, "Atticus can't read that file", `${changesRelOf(root)}/${change}/${NEEDS_FILE} isn't a valid needs file.`);
|
|
2902
|
+
const k = f ? await readKnowledge(root, { cacheRoot: root }) : void 0;
|
|
2903
|
+
const view2 = (n) => ({
|
|
2904
|
+
...n,
|
|
2905
|
+
...k?.reviews[n.item] ? { review: k.reviews[n.item] } : {}
|
|
2906
|
+
});
|
|
2907
|
+
const body = {
|
|
2908
|
+
change,
|
|
2909
|
+
file: `${changesRelOf(root)}/${change}/${NEEDS_FILE}`,
|
|
2910
|
+
present: !!f,
|
|
2911
|
+
...f ? { updatedAt: f.updatedAt } : {},
|
|
2912
|
+
needs: (f?.needs ?? []).map(view2),
|
|
2913
|
+
considered: f?.considered ?? [],
|
|
2914
|
+
command: `atticus change needs ${change}`,
|
|
2915
|
+
slash: `/atticus-needs ${change}`
|
|
2916
|
+
};
|
|
2917
|
+
return { status: 200, body };
|
|
2918
|
+
}).add("POST", "/api/changes/{changeId}/needs/{itemId}", async ({ params, body }) => {
|
|
2919
|
+
const change = idOf3(params.changeId, "change");
|
|
2920
|
+
const item = idOf3(params.itemId, "knowledge item");
|
|
2921
|
+
const b = api_exports.MarkNeedRequest.safeParse(body ?? {});
|
|
2922
|
+
if (!b.success)
|
|
2923
|
+
throw invalid("Say done, not-needed or open.", b.error);
|
|
2924
|
+
if (!await exists(changeFolder(root, change)))
|
|
2925
|
+
throw notFound(`There is no change ${change}.`);
|
|
2926
|
+
const res = await markNeed(root, change, item, {
|
|
2927
|
+
status: b.data.status,
|
|
2928
|
+
by: ctx.user,
|
|
2929
|
+
at: stamp(now()),
|
|
2930
|
+
...b.data.note ? { note: b.data.note } : {},
|
|
2931
|
+
...b.data.for ? { for: b.data.for } : {}
|
|
2932
|
+
});
|
|
2933
|
+
if ("error" in res) {
|
|
2934
|
+
if (/has no need /.test(res.error))
|
|
2935
|
+
throw notFound(res.error);
|
|
2936
|
+
if (/has no needs yet/.test(res.error))
|
|
2937
|
+
throw new HttpError(422, "Atticus can't do that yet", res.error);
|
|
2938
|
+
throw badRequest(res.error);
|
|
2939
|
+
}
|
|
2940
|
+
const file = `${changesRelOf(root)}/${change}/${NEEDS_FILE}`;
|
|
2941
|
+
changed(file);
|
|
2942
|
+
const k = await readKnowledge(root, { cacheRoot: root });
|
|
2943
|
+
return {
|
|
2944
|
+
status: 200,
|
|
2945
|
+
body: {
|
|
2946
|
+
need: { ...res.need, ...k.reviews[item] ? { review: k.reviews[item] } : {} },
|
|
2947
|
+
file
|
|
2948
|
+
}
|
|
2949
|
+
};
|
|
2950
|
+
}).add("GET", "/api/changes/{changeId}/knowledge", async ({ params }) => {
|
|
2951
|
+
const change = idOf3(params.changeId, "change");
|
|
2952
|
+
if (!await exists(changeFolder(root, change)))
|
|
2953
|
+
throw notFound(`There is no change ${change}.`);
|
|
2954
|
+
return { status: 200, body: await appliesView(change) };
|
|
2955
|
+
}).add("POST", "/api/changes/{changeId}/knowledge/{itemId}", async ({ params, body }) => {
|
|
2956
|
+
const change = idOf3(params.changeId, "change");
|
|
2957
|
+
const item = params.itemId;
|
|
2958
|
+
if (!item.trim() || item.length > 400 || /[\p{Cc}]/u.test(item))
|
|
2959
|
+
throw badRequest(`${JSON.stringify(item)} isn't a knowledge item id.`);
|
|
2960
|
+
const b = api_exports.SetChangeKnowledgeRequest.safeParse(body ?? {});
|
|
2961
|
+
if (!b.success)
|
|
2962
|
+
throw invalid("Say confirmed or excluded: { status }.", b.error);
|
|
2963
|
+
if (!await exists(changeFolder(root, change)))
|
|
2964
|
+
throw notFound(`There is no change ${change}.`);
|
|
2965
|
+
const before = await appliesView(change);
|
|
2966
|
+
if (!before.present)
|
|
2967
|
+
throw new HttpError(422, "Atticus can't do that yet", `${before.file} doesn't exist yet: its stages write it when they select context (${before.command}).`);
|
|
2968
|
+
try {
|
|
2969
|
+
await setKnowledgeStatus(root, change, item, b.data.status);
|
|
2970
|
+
} catch (e) {
|
|
2971
|
+
throw notFound(e.message);
|
|
2972
|
+
}
|
|
2973
|
+
changed(before.file);
|
|
2974
|
+
const result = {
|
|
2975
|
+
knowledge: await appliesView(change),
|
|
2976
|
+
file: before.file,
|
|
2977
|
+
commit: `git add ${before.file} && git commit -m "${change}: the knowledge that applies"`
|
|
2978
|
+
};
|
|
2979
|
+
return { status: 200, body: result };
|
|
2980
|
+
}).add("GET", "/api/orgpack/draft", async () => ({ status: 200, body: await draftView() })).add("POST", "/api/orgpack/draft", async ({ body }) => {
|
|
2981
|
+
const b = api_exports.DraftOrgPackRequest.safeParse(body ?? {});
|
|
2982
|
+
if (!b.success)
|
|
2983
|
+
throw invalid("Say which platform: { platform }.", b.error);
|
|
2984
|
+
try {
|
|
2985
|
+
await draftOrgPack(root, {
|
|
2986
|
+
...b.data.platform ? { platform: b.data.platform } : {},
|
|
2987
|
+
now: now()
|
|
2988
|
+
});
|
|
2989
|
+
} catch (e) {
|
|
2990
|
+
throw new HttpError(422, "Atticus refused", e.message);
|
|
2991
|
+
}
|
|
2992
|
+
changed(`${proposalsRelOf(root)}/orgpack`);
|
|
2993
|
+
return { status: 200, body: await draftView() };
|
|
2994
|
+
}).add("POST", "/api/orgpack/draft/{platform}/accept", async ({ params, body }) => {
|
|
2995
|
+
const platform = idOf3(params.platform, "platform");
|
|
2996
|
+
const b = api_exports.AcceptOrgPackRequest.safeParse(body ?? {});
|
|
2997
|
+
if (!b.success)
|
|
2998
|
+
throw invalid("Pick the systems to accept: { only: [ids] }.", b.error);
|
|
2999
|
+
try {
|
|
3000
|
+
const res = await acceptOrgPackDraft(root, platform, { only: b.data.only });
|
|
3001
|
+
changed(res.file);
|
|
3002
|
+
return { status: 200, body: res };
|
|
3003
|
+
} catch (e) {
|
|
3004
|
+
const words = e.message;
|
|
3005
|
+
if (/There is no draft/.test(words))
|
|
3006
|
+
throw notFound(words);
|
|
3007
|
+
throw new HttpError(422, "Atticus refused", words);
|
|
3008
|
+
}
|
|
3009
|
+
});
|
|
3010
|
+
}
|
|
3011
|
+
|
|
3012
|
+
// packages/cli/dist/ui-api-runs.js
|
|
3013
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
3014
|
+
import { join as join7 } from "node:path";
|
|
3015
|
+
var import_yaml4 = __toESM(require_dist(), 1);
|
|
3016
|
+
async function serverModel(root) {
|
|
3017
|
+
return (await configuredModel(root))?.type ?? null;
|
|
3018
|
+
}
|
|
3019
|
+
async function configuredModel(root) {
|
|
3020
|
+
const text = await readFile5(join7(orgPackDirOf(root), "runtime.yaml"), "utf8").catch(() => void 0);
|
|
3021
|
+
if (!text)
|
|
3022
|
+
return null;
|
|
3023
|
+
let y;
|
|
3024
|
+
try {
|
|
3025
|
+
y = (0, import_yaml4.parse)(text);
|
|
3026
|
+
} catch {
|
|
3027
|
+
return null;
|
|
3028
|
+
}
|
|
3029
|
+
const type = y?.adapters?.model?.type;
|
|
3030
|
+
if (typeof type !== "string" || type === "mock")
|
|
3031
|
+
return null;
|
|
3032
|
+
const allowed = y?.modes?.allowed;
|
|
3033
|
+
if (Array.isArray(allowed) && !allowed.includes("model"))
|
|
3034
|
+
return null;
|
|
3035
|
+
const max = y?.adapters?.model?.settings?.maxInput;
|
|
3036
|
+
return typeof max === "number" && Number.isInteger(max) && max >= 1e3 ? { type, maxInput: max } : { type };
|
|
3037
|
+
}
|
|
3038
|
+
function uiModelRunRoutes(r, ctx, deps = {}) {
|
|
3039
|
+
const root = ctx.root;
|
|
3040
|
+
const runStep = deps.runStep ?? (async (args) => {
|
|
3041
|
+
const { main } = await import("./chunk-MTAUZW2Z.js");
|
|
3042
|
+
const out = [];
|
|
3043
|
+
const err = [];
|
|
3044
|
+
const code = await main([...args, "--root", root], { out: (l) => out.push(l), err: (l) => err.push(l) }, { quiet: true });
|
|
3045
|
+
return { code, out, err };
|
|
3046
|
+
});
|
|
3047
|
+
return r.add("POST", "/api/runs", async ({ body }) => {
|
|
3048
|
+
const args = body?.args;
|
|
3049
|
+
if (!Array.isArray(args) || args.length < 2 || !args.every((a) => typeof a === "string" && a.trim() !== ""))
|
|
3050
|
+
throw badRequest(`args is the step's command words and arguments, for example ["scan", "survey", "forge-ledger"].`);
|
|
3051
|
+
const words = args;
|
|
3052
|
+
if (!isHandoffStep(words[0], words[1]))
|
|
3053
|
+
throw badRequest(`atticus ${words.slice(0, 2).join(" ")} isn't an AI step; the app starts only those.`);
|
|
3054
|
+
const option = words.find((a) => a.startsWith("-"));
|
|
3055
|
+
if (option)
|
|
3056
|
+
throw badRequest(`${option} would be read as an option; give the step's arguments only.`);
|
|
3057
|
+
const configured = await configuredModel(root);
|
|
3058
|
+
const model = configured?.type;
|
|
3059
|
+
if (!model)
|
|
3060
|
+
throw conflict("This server's runtime.yaml configures no model, so it can't run an AI step. Run it in your assistant (its slash command), or from VS Code with Copilot.");
|
|
3061
|
+
const job = ctx.jobs.start("step", async (c) => {
|
|
3062
|
+
c.progress(0, 1, `Running atticus ${words.join(" ")} with ${model}`);
|
|
3063
|
+
const before = new Set((await listRuns(root, { limit: 1e5 })).map((x) => x.runId));
|
|
3064
|
+
const res = await runStep([
|
|
3065
|
+
...words,
|
|
3066
|
+
"--mode",
|
|
3067
|
+
"model",
|
|
3068
|
+
...configured?.maxInput ? ["--max-tokens", String(configured.maxInput)] : []
|
|
3069
|
+
]);
|
|
3070
|
+
const made = (await listRuns(root, { limit: 1e5 })).find((x) => !before.has(x.runId));
|
|
3071
|
+
c.progress(1, 1, res.code === 0 ? "Done" : "Stopped");
|
|
3072
|
+
ctx.events.publish({ type: "file.changed", path: runsRelOf(root) });
|
|
3073
|
+
return {
|
|
3074
|
+
result: {
|
|
3075
|
+
code: res.code,
|
|
3076
|
+
lines: [...res.out, ...res.err],
|
|
3077
|
+
...made ? { runId: made.runId } : {}
|
|
3078
|
+
},
|
|
3079
|
+
errors: res.code === 0 ? [] : res.err
|
|
3080
|
+
};
|
|
3081
|
+
});
|
|
3082
|
+
return { status: 202, body: { jobId: job.id, model } };
|
|
3083
|
+
}).add("GET", "/api/runs", async ({ query }) => {
|
|
3084
|
+
const change = query.get("change") ?? void 0;
|
|
3085
|
+
const limitText = query.get("limit");
|
|
3086
|
+
const limit = limitText === null ? 200 : Number(limitText);
|
|
3087
|
+
if (!Number.isInteger(limit) || limit < 1)
|
|
3088
|
+
throw badRequest("limit is a whole number of 1 or more.");
|
|
3089
|
+
const runs = await listRuns(root, { ...change ? { change } : {}, limit });
|
|
3090
|
+
return {
|
|
3091
|
+
status: 200,
|
|
3092
|
+
body: { runs, serverModel: await serverModel(root) }
|
|
3093
|
+
};
|
|
3094
|
+
}).add("GET", "/api/runs/{runId}/transcript", async ({ params }) => {
|
|
3095
|
+
const t = await runTranscript(root, params.runId);
|
|
3096
|
+
if (!t)
|
|
3097
|
+
throw notFound(`There is no run ${params.runId}.`);
|
|
3098
|
+
return { status: 200, body: t };
|
|
3099
|
+
});
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
// packages/cli/dist/ui-api-history.js
|
|
3103
|
+
function uiHistoryRoutes(r, ctx) {
|
|
3104
|
+
const root = ctx.root;
|
|
3105
|
+
return r.add("GET", "/api/history", async ({ query }) => {
|
|
3106
|
+
const limitText = query.get("limit");
|
|
3107
|
+
const limit = limitText === null ? 200 : Number(limitText);
|
|
3108
|
+
if (!Number.isInteger(limit) || limit < 1)
|
|
3109
|
+
throw badRequest("limit is a whole number of 1 or more.");
|
|
3110
|
+
const opt = (k) => {
|
|
3111
|
+
const v = query.get(k)?.trim();
|
|
3112
|
+
return v ? v : void 0;
|
|
3113
|
+
};
|
|
3114
|
+
const change = opt("change");
|
|
3115
|
+
const session = opt("session");
|
|
3116
|
+
const filter = opt("q");
|
|
3117
|
+
const body = await listHistory(root, {
|
|
3118
|
+
...change ? { change } : {},
|
|
3119
|
+
...session ? { session } : {},
|
|
3120
|
+
...filter ? { filter } : {},
|
|
3121
|
+
limit
|
|
3122
|
+
});
|
|
3123
|
+
return { status: 200, body };
|
|
3124
|
+
}).add("GET", "/api/history/{historyId}", async ({ params }) => {
|
|
3125
|
+
const e = await historyEntry(root, params.historyId);
|
|
3126
|
+
if (!e)
|
|
3127
|
+
throw notFound(`There is no record ${params.historyId}.`);
|
|
3128
|
+
return { status: 200, body: e };
|
|
3129
|
+
});
|
|
3130
|
+
}
|
|
3131
|
+
|
|
3132
|
+
// packages/cli/dist/ui-api-ai.js
|
|
3133
|
+
var import_yaml5 = __toESM(require_dist(), 1);
|
|
3134
|
+
import { existsSync } from "node:fs";
|
|
3135
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
3136
|
+
import { join as join8, relative, sep } from "node:path";
|
|
3137
|
+
var unprocessable = (detail) => new HttpError(422, "The settings aren't valid", detail);
|
|
3138
|
+
var ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
|
|
3139
|
+
var FIELDS = ["ai", "modes", "assistants", "models", "budgets"];
|
|
3140
|
+
function uiAiRoutes(r, ctx, deps = {}) {
|
|
3141
|
+
const root = ctx.root;
|
|
3142
|
+
const env = deps.env ?? process.env;
|
|
3143
|
+
const keys = () => deps.keys ?? defaultKeyStore();
|
|
3144
|
+
const file = () => join8(orgPackDirOf(root), "runtime.yaml");
|
|
3145
|
+
const rel = () => relative(root, file()).split(sep).join("/");
|
|
3146
|
+
const build = async (op, type, settings) => {
|
|
3147
|
+
if (deps.provider)
|
|
3148
|
+
return deps.provider(type, settings);
|
|
3149
|
+
const make = await providerFactoryFor(op, type);
|
|
3150
|
+
return make?.(settings);
|
|
3151
|
+
};
|
|
3152
|
+
return r.add("GET", "/api/settings/ai", async () => {
|
|
3153
|
+
const op = await loadOrgPack(orgPackDirOf(root));
|
|
3154
|
+
const rt = op.runtime;
|
|
3155
|
+
const model = rt.adapters.model;
|
|
3156
|
+
const keyEnv = typeof model?.settings.keyEnv === "string" ? model.settings.keyEnv : void 0;
|
|
3157
|
+
return {
|
|
3158
|
+
status: 200,
|
|
3159
|
+
body: {
|
|
3160
|
+
// The assistant atticus probe measured here, when one was (ADR 0124 addendum A-probe).
|
|
3161
|
+
runs: whatRunsWhere(rt, { probes: (await readProbes(root)).results }),
|
|
3162
|
+
ai: rt.ai,
|
|
3163
|
+
modes: rt.modes,
|
|
3164
|
+
assistants: ASSISTANTS.map((id) => ({
|
|
3165
|
+
id,
|
|
3166
|
+
name: ASSISTANT_FORMATS[id].name,
|
|
3167
|
+
allowed: rt.assistants.allowed.includes(id),
|
|
3168
|
+
default: rt.assistants.default === id
|
|
3169
|
+
})),
|
|
3170
|
+
providers: [
|
|
3171
|
+
...Object.entries(PROVIDER_SETTINGS).map(([type, settings]) => ({
|
|
3172
|
+
type,
|
|
3173
|
+
settings: [...settings]
|
|
3174
|
+
})),
|
|
3175
|
+
// The org pack's model-provider plugins, with what each takes (ADR 0121 item 4).
|
|
3176
|
+
...op.modelProviders.map(({ manifest: m }) => ({
|
|
3177
|
+
type: m.id,
|
|
3178
|
+
settings: [
|
|
3179
|
+
...COMMON_SETTINGS,
|
|
3180
|
+
...m.auth === "none" ? [] : ["keyEnv"],
|
|
3181
|
+
...m.settings
|
|
3182
|
+
],
|
|
3183
|
+
plugin: true,
|
|
3184
|
+
pinned: !!m.sha256
|
|
3185
|
+
}))
|
|
3186
|
+
],
|
|
3187
|
+
model: model ? { type: model.type, settings: model.settings } : null,
|
|
3188
|
+
// Whether the key's variable is set here, by name: its value is never read out.
|
|
3189
|
+
keySet: keyEnv ? !!env[keyEnv]?.trim() || !!keys().get(keyEnv) : false,
|
|
3190
|
+
models: rt.models,
|
|
3191
|
+
budgets: rt.budgets,
|
|
3192
|
+
path: rel()
|
|
3193
|
+
}
|
|
3194
|
+
};
|
|
3195
|
+
}).add("PUT", "/api/settings/ai", async ({ body }) => {
|
|
3196
|
+
const b = body ?? {};
|
|
3197
|
+
const text = existsSync(file()) ? await readFile6(file(), "utf8") : void 0;
|
|
3198
|
+
const raw = text !== void 0 ? (0, import_yaml5.parse)(text) ?? {} : structuredClone(DEFAULT_RUNTIME);
|
|
3199
|
+
for (const f of FIELDS)
|
|
3200
|
+
if (b[f] !== void 0)
|
|
3201
|
+
raw[f] = b[f];
|
|
3202
|
+
if (b.model !== void 0) {
|
|
3203
|
+
const adapters = { ...raw.adapters ?? {} };
|
|
3204
|
+
if (b.model === null)
|
|
3205
|
+
delete adapters.model;
|
|
3206
|
+
else {
|
|
3207
|
+
const m = b.model;
|
|
3208
|
+
const settings = Object.fromEntries(Object.entries(m.settings ?? {}).filter(([, v]) => v !== "" && v !== void 0));
|
|
3209
|
+
for (const [name, v] of Object.entries(settings))
|
|
3210
|
+
for (const one of Array.isArray(v) ? v : [v])
|
|
3211
|
+
if (typeof one === "string" && looksLikeKey(one))
|
|
3212
|
+
throw unprocessable(`${name} looks like a key. A key never goes in runtime.yaml: keep it in an environment variable (or the OS's credential store) and give that variable's name as keyEnv.`);
|
|
3213
|
+
if (settings.keyEnv !== void 0 && !ENV_NAME.test(String(settings.keyEnv)))
|
|
3214
|
+
throw unprocessable(`keyEnv is the name of an environment variable, such as ATTICUS_MODEL_KEY.`);
|
|
3215
|
+
adapters.model = { type: m.type, settings };
|
|
3216
|
+
}
|
|
3217
|
+
raw.adapters = adapters;
|
|
3218
|
+
}
|
|
3219
|
+
const checked = Runtime.safeParse(raw);
|
|
3220
|
+
if (!checked.success)
|
|
3221
|
+
throw unprocessable(checked.error.issues.map((i) => `${i.path.join(".") || "runtime.yaml"}: ${i.message}`).join("; "));
|
|
3222
|
+
const header = (text ?? "").split(/\r?\n/).filter((l, i, a) => a.slice(0, i + 1).every((x) => x.startsWith("#")));
|
|
3223
|
+
await writeFileAtomic(file(), `${header.length ? `${header.join("\n")}
|
|
3224
|
+
` : ""}${(0, import_yaml5.stringify)(raw, { lineWidth: 0 })}`);
|
|
3225
|
+
return {
|
|
3226
|
+
status: 200,
|
|
3227
|
+
body: {
|
|
3228
|
+
ok: true,
|
|
3229
|
+
path: rel(),
|
|
3230
|
+
note: `Saved ${rel()}. Commit it in a pull request: the org pack is reviewed like code.`
|
|
3231
|
+
}
|
|
3232
|
+
};
|
|
3233
|
+
}).add("POST", "/api/settings/ai/key", async ({ body }) => {
|
|
3234
|
+
const b = body ?? {};
|
|
3235
|
+
const name = typeof b.name === "string" ? b.name.trim() : "";
|
|
3236
|
+
const value = typeof b.value === "string" ? b.value.trim() : "";
|
|
3237
|
+
if (!ENV_NAME.test(name))
|
|
3238
|
+
throw unprocessable("The key's name is keyEnv's: a variable's name, such as ATTICUS_MODEL_KEY.");
|
|
3239
|
+
if (!value)
|
|
3240
|
+
throw unprocessable("No key was given.");
|
|
3241
|
+
keys().set(name, value);
|
|
3242
|
+
return { status: 200, body: { ok: true, name } };
|
|
3243
|
+
}).add("POST", "/api/settings/ai/test", async () => {
|
|
3244
|
+
const op = await loadOrgPack(orgPackDirOf(root));
|
|
3245
|
+
const model = op.runtime.adapters.model;
|
|
3246
|
+
if (!model || model.type === "mock")
|
|
3247
|
+
throw conflict("No model provider is set in runtime.yaml: choose one first.");
|
|
3248
|
+
const started = Date.now();
|
|
3249
|
+
try {
|
|
3250
|
+
const p = await build(op, model.type, model.settings);
|
|
3251
|
+
if (!p)
|
|
3252
|
+
return {
|
|
3253
|
+
status: 200,
|
|
3254
|
+
body: { ok: false, error: `No ${model.type} provider is installed here.` }
|
|
3255
|
+
};
|
|
3256
|
+
await p.complete({
|
|
3257
|
+
system: "You are checking that a connection works.",
|
|
3258
|
+
prompt: "Reply with the word OK.",
|
|
3259
|
+
maxOutputTokens: 5
|
|
3260
|
+
});
|
|
3261
|
+
return { status: 200, body: { ok: true, model: p.model, ms: Date.now() - started } };
|
|
3262
|
+
} catch (e) {
|
|
3263
|
+
return {
|
|
3264
|
+
status: 200,
|
|
3265
|
+
body: { ok: false, error: e instanceof Error ? e.message : String(e) }
|
|
3266
|
+
};
|
|
3267
|
+
}
|
|
3268
|
+
});
|
|
3269
|
+
}
|
|
3270
|
+
|
|
3271
|
+
// packages/cli/dist/ui-api-documents.js
|
|
3272
|
+
var MAX_FILE = 15 * 1024 * 1024;
|
|
3273
|
+
function uiDocumentRoutes(r, ctx) {
|
|
3274
|
+
const root = ctx.root;
|
|
3275
|
+
const changed = (path) => ctx.events.publish({ type: "file.changed", path });
|
|
3276
|
+
return r.add("GET", "/api/knowledge/documents", async () => {
|
|
3277
|
+
const { listDocuments } = await import("./chunk-7URGP4DD.js");
|
|
3278
|
+
return { status: 200, body: api_exports.DocumentList.parse(await listDocuments(root)) };
|
|
3279
|
+
}).add("POST", "/api/knowledge/documents", async ({ body }) => {
|
|
3280
|
+
const b = api_exports.AttachFileRequest.safeParse(body ?? {});
|
|
3281
|
+
if (!b.success)
|
|
3282
|
+
throw badRequest("Send the file's name and its bytes in base64.");
|
|
3283
|
+
const bytes = new Uint8Array(Buffer.from(b.data.content, "base64"));
|
|
3284
|
+
if (bytes.length > MAX_FILE)
|
|
3285
|
+
throw new HttpError(413, "The file is too large", "Add a file of 15 MB or less.");
|
|
3286
|
+
const { addToInbox } = await import("./chunk-NNUASOAA.js");
|
|
3287
|
+
const res = await addToInbox(root, [{ name: b.data.name, bytes }]);
|
|
3288
|
+
if (!res.added.length)
|
|
3289
|
+
throw new HttpError(422, "Atticus can't add that", res.refused[0]?.why ?? "", {
|
|
3290
|
+
lines: res.refused.map((x) => `${x.name}: ${x.why}`)
|
|
3291
|
+
});
|
|
3292
|
+
for (const a of res.added)
|
|
3293
|
+
changed(a.path);
|
|
3294
|
+
return { status: 201, body: api_exports.AddDocumentResult.parse(res) };
|
|
3295
|
+
}, { maxBody: Math.ceil(MAX_FILE * 4 / 3) + 64 * 1024 }).add("POST", "/api/changes/{changeId}/inputs/{name}/promote", async ({ params }) => {
|
|
3296
|
+
const { AttachError, promoteChangeInput } = await import("./chunk-Y7KBQ4BD.js");
|
|
3297
|
+
try {
|
|
3298
|
+
const res = await promoteChangeInput(root, params.changeId, params.name);
|
|
3299
|
+
changed(res.path);
|
|
3300
|
+
return { status: 200, body: api_exports.PromoteResult.parse(res) };
|
|
3301
|
+
} catch (e) {
|
|
3302
|
+
if (e instanceof AttachError)
|
|
3303
|
+
throw e.code === "not-found" ? notFound(e.message) : new HttpError(422, "Atticus can't copy that", e.message);
|
|
3304
|
+
throw e;
|
|
3305
|
+
}
|
|
3306
|
+
});
|
|
3307
|
+
}
|
|
3308
|
+
|
|
3309
|
+
// packages/cli/dist/ui-api-repos.js
|
|
3310
|
+
function uiRepoRoutes(r, ctx) {
|
|
3311
|
+
const root = ctx.root;
|
|
3312
|
+
return r.add("GET", "/api/repos/branches", async () => {
|
|
3313
|
+
const { repoBranches } = await import("./chunk-YLAJAI2W.js");
|
|
3314
|
+
return { status: 200, body: api_exports.RepoBranchesList.parse(await repoBranches(root)) };
|
|
3315
|
+
}).add("GET", "/api/repos/compare", async ({ query }) => {
|
|
3316
|
+
const branch = query.get("branch")?.trim() ?? "";
|
|
3317
|
+
if (!branch)
|
|
3318
|
+
throw badRequest("Say which branch: ?branch=<name>.");
|
|
3319
|
+
const { compareBranch } = await import("./chunk-YLAJAI2W.js");
|
|
3320
|
+
const c = await compareBranch(root, branch);
|
|
3321
|
+
if (!c)
|
|
3322
|
+
throw notFound(`${branch} hasn't been scanned beside the baseline yet.`);
|
|
3323
|
+
return { status: 200, body: api_exports.BranchCompare.parse(c) };
|
|
3324
|
+
}).add("PUT", "/api/repos/{repoId}/branches", async ({ params, body }) => {
|
|
3325
|
+
const b = api_exports.EditRepoBranchesRequest.safeParse(body ?? {});
|
|
3326
|
+
if (!b.success)
|
|
3327
|
+
throw badRequest("Send defaultBranch and branches as branch names.");
|
|
3328
|
+
const { editRepoBranches, RepoEditError } = await import("./chunk-YLAJAI2W.js");
|
|
3329
|
+
try {
|
|
3330
|
+
const file = await editRepoBranches(root, params.repoId, b.data);
|
|
3331
|
+
ctx.events.publish({ type: "file.changed", path: file });
|
|
3332
|
+
return {
|
|
3333
|
+
status: 200,
|
|
3334
|
+
body: {
|
|
3335
|
+
file,
|
|
3336
|
+
note: `${file} changed: commit it in a pull request. Run atticus workspace sync if the default branch changed.`
|
|
3337
|
+
}
|
|
3338
|
+
};
|
|
3339
|
+
} catch (e) {
|
|
3340
|
+
if (e instanceof RepoEditError)
|
|
3341
|
+
throw e.code === "not-found" ? notFound(e.message) : badRequest(e.message);
|
|
3342
|
+
throw e;
|
|
3343
|
+
}
|
|
3344
|
+
}).add("PUT", "/api/repos/{repoId}/description", async ({ params, body }) => {
|
|
3345
|
+
const b = api_exports.EditRepoDescriptionRequest.safeParse(body ?? {});
|
|
3346
|
+
if (!b.success)
|
|
3347
|
+
throw badRequest("Send what the repo is as text, or accept: true to confirm the draft, or reject: true to drop it.");
|
|
3348
|
+
const { editRepoDescription, RepoEditError } = await import("./chunk-YLAJAI2W.js");
|
|
3349
|
+
try {
|
|
3350
|
+
const r2 = await editRepoDescription(root, params.repoId, b.data);
|
|
3351
|
+
ctx.events.publish({ type: "file.changed", path: r2.file });
|
|
3352
|
+
return {
|
|
3353
|
+
status: 200,
|
|
3354
|
+
body: {
|
|
3355
|
+
file: r2.file,
|
|
3356
|
+
...r2.description ? { description: r2.description } : {},
|
|
3357
|
+
note: r2.description ? `${r2.file} changed: commit it in a pull request. Every reading prompt, question and view of ${params.repoId} now says what it is.` : `${r2.file} changed: commit it in a pull request. Nothing says what ${params.repoId} is now.`
|
|
3358
|
+
}
|
|
3359
|
+
};
|
|
3360
|
+
} catch (e) {
|
|
3361
|
+
if (e instanceof RepoEditError)
|
|
3362
|
+
throw e.code === "not-found" ? notFound(e.message) : badRequest(e.message);
|
|
3363
|
+
throw e;
|
|
3364
|
+
}
|
|
3365
|
+
});
|
|
3366
|
+
}
|
|
3367
|
+
|
|
3368
|
+
// packages/cli/dist/ui-api-cockpit.js
|
|
3369
|
+
function uiCockpitRoutes(r, ctx) {
|
|
3370
|
+
return r.add("GET", "/api/changes/{changeId}/cockpit", async ({ params }) => {
|
|
3371
|
+
const { cockpit } = await import("./chunk-PKCMRPPB.js");
|
|
3372
|
+
try {
|
|
3373
|
+
return {
|
|
3374
|
+
status: 200,
|
|
3375
|
+
body: api_exports.ChangeCockpit.parse(await cockpit(ctx.root, params.changeId))
|
|
3376
|
+
};
|
|
3377
|
+
} catch (e) {
|
|
3378
|
+
if (e instanceof Error && e.name === "ChangeFileError")
|
|
3379
|
+
throw notFound(`There is no change ${params.changeId}.`);
|
|
3380
|
+
throw e;
|
|
3381
|
+
}
|
|
3382
|
+
}).add("GET", "/api/past-changes", async ({ query }) => {
|
|
3383
|
+
const { changeAboutText, similarChanges } = await import("./chunk-MF7QH4AX.js");
|
|
3384
|
+
const change = query.get("change");
|
|
3385
|
+
const text = change ? await changeAboutText(ctx.root, change) : query.get("text")?.trim();
|
|
3386
|
+
if (change && !text)
|
|
3387
|
+
throw notFound(`There is no change ${change}.`);
|
|
3388
|
+
if (!text)
|
|
3389
|
+
throw badRequest("Say what the new change is about: ?text=<its title or brief>, or ?change=<a change's id>.");
|
|
3390
|
+
const limit = Number(query.get("limit") ?? 5);
|
|
3391
|
+
return {
|
|
3392
|
+
status: 200,
|
|
3393
|
+
body: api_exports.SimilarChanges.parse(await similarChanges(ctx.root, text.slice(0, 2e4), {
|
|
3394
|
+
limit: Number.isInteger(limit) && limit > 0 && limit <= 20 ? limit : 5
|
|
3395
|
+
}))
|
|
3396
|
+
};
|
|
3397
|
+
}).add("POST", "/api/changes/{changeId}/like", async ({ params, body }) => {
|
|
3398
|
+
const b = api_exports.AttachPastChangeRequest.safeParse(body ?? {});
|
|
3399
|
+
if (!b.success)
|
|
3400
|
+
throw badRequest("Say which archived change: { like: <its folder or id> }.");
|
|
3401
|
+
const { attachPastChange, findPastChange } = await import("./chunk-MF7QH4AX.js");
|
|
3402
|
+
const folder = await findPastChange(ctx.root, b.data.like);
|
|
3403
|
+
if (!folder)
|
|
3404
|
+
throw notFound(`There is no archived change ${b.data.like}.`);
|
|
3405
|
+
const { AttachError } = await import("./chunk-Y7KBQ4BD.js");
|
|
3406
|
+
try {
|
|
3407
|
+
const name = await attachPastChange(ctx.root, params.changeId, folder, ctx.user);
|
|
3408
|
+
ctx.events.publish({ type: "file.changed", path: `inputs/${name}` });
|
|
3409
|
+
return {
|
|
3410
|
+
status: 201,
|
|
3411
|
+
body: api_exports.AttachPastChangeResult.parse({ like: folder, file: `inputs/${name}` })
|
|
3412
|
+
};
|
|
3413
|
+
} catch (e) {
|
|
3414
|
+
if (e instanceof AttachError)
|
|
3415
|
+
throw e.code === "not-found" ? notFound(e.message) : new HttpError(422, "Atticus can't attach that", e.message);
|
|
3416
|
+
throw e;
|
|
3417
|
+
}
|
|
3418
|
+
});
|
|
3419
|
+
}
|
|
3420
|
+
|
|
3421
|
+
// packages/cli/dist/ui-api-audit.js
|
|
3422
|
+
var REPO = /^[a-z0-9][a-z0-9._-]{0,99}$/i;
|
|
3423
|
+
var repoOf = (v) => {
|
|
3424
|
+
if (!REPO.test(v))
|
|
3425
|
+
throw badRequest(`${JSON.stringify(v)} isn't a repo name.`);
|
|
3426
|
+
return v;
|
|
3427
|
+
};
|
|
3428
|
+
async function itemsWithLines(root, repo, items) {
|
|
3429
|
+
const text = await auditLines(root, repo, items);
|
|
3430
|
+
return items.map((i) => ({
|
|
3431
|
+
...i,
|
|
3432
|
+
lines: i.cites.map((c) => {
|
|
3433
|
+
const t = text.get(c);
|
|
3434
|
+
return { cite: c, ...t ? { text: t.length > 300 ? `${t.slice(0, 297)}...` : t } : {} };
|
|
3435
|
+
})
|
|
3436
|
+
}));
|
|
3437
|
+
}
|
|
3438
|
+
var ofDraw = (audit, drawn) => {
|
|
3439
|
+
const byId = new Map(audit.items.map((i) => [i.id, i]));
|
|
3440
|
+
return drawn.map((d) => byId.get(d.id) ?? d);
|
|
3441
|
+
};
|
|
3442
|
+
function uiAuditRoutes(r, ctx, deps = {}) {
|
|
3443
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
3444
|
+
return r.add("GET", "/api/facts/{repo}/audit", async ({ params, query }) => {
|
|
3445
|
+
const repo = repoOf(params.repo);
|
|
3446
|
+
const seed = query.get("seed") || void 0;
|
|
3447
|
+
const sizeText = query.get("size");
|
|
3448
|
+
const size = sizeText ? Number(sizeText) : void 0;
|
|
3449
|
+
if (size !== void 0 && (!Number.isInteger(size) || size < 1 || size > 100))
|
|
3450
|
+
throw badRequest("size is a whole number from 1 to 100.");
|
|
3451
|
+
const p = await prepareAudit(ctx.root, repo, {
|
|
3452
|
+
...seed ? { seed } : {},
|
|
3453
|
+
...size ? { size } : {},
|
|
3454
|
+
now
|
|
3455
|
+
});
|
|
3456
|
+
const command = (s, n) => `atticus verify audit ${repo} --seed ${s} --size ${n}`;
|
|
3457
|
+
if ("refused" in p) {
|
|
3458
|
+
const body2 = {
|
|
3459
|
+
repo,
|
|
3460
|
+
file: `${factsRelOf(ctx.root)}/${repo}.audit.yaml`,
|
|
3461
|
+
seed: seed ?? "",
|
|
3462
|
+
size: size ?? 10,
|
|
3463
|
+
items: [],
|
|
3464
|
+
refused: p.refused,
|
|
3465
|
+
...p.unverified ? { unverified: p.unverified } : {},
|
|
3466
|
+
command: `atticus verify audit ${repo}`
|
|
3467
|
+
};
|
|
3468
|
+
return { status: 200, body: body2 };
|
|
3469
|
+
}
|
|
3470
|
+
const precision = await precisionOf(ctx.root, repo, p.audit, p.facts);
|
|
3471
|
+
const commit = [...new Set(p.facts.map((f) => f.commit))].sort()[0];
|
|
3472
|
+
const body = {
|
|
3473
|
+
repo,
|
|
3474
|
+
file: p.fileRel,
|
|
3475
|
+
seed: p.seed,
|
|
3476
|
+
size: p.size,
|
|
3477
|
+
defaultSeed: defaultAuditSeed(repo, commit),
|
|
3478
|
+
items: await itemsWithLines(ctx.root, repo, ofDraw(p.audit, p.drawn)),
|
|
3479
|
+
precision: { facts: precision.facts, links: precision.links },
|
|
3480
|
+
command: command(p.seed, p.size)
|
|
3481
|
+
};
|
|
3482
|
+
return { status: 200, body };
|
|
3483
|
+
}).add("POST", "/api/facts/{repo}/audit", async ({ params, body }) => {
|
|
3484
|
+
const repo = repoOf(params.repo);
|
|
3485
|
+
const b = api_exports.AuditAnswerRequest.safeParse(body ?? {});
|
|
3486
|
+
if (!b.success)
|
|
3487
|
+
throw badRequest("Say the draw and the answers: { seed, size, answers: [{ id, answer, note }] }.", {
|
|
3488
|
+
errors: b.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
|
|
3489
|
+
});
|
|
3490
|
+
const p = await prepareAudit(ctx.root, repo, {
|
|
3491
|
+
seed: b.data.seed,
|
|
3492
|
+
size: b.data.size,
|
|
3493
|
+
now
|
|
3494
|
+
});
|
|
3495
|
+
if ("refused" in p) {
|
|
3496
|
+
if (p.refused === "no-facts")
|
|
3497
|
+
throw notFound(p.lines.join(" "));
|
|
3498
|
+
throw new HttpError(422, "Atticus can't audit this repo yet", p.lines.join(" "));
|
|
3499
|
+
}
|
|
3500
|
+
const inDraw = new Set(p.drawn.map((d) => d.id));
|
|
3501
|
+
const outside = b.data.answers.filter((a) => !inDraw.has(a.id)).map((a) => a.id);
|
|
3502
|
+
if (outside.length)
|
|
3503
|
+
throw new HttpError(422, "Not in this sample", `Not in this sample: ${outside.join(", ")}. Answer the items the draw lists.`);
|
|
3504
|
+
const answers = new Map(b.data.answers.map((a) => [
|
|
3505
|
+
a.id,
|
|
3506
|
+
{ answer: a.answer, ...a.note ? { note: a.note } : {} }
|
|
3507
|
+
]));
|
|
3508
|
+
answerAudit(p.audit, answers, ctx.user, p.now);
|
|
3509
|
+
await writeAudit(ctx.root, repo, p.audit);
|
|
3510
|
+
ctx.events.publish({ type: "file.changed", path: p.fileRel });
|
|
3511
|
+
const precision = await precisionOf(ctx.root, repo, p.audit, p.facts);
|
|
3512
|
+
const result = {
|
|
3513
|
+
file: p.fileRel,
|
|
3514
|
+
answered: p.audit.items.filter((i) => i.answer).length,
|
|
3515
|
+
open: p.audit.items.filter((i) => !i.answer).length,
|
|
3516
|
+
items: await itemsWithLines(ctx.root, repo, ofDraw(p.audit, p.drawn)),
|
|
3517
|
+
precision: { facts: precision.facts, links: precision.links },
|
|
3518
|
+
commit: `git add ${p.fileRel} && git commit -m "${repo}: a person's sample audit answers"`
|
|
3519
|
+
};
|
|
3520
|
+
return { status: 200, body: result };
|
|
3521
|
+
});
|
|
3522
|
+
}
|
|
3523
|
+
|
|
3524
|
+
// packages/cli/dist/ui-api-outside.js
|
|
3525
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
3526
|
+
import { join as join9 } from "node:path";
|
|
3527
|
+
var SPECS = /* @__PURE__ */ new Set(["openapi", "swagger"]);
|
|
3528
|
+
var cmp = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
3529
|
+
var OUTSIDE_ADD_SPEC = (id) => [
|
|
3530
|
+
`atticus intake add <the ${id} spec file>`,
|
|
3531
|
+
"atticus intake list",
|
|
3532
|
+
"atticus triage"
|
|
3533
|
+
];
|
|
3534
|
+
async function outsideView(root) {
|
|
3535
|
+
const quiet = { out: () => void 0, err: () => void 0 };
|
|
3536
|
+
const { op, records, inputs } = await intakeContext(root, quiet);
|
|
3537
|
+
const systems = outsideProfileSystems(op);
|
|
3538
|
+
const estate = await estateFacts(root, op, "");
|
|
3539
|
+
const facts = /* @__PURE__ */ new Map();
|
|
3540
|
+
for (const r of estate.repos)
|
|
3541
|
+
for (const f of r.facts)
|
|
3542
|
+
facts.set(`${r.repo}\0${f.id}`, f);
|
|
3543
|
+
const joined = estateJoinOf(op, estate.repos, estate.targets);
|
|
3544
|
+
const repos = joinReposOf(op, estate.repos, componentRenames(estate.repos, op));
|
|
3545
|
+
const { maps: sharedMaps } = await readSharedMaps(root);
|
|
3546
|
+
const sharedJoin = sharedMaps.length ? joinSharedMaps({ outside: joined.outside ?? [], repos, maps: sharedMaps, systems }) : { matches: [], gaps: [], asks: [] };
|
|
3547
|
+
const specs = /* @__PURE__ */ new Map();
|
|
3548
|
+
for (const d of inputs.documents) {
|
|
3549
|
+
if (!d.id || d.supersededBy || !SPECS.has(d.classification))
|
|
3550
|
+
continue;
|
|
3551
|
+
const system = systems.find((s) => s.id === d.systems[0]);
|
|
3552
|
+
if (!system || specs.has(system.id))
|
|
3553
|
+
continue;
|
|
3554
|
+
const body = await readFile7(join9(inputsDirOf(root), ...d.path.split("/").slice(1)), "utf8");
|
|
3555
|
+
const spec = readVendorSpec(d.path, body, system.id);
|
|
3556
|
+
if (spec)
|
|
3557
|
+
specs.set(system.id, spec);
|
|
3558
|
+
}
|
|
3559
|
+
const vendors = systems.flatMap((s) => {
|
|
3560
|
+
const spec = specs.get(s.id);
|
|
3561
|
+
if (!spec)
|
|
3562
|
+
return [];
|
|
3563
|
+
return [
|
|
3564
|
+
{
|
|
3565
|
+
system: s.id,
|
|
3566
|
+
boundary: "internal",
|
|
3567
|
+
names: [s.name, ...s.aliases],
|
|
3568
|
+
hosts: [.../* @__PURE__ */ new Set([...spec.hosts, ...hostAliases(s.aliases)])],
|
|
3569
|
+
operations: spec.operations.map((o) => ({
|
|
3570
|
+
id: o.id,
|
|
3571
|
+
method: o.method,
|
|
3572
|
+
path: o.path,
|
|
3573
|
+
fullPath: o.fullPath,
|
|
3574
|
+
cite: o.cite
|
|
3575
|
+
}))
|
|
3576
|
+
}
|
|
3577
|
+
];
|
|
3578
|
+
});
|
|
3579
|
+
const matched = vendors.length ? joinVendorOperations({
|
|
3580
|
+
repos,
|
|
3581
|
+
vendors,
|
|
3582
|
+
targets: estate.targets,
|
|
3583
|
+
...joinConventions(op)
|
|
3584
|
+
}) : { matches: [], gaps: [], asks: [] };
|
|
3585
|
+
const catalog = records.length ? records : (await readCatalog(join9(catalogDirOf(root), "generated"), { cacheRoot: root })).records;
|
|
3586
|
+
const ifaces = catalog.filter((r) => r.kind === "Interface");
|
|
3587
|
+
const componentsOf = (id) => /* @__PURE__ */ new Set([
|
|
3588
|
+
id,
|
|
3589
|
+
...catalog.filter((r) => r.kind === "Component" && r.system === id).map((r) => r.id)
|
|
3590
|
+
]);
|
|
3591
|
+
const open = await listQuestions(root, { state: "open" }).then((l) => l.questions, () => []);
|
|
3592
|
+
const callOf = (e) => {
|
|
3593
|
+
const f = facts.get(`${e.repo}\0${e.fact}`);
|
|
3594
|
+
const x = f?.kind === "call" ? f : void 0;
|
|
3595
|
+
return {
|
|
3596
|
+
repo: e.repo,
|
|
3597
|
+
...e.component ? { component: e.component } : {},
|
|
3598
|
+
fact: e.fact,
|
|
3599
|
+
at: e.at,
|
|
3600
|
+
...x?.method ? { method: x.method.toUpperCase() } : {},
|
|
3601
|
+
...x?.path ? { path: x.path } : {}
|
|
3602
|
+
};
|
|
3603
|
+
};
|
|
3604
|
+
const out = systems.map((s) => {
|
|
3605
|
+
const names = new Set([s.id, s.name, ...s.aliases].map(factIdPart));
|
|
3606
|
+
const comps = componentsOf(s.id);
|
|
3607
|
+
const callers = /* @__PURE__ */ new Map();
|
|
3608
|
+
const add = (c, seen) => {
|
|
3609
|
+
const k = `${c.repo}|${c.at}`;
|
|
3610
|
+
const had = callers.get(k);
|
|
3611
|
+
if (had) {
|
|
3612
|
+
if (!had.seen.includes(seen))
|
|
3613
|
+
had.seen = [...had.seen, seen].sort();
|
|
3614
|
+
Object.assign(had, { ...c, ...had, seen: had.seen });
|
|
3615
|
+
} else
|
|
3616
|
+
callers.set(k, { ...c, seen: [seen] });
|
|
3617
|
+
};
|
|
3618
|
+
for (const o of joined.outside ?? [])
|
|
3619
|
+
if (names.has(factIdPart(o.target)))
|
|
3620
|
+
add(callOf(o.call), "join");
|
|
3621
|
+
for (const r of ifaces) {
|
|
3622
|
+
if (!comps.has(r.to))
|
|
3623
|
+
continue;
|
|
3624
|
+
for (const e of r.evidence)
|
|
3625
|
+
if (e.side !== "callee" && e.source !== "config" && e.repo && e.path && e.line !== void 0)
|
|
3626
|
+
add({
|
|
3627
|
+
repo: e.repo,
|
|
3628
|
+
component: r.from,
|
|
3629
|
+
at: `${e.path}:${e.line}`,
|
|
3630
|
+
...r.method ? { method: r.method } : {},
|
|
3631
|
+
path: r.normalizedPath,
|
|
3632
|
+
interface: r.id
|
|
3633
|
+
}, "interface");
|
|
3634
|
+
}
|
|
3635
|
+
for (const g of [...matched.gaps, ...matched.asks].filter((x) => x.vendor === s.id))
|
|
3636
|
+
add(callOf(g.call), "spec");
|
|
3637
|
+
for (const m of matched.matches.filter((x) => x.vendor === s.id)) {
|
|
3638
|
+
add({ ...callOf(m.call), operation: m.operation }, "spec");
|
|
3639
|
+
if (m.direct || !m.route)
|
|
3640
|
+
continue;
|
|
3641
|
+
const route = m.route;
|
|
3642
|
+
for (const j of joined.joins)
|
|
3643
|
+
if (j.entry.repo === route.repo && j.entry.fact === route.fact)
|
|
3644
|
+
add({
|
|
3645
|
+
...callOf(j.call),
|
|
3646
|
+
operation: m.operation,
|
|
3647
|
+
through: { repo: route.repo, at: route.at }
|
|
3648
|
+
}, "spec");
|
|
3649
|
+
}
|
|
3650
|
+
for (const m of sharedJoin.matches.filter((x) => x.system === s.id))
|
|
3651
|
+
add({
|
|
3652
|
+
...callOf(m.call),
|
|
3653
|
+
entry: { team: m.team, repo: m.entry.repo, fact: m.entry.fact, at: m.entry.at }
|
|
3654
|
+
}, "shared");
|
|
3655
|
+
for (const g of sharedJoin.gaps.filter((x) => x.system === s.id))
|
|
3656
|
+
add(callOf(g.call), "shared");
|
|
3657
|
+
const list = [...callers.values()].sort((a, b) => cmp(a.repo, b.repo) || cmp(a.at, b.at));
|
|
3658
|
+
const spec = specs.get(s.id);
|
|
3659
|
+
const map = sharedMaps.find((m) => m.system === s.id);
|
|
3660
|
+
const lines = new Set(list.map((c) => `${c.repo}|${c.at}`));
|
|
3661
|
+
const named = [s.id, s.name, ...s.aliases].map((n) => n.toLowerCase());
|
|
3662
|
+
const questions = open.filter((q) => named.some((n) => q.question.toLowerCase().includes(n)) || q.options.some((o) => named.some((n) => o.label.toLowerCase().includes(n))) || q.evidence.some((e) => lines.has(`${q.repo}|${e}`))).map((q) => ({ id: q.id, repo: q.repo, question: q.question })).sort((a, b) => cmp(a.id, b.id));
|
|
3663
|
+
return {
|
|
3664
|
+
id: s.id,
|
|
3665
|
+
name: s.name,
|
|
3666
|
+
boundary: s.boundary,
|
|
3667
|
+
...s.owner ? { owner: s.owner } : {},
|
|
3668
|
+
aliases: [...s.aliases],
|
|
3669
|
+
callers: list,
|
|
3670
|
+
...spec ? {
|
|
3671
|
+
spec: {
|
|
3672
|
+
path: spec.path,
|
|
3673
|
+
...spec.title ? { title: spec.title } : {},
|
|
3674
|
+
...spec.version ? { version: spec.version } : {},
|
|
3675
|
+
operations: spec.operations.map((o) => ({
|
|
3676
|
+
id: o.id,
|
|
3677
|
+
method: o.method,
|
|
3678
|
+
path: o.fullPath,
|
|
3679
|
+
cite: o.cite,
|
|
3680
|
+
calls: list.filter((c) => c.operation === o.id).length
|
|
3681
|
+
}))
|
|
3682
|
+
}
|
|
3683
|
+
} : {},
|
|
3684
|
+
unmatched: matched.gaps.filter((g) => g.vendor === s.id).map((g) => ({ ...callOf(g.call), seen: ["spec"], message: g.message })),
|
|
3685
|
+
ambiguous: matched.asks.filter((a) => a.vendor === s.id).map((a) => ({ ...callOf(a.call), seen: ["spec"], candidates: a.candidates })),
|
|
3686
|
+
questions,
|
|
3687
|
+
...spec ? {} : { addSpec: OUTSIDE_ADD_SPEC(s.id) },
|
|
3688
|
+
...map ? {
|
|
3689
|
+
shared: {
|
|
3690
|
+
team: map.team,
|
|
3691
|
+
name: map.name,
|
|
3692
|
+
commit: map.commit,
|
|
3693
|
+
importedAt: map.file.importedAt,
|
|
3694
|
+
entries: map.repos.flatMap((r) => r.facts.flatMap((f) => f.kind === "entry" ? [
|
|
3695
|
+
{
|
|
3696
|
+
repo: r.repo,
|
|
3697
|
+
component: factIdPart(f.component ?? r.component ?? r.repo),
|
|
3698
|
+
fact: f.id,
|
|
3699
|
+
...f.method ? { method: f.method.toUpperCase() } : {},
|
|
3700
|
+
path: f.path,
|
|
3701
|
+
at: `${f.cite.file}:${f.cite.line}`,
|
|
3702
|
+
...f.commit ? { commit: f.commit } : {},
|
|
3703
|
+
calls: list.filter((c) => c.entry?.repo === r.repo && c.entry.fact === f.id).length
|
|
3704
|
+
}
|
|
3705
|
+
] : [])).sort((a, b) => cmp(a.repo, b.repo) || cmp(a.at, b.at)),
|
|
3706
|
+
unmatched: sharedJoin.gaps.filter((g) => g.system === s.id).map((g) => ({ ...callOf(g.call), seen: ["shared"], message: g.message }))
|
|
3707
|
+
}
|
|
3708
|
+
} : {}
|
|
3709
|
+
};
|
|
3710
|
+
});
|
|
3711
|
+
return { systems: out, notes: estate.notes };
|
|
3712
|
+
}
|
|
3713
|
+
function uiOutsideRoutes(r, ctx) {
|
|
3714
|
+
return r.add("GET", "/api/outside", async () => ({
|
|
3715
|
+
status: 200,
|
|
3716
|
+
body: await outsideView(ctx.root)
|
|
3717
|
+
}));
|
|
3718
|
+
}
|
|
3719
|
+
|
|
3720
|
+
// packages/cli/dist/ui-api-stages.js
|
|
3721
|
+
import { stat as stat4 } from "node:fs/promises";
|
|
3722
|
+
import { join as join10 } from "node:path";
|
|
3723
|
+
var ID2 = /^[a-z0-9][a-z0-9._-]{0,99}$/;
|
|
3724
|
+
function findingOptions(f) {
|
|
3725
|
+
const open = f.state === "open";
|
|
3726
|
+
const issue = f.kind === "issue";
|
|
3727
|
+
return {
|
|
3728
|
+
fix: f.fix.text,
|
|
3729
|
+
apply: open && issue,
|
|
3730
|
+
dismiss: open && issue,
|
|
3731
|
+
waive: open && issue && f.severity === "blocking" && f.waivable,
|
|
3732
|
+
...open && f.question ? { ask: { text: f.question.text, owner: f.question.owner } } : {}
|
|
3733
|
+
};
|
|
3734
|
+
}
|
|
3735
|
+
var exists2 = (p) => stat4(p).then(() => true, () => false);
|
|
3736
|
+
var slashOf = (stage, change) => `/atticus-${stage} ${change}`;
|
|
3737
|
+
async function gateOf(root, change, stage) {
|
|
3738
|
+
const problems = [];
|
|
3739
|
+
let gate;
|
|
3740
|
+
try {
|
|
3741
|
+
gate = stageGate(await loadChangeContext(root), stage);
|
|
3742
|
+
} catch (e) {
|
|
3743
|
+
problems.push({
|
|
3744
|
+
file: "org-pack",
|
|
3745
|
+
message: `The org pack doesn't load, so the stage's gate isn't known: ${String(e.message ?? e).split("\n")[0]}`
|
|
3746
|
+
});
|
|
3747
|
+
}
|
|
3748
|
+
if (!gate && (await reviewedGates(root, change)).includes(STAGE_PHASE[stage]))
|
|
3749
|
+
gate = STAGE_PHASE[stage];
|
|
3750
|
+
return { gate, problems };
|
|
3751
|
+
}
|
|
3752
|
+
async function latest(root, change, gate) {
|
|
3753
|
+
if (!gate)
|
|
3754
|
+
return { round: null, findings: [], bad: [] };
|
|
3755
|
+
const rounds = await roundsOf(root, change, gate);
|
|
3756
|
+
const round = rounds.at(-1);
|
|
3757
|
+
if (round === void 0)
|
|
3758
|
+
return { round: null, findings: [], bad: [] };
|
|
3759
|
+
const r = await readRound(root, change, gate, round);
|
|
3760
|
+
return { round, findings: r.findings, bad: r.bad };
|
|
3761
|
+
}
|
|
3762
|
+
var view = (c) => ({
|
|
3763
|
+
question: c.question,
|
|
3764
|
+
text: c.text,
|
|
3765
|
+
by: c.by,
|
|
3766
|
+
at: c.at,
|
|
3767
|
+
read: c.read
|
|
3768
|
+
});
|
|
3769
|
+
async function stageReviewView(root, change, stage) {
|
|
3770
|
+
const rel = `${changesRelOf(root)}/${change}`;
|
|
3771
|
+
const { gate, problems } = await gateOf(root, change, stage);
|
|
3772
|
+
const l = await latest(root, change, gate);
|
|
3773
|
+
for (const b of l.bad)
|
|
3774
|
+
problems.push({ file: `${rel}/${b.path}`, message: b.message });
|
|
3775
|
+
const comments = await stageComments(root, change, stage);
|
|
3776
|
+
const { loop, present, file } = await readStageLoop(changeFolder(root, change), change, stage);
|
|
3777
|
+
const order = { blocking: 0, gap: 1, risk: 2, idea: 3 };
|
|
3778
|
+
const proposals = gate && l.round !== null ? await readProposals(root, change, gate, l.round) : [];
|
|
3779
|
+
const findings = l.findings.map((s) => {
|
|
3780
|
+
const fileRel = `${rel}/${s.path}`;
|
|
3781
|
+
const p = s.finding.state === "open" ? proposals.find((x) => x.finding === s.finding.id) : void 0;
|
|
3782
|
+
return {
|
|
3783
|
+
finding: s.finding,
|
|
3784
|
+
file: fileRel,
|
|
3785
|
+
options: findingOptions(s.finding),
|
|
3786
|
+
comments: comments.comments.filter((c) => c.about === fileRel).map(view),
|
|
3787
|
+
...p ? {
|
|
3788
|
+
proposal: {
|
|
3789
|
+
claim: p.claim,
|
|
3790
|
+
by: p.by,
|
|
3791
|
+
at: p.at,
|
|
3792
|
+
...p.reason ? { reason: p.reason } : {}
|
|
3793
|
+
}
|
|
3794
|
+
} : {}
|
|
3795
|
+
};
|
|
3796
|
+
}).sort((a, b) => Number(a.finding.state !== "open") - Number(b.finding.state !== "open") || order[a.finding.severity] - order[b.finding.severity] || a.finding.id.localeCompare(b.finding.id));
|
|
3797
|
+
const questions = [];
|
|
3798
|
+
for (const id of comments.others) {
|
|
3799
|
+
const q = await readQuestion(root, id).catch((e) => {
|
|
3800
|
+
problems.push({ file: `questions/${id}.yaml`, message: String(e.message ?? e) });
|
|
3801
|
+
return void 0;
|
|
3802
|
+
});
|
|
3803
|
+
if (q && q.state !== "retired")
|
|
3804
|
+
questions.push(q);
|
|
3805
|
+
}
|
|
3806
|
+
questions.sort((a, b) => Number(a.state !== "open") - Number(b.state !== "open"));
|
|
3807
|
+
return {
|
|
3808
|
+
change,
|
|
3809
|
+
stage,
|
|
3810
|
+
gate: gate ?? null,
|
|
3811
|
+
round: l.round,
|
|
3812
|
+
loop: {
|
|
3813
|
+
file: `${rel}/${file}`,
|
|
3814
|
+
present,
|
|
3815
|
+
...present ? { state: loop.state, iteration: loop.iteration } : {}
|
|
3816
|
+
},
|
|
3817
|
+
findings,
|
|
3818
|
+
questions,
|
|
3819
|
+
next: { command: rerunOf(stage, change), slash: slashOf(stage, change) },
|
|
3820
|
+
problems
|
|
3821
|
+
};
|
|
3822
|
+
}
|
|
3823
|
+
function tabOf(v) {
|
|
3824
|
+
const r = api_exports.StageTab.safeParse(v);
|
|
3825
|
+
if (!r.success)
|
|
3826
|
+
throw badRequest(`${JSON.stringify(v)} isn't a stage: feature, architecture or stories.`);
|
|
3827
|
+
return r.data;
|
|
3828
|
+
}
|
|
3829
|
+
function uiStageRoutes(r, ctx, deps = {}) {
|
|
3830
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
3831
|
+
const changeOf = async (v) => {
|
|
3832
|
+
if (!ID2.test(v))
|
|
3833
|
+
throw badRequest(`${JSON.stringify(v)} isn't a change id.`);
|
|
3834
|
+
if (!await exists2(join10(changeFolder(ctx.root, v), "change.yaml")))
|
|
3835
|
+
throw notFound(`There is no change ${v}.`);
|
|
3836
|
+
return v;
|
|
3837
|
+
};
|
|
3838
|
+
return r.add("GET", "/api/changes/{changeId}/stages/{stage}", async ({ params }) => {
|
|
3839
|
+
const stage = tabOf(params.stage);
|
|
3840
|
+
const change = await changeOf(params.changeId);
|
|
3841
|
+
return { status: 200, body: await stageReviewView(ctx.root, change, stage) };
|
|
3842
|
+
}).add("POST", "/api/changes/{changeId}/stages/{stage}/comments", async ({ params, body }) => {
|
|
3843
|
+
const stage = tabOf(params.stage);
|
|
3844
|
+
const change = await changeOf(params.changeId);
|
|
3845
|
+
const b = api_exports.StageCommentRequest.safeParse(body ?? {});
|
|
3846
|
+
if (!b.success)
|
|
3847
|
+
throw badRequest("Say which finding and write the comment: { finding, text }.", {
|
|
3848
|
+
errors: b.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
|
|
3849
|
+
});
|
|
3850
|
+
const v = await stageReviewView(ctx.root, change, stage);
|
|
3851
|
+
const hits = v.findings.filter((f2) => f2.finding.id === b.data.finding && (!b.data.gate || f2.finding.gate === b.data.gate));
|
|
3852
|
+
if (!hits.length)
|
|
3853
|
+
throw notFound(`The ${stage} stage of ${change} has no finding ${b.data.finding} in ${v.gate ? `the ${v.gate} gate's latest round` : "any gate"}.`);
|
|
3854
|
+
const f = hits[0];
|
|
3855
|
+
let written;
|
|
3856
|
+
try {
|
|
3857
|
+
written = await commentOnFinding(ctx.root, {
|
|
3858
|
+
change,
|
|
3859
|
+
stage,
|
|
3860
|
+
finding: {
|
|
3861
|
+
id: f.finding.id,
|
|
3862
|
+
rule: f.finding.rule,
|
|
3863
|
+
summary: f.finding.summary,
|
|
3864
|
+
artifact: f.finding.artifact,
|
|
3865
|
+
...f.finding.locator.line ? { line: f.finding.locator.line } : {},
|
|
3866
|
+
file: f.file
|
|
3867
|
+
},
|
|
3868
|
+
text: b.data.text,
|
|
3869
|
+
by: ctx.user,
|
|
3870
|
+
at: now().toISOString()
|
|
3871
|
+
});
|
|
3872
|
+
} catch (e) {
|
|
3873
|
+
if (e instanceof PlannerError && e.code === "refused")
|
|
3874
|
+
throw conflict(e.message);
|
|
3875
|
+
if (e instanceof PlannerError)
|
|
3876
|
+
throw new HttpError(422, "Atticus refused", e.message);
|
|
3877
|
+
throw e;
|
|
3878
|
+
}
|
|
3879
|
+
for (const p of written.files)
|
|
3880
|
+
ctx.events.publish({ type: "file.changed", path: p });
|
|
3881
|
+
const q = written.question;
|
|
3882
|
+
const result = {
|
|
3883
|
+
comment: {
|
|
3884
|
+
question: q.id,
|
|
3885
|
+
text: q.answer.text,
|
|
3886
|
+
by: q.answer.by,
|
|
3887
|
+
at: q.answer.at,
|
|
3888
|
+
read: false
|
|
3889
|
+
},
|
|
3890
|
+
files: written.files,
|
|
3891
|
+
commit: `git add ${written.files.join(" ")} && git commit -m "${change}: a comment on ${f.finding.id}"`,
|
|
3892
|
+
next: v.next
|
|
3893
|
+
};
|
|
3894
|
+
return { status: 200, body: result };
|
|
3895
|
+
});
|
|
3896
|
+
}
|
|
3897
|
+
|
|
3898
|
+
// packages/cli/dist/ui-commands.js
|
|
3899
|
+
function catalogMayHaveChanged(root, path) {
|
|
3900
|
+
const p = path.replace(/^\.\/?/, "").replace(/\/+$/, "");
|
|
3901
|
+
const near = (dir) => {
|
|
3902
|
+
const rel = relative2(root, dir).split(sep2).join("/");
|
|
3903
|
+
if (rel.startsWith("..") || isAbsolute(rel))
|
|
3904
|
+
return true;
|
|
3905
|
+
return p === "" || p === rel || p.startsWith(rel + "/") || rel.startsWith(p + "/");
|
|
3906
|
+
};
|
|
3907
|
+
return near(catalogDirOf(root)) || near(orgPackDirOf(root));
|
|
3908
|
+
}
|
|
3909
|
+
async function startUi(root, o = {}, deps = {}) {
|
|
3910
|
+
const srv = await startServer({
|
|
3911
|
+
root,
|
|
3912
|
+
port: o.port ?? DEFAULT_PORT,
|
|
3913
|
+
...o.embed ? { embed: o.embed } : {},
|
|
3914
|
+
...o.full ? { full: true } : {},
|
|
3915
|
+
...o.token ? { token: o.token } : {},
|
|
3916
|
+
staticDir: o.staticDir ?? uiDistDir(),
|
|
3917
|
+
...o.user ? { user: o.user } : {},
|
|
3918
|
+
...o.version ? { version: o.version } : {},
|
|
3919
|
+
onRequest: (() => {
|
|
3920
|
+
const log = createLog(root);
|
|
3921
|
+
return (r) => {
|
|
3922
|
+
const fields = { method: r.method, path: r.path, status: r.status, ms: r.ms };
|
|
3923
|
+
if (r.error)
|
|
3924
|
+
log.error("request", r.error, fields);
|
|
3925
|
+
else if (r.status >= 500)
|
|
3926
|
+
log.warn("request", fields);
|
|
3927
|
+
else
|
|
3928
|
+
log.debug("request", fields);
|
|
3929
|
+
};
|
|
3930
|
+
})(),
|
|
3931
|
+
extend: (r, ctx) => {
|
|
3932
|
+
const base = uiKnowRoutes(uiRunRoutes(uiRoutes(r, ctx, deps), ctx, deps), ctx, {
|
|
3933
|
+
warm: o.warm === true
|
|
3934
|
+
});
|
|
3935
|
+
const views = uiGraphRoutes(uiAcrossRoutes(uiMeRoutes(base, ctx), ctx), ctx);
|
|
3936
|
+
const specs = uiSpecRoutes(uiOnboardingRoutes(uiQuestionRoutes(views, ctx), ctx), ctx);
|
|
3937
|
+
return uiStageRoutes(uiOutsideRoutes(uiAuditRoutes(uiFactsRoutes(uiTrustRoutes(uiCockpitRoutes(uiRepoRoutes(uiDocumentRoutes(uiAiRoutes(uiHistoryRoutes(uiModelRunRoutes(uiKnowledgeRoutes(uiCiteRoutes(specs, ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx), ctx);
|
|
3938
|
+
}
|
|
3939
|
+
});
|
|
3940
|
+
keepCatalogsInMemory(true);
|
|
3941
|
+
void readCatalog(join11(catalogDirOf(root), "generated"), { cacheRoot: root }).catch(() => void 0);
|
|
3942
|
+
const stop = srv.events.listen((e) => {
|
|
3943
|
+
const changed = e.type === "file.changed" && (typeof e.path !== "string" || catalogMayHaveChanged(root, e.path));
|
|
3944
|
+
if (changed || e.type === "index.rebuilt")
|
|
3945
|
+
forgetCatalogs();
|
|
3946
|
+
});
|
|
3947
|
+
const close = srv.close;
|
|
3948
|
+
return Object.assign(srv, {
|
|
3949
|
+
close: async () => {
|
|
3950
|
+
stop();
|
|
3951
|
+
keepCatalogsInMemory(false);
|
|
3952
|
+
await close();
|
|
3953
|
+
}
|
|
3954
|
+
});
|
|
3955
|
+
}
|
|
3956
|
+
function openBrowser(url) {
|
|
3957
|
+
const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
3958
|
+
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
3959
|
+
try {
|
|
3960
|
+
spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
|
|
3961
|
+
} catch {
|
|
3962
|
+
}
|
|
3963
|
+
}
|
|
3964
|
+
function untilStopped(embedded, stdin = process.stdin, o = {}) {
|
|
3965
|
+
return new Promise((resolve2) => {
|
|
3966
|
+
const alive = o.alive ?? ((pid) => {
|
|
3967
|
+
try {
|
|
3968
|
+
process.kill(pid, 0);
|
|
3969
|
+
return true;
|
|
3970
|
+
} catch (e) {
|
|
3971
|
+
return e.code === "EPERM";
|
|
3972
|
+
}
|
|
3973
|
+
});
|
|
3974
|
+
const watch = o.parent !== void 0 ? setInterval(() => {
|
|
3975
|
+
if (!alive(o.parent))
|
|
3976
|
+
stop();
|
|
3977
|
+
}, o.everyMs ?? 2e3) : void 0;
|
|
3978
|
+
const stop = () => {
|
|
3979
|
+
if (watch)
|
|
3980
|
+
clearInterval(watch);
|
|
3981
|
+
process.off("SIGINT", stop);
|
|
3982
|
+
process.off("SIGTERM", stop);
|
|
3983
|
+
if (embedded) {
|
|
3984
|
+
stdin.off("end", stop);
|
|
3985
|
+
stdin.off("close", stop);
|
|
3986
|
+
stdin.pause();
|
|
3987
|
+
}
|
|
3988
|
+
resolve2();
|
|
3989
|
+
};
|
|
3990
|
+
process.once("SIGINT", stop);
|
|
3991
|
+
process.once("SIGTERM", stop);
|
|
3992
|
+
if (embedded) {
|
|
3993
|
+
stdin.once("end", stop);
|
|
3994
|
+
stdin.once("close", stop);
|
|
3995
|
+
stdin.on("data", () => void 0);
|
|
3996
|
+
stdin.resume();
|
|
3997
|
+
}
|
|
3998
|
+
});
|
|
3999
|
+
}
|
|
4000
|
+
function startedLines(url, token, o) {
|
|
4001
|
+
return [
|
|
4002
|
+
`Atticus is running at ${url} (Ctrl+C stops it).`,
|
|
4003
|
+
o.embed || o.parent !== void 0 ? "The session token isn't printed: the editor that started the app has it." : o.showToken ? `For assistants and scripts, the session token is ${token}` : "The page has the session token; --show-token prints it for an assistant or a script."
|
|
4004
|
+
];
|
|
4005
|
+
}
|
|
4006
|
+
async function uiCommand(root, o, io) {
|
|
4007
|
+
const given = o.embed ? process.env.ATTICUS_UI_TOKEN : void 0;
|
|
4008
|
+
delete process.env.ATTICUS_UI_TOKEN;
|
|
4009
|
+
let srv;
|
|
4010
|
+
try {
|
|
4011
|
+
srv = await startUi(root, {
|
|
4012
|
+
...given ? { token: given } : {},
|
|
4013
|
+
warm: true,
|
|
4014
|
+
...o.full ? { full: true } : {},
|
|
4015
|
+
...o.port !== void 0 ? { port: o.port } : {},
|
|
4016
|
+
...o.user ? { user: o.user } : {},
|
|
4017
|
+
...o.version ? { version: o.version } : {},
|
|
4018
|
+
...o.embed ? { embed: o.embed } : {}
|
|
4019
|
+
});
|
|
4020
|
+
} catch (e) {
|
|
4021
|
+
const code = e.code;
|
|
4022
|
+
io.err(code === "EADDRINUSE" ? `Port ${o.port ?? DEFAULT_PORT} is in use. Start with --port <another port>.` : `Couldn't start the app: ${e instanceof Error ? e.message : String(e)}`);
|
|
4023
|
+
return 1;
|
|
4024
|
+
}
|
|
4025
|
+
for (const l of startedLines(srv.url, srv.token, o))
|
|
4026
|
+
io.out(l);
|
|
4027
|
+
const p = srv.person;
|
|
4028
|
+
io.out(p.source === "git" ? `You are ${p.handle}${p.name ? ` (${p.name})` : ""}, from your Git user.` : `You are ${p.handle}.`);
|
|
4029
|
+
if (p.warning)
|
|
4030
|
+
io.err(p.warning);
|
|
4031
|
+
if (o.open)
|
|
4032
|
+
openBrowser(srv.url);
|
|
4033
|
+
await untilStopped(o.embed === "vscode", process.stdin, o.parent !== void 0 ? { parent: o.parent } : {});
|
|
4034
|
+
await srv.close();
|
|
4035
|
+
io.out("Stopped.");
|
|
4036
|
+
return 0;
|
|
4037
|
+
}
|
|
4038
|
+
export {
|
|
4039
|
+
catalogMayHaveChanged,
|
|
4040
|
+
startUi,
|
|
4041
|
+
startedLines,
|
|
4042
|
+
uiCommand,
|
|
4043
|
+
untilStopped
|
|
4044
|
+
};
|