agentic-qe 3.12.2 → 3.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/helpers/.helpers-version +1 -1
- package/.claude/helpers/auto-memory-hook.mjs +430 -430
- package/.claude/helpers/helpers.manifest.json +6 -5
- package/.claude/helpers/hook-handler.cjs +565 -311
- package/.claude/helpers/intelligence.cjs +1058 -1058
- package/.claude/helpers/statusline.cjs +995 -682
- package/.claude/skills/qe-court/SKILL.md +186 -0
- package/.claude/skills/qe-court/config.json +27 -0
- package/.claude/skills/qe-court/evals/fixtures/seeded-mutant-delivery/README.md +38 -0
- package/.claude/skills/qe-court/evals/fixtures/seeded-mutant-delivery/budget.test.ts +21 -0
- package/.claude/skills/qe-court/evals/fixtures/seeded-mutant-delivery/budget.ts +25 -0
- package/.claude/skills/qe-court/evals/qe-court.yaml +70 -0
- package/.claude/skills/qe-court/schemas/output.json +92 -0
- package/.claude/skills/qe-court/scripts/validate-config.json +30 -0
- package/.claude/skills/skills-manifest.json +9 -8
- package/CHANGELOG.md +104 -0
- package/README.md +3 -3
- package/assets/skills/qe-court/SKILL.md +186 -0
- package/assets/skills/qe-court/config.json +27 -0
- package/assets/skills/qe-court/evals/fixtures/seeded-mutant-delivery/README.md +38 -0
- package/assets/skills/qe-court/evals/fixtures/seeded-mutant-delivery/budget.test.ts +21 -0
- package/assets/skills/qe-court/evals/fixtures/seeded-mutant-delivery/budget.ts +25 -0
- package/assets/skills/qe-court/evals/qe-court.yaml +70 -0
- package/assets/skills/qe-court/schemas/output.json +92 -0
- package/assets/skills/qe-court/scripts/validate-config.json +30 -0
- package/assets/skills/skills-manifest.json +9 -8
- package/dist/cli/bundle.js +5 -5
- package/dist/cli/chunks/adapter-CWIMFWAI.js +2 -0
- package/dist/cli/chunks/{agent-booster-wasm-3KYNICBC.js → agent-booster-wasm-ACX2DK7Y.js} +2 -2
- package/dist/cli/chunks/{agent-handler-NGPTMI6E.js → agent-handler-B45WP4ZJ.js} +2 -2
- package/dist/cli/chunks/{agent-memory-branch-GNTQKQMG.js → agent-memory-branch-QQLZDE56.js} +2 -2
- package/dist/cli/chunks/aqe-learning-engine-OTXT2DLN.js +2 -0
- package/dist/cli/chunks/{arena-HQEP2NWI.js → arena-G3IKZAG5.js} +4 -4
- package/dist/cli/chunks/{arena-PA7VIYD4.js → arena-SDB7723R.js} +2 -2
- package/dist/cli/chunks/{audit-XSDM57GC.js → audit-GY46XUZA.js} +3 -3
- package/dist/cli/chunks/base-O3SPSWBA.js +2 -0
- package/dist/cli/chunks/{hnswlib-node-BCASOCNU.js → better-sqlite3-DUZ6FFAH.js} +2 -2
- package/dist/cli/chunks/brain-handler-347CYMPN.js +89 -0
- package/dist/cli/chunks/{branch-enumerator-ZW7RNMUF.js → branch-enumerator-Z3ULBFSM.js} +2 -2
- package/dist/cli/chunks/{browser-4LU22CPP.js → browser-OYEIHD7F.js} +2 -2
- package/dist/cli/chunks/browser-workflow-MXS3ZKY2.js +2 -0
- package/dist/cli/chunks/{chunk-CKFZOPZN.js → chunk-2DIT5AUU.js} +2 -2
- package/dist/cli/chunks/{chunk-RXH2TC2K.js → chunk-2NRZYQSF.js} +2 -2
- package/dist/cli/chunks/chunk-2VQVEQS4.js +153 -0
- package/dist/cli/chunks/{chunk-KDGLNJPB.js → chunk-32ZGV4Q4.js} +1 -1
- package/dist/cli/chunks/{chunk-327YEKBU.js → chunk-34PEINFS.js} +2 -2
- package/dist/cli/chunks/{chunk-CTFW7CMI.js → chunk-3GSYD5SL.js} +2 -2
- package/dist/cli/chunks/{chunk-YRO7HNS4.js → chunk-3JVUWKCO.js} +2 -2
- package/dist/cli/chunks/{chunk-TERRRQVY.js → chunk-3K4SF57Q.js} +1 -1
- package/dist/cli/chunks/{chunk-6N62KS3C.js → chunk-3NOCK6ED.js} +2 -2
- package/dist/cli/chunks/{chunk-7DV42KFP.js → chunk-3UHEZ5HZ.js} +2 -2
- package/dist/cli/chunks/{chunk-N57BKUVE.js → chunk-4BDLD7V2.js} +1 -1
- package/dist/cli/chunks/{chunk-5SM57X4D.js → chunk-4BESADKE.js} +2 -2
- package/dist/cli/chunks/{chunk-GCQATSMK.js → chunk-54MFKCLT.js} +2 -2
- package/dist/cli/chunks/{chunk-GUGUYFU5.js → chunk-56WZFXTQ.js} +2 -2
- package/dist/cli/chunks/{chunk-OELLHV7S.js → chunk-5EIOQCGN.js} +2 -2
- package/dist/cli/chunks/{chunk-Z63IOTDW.js → chunk-5FLTZ3KT.js} +2 -2
- package/dist/cli/chunks/{chunk-EKTWIEEO.js → chunk-5ICQL42E.js} +2 -2
- package/dist/cli/chunks/{chunk-VQU2JXTK.js → chunk-5KAT6WBQ.js} +2 -2
- package/dist/cli/chunks/{chunk-VHYACXLN.js → chunk-6AOBLBR4.js} +2 -2
- package/dist/cli/chunks/{chunk-Z3FA5KR6.js → chunk-6DTOGSDV.js} +4 -4
- package/dist/cli/chunks/{chunk-SYLTJKN2.js → chunk-6SXKCIN5.js} +2 -2
- package/dist/cli/chunks/{chunk-MF4FULAI.js → chunk-7DMXCCSD.js} +1 -1
- package/dist/cli/chunks/{chunk-BOR5MME3.js → chunk-7UNY32PU.js} +1 -1
- package/dist/cli/chunks/{chunk-T6TQPF5P.js → chunk-7YXT6BXN.js} +3 -3
- package/dist/cli/chunks/{chunk-ADZTWGEB.js → chunk-A4QLGPYO.js} +2 -2
- package/dist/cli/chunks/{chunk-RFQIC6ZF.js → chunk-A7GLWWSE.js} +1 -1
- package/dist/cli/chunks/{chunk-WC5PM33J.js → chunk-ANIDFJCT.js} +3 -3
- package/dist/cli/chunks/{chunk-TFVWUR2U.js → chunk-ANYYTWOQ.js} +2 -2
- package/dist/cli/chunks/{chunk-YZ44FUVJ.js → chunk-AO7AHX6K.js} +2 -2
- package/dist/cli/chunks/{chunk-PHIEJAQK.js → chunk-BCKTBGDM.js} +3 -3
- package/dist/cli/chunks/{chunk-GECWGFRR.js → chunk-BGYLNCL6.js} +2 -2
- package/dist/cli/chunks/{chunk-PXKVW6PT.js → chunk-BOVH6WSM.js} +2 -2
- package/dist/cli/chunks/{chunk-P65S7AZA.js → chunk-BUGWPTMF.js} +2 -2
- package/dist/cli/chunks/{chunk-RXEQB4OT.js → chunk-CFVEDUTW.js} +2 -2
- package/dist/cli/chunks/{chunk-HNU32LN3.js → chunk-CPH5JP4L.js} +1 -1
- package/dist/cli/chunks/{chunk-LIAW5FD7.js → chunk-CQFT7H3F.js} +2 -2
- package/dist/cli/chunks/{chunk-WY5CX3VH.js → chunk-CWUBOLKY.js} +2 -2
- package/dist/cli/chunks/{chunk-X47GHZY3.js → chunk-CYL2FV23.js} +2 -2
- package/dist/cli/chunks/{chunk-W7LBT3BF.js → chunk-D3MPV5G7.js} +2 -2
- package/dist/cli/chunks/{chunk-7KMYBRKJ.js → chunk-DRIJYTSK.js} +2 -2
- package/dist/cli/chunks/{chunk-BDVOXGY5.js → chunk-DYUC7WFA.js} +1 -1
- package/dist/cli/chunks/{chunk-ZBYMF2QJ.js → chunk-E3B5XTXJ.js} +1 -1
- package/dist/cli/chunks/{chunk-TMQ3S7HR.js → chunk-EECM5XKY.js} +1 -1
- package/dist/cli/chunks/{chunk-7OD6HFCP.js → chunk-EFRESRLB.js} +3 -3
- package/dist/cli/chunks/{chunk-HU6ETNA7.js → chunk-ES7WCYOU.js} +1 -1
- package/dist/cli/chunks/chunk-FAQLI3P5.js +2 -0
- package/dist/cli/chunks/{chunk-K43YXI7C.js → chunk-FBBZHATF.js} +2 -2
- package/dist/cli/chunks/{chunk-2R7J4ZGT.js → chunk-FL3BM54K.js} +1 -1
- package/dist/cli/chunks/{chunk-KBXW7S3W.js → chunk-FQLWQWRU.js} +3 -3
- package/dist/cli/chunks/{chunk-LWVUNBZO.js → chunk-FQY3UTGA.js} +2 -2
- package/dist/cli/chunks/{chunk-2L7K2J2T.js → chunk-FUU32QY5.js} +2 -2
- package/dist/cli/chunks/{chunk-3LELACAQ.js → chunk-G4GTE3RJ.js} +2 -2
- package/dist/cli/chunks/{chunk-APVXTHTA.js → chunk-G5DFYX3J.js} +3 -3
- package/dist/cli/chunks/chunk-G7FMYFTN.js +2 -0
- package/dist/cli/chunks/{chunk-CMKCPRWX.js → chunk-GFBLWFRV.js} +1 -1
- package/dist/cli/chunks/{chunk-5YLY2WT2.js → chunk-GJMZR7WU.js} +3 -3
- package/dist/cli/chunks/{chunk-R6LDR3IO.js → chunk-GTUFX7AR.js} +2 -2
- package/dist/cli/chunks/{chunk-5CGTBAVF.js → chunk-GUEZQOHL.js} +1 -1
- package/dist/cli/chunks/{chunk-DGOZ3CGB.js → chunk-HL2QMM4M.js} +2 -2
- package/dist/cli/chunks/{chunk-VOHYQ6MX.js → chunk-HOKON5EL.js} +2 -2
- package/dist/cli/chunks/{chunk-DTXHRALB.js → chunk-HTPM3URI.js} +2 -2
- package/dist/cli/chunks/chunk-HVB2L52I.js +2 -0
- package/dist/cli/chunks/{chunk-GP7IXYOY.js → chunk-IHQTXJAQ.js} +1 -1
- package/dist/cli/chunks/{chunk-LOZAUP6L.js → chunk-IPHLXAMX.js} +2 -2
- package/dist/cli/chunks/{chunk-56MXYSYY.js → chunk-IYDYJ33J.js} +2 -2
- package/dist/cli/chunks/{chunk-QYST2TZW.js → chunk-IZRLIWFN.js} +2 -2
- package/dist/cli/chunks/{chunk-XNNYHQLW.js → chunk-J3L52EA4.js} +5 -5
- package/dist/cli/chunks/{chunk-P2FYRAC5.js → chunk-JGM6XCCP.js} +1 -1
- package/dist/cli/chunks/chunk-JPIBPKBP.js +54 -0
- package/dist/cli/chunks/{chunk-ILNQPPKB.js → chunk-K5N6SLHO.js} +1 -1
- package/dist/cli/chunks/{chunk-UEIIIDBJ.js → chunk-KSYLRM4Q.js} +2 -2
- package/dist/cli/chunks/{chunk-TRHUH24V.js → chunk-KUA4HHU6.js} +2 -2
- package/dist/cli/chunks/{chunk-OEZNUXY7.js → chunk-KVL63BSU.js} +1 -1
- package/dist/cli/chunks/{chunk-WMNGPIP3.js → chunk-KZTBNDNE.js} +1 -1
- package/dist/cli/chunks/{chunk-OSAPKOGN.js → chunk-L2SFGFE6.js} +1 -1
- package/dist/cli/chunks/{chunk-IQ56KMMH.js → chunk-L4LLA5U7.js} +1 -1
- package/dist/cli/chunks/{chunk-KBAXD6PH.js → chunk-L4S722JD.js} +1 -1
- package/dist/cli/chunks/{chunk-C7VNQ4PY.js → chunk-LBE2P6UC.js} +2 -2
- package/dist/cli/chunks/{chunk-KNOO6TFO.js → chunk-LOXVVMS4.js} +3 -3
- package/dist/cli/chunks/{chunk-B7DI76SY.js → chunk-LWYLW7RX.js} +2 -2
- package/dist/cli/chunks/{chunk-6SWR2BN7.js → chunk-LXPB7H2P.js} +1 -1
- package/dist/cli/chunks/{chunk-LGEPR7MH.js → chunk-MBCGI3M2.js} +3 -3
- package/dist/cli/chunks/{chunk-WL5VMIO4.js → chunk-MVI2QMRW.js} +1 -1
- package/dist/cli/chunks/{chunk-OIUIVV2N.js → chunk-N5MMBA35.js} +1 -1
- package/dist/cli/chunks/{chunk-TQNNKW7K.js → chunk-N7VOOI3U.js} +2 -2
- package/dist/cli/chunks/{chunk-BPSEFLJ6.js → chunk-NETN3OUG.js} +1 -1
- package/dist/cli/chunks/chunk-NQNKKVNO.js +29 -0
- package/dist/cli/chunks/{chunk-ACDZ2CRV.js → chunk-NUQBJGGF.js} +2 -2
- package/dist/cli/chunks/chunk-OETDMOOE.js +2 -0
- package/dist/cli/chunks/{chunk-R5HC6JVJ.js → chunk-OXXMVQDF.js} +2 -2
- package/dist/cli/chunks/{chunk-J6EFAKDQ.js → chunk-OZR25R7W.js} +2 -2
- package/dist/cli/chunks/{chunk-ZL7ZOIHO.js → chunk-PERJO2DY.js} +1 -1
- package/dist/cli/chunks/{chunk-WKWIUS5G.js → chunk-PP2RYSHT.js} +3 -3
- package/dist/cli/chunks/{chunk-WE5QHYL7.js → chunk-Q7PBGNUD.js} +1 -1
- package/dist/cli/chunks/{chunk-E5XWCGSX.js → chunk-QCEW4ZDW.js} +2 -2
- package/dist/cli/chunks/{chunk-EGU54SNK.js → chunk-QI4CGJNK.js} +1 -1
- package/dist/cli/chunks/{chunk-MNNNA57O.js → chunk-QQB55ZJJ.js} +2 -2
- package/dist/cli/chunks/{chunk-KV5OZ6T3.js → chunk-QTU3CVOB.js} +2 -2
- package/dist/cli/chunks/{chunk-M4XVEUUM.js → chunk-QWYISZTN.js} +2 -2
- package/dist/cli/chunks/{chunk-OMIGKGJJ.js → chunk-R3GEXYO4.js} +2 -2
- package/dist/cli/chunks/{chunk-4KEOBDEI.js → chunk-RDU4WTV2.js} +2 -2
- package/dist/cli/chunks/{chunk-3AJUJAUJ.js → chunk-RJZRWVL4.js} +1 -1
- package/dist/cli/chunks/{chunk-UFPCNUJA.js → chunk-RLCWR7TS.js} +10 -10
- package/dist/cli/chunks/{chunk-FTG4ZVTX.js → chunk-RSFIOR2I.js} +10 -10
- package/dist/cli/chunks/{chunk-FKUH33L3.js → chunk-RVJPWRV6.js} +2 -2
- package/dist/cli/chunks/{chunk-AMPYON7M.js → chunk-RVRBTOWX.js} +2 -2
- package/dist/cli/chunks/{chunk-AJ6DZXKK.js → chunk-SHCTEXNH.js} +2 -2
- package/dist/cli/chunks/chunk-SNTUKW4Q.js +2 -0
- package/dist/cli/chunks/{chunk-F3CKOVZM.js → chunk-SRQ3CPJE.js} +6 -6
- package/dist/cli/chunks/{chunk-V2QYW5KW.js → chunk-THBTN7XB.js} +2 -2
- package/dist/cli/chunks/{chunk-6FBXYS6X.js → chunk-TNRIGQ6U.js} +2 -2
- package/dist/cli/chunks/{chunk-UF3YOGHV.js → chunk-TVMFH5N2.js} +2 -2
- package/dist/cli/chunks/{chunk-FUBN5GPY.js → chunk-TZ73SXQ5.js} +1 -1
- package/dist/cli/chunks/chunk-TZPKTRJB.js +9 -0
- package/dist/cli/chunks/{chunk-IKIKIHFQ.js → chunk-U2SW6KK5.js} +2 -2
- package/dist/cli/chunks/{chunk-ZHWXI4SK.js → chunk-UIPPJKON.js} +2 -2
- package/dist/cli/chunks/{chunk-MNHGBG4D.js → chunk-UMG5URNV.js} +1 -1
- package/dist/cli/chunks/{chunk-YYV3FYVB.js → chunk-UNIBND6A.js} +1 -1
- package/dist/cli/chunks/{chunk-HH5Y4NCR.js → chunk-USMDIIHY.js} +2 -2
- package/dist/cli/chunks/{chunk-TLYZPOA7.js → chunk-VC6EXDGY.js} +2 -2
- package/dist/cli/chunks/{chunk-BMRR3MBR.js → chunk-VHYMSZNF.js} +2 -2
- package/dist/cli/chunks/{chunk-SVUWAV6Y.js → chunk-VLBV2VL3.js} +1 -1
- package/dist/cli/chunks/{chunk-BDX54WJV.js → chunk-VLNPJAVH.js} +1 -1
- package/dist/cli/chunks/{chunk-R554WVIG.js → chunk-VSNRPMAZ.js} +2 -2
- package/dist/cli/chunks/{chunk-AL5QT6OX.js → chunk-W5YEIHIO.js} +2 -2
- package/dist/cli/chunks/{chunk-NDAJWCXN.js → chunk-WRYT4HTZ.js} +2 -2
- package/dist/cli/chunks/{chunk-PSA2SOON.js → chunk-XFCWYQHW.js} +2 -2
- package/dist/cli/chunks/{chunk-6X65BGGK.js → chunk-XKFE3FLI.js} +2 -2
- package/dist/cli/chunks/{chunk-ZF65VOQH.js → chunk-YFGAHNCG.js} +2 -2
- package/dist/cli/chunks/{chunk-YSCEUK55.js → chunk-YPTGE5NJ.js} +2 -2
- package/dist/cli/chunks/{chunk-YDZIPP3Y.js → chunk-YRQLVGCI.js} +2 -2
- package/dist/cli/chunks/{chunk-ERQL3D2O.js → chunk-Z6QCOZ2H.js} +2 -2
- package/dist/cli/chunks/{chunk-WYFEVPLO.js → chunk-ZCFZ4USU.js} +1 -1
- package/dist/cli/chunks/{chunk-2R7XZ2WP.js → chunk-ZEIG5IQ4.js} +2 -2
- package/dist/cli/chunks/{chunk-KJJP446A.js → chunk-ZJG6N2OK.js} +1 -1
- package/dist/cli/chunks/{chunk-BUKGW63B.js → chunk-ZJIX5FGQ.js} +3 -3
- package/dist/cli/chunks/{chunk-EZ3JVN6S.js → chunk-ZLXBNNNT.js} +2 -2
- package/dist/cli/chunks/{chunk-4FHJYJCH.js → chunk-ZOXWASCC.js} +2 -2
- package/dist/cli/chunks/{ci-NT4DUGQ4.js → ci-74ZUCO5V.js} +2 -2
- package/dist/cli/chunks/{ci-output-PXBD2QPH.js → ci-output-HD6IZNJA.js} +2 -2
- package/dist/cli/chunks/{circuit-breaker-ZPCCEEDX.js → circuit-breaker-USZVMA42.js} +2 -2
- package/dist/cli/chunks/{claude-flow-setup-CZCDFZOE.js → claude-flow-setup-S4XKNCD5.js} +2 -2
- package/dist/cli/chunks/client-33PGO5FA.js +2 -0
- package/dist/cli/chunks/{cline-installer-6J2S7GLM.js → cline-installer-6EW4CHAR.js} +2 -2
- package/dist/cli/chunks/{code-UTERK5AP.js → code-TOLWD2XL.js} +2 -2
- package/dist/cli/chunks/{code-index-extractor-O3MJTOS2.js → code-index-extractor-UA4QQ7B3.js} +2 -2
- package/dist/cli/chunks/{codex-installer-AZJIVXJR.js → codex-installer-C2CUP4FW.js} +2 -2
- package/dist/cli/chunks/{completions-A6ANQSZN.js → completions-KVUMQQ4K.js} +2 -2
- package/dist/cli/chunks/{complexity-analyzer-MRSN7VUP.js → complexity-analyzer-DFH6LMK2.js} +2 -2
- package/dist/cli/chunks/{continuedev-installer-6LMKQRUI.js → continuedev-installer-LYDQSAU7.js} +2 -2
- package/dist/cli/chunks/{copilot-installer-KP3TWUNO.js → copilot-installer-7USXLZHZ.js} +2 -2
- package/dist/cli/chunks/core-handlers-CSNZIM3A.js +2 -0
- package/dist/cli/chunks/{cost-tracker-R3C5IQPZ.js → cost-tracker-JBDIUMKZ.js} +2 -2
- package/dist/cli/chunks/{coverage-MR4QA2NM.js → coverage-RGO3K357.js} +3 -3
- package/dist/cli/chunks/cross-domain-router-DRIBR5L3.js +2 -0
- package/dist/cli/chunks/{cursor-installer-LPZ7NGOS.js → cursor-installer-YEZGD37F.js} +2 -2
- package/dist/cli/chunks/{daemon-X5KXHRY3.js → daemon-FVETQ3LL.js} +3 -3
- package/dist/cli/chunks/{daemon-K3KW2UDV.js → daemon-LPEXHNYG.js} +4 -4
- package/dist/cli/chunks/{dag-attention-scheduler-J4FJSGNN.js → dag-attention-scheduler-UKYE2FII.js} +2 -2
- package/dist/cli/chunks/{detect-22TX2NCA.js → detect-WBG5P7ER.js} +2 -2
- package/dist/cli/chunks/{dist-node-QI4I4QPS.js → dist-node-YW6YRBCU.js} +2 -2
- package/dist/cli/chunks/{domain-handler-ZYZGPNOQ.js → domain-handler-HH7J4A32.js} +2 -2
- package/dist/cli/chunks/{domain-transfer-2EEQTNIU.js → domain-transfer-V34GXD5I.js} +2 -2
- package/dist/cli/chunks/dream-WUNO723O.js +2 -0
- package/dist/cli/chunks/{embed-and-insert-pattern-LYCTU5BS.js → embed-and-insert-pattern-WYLRS3CL.js} +2 -2
- package/dist/cli/chunks/{eval-SOA72GUX.js → eval-EIMVXY5J.js} +2 -2
- package/dist/cli/chunks/{experience-capture-middleware-AFFYOMYM.js → experience-capture-middleware-QMZHRUKM.js} +3 -3
- package/dist/cli/chunks/{fast-paths-WNMHOSUH.js → fast-paths-NHLW6XS6.js} +2 -2
- package/dist/cli/chunks/{feature-flags-HAP5IAAY.js → feature-flags-N3UHMSZ3.js} +2 -2
- package/dist/cli/chunks/{feature-flags-FT4YBZH4.js → feature-flags-NE3MBL3P.js} +2 -2
- package/dist/cli/chunks/{file-discovery-B4FFOH3Z.js → file-discovery-SI44X4YS.js} +2 -2
- package/dist/cli/chunks/{fleet-2ICPYV3J.js → fleet-MMYUXRRM.js} +3 -3
- package/dist/cli/chunks/{gnn-wrapper-XIBJAHMW.js → gnn-wrapper-RLYXYU2Q.js} +2 -2
- package/dist/cli/chunks/{heartbeat-handler-NSLSKDOI.js → heartbeat-handler-6EDRUYLV.js} +4 -4
- package/dist/cli/chunks/heartbeat-scheduler-VAFQDJLL.js +2 -0
- package/dist/cli/chunks/hnsw-adapter-TIRPA7XE.js +2 -0
- package/dist/cli/chunks/hnsw-index-SSQBRQCQ.js +2 -0
- package/dist/cli/chunks/{hnsw-legacy-bridge-FTV4CCBB.js → hnsw-legacy-bridge-RVM7EAIH.js} +2 -2
- package/dist/cli/chunks/{better-sqlite3-RQCOOAZC.js → hnswlib-node-RJPBJU2Q.js} +2 -2
- package/dist/cli/chunks/{hooks-SM4EU52N.js → hooks-ZF4VKHEO.js} +10 -10
- package/dist/cli/chunks/hybrid-router-AKBWZOQ3.js +2 -0
- package/dist/cli/chunks/{hypergraph-engine-RRMX4N3L.js → hypergraph-engine-YWS66PGO.js} +2 -2
- package/dist/cli/chunks/{hypergraph-handler-MNFGDKAP.js → hypergraph-handler-OQHVRPWB.js} +3 -3
- package/dist/cli/chunks/impact-analyzer-46FQIZ6Y.js +2 -0
- package/dist/cli/chunks/{init-handler-BNHIJ5TX.js → init-handler-YW74YF2V.js} +6 -6
- package/dist/cli/chunks/init-wizard-U3NHGYSS.js +2 -0
- package/dist/cli/chunks/kernel-ZDXHT6QF.js +2 -0
- package/dist/cli/chunks/{kilocode-installer-GFKUKPLY.js → kilocode-installer-APWB2GMA.js} +2 -2
- package/dist/cli/chunks/{kiro-installer-YBIYKODN.js → kiro-installer-DICKFVNC.js} +2 -2
- package/dist/cli/chunks/knowledge-graph-6XTW6WO6.js +2 -0
- package/dist/cli/chunks/{learning-QEWUPCK6.js → learning-MXHNYW5D.js} +3 -3
- package/dist/cli/chunks/{llm-router-D5FQIMVQ.js → llm-router-LTFIX7NY.js} +5 -5
- package/dist/cli/chunks/llm-router-service-3F4E64SY.js +2 -0
- package/dist/cli/chunks/{load-CLBUCKZQ.js → load-JUMIQ4LZ.js} +2 -2
- package/dist/cli/chunks/load-test-SWZNGEIY.js +2 -0
- package/dist/cli/chunks/{mcp-VOMG5DPV.js → mcp-WHIARJGO.js} +2 -2
- package/dist/cli/chunks/{memory-WZO63VSC.js → memory-KAHS6Z5A.js} +5 -5
- package/dist/cli/chunks/memory-backend-PR74ZPBA.js +2 -0
- package/dist/cli/chunks/memory-handlers-DG3RRXB6.js +2 -0
- package/dist/cli/chunks/{multi-model-executor-7UKW43VV.js → multi-model-executor-E3Y2RHMX.js} +2 -2
- package/dist/cli/chunks/{opencode-installer-MLFPRRL2.js → opencode-installer-3RKGNGGE.js} +2 -2
- package/dist/cli/chunks/{orchestrator-XNYDBGIN.js → orchestrator-TXITUKWD.js} +5 -5
- package/dist/cli/chunks/pattern-null-store-EPE36BVT.js +2 -0
- package/dist/cli/chunks/{pattern-usage-recorder-ZAP7ASQI.js → pattern-usage-recorder-EE2BUFZT.js} +2 -2
- package/dist/cli/chunks/{pipeline-4EJYOPBC.js → pipeline-AXTT6KZB.js} +2 -2
- package/dist/cli/chunks/{platform-ALPOWCOH.js → platform-TICPVKE2.js} +2 -2
- package/dist/cli/chunks/{plugin-S2RX3KHR.js → plugin-VQKNV3EM.js} +2 -2
- package/dist/cli/chunks/{prime-radiant-advanced-wasm-P2JB73XX.js → prime-radiant-advanced-wasm-6GY5YY7D.js} +2 -2
- package/dist/cli/chunks/protocol-executor-IELENNST.js +2 -0
- package/dist/cli/chunks/{protocol-handler-ZBJXSESA.js → protocol-handler-YFWDR7UZ.js} +2 -2
- package/dist/cli/chunks/{prove-MYJ5YV45.js → prove-FUMRCXAA.js} +2 -2
- package/dist/cli/chunks/provider-manager-3R3KOBHG.js +2 -0
- package/dist/cli/chunks/qe-reasoning-bank-S2R2PCBS.js +2 -0
- package/dist/cli/chunks/{quality-NIACQC4M.js → quality-2APISBW5.js} +2 -2
- package/dist/cli/chunks/{quality-gate-OAZ23ON5.js → quality-gate-MCC3E5TM.js} +3 -3
- package/dist/cli/chunks/queen-coordinator-2E23NEV4.js +2 -0
- package/dist/cli/chunks/{real-embeddings-6I4LP7PL.js → real-embeddings-CC5TNOTI.js} +2 -2
- package/dist/cli/chunks/{roocode-installer-LRQHP6MK.js → roocode-installer-YAHTEVPI.js} +2 -2
- package/dist/cli/chunks/router-3FVLWO6S.js +2 -0
- package/dist/cli/chunks/routing-feedback-ZQLRNNR7.js +2 -0
- package/dist/cli/chunks/{routing-handler-A7BW7MV4.js → routing-handler-6NQBW4TQ.js} +2 -2
- package/dist/cli/chunks/{ruvector-commands-ZDRRFSJS.js → ruvector-commands-ZHXLHUPG.js} +2 -2
- package/dist/cli/chunks/{rvf-dual-writer-QWE3Z6BB.js → rvf-dual-writer-5KF2DGCW.js} +2 -2
- package/dist/cli/chunks/{rvf-migration-adapter-6UDAP7QB.js → rvf-migration-adapter-BBU57BGF.js} +2 -2
- package/dist/cli/chunks/{rvf-migration-coordinator-FEW2N4R4.js → rvf-migration-coordinator-VN4VRZA4.js} +2 -2
- package/dist/cli/chunks/rvf-native-adapter-YFGLQNEB.js +2 -0
- package/dist/cli/chunks/rvf-store-integrity-XCY2XQZA.js +2 -0
- package/dist/cli/chunks/safe-db-4SBLBTW7.js +2 -0
- package/dist/cli/chunks/schedule-5Y3BGVAA.js +2 -0
- package/dist/cli/chunks/scheduler-DOID3QF3.js +2 -0
- package/dist/cli/chunks/{security-KFXBV65O.js → security-PYP4H3KN.js} +3 -3
- package/dist/cli/chunks/shared-rvf-adapter-3CJHRNX6.js +2 -0
- package/dist/cli/chunks/{shared-rvf-dual-writer-CXXTB7CU.js → shared-rvf-dual-writer-MKSLKWDI.js} +2 -2
- package/dist/cli/chunks/sqlite-persistence-M4JEQO57.js +2 -0
- package/dist/cli/chunks/{status-handler-CXSVM236.js → status-handler-VYIZ3VJF.js} +2 -2
- package/dist/cli/chunks/{structural-health-LMHSM7MJ.js → structural-health-MVV4OWXP.js} +2 -2
- package/dist/cli/chunks/{sync-DMOUY2EY.js → sync-G47OYIQ3.js} +2 -2
- package/dist/cli/chunks/sync-VNX6L3TI.js +2 -0
- package/dist/cli/chunks/{task-handler-DMSNFIH3.js → task-handler-GOWF2GEH.js} +2 -2
- package/dist/cli/chunks/{task-handlers-3Z3QV7X7.js → task-handlers-J2WC6J37.js} +3 -3
- package/dist/cli/chunks/{test-U6CGMDLN.js → test-DMCJ3BXS.js} +4 -4
- package/dist/cli/chunks/{test-scheduling-YHYA3BP5.js → test-scheduling-TRFHHKFM.js} +3 -3
- package/dist/cli/chunks/token-bootstrap-KEQ7AHFF.js +2 -0
- package/dist/cli/chunks/{token-usage-EOOTM7Q3.js → token-usage-B54FOFOF.js} +2 -2
- package/dist/cli/chunks/transformers-IACZG7IV.js +2 -0
- package/dist/cli/chunks/{tree-sitter-wasm-parser-HTGAXDQA.js → tree-sitter-wasm-parser-N7H4X37E.js} +2 -2
- package/dist/cli/chunks/{types-GLRXY4RY.js → types-B3SHT4OD.js} +2 -2
- package/dist/cli/chunks/unified-memory-WNYWTGVX.js +2 -0
- package/dist/cli/chunks/unified-memory-hnsw-YPAV7U4E.js +2 -0
- package/dist/cli/chunks/unified-persistence-TJ252S7L.js +2 -0
- package/dist/cli/chunks/{upgrade-KU55H77X.js → upgrade-A2PQ5EWK.js} +2 -2
- package/dist/cli/chunks/{validate-26VSBOJN.js → validate-UXV7UQUT.js} +2 -2
- package/dist/cli/chunks/{validate-swarm-OIALRDKF.js → validate-swarm-MMBU4FPP.js} +2 -2
- package/dist/cli/chunks/{vibium-LOTWW25P.js → vibium-R4GUVADA.js} +2 -2
- package/dist/cli/chunks/visual-security-VKYXEGJB.js +2 -0
- package/dist/cli/chunks/{web-tree-sitter-6LWPPBYU.js → web-tree-sitter-MP6E6CJB.js} +2 -2
- package/dist/cli/chunks/{windsurf-installer-ILOQYENG.js → windsurf-installer-Z6BWMUUO.js} +2 -2
- package/dist/cli/chunks/{witness-chain-YBS6OHS4.js → witness-chain-EFLTABBA.js} +2 -2
- package/dist/cli/chunks/witness-chain-WVXRXKQP.js +2 -0
- package/dist/cli/chunks/{witness-key-manager-SI3WV3ZQ.js → witness-key-manager-PN36TV43.js} +2 -2
- package/dist/cli/chunks/{workflow-GY6SJRQW.js → workflow-A2AX3RJU.js} +4 -4
- package/dist/cli/chunks/workflow-orchestrator-GOWSPR3G.js +2 -0
- package/dist/cli/chunks/{wrappers-MMO5YKOZ.js → wrappers-NA6FHA63.js} +2 -2
- package/dist/cli/commands/llm-router.js +1 -0
- package/dist/init/skills-installer.js +1 -0
- package/dist/integrations/embeddings/base/EmbeddingGenerator.js +13 -1
- package/dist/integrations/ruvector/brain-rvf-exporter.js +115 -13
- package/dist/integrations/ruvector/rvf-dual-writer.js +20 -3
- package/dist/integrations/ruvector/rvf-store-integrity.d.ts +49 -0
- package/dist/integrations/ruvector/rvf-store-integrity.js +137 -0
- package/dist/integrations/ruvector/shared-rvf-adapter.js +23 -5
- package/dist/kernel/unified-memory.js +4 -0
- package/dist/learning/pattern-store.js +23 -5
- package/dist/learning/qe-flywheel/corpus-scorer.js +10 -7
- package/dist/learning/qe-flywheel/generation.d.ts +7 -0
- package/dist/learning/qe-flywheel/generation.js +5 -0
- package/dist/learning/qe-flywheel/platform-signer.d.ts +32 -0
- package/dist/learning/qe-flywheel/platform-signer.js +60 -0
- package/dist/learning/qe-flywheel/receipt.js +9 -2
- package/dist/learning/qe-flywheel/witness-verify.d.ts +86 -0
- package/dist/learning/qe-flywheel/witness-verify.js +156 -0
- package/dist/mcp/bundle.js +398 -387
- package/dist/shared/db-recovery.d.ts +55 -0
- package/dist/shared/db-recovery.js +184 -0
- package/dist/shared/llm/billing-modes.js +5 -2
- package/dist/shared/llm/cost-tracker.js +1 -0
- package/dist/shared/llm/interfaces.d.ts +14 -1
- package/dist/shared/llm/provider-manager.d.ts +1 -1
- package/dist/shared/llm/provider-manager.js +7 -3
- package/dist/shared/llm/providers/claude-code.js +12 -3
- package/dist/shared/llm/providers/codex.d.ts +58 -0
- package/dist/shared/llm/providers/codex.js +305 -0
- package/dist/shared/llm/providers/index.d.ts +1 -0
- package/dist/shared/llm/providers/index.js +1 -0
- package/dist/shared/llm/router/config-store.js +5 -0
- package/dist/shared/llm/router/hybrid-router.js +2 -2
- package/dist/shared/llm/spend-ledger.d.ts +24 -0
- package/dist/shared/llm/spend-ledger.js +86 -15
- package/dist/shared/llm/translation/message-formatter.js +1 -0
- package/dist/shared/safe-db.d.ts +10 -0
- package/dist/shared/safe-db.js +17 -0
- package/dist/skills/qe-court/referee.d.ts +62 -0
- package/dist/skills/qe-court/referee.js +78 -0
- package/dist/validation/gate-reexecute.d.ts +9 -0
- package/dist/validation/gate-reexecute.js +61 -0
- package/dist/validation/paired-bootstrap.d.ts +36 -0
- package/dist/validation/paired-bootstrap.js +58 -0
- package/package.json +8 -4
- package/dist/cli/chunks/adapter-RL6GNUOS.js +0 -2
- package/dist/cli/chunks/aqe-learning-engine-UEEW6AQG.js +0 -2
- package/dist/cli/chunks/base-JQA5YULE.js +0 -2
- package/dist/cli/chunks/brain-handler-T7RDPKNM.js +0 -89
- package/dist/cli/chunks/browser-workflow-XTKUHTIT.js +0 -2
- package/dist/cli/chunks/chunk-ABHKXMOJ.js +0 -2
- package/dist/cli/chunks/chunk-CDMAVNV5.js +0 -2
- package/dist/cli/chunks/chunk-CQKEVUMO.js +0 -153
- package/dist/cli/chunks/chunk-KPV4N4KT.js +0 -9
- package/dist/cli/chunks/chunk-MALFTRCT.js +0 -29
- package/dist/cli/chunks/chunk-T75VH6NT.js +0 -2
- package/dist/cli/chunks/chunk-VM6VBRRU.js +0 -43
- package/dist/cli/chunks/chunk-WRMUQVKY.js +0 -2
- package/dist/cli/chunks/chunk-YY4KXDI6.js +0 -2
- package/dist/cli/chunks/client-S7DIRNFJ.js +0 -2
- package/dist/cli/chunks/core-handlers-NM2KCI6Z.js +0 -2
- package/dist/cli/chunks/cross-domain-router-3TSZMCBM.js +0 -2
- package/dist/cli/chunks/dream-QIIJONYI.js +0 -2
- package/dist/cli/chunks/heartbeat-scheduler-CDNGLJJO.js +0 -2
- package/dist/cli/chunks/hnsw-adapter-CR7Y4OAM.js +0 -2
- package/dist/cli/chunks/hnsw-index-TEDO4MQC.js +0 -2
- package/dist/cli/chunks/hybrid-router-O24ZDIP5.js +0 -2
- package/dist/cli/chunks/impact-analyzer-PDM4SDB6.js +0 -2
- package/dist/cli/chunks/init-wizard-F4KODPNW.js +0 -2
- package/dist/cli/chunks/kernel-77UM35L6.js +0 -2
- package/dist/cli/chunks/knowledge-graph-R6ZWWV2K.js +0 -2
- package/dist/cli/chunks/llm-router-service-7QYCZ66H.js +0 -2
- package/dist/cli/chunks/load-test-AWVA3CAQ.js +0 -2
- package/dist/cli/chunks/memory-backend-YESSIHFW.js +0 -2
- package/dist/cli/chunks/memory-handlers-EOOWSUHI.js +0 -2
- package/dist/cli/chunks/pattern-null-store-X3UXLJNL.js +0 -2
- package/dist/cli/chunks/protocol-executor-KXDCTMWI.js +0 -2
- package/dist/cli/chunks/provider-manager-XJZVHYI6.js +0 -2
- package/dist/cli/chunks/qe-reasoning-bank-DPHPNIC6.js +0 -2
- package/dist/cli/chunks/queen-coordinator-EVGWME5C.js +0 -2
- package/dist/cli/chunks/router-R2N7TMAS.js +0 -2
- package/dist/cli/chunks/routing-feedback-G6W3H26L.js +0 -2
- package/dist/cli/chunks/rvf-native-adapter-D7R2GWXK.js +0 -2
- package/dist/cli/chunks/safe-db-CQZGF2KA.js +0 -2
- package/dist/cli/chunks/schedule-UP7E3LRC.js +0 -2
- package/dist/cli/chunks/scheduler-ENIZHYSL.js +0 -2
- package/dist/cli/chunks/shared-rvf-adapter-HDDCXCHY.js +0 -2
- package/dist/cli/chunks/sqlite-persistence-CEVJVWS5.js +0 -2
- package/dist/cli/chunks/sync-REEFKICL.js +0 -2
- package/dist/cli/chunks/token-bootstrap-MQCTJURQ.js +0 -2
- package/dist/cli/chunks/transformers-EKDJW2H6.js +0 -2
- package/dist/cli/chunks/unified-memory-PXOQ4N53.js +0 -2
- package/dist/cli/chunks/unified-memory-hnsw-VEXD6ASE.js +0 -2
- package/dist/cli/chunks/unified-persistence-46G6MZYC.js +0 -2
- package/dist/cli/chunks/visual-security-4X3T3MFL.js +0 -2
- package/dist/cli/chunks/witness-chain-XW4S3LVW.js +0 -2
- package/dist/cli/chunks/workflow-orchestrator-IIVV6TCP.js +0 -2
|
@@ -1,693 +1,1006 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* RuFlo V3 Statusline — delegation build (#2195)
|
|
4
|
-
*
|
|
5
|
-
* Fix for ruvnet/ruflo#2195: the previous version re-implemented all data
|
|
6
|
-
* readers locally using fragile file probes that missed AgentDB patterns,
|
|
7
|
-
* the v3/docs/adr/ ADR directory, and the real vector count.
|
|
8
|
-
*
|
|
9
|
-
* This version delegates to 'npx @claude-flow/cli hooks statusline --json'
|
|
10
|
-
* as the single source of truth. That command queries AgentDB directly,
|
|
11
|
-
* counts ADRs in both directories, and reports the real intelligence pct.
|
|
12
|
-
*
|
|
13
|
-
* ADR counting falls back to local file reads so the display still works
|
|
14
|
-
* without network access (counts both v3/docs/adr/ and v3/implementation/adrs/).
|
|
15
|
-
*
|
|
16
|
-
* Cache: JSON result is cached in /tmp for 10s so rapid prompt triggers
|
|
17
|
-
* (every keystroke in some shells) don't hammer the CLI on every call.
|
|
18
|
-
*
|
|
19
|
-
* Usage: node statusline.cjs [--json] [--compact] [--dashboard]
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
23
|
-
const fs = require('fs');
|
|
24
|
-
const path = require('path');
|
|
25
|
-
const { execSync } = require('child_process');
|
|
26
|
-
const os = require('os');
|
|
27
|
-
|
|
28
|
-
// Configuration
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* RuFlo V3 Statusline — delegation build (#2195)
|
|
4
|
+
*
|
|
5
|
+
* Fix for ruvnet/ruflo#2195: the previous version re-implemented all data
|
|
6
|
+
* readers locally using fragile file probes that missed AgentDB patterns,
|
|
7
|
+
* the v3/docs/adr/ ADR directory, and the real vector count.
|
|
8
|
+
*
|
|
9
|
+
* This version delegates to 'npx @claude-flow/cli hooks statusline --json'
|
|
10
|
+
* as the single source of truth. That command queries AgentDB directly,
|
|
11
|
+
* counts ADRs in both directories, and reports the real intelligence pct.
|
|
12
|
+
*
|
|
13
|
+
* ADR counting falls back to local file reads so the display still works
|
|
14
|
+
* without network access (counts both v3/docs/adr/ and v3/implementation/adrs/).
|
|
15
|
+
*
|
|
16
|
+
* Cache: JSON result is cached in /tmp for 10s so rapid prompt triggers
|
|
17
|
+
* (every keystroke in some shells) don't hammer the CLI on every call.
|
|
18
|
+
*
|
|
19
|
+
* Usage: node statusline.cjs [--json] [--compact] [--dashboard]
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
const { execSync } = require('child_process');
|
|
26
|
+
const os = require('os');
|
|
27
|
+
|
|
28
|
+
// Configuration
|
|
29
29
|
const CONFIG = {
|
|
30
30
|
maxAgents: 15,
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
function
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
for (const d of ['tests', 'test', '__tests__', 'src', 'v3']) countTests(path.join(CWD, d));
|
|
220
|
-
return { testFiles, testCases: testFiles * 4 };
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Count configured hooks from project .claude/settings.json. Claude Code hooks
|
|
224
|
-
// have no enabled/disabled flag, so every configured hook counts as enabled.
|
|
225
|
-
function getLocalHooks() {
|
|
226
|
-
const result = { enabled: 0, total: 0 };
|
|
227
|
-
try {
|
|
228
|
-
const settings = readJSON(path.join(CWD, '.claude', 'settings.json'));
|
|
229
|
-
const hooks = settings && settings.hooks;
|
|
230
|
-
if (hooks && typeof hooks === 'object') {
|
|
231
|
-
let n = 0;
|
|
232
|
-
for (const ev of Object.keys(hooks)) {
|
|
233
|
-
const groups = hooks[ev];
|
|
234
|
-
if (Array.isArray(groups)) {
|
|
235
|
-
for (const g of groups) {
|
|
236
|
-
if (g && Array.isArray(g.hooks)) n += g.hooks.length;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
result.total = n;
|
|
241
|
-
result.enabled = n;
|
|
242
|
-
}
|
|
243
|
-
} catch { /* ignore */ }
|
|
244
|
-
return result;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Best-effort integration block: DB presence + locally-configured stdio MCP
|
|
248
|
-
// servers (project .mcp.json + global ~/.claude.json). Remote connectors are
|
|
249
|
-
// account-managed and not present in local config, so they are not counted.
|
|
250
|
-
function getLocalIntegration() {
|
|
251
|
-
const integration = { mcpServers: { enabled: 0, total: 0 }, hasDatabase: false };
|
|
252
|
-
try {
|
|
253
|
-
for (const f of ['.swarm/memory.db', 'ruvector.db']) {
|
|
254
|
-
if (fs.existsSync(path.join(CWD, f))) { integration.hasDatabase = true; break; }
|
|
255
|
-
}
|
|
256
|
-
const names = new Set();
|
|
257
|
-
const projMcp = readJSON(path.join(CWD, '.mcp.json'));
|
|
258
|
-
if (projMcp && projMcp.mcpServers) for (const k of Object.keys(projMcp.mcpServers)) names.add(k);
|
|
259
|
-
const claudeJson = readJSON(path.join(os.homedir(), '.claude.json'));
|
|
260
|
-
if (claudeJson) {
|
|
261
|
-
if (claudeJson.mcpServers) for (const k of Object.keys(claudeJson.mcpServers)) names.add(k);
|
|
262
|
-
const proj = claudeJson.projects && claudeJson.projects[CWD];
|
|
263
|
-
if (proj && proj.mcpServers && !Array.isArray(proj.mcpServers)) {
|
|
264
|
-
for (const k of Object.keys(proj.mcpServers)) names.add(k);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
integration.mcpServers.total = names.size;
|
|
268
|
-
integration.mcpServers.enabled = names.size;
|
|
269
|
-
} catch { /* ignore */ }
|
|
270
|
-
return integration;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Overlay every locally-derived block onto the CLI data (mutates in place).
|
|
274
|
-
function applyLocalOverlays(data) {
|
|
275
|
-
data.adrs = getLocalADRCount();
|
|
276
|
-
data.agentdb = getLocalAgentDB();
|
|
277
|
-
data.tests = getLocalTests();
|
|
278
|
-
data.hooks = getLocalHooks();
|
|
279
|
-
data.integration = getLocalIntegration();
|
|
280
|
-
return data;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Minimal local fallback when the CLI is not installed or times out.
|
|
284
|
-
// Returns a structure that matches the CLI JSON schema so the renderer works.
|
|
285
|
-
function buildLocalFallback() {
|
|
286
|
-
const memMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
|
|
287
|
-
|
|
288
|
-
return applyLocalOverlays({
|
|
289
|
-
user: { name: 'user', gitBranch: '', modelName: 'Claude Code' },
|
|
290
|
-
v3Progress: { domainsCompleted: 0, totalDomains: 5, dddProgress: 0, patternsLearned: 0, sessionsCompleted: 0 },
|
|
291
|
-
security: { status: 'NONE', cvesFixed: 0, totalCves: 0 },
|
|
292
|
-
swarm: { activeAgents: 0, maxAgents: CONFIG.maxAgents, coordinationActive: false },
|
|
293
|
-
system: { memoryMB: memMB, contextPct: 0, intelligencePct: 0, subAgents: 0 },
|
|
294
|
-
lastUpdated: new Date().toISOString(),
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// ANSI colors
|
|
299
|
-
const c = {
|
|
300
|
-
reset: '\x1b[0m',
|
|
301
|
-
bold: '\x1b[1m',
|
|
302
|
-
dim: '\x1b[2m',
|
|
303
|
-
red: '\x1b[0;31m',
|
|
304
|
-
green: '\x1b[0;32m',
|
|
305
|
-
yellow: '\x1b[0;33m',
|
|
306
|
-
blue: '\x1b[0;34m',
|
|
307
|
-
purple: '\x1b[0;35m',
|
|
308
|
-
cyan: '\x1b[0;36m',
|
|
309
|
-
brightRed: '\x1b[1;31m',
|
|
310
|
-
brightGreen: '\x1b[1;32m',
|
|
311
|
-
brightYellow: '\x1b[1;33m',
|
|
312
|
-
brightBlue: '\x1b[1;34m',
|
|
313
|
-
brightPurple: '\x1b[1;35m',
|
|
314
|
-
brightCyan: '\x1b[1;36m',
|
|
315
|
-
brightWhite: '\x1b[1;37m',
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
// Safe execSync with strict timeout (returns empty string on failure)
|
|
319
|
-
function safeExec(cmd, timeoutMs) {
|
|
320
|
-
try {
|
|
321
|
-
return execSync(cmd, {
|
|
322
|
-
encoding: 'utf-8',
|
|
323
|
-
timeout: timeoutMs || 2000,
|
|
324
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
325
|
-
}).trim();
|
|
326
|
-
} catch {
|
|
327
|
-
return '';
|
|
31
|
+
// Header identity defaults to project/repository name. Set `author` to
|
|
32
|
+
// retain the previous `git config user.name` display (#2682).
|
|
33
|
+
identityMode: (process.env.RUFLO_STATUSLINE_IDENTITY || 'project').toLowerCase(),
|
|
34
|
+
// Session-cost display. Claude Code's cost.total_cost_usd is a client-side
|
|
35
|
+
// estimate that "may differ from your actual bill" and reads as misleading on
|
|
36
|
+
// subscription plans, where token usage is not billed per dollar. These let
|
|
37
|
+
// each user pick what the segment means to them without changing the default.
|
|
38
|
+
// RUFLO_STATUSLINE_COST_SYMBOL override the leading '$' (e.g. ⚡, €, 🌱);
|
|
39
|
+
// set to an empty string for the number alone.
|
|
40
|
+
// RUFLO_STATUSLINE_HIDE_COST 1/true/yes/on removes the segment entirely.
|
|
41
|
+
costSymbol: process.env.RUFLO_STATUSLINE_COST_SYMBOL ?? '$',
|
|
42
|
+
hideCost: /^(1|true|yes|on)$/i.test(process.env.RUFLO_STATUSLINE_HIDE_COST || ''),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const CWD = process.cwd();
|
|
46
|
+
|
|
47
|
+
// ─── Delegation cache ───────────────────────────────────────────
|
|
48
|
+
// Cache the CLI JSON result so rapid prompt re-renders (Claude Code
|
|
49
|
+
// refreshes the statusline several times a second while streaming) don't
|
|
50
|
+
// re-invoke the CLI each time.
|
|
51
|
+
// #2337 bumped 10s → 60s.
|
|
52
|
+
// Followup for anthropics/claude-code#70200 (Windows console-flash bug —
|
|
53
|
+
// claude.exe spawns hook/statusline subprocesses without CREATE_NO_WINDOW,
|
|
54
|
+
// producing a visible cmd flash on every render): bumped 60s → 300s to
|
|
55
|
+
// reduce the flash rate 5x on Windows until the upstream fix ships.
|
|
56
|
+
// Tradeoff: stat/git counters update every 5min instead of every 1min;
|
|
57
|
+
// promo/insight row still rotates on its own tighter 20s promoFresh clock.
|
|
58
|
+
const CACHE_FILE = path.join(os.tmpdir(), 'ruflo-statusline-cache-' + require('crypto').createHash('md5').update(CWD).digest('hex').slice(0, 8) + '.json');
|
|
59
|
+
const CACHE_TTL_MS = 300000;
|
|
60
|
+
|
|
61
|
+
// The promo/insight row is designed to rotate on a 20s cadence (funnel/
|
|
62
|
+
// rotation.ts's ROTATION_SLOT_MS / funnel/promo.ts's insight-slot check —
|
|
63
|
+
// duplicated here as a bare number since this generated script has no
|
|
64
|
+
// runtime import of the funnel module; keep in sync if that constant ever
|
|
65
|
+
// changes). The rotation slot is only ever (re)computed SERVER-SIDE inside
|
|
66
|
+
// the CLI subprocess this file shells out to — so a general 60s data cache
|
|
67
|
+
// (correct and necessary for #2337) silently made that 20s design
|
|
68
|
+
// unreachable: cache.fresh stayed true across 2-3 whole rotation slots,
|
|
69
|
+
// so the row visibly "didn't rotate" (user report). Fix: track promo
|
|
70
|
+
// freshness on its OWN, tighter clock — when it lags behind the current
|
|
71
|
+
// slot, fall through to a real CLI call even though the REST of the
|
|
72
|
+
// cached data (security/swarm/system) is still within CACHE_TTL_MS. This
|
|
73
|
+
// does not touch or regress #2337's fix; it only adds a narrower check.
|
|
74
|
+
const PROMO_ROTATION_SLOT_MS = 20000;
|
|
75
|
+
|
|
76
|
+
// Persistent last-known-good promo record. Lives outside the /tmp cache so it
|
|
77
|
+
// survives a full cache wipe / cache write race / CLI failure combo. Written
|
|
78
|
+
// every time we successfully render a promo; read as a last resort so the row
|
|
79
|
+
// never blinks out mid-session (was: 'promo shows then hides' bug report).
|
|
80
|
+
const PROMO_MEMO_FILE = path.join(os.homedir(), '.ruflo', 'statusline-promo.json');
|
|
81
|
+
const PROMO_MEMO_TTL_MS = 6 * 60 * 60 * 1000; // 6h — long enough to bridge any hiccup, short enough that a real disable takes effect fast.
|
|
82
|
+
|
|
83
|
+
// #2337: resolve an already-installed @claude-flow/cli (or ruflo) bin so we
|
|
84
|
+
// can invoke it directly via `node`. The previous version called
|
|
85
|
+
// `npx --yes @claude-flow/cli@latest` on every uncached render, which forces
|
|
86
|
+
// a registry resolution + cold-start of the entire CLI per render. With
|
|
87
|
+
// multiple concurrent Claude Code sessions this storms the host (reporter
|
|
88
|
+
// saw load average 40-65 on a 12-core box).
|
|
89
|
+
//
|
|
90
|
+
// Returns EVERY existing bin/cli.js candidate, in preference order (project,
|
|
91
|
+
// monorepo, plugin marketplace, global node_modules including custom-prefix
|
|
92
|
+
// layouts like ~/.npm-global) — mirrors getPkgVersion()'s own path probing.
|
|
93
|
+
//
|
|
94
|
+
// Returns a list, not a single winner: `fs.existsSync` only proves a file is
|
|
95
|
+
// present, not that it actually runs. A marketplace/npx-cached install can
|
|
96
|
+
// exist on disk but be broken (observed in practice: a stale marketplace
|
|
97
|
+
// checkout whose dist/ imports a workspace package, '@claude-flow/cli-core',
|
|
98
|
+
// that isn't bundled there — every invocation throws ERR_MODULE_NOT_FOUND).
|
|
99
|
+
// Picking the first EXISTING path and never falling through meant a single
|
|
100
|
+
// broken install silently killed the promo row for the entire session (the
|
|
101
|
+
// CLI call always failed, so the memo could never refresh and eventually
|
|
102
|
+
// expired). getStatuslineData() now walks this whole list and tries the next
|
|
103
|
+
// candidate on failure, so one broken install can't permanently wedge it.
|
|
104
|
+
function resolveCliBinCandidates() {
|
|
105
|
+
const candidates = [];
|
|
106
|
+
try {
|
|
107
|
+
const home = os.homedir();
|
|
108
|
+
candidates.push(
|
|
109
|
+
path.join(home, '.claude', 'plugins', 'marketplaces', 'ruflo', 'bin', 'cli.js'),
|
|
110
|
+
path.join(CWD, 'node_modules', '@claude-flow', 'cli', 'bin', 'cli.js'),
|
|
111
|
+
path.join(CWD, 'node_modules', 'ruflo', 'bin', 'cli.js'),
|
|
112
|
+
path.join(CWD, 'v3', '@claude-flow', 'cli', 'bin', 'cli.js'),
|
|
113
|
+
);
|
|
114
|
+
try {
|
|
115
|
+
const binDir = path.dirname(process.execPath);
|
|
116
|
+
const globalModuleDirs = [path.join(binDir, '..', 'lib', 'node_modules'), path.join(binDir, 'node_modules')];
|
|
117
|
+
for (const prefix of [process.env.npm_config_prefix, process.env.PREFIX, path.join(home, '.npm-global')]) {
|
|
118
|
+
if (prefix) globalModuleDirs.push(path.join(prefix, 'lib', 'node_modules'));
|
|
119
|
+
}
|
|
120
|
+
for (const gm of globalModuleDirs) {
|
|
121
|
+
candidates.push(
|
|
122
|
+
path.join(gm, 'ruflo', 'bin', 'cli.js'),
|
|
123
|
+
path.join(gm, '@claude-flow', 'cli', 'bin', 'cli.js'),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
} catch { /* ignore */ }
|
|
127
|
+
} catch { /* ignore */ }
|
|
128
|
+
return candidates.filter((p) => {
|
|
129
|
+
try {
|
|
130
|
+
if (!fs.existsSync(p)) return false;
|
|
131
|
+
// A candidate's bin/cli.js can exist on disk while its compiled
|
|
132
|
+
// dist/ never got built (Claude Code's own plugin marketplace just
|
|
133
|
+
// git-clones the repo — no install/build step — so every marketplace
|
|
134
|
+
// install is a source-only checkout by construction). Importing
|
|
135
|
+
// dist/src/index.js from bin/cli.js then throws MODULE_NOT_FOUND on
|
|
136
|
+
// every real command; only --version happens to survive it. Check
|
|
137
|
+
// for the compiled entrypoint too so a doomed candidate is skipped
|
|
138
|
+
// up front instead of wasting a spawn-and-fail on every render.
|
|
139
|
+
return fs.existsSync(path.join(path.dirname(p), '..', 'dist', 'src', 'index.js'));
|
|
140
|
+
} catch { return false; }
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Return { fresh, promoFresh, data }. 'fresh' is true only if within the TTL
|
|
145
|
+
// — but data is returned regardless (stale-while-revalidate). This lets us
|
|
146
|
+
// serve last known state (specifically the promo row) when the CLI is
|
|
147
|
+
// slow/unavailable, so users don't see the funnel row flicker in and out on
|
|
148
|
+
// cache expiry. 'promoFresh' is a SEPARATE, tighter check on the same clock
|
|
149
|
+
// as PROMO_ROTATION_SLOT_MS — see that constant's comment for why the promo
|
|
150
|
+
// row needs its own freshness bound distinct from the general 60s TTL.
|
|
151
|
+
function readCache() {
|
|
152
|
+
try {
|
|
153
|
+
if (fs.existsSync(CACHE_FILE)) {
|
|
154
|
+
const raw = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf-8'));
|
|
155
|
+
if (raw && raw._ts && raw.data) {
|
|
156
|
+
const age = Date.now() - raw._ts;
|
|
157
|
+
return { fresh: age < CACHE_TTL_MS, promoFresh: age < PROMO_ROTATION_SLOT_MS, data: raw.data };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
} catch { /* ignore */ }
|
|
161
|
+
return { fresh: false, promoFresh: false, data: null };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function writeCache(data) {
|
|
165
|
+
try { fs.writeFileSync(CACHE_FILE, JSON.stringify({ _ts: Date.now(), data }), 'utf-8'); } catch { /* ignore */ }
|
|
166
|
+
// Also memoize any promo we saw so the row can survive future CLI hiccups.
|
|
167
|
+
try {
|
|
168
|
+
if (data && data.promo && typeof data.promo === 'object') {
|
|
169
|
+
fs.mkdirSync(path.dirname(PROMO_MEMO_FILE), { recursive: true, mode: 0o700 });
|
|
170
|
+
fs.writeFileSync(PROMO_MEMO_FILE, JSON.stringify({ _ts: Date.now(), promo: data.promo }), { encoding: 'utf-8', mode: 0o600 });
|
|
171
|
+
}
|
|
172
|
+
} catch { /* ignore */ }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Last resort: read a memoized promo (up to 6h old). Used when no cache and
|
|
176
|
+
// no CLI response is available — the row still renders, so users don't see
|
|
177
|
+
// the disclosure blink out. Returns null when the memo is absent, expired,
|
|
178
|
+
// or malformed. Never throws.
|
|
179
|
+
function readPromoMemo() {
|
|
180
|
+
try {
|
|
181
|
+
if (!fs.existsSync(PROMO_MEMO_FILE)) return null;
|
|
182
|
+
const raw = JSON.parse(fs.readFileSync(PROMO_MEMO_FILE, 'utf-8'));
|
|
183
|
+
if (raw && raw._ts && (Date.now() - raw._ts) < PROMO_MEMO_TTL_MS && raw.promo) {
|
|
184
|
+
return raw.promo;
|
|
185
|
+
}
|
|
186
|
+
} catch { /* ignore */ }
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Single source of truth: delegate to the CLI hooks statusline --json command.
|
|
192
|
+
* Falls back to a minimal static object on failure so the statusline still renders.
|
|
193
|
+
*
|
|
194
|
+
* Fix for ruflo#2195: the previous local readers returned 0 for AgentDB patterns
|
|
195
|
+
* (missed the .swarm/memory.db → AgentDB path), computed dddProgress wrong,
|
|
196
|
+
* and only counted ADRs in v3/implementation/adrs/ (missed v3/docs/adr/).
|
|
197
|
+
*/
|
|
198
|
+
// Overlay the memoized promo onto any data object that's missing one. This is
|
|
199
|
+
// the safety net that keeps the funnel row rendered when an OLDER cached CLI
|
|
200
|
+
// version is picked up by npx — that older CLI succeeds but omits promo, so
|
|
201
|
+
// the JSON round-trips clean but without our row. We patch it back here.
|
|
202
|
+
function overlayMemoPromo(data) {
|
|
203
|
+
if (data && !data.promo) {
|
|
204
|
+
const memoPromo = readPromoMemo();
|
|
205
|
+
if (memoPromo) data.promo = memoPromo;
|
|
206
|
+
}
|
|
207
|
+
return data;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function getStatuslineData() {
|
|
211
|
+
const cache = readCache();
|
|
212
|
+
// Both clocks must be satisfied to skip the CLI call entirely: the general
|
|
213
|
+
// 60s TTL (#2337 — don't re-spawn the CLI on every rapid re-render) AND the
|
|
214
|
+
// tighter promo-rotation clock (this fix — don't let a still-fresh 60s
|
|
215
|
+
// cache silently freeze the promo/insight row across multiple 20s slots).
|
|
216
|
+
if (cache.fresh && cache.promoFresh) {
|
|
217
|
+
return applyLocalOverlays(overlayMemoPromo(cache.data));
|
|
328
218
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
//
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
//
|
|
342
|
-
|
|
219
|
+
|
|
220
|
+
// #2337: prefer an already-installed CLI bin via direct `node` invocation —
|
|
221
|
+
// no npx, no registry round-trip, no @latest re-resolve per render. Try
|
|
222
|
+
// every candidate that actually EXISTS (not just the first) before falling
|
|
223
|
+
// back to `npx --prefer-offline @claude-flow/cli` (no @latest); an existing
|
|
224
|
+
// but broken install (e.g. a stale marketplace checkout missing a bundled
|
|
225
|
+
// workspace dep) must not block trying the next one.
|
|
226
|
+
//
|
|
227
|
+
// No `2>/dev/null` here (deliberately) — the execSync call below already
|
|
228
|
+
// sets stdio: ['pipe','pipe','pipe'], which captures/discards stderr at the
|
|
229
|
+
// Node level regardless of shell. The redirect was redundant on POSIX and
|
|
230
|
+
// actively broke every candidate on Windows: cmd.exe (execSync's default
|
|
231
|
+
// shell there) doesn't understand /dev/null, so the CLI delegation always
|
|
232
|
+
// failed, silently degrading every render to buildLocalFallback() — 0%
|
|
233
|
+
// intelligence and an empty promo row (the memo cache that keeps the row
|
|
234
|
+
// populated across CLI hiccups is only ever written from a SUCCESSFUL
|
|
235
|
+
// delegation, so it could never get seeded on Windows either).
|
|
236
|
+
const cmds = resolveCliBinCandidates()
|
|
237
|
+
.map((bin) => '"' + process.execPath + '" "' + bin + '" hooks statusline --json')
|
|
238
|
+
.concat(['npx --prefer-offline @claude-flow/cli hooks statusline --json']);
|
|
239
|
+
for (const cmd of cmds) {
|
|
240
|
+
try {
|
|
241
|
+
const raw = execSync(
|
|
242
|
+
cmd,
|
|
243
|
+
{ encoding: 'utf-8', timeout: 8000, stdio: ['pipe', 'pipe', 'pipe'], cwd: CWD, windowsHide: true }
|
|
244
|
+
).trim();
|
|
245
|
+
// The CLI may emit preamble lines before the JSON — find the first '{'.
|
|
246
|
+
const jsonStart = raw.indexOf('{');
|
|
247
|
+
if (jsonStart === -1) throw new Error('no JSON in CLI output');
|
|
248
|
+
const data = JSON.parse(raw.slice(jsonStart));
|
|
249
|
+
// Overlay every block the CLI JSON omits (adrs/agentdb/tests/hooks/integration)
|
|
250
|
+
// with real local reads, so those segments reflect actual state instead of 0.
|
|
251
|
+
applyLocalOverlays(data);
|
|
252
|
+
overlayMemoPromo(data);
|
|
253
|
+
writeCache(data);
|
|
254
|
+
return data;
|
|
255
|
+
} catch { /* this candidate unavailable, broken, or timed out — try the next */ }
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Stale-while-revalidate: if we have any cached data, keep serving it so the
|
|
259
|
+
// funnel row doesn't flicker on CLI hiccups. Overlay fresh local reads for
|
|
260
|
+
// the segments the CLI JSON doesn't populate; the promo row survives.
|
|
261
|
+
if (cache.data) {
|
|
262
|
+
applyLocalOverlays(cache.data);
|
|
263
|
+
overlayMemoPromo(cache.data);
|
|
264
|
+
return cache.data;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Last resort: local probes + memo. Users still see the funnel row.
|
|
268
|
+
return overlayMemoPromo(buildLocalFallback());
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Count ADRs from BOTH known directories (fix for ruflo#2195: old code missed
|
|
272
|
+
// v3/docs/adr/ which holds ADR-088..ADR-137, i.e. 41 of the 128 total ADRs).
|
|
273
|
+
function getLocalADRCount() {
|
|
274
|
+
const adrDirs = [
|
|
275
|
+
path.join(CWD, 'v3', 'implementation', 'adrs'),
|
|
276
|
+
path.join(CWD, 'v3', 'docs', 'adr'),
|
|
277
|
+
path.join(CWD, 'docs', 'adrs'),
|
|
278
|
+
path.join(CWD, '.claude-flow', 'adrs'),
|
|
279
|
+
];
|
|
280
|
+
let total = 0;
|
|
281
|
+
for (const dir of adrDirs) {
|
|
282
|
+
try {
|
|
283
|
+
if (fs.existsSync(dir)) {
|
|
284
|
+
const files = fs.readdirSync(dir).filter(function(f) {
|
|
285
|
+
return f.endsWith('.md') && (f.startsWith('ADR-') || f.startsWith('adr-') || /^\d{4}-/.test(f));
|
|
286
|
+
});
|
|
287
|
+
total += files.length;
|
|
288
|
+
}
|
|
289
|
+
} catch { /* ignore */ }
|
|
290
|
+
}
|
|
291
|
+
return { count: total, implemented: total, compliance: 0 };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ─── Local overlays for segments the CLI JSON omits ──────────────
|
|
295
|
+
// 'hooks statusline --json' only returns user/v3Progress/security/swarm/system.
|
|
296
|
+
// agentdb/tests/hooks/integration are never populated, so without these overlays
|
|
297
|
+
// they render as a permanent 0. Each reader is cheap and degrades to zeros.
|
|
298
|
+
|
|
299
|
+
// Real AgentDB stats from the local memory DB. Vectors live in .swarm/memory.db
|
|
300
|
+
// (sql.js + HNSW); ruvector.db is an opaque redb store counted only toward size.
|
|
301
|
+
// One read-only sqlite3 query (mode=ro never takes a write lock the daemon owns).
|
|
302
|
+
function getLocalAgentDB() {
|
|
303
|
+
const result = { vectorCount: 0, dbSizeKB: 0, hasHnsw: false };
|
|
304
|
+
try {
|
|
305
|
+
let bytes = 0;
|
|
306
|
+
for (const f of ['.swarm/memory.db', 'ruvector.db']) {
|
|
307
|
+
try { bytes += fs.statSync(path.join(CWD, f)).size; } catch { /* missing */ }
|
|
308
|
+
}
|
|
309
|
+
result.dbSizeKB = Math.round(bytes / 1024);
|
|
310
|
+
|
|
311
|
+
const memDb = path.join(CWD, '.swarm', 'memory.db');
|
|
312
|
+
if (fs.existsSync(memDb)) {
|
|
313
|
+
const Q = String.fromCharCode(34);
|
|
314
|
+
// Two INDEPENDENT statements -- do NOT combine into one. Coupling the
|
|
315
|
+
// vector count with the vector_indexes row count in a single statement
|
|
316
|
+
// meant that on a DB missing the vector_indexes table (older/agentdb-
|
|
317
|
+
// written DBs), the whole statement failed at PREPARE time (SQLite
|
|
318
|
+
// compiles the full SQL before running), so the valid memory_entries
|
|
319
|
+
// count was discarded too and the statusline showed Vectors 0 despite
|
|
320
|
+
// thousands of real vectors. Split so a missing table can only zero the
|
|
321
|
+
// HNSW flag, never the count. The init self-heal provisions the table so
|
|
322
|
+
// the flag recovers on the next ruflo init / MCP start.
|
|
323
|
+
const countSql = Q + 'SELECT COUNT(*) FROM memory_entries WHERE embedding IS NOT NULL;' + Q;
|
|
324
|
+
const vc = safeExec("sqlite3 'file:" + memDb + "?mode=ro' " + countSql, 1500);
|
|
325
|
+
if (vc) result.vectorCount = parseInt(vc, 10) || 0;
|
|
326
|
+
// HNSW flag: separate statement. If vector_indexes is absent, sqlite3
|
|
327
|
+
// exits non-zero and safeExec returns empty -- hasHnsw stays false (exact
|
|
328
|
+
// original semantics: at least one index-config row present).
|
|
329
|
+
const hnswSql = Q + 'SELECT COUNT(*) FROM vector_indexes;' + Q;
|
|
330
|
+
const hn = safeExec("sqlite3 'file:" + memDb + "?mode=ro' " + hnswSql, 1500);
|
|
331
|
+
if (hn) result.hasHnsw = (parseInt(hn, 10) || 0) > 0;
|
|
332
|
+
}
|
|
333
|
+
} catch { /* ignore */ }
|
|
334
|
+
return result;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Count test files via a bounded directory walk (no file reads).
|
|
338
|
+
function getLocalTests() {
|
|
339
|
+
let testFiles = 0;
|
|
340
|
+
function countTests(dir, depth) {
|
|
341
|
+
if ((depth || 0) > 4) return;
|
|
342
|
+
try {
|
|
343
|
+
if (!fs.existsSync(dir)) return;
|
|
344
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
345
|
+
if (e.isDirectory() && !e.name.startsWith('.') && e.name !== 'node_modules') {
|
|
346
|
+
countTests(path.join(dir, e.name), (depth || 0) + 1);
|
|
347
|
+
} else if (e.isFile() && (e.name.includes('.test.') || e.name.includes('.spec.') || e.name.startsWith('test_') || e.name.startsWith('spec_'))) {
|
|
348
|
+
testFiles++;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
} catch { /* ignore */ }
|
|
352
|
+
}
|
|
353
|
+
for (const d of ['tests', 'test', '__tests__', 'src', 'v3']) countTests(path.join(CWD, d));
|
|
354
|
+
return { testFiles, testCases: testFiles * 4 };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Count configured hooks from project .claude/settings.json. Claude Code hooks
|
|
358
|
+
// have no enabled/disabled flag, so every configured hook counts as enabled.
|
|
359
|
+
function getLocalHooks() {
|
|
360
|
+
const result = { enabled: 0, total: 0 };
|
|
361
|
+
try {
|
|
362
|
+
const settings = readJSON(path.join(CWD, '.claude', 'settings.json'));
|
|
363
|
+
const hooks = settings && settings.hooks;
|
|
364
|
+
if (hooks && typeof hooks === 'object') {
|
|
365
|
+
let n = 0;
|
|
366
|
+
for (const ev of Object.keys(hooks)) {
|
|
367
|
+
const groups = hooks[ev];
|
|
368
|
+
if (Array.isArray(groups)) {
|
|
369
|
+
for (const g of groups) {
|
|
370
|
+
if (g && Array.isArray(g.hooks)) n += g.hooks.length;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
result.total = n;
|
|
375
|
+
result.enabled = n;
|
|
376
|
+
}
|
|
377
|
+
} catch { /* ignore */ }
|
|
378
|
+
return result;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Best-effort integration block: DB presence + locally-configured stdio MCP
|
|
382
|
+
// servers (project .mcp.json + global ~/.claude.json). Remote connectors are
|
|
383
|
+
// account-managed and not present in local config, so they are not counted.
|
|
384
|
+
function getLocalIntegration() {
|
|
385
|
+
const integration = { mcpServers: { enabled: 0, total: 0 }, hasDatabase: false };
|
|
386
|
+
try {
|
|
387
|
+
for (const f of ['.swarm/memory.db', 'ruvector.db']) {
|
|
388
|
+
if (fs.existsSync(path.join(CWD, f))) { integration.hasDatabase = true; break; }
|
|
389
|
+
}
|
|
390
|
+
const names = new Set();
|
|
391
|
+
const projMcp = readJSON(path.join(CWD, '.mcp.json'));
|
|
392
|
+
if (projMcp && projMcp.mcpServers) for (const k of Object.keys(projMcp.mcpServers)) names.add(k);
|
|
393
|
+
const claudeJson = readJSON(path.join(os.homedir(), '.claude.json'));
|
|
394
|
+
if (claudeJson) {
|
|
395
|
+
if (claudeJson.mcpServers) for (const k of Object.keys(claudeJson.mcpServers)) names.add(k);
|
|
396
|
+
const proj = claudeJson.projects && claudeJson.projects[CWD];
|
|
397
|
+
if (proj && proj.mcpServers && !Array.isArray(proj.mcpServers)) {
|
|
398
|
+
for (const k of Object.keys(proj.mcpServers)) names.add(k);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
integration.mcpServers.total = names.size;
|
|
402
|
+
integration.mcpServers.enabled = names.size;
|
|
403
|
+
} catch { /* ignore */ }
|
|
404
|
+
return integration;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Overlay every locally-derived block onto the CLI data (mutates in place).
|
|
408
|
+
function applyLocalOverlays(data) {
|
|
409
|
+
data.adrs = getLocalADRCount();
|
|
410
|
+
data.agentdb = getLocalAgentDB();
|
|
411
|
+
data.tests = getLocalTests();
|
|
412
|
+
data.hooks = getLocalHooks();
|
|
413
|
+
data.integration = getLocalIntegration();
|
|
414
|
+
return data;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Minimal local fallback when the CLI is not installed or times out.
|
|
418
|
+
// Returns a structure that matches the CLI JSON schema so the renderer works.
|
|
419
|
+
function buildLocalFallback() {
|
|
420
|
+
const memMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
|
|
421
|
+
|
|
422
|
+
return applyLocalOverlays({
|
|
423
|
+
user: { name: 'user', gitBranch: '', modelName: 'Claude Code' },
|
|
424
|
+
v3Progress: { domainsCompleted: 0, totalDomains: 5, dddProgress: 0, patternsLearned: 0, sessionsCompleted: 0 },
|
|
425
|
+
security: { status: 'NONE', findings: 0, cvesFixed: 0, totalCves: 0 },
|
|
426
|
+
swarm: { activeAgents: 0, maxAgents: CONFIG.maxAgents, coordinationActive: false },
|
|
427
|
+
system: { memoryMB: memMB, contextPct: 0, intelligencePct: 0, subAgents: 0 },
|
|
428
|
+
lastUpdated: new Date().toISOString(),
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// ANSI colors
|
|
433
|
+
const c = {
|
|
434
|
+
reset: '\x1b[0m',
|
|
435
|
+
bold: '\x1b[1m',
|
|
436
|
+
dim: '\x1b[2m',
|
|
437
|
+
red: '\x1b[0;31m',
|
|
438
|
+
green: '\x1b[0;32m',
|
|
439
|
+
yellow: '\x1b[0;33m',
|
|
440
|
+
blue: '\x1b[0;34m',
|
|
441
|
+
purple: '\x1b[0;35m',
|
|
442
|
+
cyan: '\x1b[0;36m',
|
|
443
|
+
brightRed: '\x1b[1;31m',
|
|
444
|
+
brightGreen: '\x1b[1;32m',
|
|
445
|
+
brightYellow: '\x1b[1;33m',
|
|
446
|
+
brightBlue: '\x1b[1;34m',
|
|
447
|
+
brightPurple: '\x1b[1;35m',
|
|
448
|
+
brightCyan: '\x1b[1;36m',
|
|
449
|
+
brightWhite: '\x1b[1;37m',
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
// Safe execSync with strict timeout (returns empty string on failure)
|
|
453
|
+
function safeExec(cmd, timeoutMs) {
|
|
454
|
+
try {
|
|
455
|
+
return execSync(cmd, {
|
|
456
|
+
encoding: 'utf-8',
|
|
457
|
+
timeout: timeoutMs || 2000,
|
|
458
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
459
|
+
// Windows: without this, every execSync spawns cmd.exe /d /s /c which
|
|
460
|
+
// flashes a visible console window every render (~1/min via the 60s
|
|
461
|
+
// cache TTL). windowsHide runs the child in a hidden window instead.
|
|
462
|
+
// No-op on POSIX. Fix for #2XXX (user report: "cmd prompt keeps opening").
|
|
463
|
+
windowsHide: true,
|
|
464
|
+
}).trim();
|
|
465
|
+
} catch {
|
|
466
|
+
return '';
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Safe JSON file reader (returns null on failure)
|
|
471
|
+
function readJSON(filePath) {
|
|
472
|
+
try {
|
|
473
|
+
if (fs.existsSync(filePath)) {
|
|
474
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
475
|
+
}
|
|
476
|
+
} catch { /* ignore */ }
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// ─── Git info (pure-Node / single exec — needed for branch display) ──────────
|
|
481
|
+
|
|
343
482
|
function getGitInfo() {
|
|
344
483
|
const result = {
|
|
345
|
-
name: '
|
|
484
|
+
name: path.basename(CWD) || 'project', gitBranch: '', modified: 0, untracked: 0,
|
|
346
485
|
staged: 0, ahead: 0, behind: 0,
|
|
347
486
|
};
|
|
348
487
|
|
|
349
488
|
const script = [
|
|
350
|
-
'git
|
|
351
|
-
'echo "---SEP---"',
|
|
352
|
-
'git branch --show-current 2>/dev/null',
|
|
489
|
+
'git rev-parse --show-toplevel 2>/dev/null || pwd',
|
|
353
490
|
'echo "---SEP---"',
|
|
354
|
-
'git
|
|
355
|
-
'echo "---SEP---"',
|
|
356
|
-
'git
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
const
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
491
|
+
'git config user.name 2>/dev/null || echo user',
|
|
492
|
+
'echo "---SEP---"',
|
|
493
|
+
'git branch --show-current 2>/dev/null',
|
|
494
|
+
'echo "---SEP---"',
|
|
495
|
+
'git status --porcelain 2>/dev/null',
|
|
496
|
+
'echo "---SEP---"',
|
|
497
|
+
'git rev-list --left-right --count HEAD...@{upstream} 2>/dev/null || echo "0 0"',
|
|
498
|
+
].join('; ');
|
|
499
|
+
|
|
500
|
+
const raw = safeExec("sh -c '" + script + "'", 3000);
|
|
501
|
+
if (!raw) return result;
|
|
502
|
+
|
|
503
|
+
const parts = raw.split('---SEP---').map(function(s) { return s.trim(); });
|
|
504
|
+
if (parts.length >= 5) {
|
|
505
|
+
const projectName = path.basename(parts[0] || CWD) || path.basename(CWD) || 'project';
|
|
506
|
+
const authorName = parts[1] || 'user';
|
|
507
|
+
result.name = CONFIG.identityMode === 'author' ? authorName : projectName;
|
|
508
|
+
result.gitBranch = parts[2] || '';
|
|
509
|
+
|
|
510
|
+
if (parts[3]) {
|
|
511
|
+
for (const line of parts[3].split('\n')) {
|
|
512
|
+
if (!line || line.length < 2) continue;
|
|
513
|
+
const x = line[0], y = line[1];
|
|
514
|
+
if (x === '?' && y === '?') { result.untracked++; continue; }
|
|
515
|
+
if (x !== ' ' && x !== '?') result.staged++;
|
|
516
|
+
if (y !== ' ' && y !== '?') result.modified++;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const ab = (parts[4] || '0 0').split(/\s+/);
|
|
521
|
+
result.ahead = parseInt(ab[0]) || 0;
|
|
522
|
+
result.behind = parseInt(ab[1]) || 0;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
return result;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Detect model name from Claude config (pure file reads, no exec)
|
|
529
|
+
function getModelName() {
|
|
530
|
+
try {
|
|
531
|
+
const claudeConfig = readJSON(path.join(os.homedir(), '.claude.json'));
|
|
532
|
+
if (claudeConfig && claudeConfig.projects) {
|
|
533
|
+
for (const [projectPath, projectConfig] of Object.entries(claudeConfig.projects)) {
|
|
534
|
+
if (CWD === projectPath || CWD.startsWith(projectPath + '/')) {
|
|
535
|
+
const usage = projectConfig.lastModelUsage;
|
|
536
|
+
if (usage) {
|
|
537
|
+
const ids = Object.keys(usage);
|
|
538
|
+
if (ids.length > 0) {
|
|
539
|
+
let modelId = ids[ids.length - 1];
|
|
540
|
+
let latest = 0;
|
|
541
|
+
for (const id of ids) {
|
|
542
|
+
const ts = usage[id] && usage[id].lastUsedAt ? new Date(usage[id].lastUsedAt).getTime() : 0;
|
|
543
|
+
if (ts > latest) { latest = ts; modelId = id; }
|
|
544
|
+
}
|
|
545
|
+
if (modelId.includes('opus')) return 'Opus 4.8';
|
|
546
|
+
if (modelId.includes('sonnet')) return 'Sonnet 4.6';
|
|
547
|
+
if (modelId.includes('haiku')) return 'Haiku 4.5';
|
|
548
|
+
return modelId.split('-').slice(1, 3).join(' ');
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
break;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
} catch { /* ignore */ }
|
|
556
|
+
|
|
557
|
+
// Fallback: settings.json model field
|
|
558
|
+
const settings = getSettings();
|
|
559
|
+
if (settings && settings.model) {
|
|
560
|
+
const m = settings.model;
|
|
561
|
+
if (m.includes('opus')) return 'Opus 4.8';
|
|
562
|
+
if (m.includes('sonnet')) return 'Sonnet 4.6';
|
|
563
|
+
if (m.includes('haiku')) return 'Haiku 4.5';
|
|
564
|
+
}
|
|
565
|
+
return 'Claude Code';
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// ─── Stdin reader (Claude Code pipes session JSON) ──────────────
|
|
569
|
+
// Claude Code sends session JSON via stdin. Read synchronously so the
|
|
570
|
+
// script works both when invoked by Claude Code (stdin has JSON) and
|
|
571
|
+
// when run manually from terminal (stdin is empty/tty).
|
|
572
|
+
let _stdinData = null;
|
|
573
|
+
function getStdinData() {
|
|
574
|
+
if (_stdinData !== undefined && _stdinData !== null) return _stdinData;
|
|
575
|
+
try {
|
|
576
|
+
if (process.stdin.isTTY) { _stdinData = null; return null; }
|
|
577
|
+
const chunks = [];
|
|
578
|
+
const buf = Buffer.alloc(4096);
|
|
579
|
+
let bytesRead;
|
|
580
|
+
try {
|
|
581
|
+
while ((bytesRead = fs.readSync(0, buf, 0, buf.length, null)) > 0) {
|
|
582
|
+
chunks.push(buf.slice(0, bytesRead));
|
|
583
|
+
}
|
|
584
|
+
} catch { /* EOF or read error */ }
|
|
585
|
+
const raw = Buffer.concat(chunks).toString('utf-8').trim();
|
|
586
|
+
_stdinData = (raw && raw.startsWith('{')) ? JSON.parse(raw) : null;
|
|
587
|
+
} catch {
|
|
588
|
+
_stdinData = null;
|
|
589
|
+
}
|
|
590
|
+
return _stdinData;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function getModelFromStdin() {
|
|
594
|
+
const data = getStdinData();
|
|
595
|
+
return (data && data.model && data.model.display_name) ? data.model.display_name : null;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function getContextFromStdin() {
|
|
599
|
+
const data = getStdinData();
|
|
600
|
+
if (data && data.context_window) {
|
|
601
|
+
return { usedPct: Math.floor(data.context_window.used_percentage || 0) };
|
|
602
|
+
}
|
|
603
|
+
return null;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function getCostFromStdin() {
|
|
607
|
+
const data = getStdinData();
|
|
608
|
+
if (data && data.cost) {
|
|
609
|
+
const durationMs = data.cost.total_duration_ms || 0;
|
|
610
|
+
const mins = Math.floor(durationMs / 60000);
|
|
611
|
+
const secs = Math.floor((durationMs % 60000) / 1000);
|
|
612
|
+
return {
|
|
613
|
+
costUsd: data.cost.total_cost_usd || 0,
|
|
614
|
+
duration: mins > 0 ? mins + 'm' + secs + 's' : secs + 's',
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
return null;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Compares dotted-numeric version strings (e.g. "3.27.1" vs "3.27.10").
|
|
621
|
+
// Returns >0 if a>b, <0 if a<b, 0 if equal-as-far-as-parseable. Deliberately
|
|
622
|
+
// simple (no prerelease/build-metadata handling) — this only orders local
|
|
623
|
+
// package.json versions against each other, never anything untrusted from
|
|
624
|
+
// a payload, so a full semver implementation would be dead weight here.
|
|
625
|
+
function compareVersions(a, b) {
|
|
626
|
+
const pa = String(a).split('.').map((n) => parseInt(n, 10));
|
|
627
|
+
const pb = String(b).split('.').map((n) => parseInt(n, 10));
|
|
628
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
629
|
+
const na = Number.isFinite(pa[i]) ? pa[i] : 0;
|
|
630
|
+
const nb = Number.isFinite(pb[i]) ? pb[i] : 0;
|
|
631
|
+
if (na !== nb) return na - nb;
|
|
632
|
+
}
|
|
633
|
+
return 0;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function getPkgVersion() {
|
|
637
|
+
// Baked in at generation time from the real running CLI's own resolved
|
|
638
|
+
// version (see generateStatuslineScript()'s doc comment) — correct even
|
|
639
|
+
// when this renders via a pure npx invocation with no local install for
|
|
640
|
+
// the candidate scan below to find.
|
|
641
|
+
let ver = "3.28.0";
|
|
642
|
+
try {
|
|
643
|
+
const home = os.homedir();
|
|
644
|
+
const pkgPaths = [
|
|
645
|
+
path.join(home, '.claude', 'plugins', 'marketplaces', 'ruflo', 'package.json'),
|
|
646
|
+
path.join(CWD, 'node_modules', '@claude-flow', 'cli', 'package.json'),
|
|
647
|
+
path.join(CWD, 'node_modules', 'ruflo', 'package.json'),
|
|
648
|
+
path.join(CWD, 'v3', '@claude-flow', 'cli', 'package.json'),
|
|
649
|
+
];
|
|
650
|
+
// #2221: global installs (npm i -g ruflo) live outside CWD/node_modules, so the
|
|
651
|
+
// probes above all miss and the version falls back to the hard-coded default.
|
|
652
|
+
// Derive the global node_modules dir from the running node binary (no npm spawn —
|
|
653
|
+
// statusline renders often). Covers nvm/mise (bin/../lib/node_modules) and Windows
|
|
654
|
+
// (bin/node_modules) layouts.
|
|
655
|
+
try {
|
|
656
|
+
const binDir = path.dirname(process.execPath);
|
|
657
|
+
const globalModuleDirs = [path.join(binDir, '..', 'lib', 'node_modules'), path.join(binDir, 'node_modules')];
|
|
658
|
+
// #2221 follow-up: a custom npm prefix (e.g. ~/.npm-global) is decoupled from
|
|
659
|
+
// the node binary location, so the binDir-derived probes above all miss. Also
|
|
660
|
+
// probe the npm prefix from the environment and the common ~/.npm-global default.
|
|
661
|
+
for (const prefix of [process.env.npm_config_prefix, process.env.PREFIX, path.join(home, '.npm-global')]) {
|
|
662
|
+
if (prefix) globalModuleDirs.push(path.join(prefix, 'lib', 'node_modules'));
|
|
663
|
+
}
|
|
664
|
+
for (const gm of globalModuleDirs) {
|
|
665
|
+
pkgPaths.push(
|
|
666
|
+
path.join(gm, 'ruflo', 'package.json'),
|
|
667
|
+
path.join(gm, '@claude-flow', 'cli', 'package.json'),
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
} catch { /* ignore */ }
|
|
671
|
+
// Pick the HIGHEST version among every candidate that exists, not the
|
|
672
|
+
// first one found. The marketplace plugin path is probed first (list
|
|
673
|
+
// order above), but Claude Code's own plugin marketplace mechanism
|
|
674
|
+
// syncs on its own git-pull cadence, independent of npm publishes — a
|
|
675
|
+
// freshly-published npm version can sit alongside a stale marketplace
|
|
676
|
+
// checkout for a while (observed live: marketplace one release behind
|
|
677
|
+
// right after a publish). Taking the first EXISTING candidate meant the
|
|
678
|
+
// header could show a stale version even when a newer install (e.g.
|
|
679
|
+
// node_modules/@claude-flow/cli from a plain npm install) was sitting right there.
|
|
680
|
+
let found = false;
|
|
681
|
+
for (const p of pkgPaths) {
|
|
682
|
+
if (!fs.existsSync(p)) continue;
|
|
683
|
+
try {
|
|
684
|
+
const pkg = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
685
|
+
if (pkg && typeof pkg.version === 'string' && pkg.version.length > 0) {
|
|
686
|
+
if (!found || compareVersions(pkg.version, ver) > 0) ver = pkg.version;
|
|
687
|
+
found = true;
|
|
688
|
+
}
|
|
689
|
+
} catch { /* ignore */ }
|
|
690
|
+
}
|
|
691
|
+
} catch { /* ignore */ }
|
|
692
|
+
return ver;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// ─── Rendering ──────────────────────────────────────────────────
|
|
696
|
+
|
|
697
|
+
function progressBar(current, total) {
|
|
698
|
+
const width = 5;
|
|
699
|
+
const filled = Math.round((current / total) * width);
|
|
700
|
+
return '[' + '●'.repeat(filled) + '○'.repeat(width - filled) + ']';
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function generateStatusline() {
|
|
704
|
+
const d = getStatuslineData();
|
|
705
|
+
const git = getGitInfo();
|
|
706
|
+
const modelName = getModelFromStdin() || (d.user && d.user.modelName) || 'Claude Code';
|
|
707
|
+
const ctxInfo = getContextFromStdin();
|
|
708
|
+
const costInfo = getCostFromStdin();
|
|
709
|
+
// Named RUFLO_VERSION (not pkgVersion) so the #1951 regression guard
|
|
710
|
+
// (scripts/audit-fix-invariants.mjs) can pin its presence in the emitted
|
|
711
|
+
// .cjs artifact — without it the header silently reverts to a hard-coded
|
|
712
|
+
// "RuFlo V3.5" for anyone whose install doesn't match the first probe path.
|
|
713
|
+
const RUFLO_VERSION = getPkgVersion();
|
|
714
|
+
|
|
715
|
+
const progress = d.v3Progress || {};
|
|
716
|
+
const security = d.security || {};
|
|
717
|
+
const swarm = d.swarm || {};
|
|
718
|
+
const system = d.system || {};
|
|
719
|
+
const adrs = d.adrs || {};
|
|
720
|
+
const hooks = d.hooks || {};
|
|
721
|
+
const agentdb = d.agentdb || {};
|
|
722
|
+
const tests = d.tests || {};
|
|
723
|
+
|
|
724
|
+
const domainsCompleted = progress.domainsCompleted || 0;
|
|
725
|
+
const totalDomains = progress.totalDomains || 5;
|
|
726
|
+
const dddProgress = progress.dddProgress || 0;
|
|
727
|
+
const patternsLearned = progress.patternsLearned || 0;
|
|
728
|
+
const activeAgents = swarm.activeAgents || 0;
|
|
729
|
+
const maxAgents = swarm.maxAgents || CONFIG.maxAgents;
|
|
730
|
+
const coordinationActive = swarm.coordinationActive || false;
|
|
731
|
+
const intelligencePct = system.intelligencePct || 0;
|
|
732
|
+
const memoryMB = system.memoryMB || 0;
|
|
733
|
+
const subAgents = system.subAgents || 0;
|
|
734
|
+
const findings = Math.max(0, security.findings || 0);
|
|
735
|
+
const secStatus = security.status || 'NONE';
|
|
736
|
+
const adrCount = adrs.count || 0;
|
|
737
|
+
const adrImpl = adrs.implemented || 0;
|
|
738
|
+
const hooksEnabled = hooks.enabled || 0;
|
|
739
|
+
const hooksTotal = hooks.total || 0;
|
|
740
|
+
const vectorCount = agentdb.vectorCount || 0;
|
|
741
|
+
const hasHnsw = agentdb.hasHnsw || false;
|
|
742
|
+
const dbSizeKB = agentdb.dbSizeKB || 0;
|
|
743
|
+
const testFiles = tests.testFiles || 0;
|
|
744
|
+
const testCases = tests.testCases || testFiles * 4;
|
|
745
|
+
|
|
746
|
+
const lines = [];
|
|
747
|
+
|
|
748
|
+
// 3-line design (fits Claude Code's visible statusline area — line 4+ gets
|
|
749
|
+
// replaced by the system guidance / input prompt line):
|
|
750
|
+
// Line 1 — Header (RuFlo version · git · model · timing · context · cost)
|
|
751
|
+
// Line 2 — Compressed ops (Swarm · Hooks · 🧠 · 💾 · Health)
|
|
752
|
+
// Line 3 — Promo / disclosure row (funnel surface, ADR-301)
|
|
753
|
+
|
|
754
|
+
// ─── Line 1: header ────────────────────────────────────────────
|
|
755
|
+
let header = c.bold + c.brightPurple + '▊ RuFlo V' + RUFLO_VERSION + ' ' + c.reset;
|
|
756
|
+
header += (coordinationActive ? c.brightCyan : c.dim) + '● ' + c.brightCyan + git.name + c.reset;
|
|
757
|
+
if (git.gitBranch) {
|
|
758
|
+
header += ' ' + c.dim + '│' + c.reset + ' ' + c.brightBlue + '⏇ ' + git.gitBranch + c.reset;
|
|
759
|
+
const changes = git.modified + git.staged + git.untracked;
|
|
760
|
+
if (changes > 0) {
|
|
761
|
+
let ind = '';
|
|
762
|
+
if (git.staged > 0) ind += c.brightGreen + '+' + git.staged + c.reset;
|
|
763
|
+
if (git.modified > 0) ind += c.brightYellow + '~' + git.modified + c.reset;
|
|
764
|
+
if (git.untracked > 0) ind += c.dim + '?' + git.untracked + c.reset;
|
|
765
|
+
header += ' ' + ind;
|
|
766
|
+
}
|
|
767
|
+
if (git.ahead > 0) header += ' ' + c.brightGreen + '↑' + git.ahead + c.reset;
|
|
768
|
+
if (git.behind > 0) header += ' ' + c.brightRed + '↓' + git.behind + c.reset;
|
|
769
|
+
}
|
|
770
|
+
header += ' ' + c.dim + '│' + c.reset + ' ' + c.purple + modelName + c.reset;
|
|
771
|
+
const duration = costInfo ? costInfo.duration : '';
|
|
772
|
+
if (duration) header += ' ' + c.dim + '│' + c.reset + ' ' + c.cyan + '⏱ ' + duration + c.reset;
|
|
773
|
+
if (ctxInfo && ctxInfo.usedPct > 0) {
|
|
774
|
+
const ctxColor = ctxInfo.usedPct >= 90 ? c.brightRed : ctxInfo.usedPct >= 70 ? c.brightYellow : c.brightGreen;
|
|
775
|
+
header += ' ' + c.dim + '│' + c.reset + ' ' + ctxColor + '● ' + ctxInfo.usedPct + '% ctx' + c.reset;
|
|
776
|
+
}
|
|
777
|
+
if (!CONFIG.hideCost && costInfo && costInfo.costUsd > 0) {
|
|
778
|
+
header += ' ' + c.dim + '│' + c.reset + ' ' + c.brightYellow + CONFIG.costSymbol + costInfo.costUsd.toFixed(2) + c.reset;
|
|
779
|
+
}
|
|
780
|
+
lines.push(header);
|
|
781
|
+
|
|
782
|
+
// ─── Line 2: compressed ops ────────────────────────────────────
|
|
783
|
+
// Everything actionable in one dense row. Show only what changes what you
|
|
784
|
+
// do next; diagnostic detail moves to `ruflo status --verbose`.
|
|
785
|
+
const agentsColor = activeAgents > 0 ? c.brightGreen : c.dim;
|
|
786
|
+
const hooksColor = hooksEnabled > 0 ? c.brightGreen : c.dim;
|
|
787
|
+
const intellColor = intelligencePct >= 80 ? c.brightGreen : intelligencePct >= 40 ? c.brightYellow : c.dim;
|
|
788
|
+
const swarmInd = coordinationActive ? c.brightGreen + '◉' + c.reset + ' ' : c.dim + '○' + c.reset + ' ';
|
|
789
|
+
const healthAllGreen = (secStatus === 'CLEAN' || secStatus === 'NONE') && findings === 0;
|
|
790
|
+
const opsParts = [];
|
|
791
|
+
opsParts.push(c.cyan + 'Swarm ' + swarmInd + agentsColor + activeAgents + c.reset + '/' + c.brightWhite + maxAgents + c.reset);
|
|
792
|
+
if (subAgents > 0) opsParts.push(c.brightPurple + '👥 ' + subAgents + c.reset);
|
|
793
|
+
opsParts.push(c.cyan + 'Hooks ' + hooksColor + hooksEnabled + c.reset + '/' + c.brightWhite + hooksTotal + c.reset);
|
|
794
|
+
opsParts.push(intellColor + '🧠 ' + intelligencePct + '%' + c.reset);
|
|
795
|
+
opsParts.push(c.brightCyan + '💾 ' + memoryMB + 'MB' + c.reset);
|
|
796
|
+
// Health: one glyph when green, terse copy when there's something to act on.
|
|
797
|
+
if (healthAllGreen) {
|
|
798
|
+
opsParts.push(c.brightGreen + '🛡 ✓' + c.reset);
|
|
799
|
+
} else {
|
|
800
|
+
if (secStatus === 'PENDING') opsParts.push(c.brightYellow + '🛡 scan pending' + c.reset);
|
|
801
|
+
else if (secStatus === 'IN_PROGRESS') opsParts.push(c.brightYellow + '🛡 scanning…' + c.reset);
|
|
802
|
+
else if (secStatus === 'ISSUES') opsParts.push(c.brightRed + '🛡 findings' + c.reset);
|
|
803
|
+
else if (secStatus === 'STALE') opsParts.push(c.brightYellow + '🛡 scan stale' + c.reset);
|
|
804
|
+
else if (secStatus !== 'NONE' && secStatus !== 'CLEAN') opsParts.push(c.brightRed + '🛡 ' + secStatus.toLowerCase() + c.reset);
|
|
805
|
+
if (findings > 0) {
|
|
806
|
+
opsParts.push(c.brightRed + '⚠ ' + findings + ' finding' + (findings === 1 ? '' : 's') + c.reset);
|
|
582
807
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
//
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
808
|
+
}
|
|
809
|
+
lines.push(opsParts.join(' ' + c.dim + '·' + c.reset + ' '));
|
|
810
|
+
|
|
811
|
+
// ─── Line 3: promo / disclosure / insight ───────────────────────
|
|
812
|
+
// Colored by content kind so it reads as *what it is*, not as noise:
|
|
813
|
+
// disclosure → brightCyan (announcement / capability link)
|
|
814
|
+
// promotional → brightPurple (Cognitum sponsor spot)
|
|
815
|
+
// educational → yellow (a tip)
|
|
816
|
+
// insight → brightRed (environment/task-aware, local, actionable —
|
|
817
|
+
// distinct from remote content on purpose)
|
|
818
|
+
const promoRow = getPromoRow(d);
|
|
819
|
+
if (promoRow) {
|
|
820
|
+
const kind = (d && d.promo && d.promo.kind) || 'disclosure';
|
|
821
|
+
const promoColor = kind === 'promotional' ? c.brightPurple
|
|
822
|
+
: kind === 'educational' ? c.yellow
|
|
823
|
+
: kind === 'insight' ? c.brightRed
|
|
824
|
+
: c.brightCyan;
|
|
825
|
+
lines.push(promoColor + promoRow + c.reset);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// Trailing blank line so Claude Code's input prompt gets breathing room
|
|
829
|
+
// instead of butting directly against the last statusline row.
|
|
830
|
+
return lines.join('\n') + '\n';
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// ─── Funnel promo row (ADR-301) ─────────────────────────────────
|
|
834
|
+
// Allowlist for OSC 8 hyperlink targets. Ships in code (not in payload) so
|
|
835
|
+
// no message can smuggle a link to an unapproved host.
|
|
836
|
+
//
|
|
837
|
+
// The final destination hosts (cognitum.one / agentics.org) AND the
|
|
838
|
+
// click-redirect host are both allowlisted here: promo.ts routes every
|
|
839
|
+
// clickable message through the server-side click-redirect (ADR-311 §7)
|
|
840
|
+
// so promo_open + geo are captured before the 302 to the real target —
|
|
841
|
+
// so the OSC 8 link the renderer emits points at the redirect host, not
|
|
842
|
+
// the final destination directly.
|
|
843
|
+
const PROMO_LINK_HOSTS = new Set([
|
|
844
|
+
'cognitum.one', 'www.cognitum.one', 'docs.cognitum.one',
|
|
845
|
+
// agentics.org — OSS foundation, distinct sponsor domain. Kept in sync
|
|
846
|
+
// with messages.ts ALLOWED_URL_HOSTS.
|
|
847
|
+
'agentics.org', 'www.agentics.org',
|
|
848
|
+
// Click-redirect host (funnel.ruv.io once its TLS cert is live; the raw
|
|
849
|
+
// Cloud Run hostname is allowlisted too since event-transport.ts /
|
|
850
|
+
// message-transport.ts / attribution.ts currently point at it as a TEMP
|
|
851
|
+
// fallback while the domain mapping's cert provisions).
|
|
852
|
+
'funnel.ruv.io',
|
|
853
|
+
'cognitum-analytics-63rzcdswba-uc.a.run.app',
|
|
854
|
+
]);
|
|
855
|
+
|
|
856
|
+
// Emit OSC 8 hyperlinks unless the environment is known-broken. tmux mangles
|
|
857
|
+
// raw OSC 8 (see anthropics/claude-code#27047) — opt in via env if wrapped.
|
|
858
|
+
function terminalSupportsHyperlinks() {
|
|
859
|
+
if (process.env.CI || process.env.GITHUB_ACTIONS) return false;
|
|
860
|
+
if (process.env.TERM === 'dumb') return false;
|
|
861
|
+
if (/^(0|false|off|no)$/i.test(String(process.env.RUFLO_STATUSLINE_HYPERLINKS || ''))) return false;
|
|
862
|
+
if (process.env.TMUX && !process.env.RUFLO_STATUSLINE_HYPERLINKS_TMUX) return false;
|
|
863
|
+
return true;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Wrap a label in an OSC 8 hyperlink escape sequence. Falls back to the raw
|
|
867
|
+
// label whenever the URL is not an allowlisted https target, when the terminal
|
|
868
|
+
// can't render hyperlinks, or when parsing fails — a broken link must never
|
|
869
|
+
// leave a raw URL or stray escape in the statusline output.
|
|
870
|
+
function safeTerminalLink(label, url) {
|
|
871
|
+
if (!terminalSupportsHyperlinks()) return label;
|
|
872
|
+
if (typeof url !== 'string' || url.length === 0) return label;
|
|
873
|
+
let parsed;
|
|
874
|
+
try { parsed = new URL(url); } catch { return label; }
|
|
875
|
+
if (parsed.protocol !== 'https:') return label;
|
|
876
|
+
if (!PROMO_LINK_HOSTS.has(parsed.hostname)) return label;
|
|
877
|
+
const cleanLabel = String(label).replace(/[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g, '');
|
|
878
|
+
if (cleanLabel.length === 0) return label;
|
|
879
|
+
const ESC = '\u001b';
|
|
880
|
+
return ESC + ']8;;' + parsed.href + ESC + '\\' + cleanLabel + ESC + ']8;;' + ESC + '\\';
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
function getPromoRow(d) {
|
|
884
|
+
try {
|
|
885
|
+
if (process.env.CI || process.env.GITHUB_ACTIONS) return null;
|
|
886
|
+
if (/^(0|false|off|no)$/i.test(String(process.env.RUFLO_FUNNEL || ''))) return null;
|
|
887
|
+
const promo = d && d.promo;
|
|
888
|
+
if (!promo || typeof promo.text !== 'string') return null;
|
|
889
|
+
// Strip control chars / ANSI / bidi overrides — promo copy is data and
|
|
890
|
+
// must never emit its own terminal sequences. Hard-cap length AFTER the
|
|
891
|
+
// strip; append an ellipsis when the cap fires so the row visibly reads
|
|
892
|
+
// as truncated instead of chopping a word mid-character (was: silent
|
|
893
|
+
// slice(0,100) that could produce output that looked like corrupt data).
|
|
894
|
+
const MAX_LEN = 100;
|
|
895
|
+
const sanitized = promo.text
|
|
896
|
+
.replace(/[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g, '')
|
|
897
|
+
;
|
|
898
|
+
const text = (sanitized.length > MAX_LEN ? sanitized.slice(0, MAX_LEN - 1).trimEnd() + '…' : sanitized).trim();
|
|
899
|
+
if (text.length === 0) return null;
|
|
900
|
+
// Split the label from the trailing "· manage: ruflo settings" instruction
|
|
901
|
+
// so each part gets styling that matches what it actually IS:
|
|
902
|
+
// 1. label — OSC 8 hyperlink + underline. A real clickable link.
|
|
903
|
+
// 2. "manage:" — dim. Just a connector word, no action implied.
|
|
904
|
+
// 3. "ruflo settings" — bold/bright, NOT underlined. This is a shell
|
|
905
|
+
// command the user TYPES, not a link they CLICK — a terminal can
|
|
906
|
+
// never safely execute a command from a click (that would let any
|
|
907
|
+
// server-served message run arbitrary commands), so we deliberately
|
|
908
|
+
// avoid the underline/OSC8 cues that imply "clickable". Bold+bright
|
|
909
|
+
// instead signals "this is the important bit — copy/type it".
|
|
910
|
+
// Educational tips have no manage tail and no URL — plain text through.
|
|
911
|
+
const manageIdx = text.indexOf(' · manage: ');
|
|
912
|
+
const label = manageIdx > 0 ? text.slice(0, manageIdx) : text;
|
|
913
|
+
const manageWord = manageIdx > 0 ? ' · manage: ' : '';
|
|
914
|
+
const command = manageIdx > 0 ? text.slice(manageIdx + manageWord.length) : '';
|
|
915
|
+
const UL_ON = '\u001b[4m';
|
|
916
|
+
const UL_OFF = '\u001b[24m';
|
|
917
|
+
const DIM_ON = '\u001b[2m';
|
|
918
|
+
const DIM_OFF = '\u001b[22m';
|
|
919
|
+
const BOLD_ON = '\u001b[1m';
|
|
920
|
+
const BOLD_OFF = '\u001b[22m';
|
|
921
|
+
const FG_BRIGHT_WHITE = '[97m';
|
|
922
|
+
// Reset FG to default so the caller's row-color code resumes coloring the
|
|
923
|
+
// rest of the row after the command portion. Without this the row-color
|
|
924
|
+
// escape wouldn't visibly re-apply because we already emitted an explicit FG.
|
|
925
|
+
const FG_DEFAULT = '[39m';
|
|
926
|
+
// Some hosts (Claude Code's Windows UI, cmd.exe, older mintty) don't
|
|
927
|
+
// render OSC 8 hyperlinks as clickable — the label just underlines and
|
|
928
|
+
// clicks do nothing. Append a "(domain)" suffix so the destination is
|
|
929
|
+
// visible/copyable everywhere. Wrap the suffix in OSC 8 too so terminals
|
|
930
|
+
// that DO support hyperlinks give users TWO click targets (label AND
|
|
931
|
+
// domain hint) instead of one — some Windows hosts render one but not
|
|
932
|
+
// the other depending on how the statusline row is parsed.
|
|
933
|
+
// Only for URLs (not educational tips), and only when the label doesn't
|
|
934
|
+
// already end in the domain to avoid duplication.
|
|
935
|
+
let visibleUrlHint = '';
|
|
936
|
+
if (promo.url) {
|
|
937
|
+
try {
|
|
938
|
+
const host = new URL(promo.url).hostname.replace(/^www\./, '');
|
|
939
|
+
// Strip the click-redirect wrapper so users see the FINAL destination,
|
|
940
|
+
// not funnel.ruv.io. If the URL is /v1/click/<id>?to=<encoded>, pull the target.
|
|
941
|
+
let displayHost = host;
|
|
942
|
+
try {
|
|
943
|
+
const to = new URL(promo.url).searchParams.get('to');
|
|
944
|
+
if (to) displayHost = new URL(to).hostname.replace(/^www\./, '');
|
|
945
|
+
} catch { /* not a click-redirect, keep the raw host */ }
|
|
946
|
+
if (displayHost && !label.toLowerCase().endsWith(displayHost.toLowerCase())) {
|
|
947
|
+
// safeTerminalLink returns the plain string if URL isn't allowlisted
|
|
948
|
+
// or the terminal can't do OSC 8 — either way the domain stays visible.
|
|
949
|
+
const clickableDomain = safeTerminalLink(displayHost, promo.url);
|
|
950
|
+
visibleUrlHint = DIM_ON + ' (' + clickableDomain + ')' + DIM_OFF;
|
|
951
|
+
}
|
|
952
|
+
} catch { /* malformed URL — omit hint, never break the row */ }
|
|
953
|
+
}
|
|
954
|
+
// "Entire row clickable" (user request) — wrap the whole assembled
|
|
955
|
+
// string in ONE OSC 8 hyperlink instead of just the label. The command
|
|
956
|
+
// portion keeps its bold + bright-white treatment (no underline) so it
|
|
957
|
+
// still VISUALLY reads as a shell command the user should type, not a
|
|
958
|
+
// link — but if the user clicks anywhere on the row (label, domain
|
|
959
|
+
// hint, connector, even the command text), the terminal opens the URL.
|
|
960
|
+
// Clicking DOES NOT execute the command; it just opens the target URL,
|
|
961
|
+
// which is safe. Terminals that ignore OSC 8 render the whole row as
|
|
962
|
+
// styled text and no click behavior — the previous fallback (visible
|
|
963
|
+
// domain suffix) still keeps the destination readable.
|
|
964
|
+
const wrapWholeRowInHyperlink = (assembled) => {
|
|
965
|
+
if (!promo.url) return assembled;
|
|
966
|
+
if (!terminalSupportsHyperlinks()) return assembled;
|
|
967
|
+
let parsed;
|
|
968
|
+
try { parsed = new URL(promo.url); } catch { return assembled; }
|
|
969
|
+
if (parsed.protocol !== 'https:') return assembled;
|
|
970
|
+
if (!PROMO_LINK_HOSTS.has(parsed.hostname)) return assembled;
|
|
971
|
+
const ESC = '';
|
|
972
|
+
return ESC + ']8;;' + parsed.href + ESC + '\\' + assembled + ESC + ']8;;' + ESC + '\\';
|
|
973
|
+
};
|
|
974
|
+
// Visual styling stays per-part. We only add the OSC 8 wrap around the
|
|
975
|
+
// combined string, so the whole row is one click target.
|
|
976
|
+
const labelStyled = promo.url ? UL_ON + label + UL_OFF : label;
|
|
977
|
+
if (!command) return wrapWholeRowInHyperlink(labelStyled + visibleUrlHint);
|
|
978
|
+
return wrapWholeRowInHyperlink(
|
|
979
|
+
labelStyled + visibleUrlHint
|
|
980
|
+
+ DIM_ON + manageWord + DIM_OFF
|
|
981
|
+
+ BOLD_ON + FG_BRIGHT_WHITE + command + FG_DEFAULT + BOLD_OFF
|
|
982
|
+
);
|
|
983
|
+
} catch (e) {
|
|
984
|
+
return null; // the promo row must never break the statusline
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// JSON output — delegates to CLI for accuracy; caller can use --json flag
|
|
989
|
+
function generateJSON() {
|
|
990
|
+
const d = getStatuslineData();
|
|
991
|
+
const git = getGitInfo();
|
|
992
|
+
return Object.assign({}, d, {
|
|
993
|
+
user: Object.assign({ name: git.name, gitBranch: git.gitBranch }, d.user || {}),
|
|
994
|
+
git: { modified: git.modified, untracked: git.untracked, staged: git.staged, ahead: git.ahead, behind: git.behind },
|
|
995
|
+
lastUpdated: new Date().toISOString(),
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// ─── Main ───────────────────────────────────────────────────────
|
|
1000
|
+
if (process.argv.includes('--json')) {
|
|
1001
|
+
console.log(JSON.stringify(generateJSON(), null, 2));
|
|
1002
|
+
} else if (process.argv.includes('--compact')) {
|
|
1003
|
+
console.log(JSON.stringify(generateJSON()));
|
|
1004
|
+
} else {
|
|
1005
|
+
console.log(generateStatusline());
|
|
1006
|
+
}
|