first commit

This commit is contained in:
Tomas Dvorak
2026-02-22 10:42:17 +01:00
commit 55885a0e8f
239 changed files with 103690 additions and 0 deletions
+328
View File
@@ -0,0 +1,328 @@
mode: atomic
github.com/yourorg/devour/cmd/devour/main.go:7.13,9.2 1 0
github.com/yourorg/devour/cmd/realtest/main.go:11.13,38.30 7 0
github.com/yourorg/devour/cmd/realtest/main.go:38.30,42.15 3 0
github.com/yourorg/devour/cmd/realtest/main.go:42.15,44.12 2 0
github.com/yourorg/devour/cmd/realtest/main.go:47.3,56.17 4 0
github.com/yourorg/devour/cmd/realtest/main.go:56.17,58.12 2 0
github.com/yourorg/devour/cmd/realtest/main.go:61.3,63.20 2 0
github.com/yourorg/devour/cmd/realtest/main.go:63.20,68.32 5 0
github.com/yourorg/devour/cmd/realtest/main.go:68.32,70.5 1 0
github.com/yourorg/devour/cmd/realtest/main.go:70.10,72.5 1 0
github.com/yourorg/devour/cmd/realtest/main.go:75.3,76.17 2 0
github.com/yourorg/devour/cmd/realtest/main.go:76.17,78.4 1 0
github.com/yourorg/devour/cmd/realtest/main.go:78.9,80.4 1 0
github.com/yourorg/devour/cmd/realtest/main.go:82.3,82.16 1 0
github.com/yourorg/devour/cmd/realtest/main.go:85.2,108.30 5 0
github.com/yourorg/devour/cmd/realtest/main.go:108.30,110.15 2 0
github.com/yourorg/devour/cmd/realtest/main.go:110.15,112.4 1 0
github.com/yourorg/devour/cmd/realtest/main.go:112.9,114.4 1 0
github.com/yourorg/devour/cmd/realtest/main.go:117.2,118.38 2 0
github.com/yourorg/devour/cmd/demo.go:26.13,28.2 1 0
github.com/yourorg/devour/cmd/demo.go:30.55,37.51 5 0
github.com/yourorg/devour/cmd/demo.go:37.51,39.3 1 0
github.com/yourorg/devour/cmd/demo.go:41.2,51.16 7 0
github.com/yourorg/devour/cmd/demo.go:51.16,53.3 1 0
github.com/yourorg/devour/cmd/demo.go:55.2,60.16 4 0
github.com/yourorg/devour/cmd/demo.go:60.16,62.3 1 0
github.com/yourorg/devour/cmd/demo.go:64.2,68.29 3 0
github.com/yourorg/devour/cmd/demo.go:68.29,69.20 1 0
github.com/yourorg/devour/cmd/demo.go:69.20,71.18 2 0
github.com/yourorg/devour/cmd/demo.go:71.18,73.5 1 0
github.com/yourorg/devour/cmd/demo.go:76.2,83.29 5 0
github.com/yourorg/devour/cmd/demo.go:83.29,84.58 1 0
github.com/yourorg/devour/cmd/demo.go:84.58,86.9 2 0
github.com/yourorg/devour/cmd/demo.go:90.2,90.21 1 0
github.com/yourorg/devour/cmd/demo.go:90.21,92.17 2 0
github.com/yourorg/devour/cmd/demo.go:92.17,97.31 4 0
github.com/yourorg/devour/cmd/demo.go:97.31,98.16 1 0
github.com/yourorg/devour/cmd/demo.go:98.16,100.11 2 0
github.com/yourorg/devour/cmd/demo.go:102.5,102.32 1 0
github.com/yourorg/devour/cmd/demo.go:107.2,124.12 17 0
github.com/yourorg/devour/cmd/get.go:40.13,45.2 3 0
github.com/yourorg/devour/cmd/get.go:47.54,53.16 4 0
github.com/yourorg/devour/cmd/get.go:53.16,55.3 1 0
github.com/yourorg/devour/cmd/get.go:58.2,70.44 8 0
github.com/yourorg/devour/cmd/get.go:73.64,74.18 1 0
github.com/yourorg/devour/cmd/get.go:75.22,76.60 1 0
github.com/yourorg/devour/cmd/get.go:77.14,78.77 1 0
github.com/yourorg/devour/cmd/get.go:79.22,80.51 1 0
github.com/yourorg/devour/cmd/get.go:80.51,82.4 1 0
github.com/yourorg/devour/cmd/get.go:83.3,83.80 1 0
github.com/yourorg/devour/cmd/get.go:84.14,85.88 1 0
github.com/yourorg/devour/cmd/get.go:86.16,87.111 1 0
github.com/yourorg/devour/cmd/get.go:88.26,89.91 1 0
github.com/yourorg/devour/cmd/get.go:90.15,91.75 1 0
github.com/yourorg/devour/cmd/get.go:92.13,93.70 1 0
github.com/yourorg/devour/cmd/get.go:94.14,95.69 1 0
github.com/yourorg/devour/cmd/get.go:96.16,97.65 1 0
github.com/yourorg/devour/cmd/get.go:98.26,99.75 1 0
github.com/yourorg/devour/cmd/get.go:100.15,101.76 1 0
github.com/yourorg/devour/cmd/get.go:102.10,103.172 1 0
github.com/yourorg/devour/cmd/get.go:107.48,108.18 1 0
github.com/yourorg/devour/cmd/get.go:109.22,110.18 1 0
github.com/yourorg/devour/cmd/get.go:111.14,112.20 1 0
github.com/yourorg/devour/cmd/get.go:113.22,114.22 1 0
github.com/yourorg/devour/cmd/get.go:115.14,116.20 1 0
github.com/yourorg/devour/cmd/get.go:117.16,118.22 1 0
github.com/yourorg/devour/cmd/get.go:119.26,120.18 1 0
github.com/yourorg/devour/cmd/get.go:121.15,122.21 1 0
github.com/yourorg/devour/cmd/get.go:123.13,124.19 1 0
github.com/yourorg/devour/cmd/get.go:125.14,126.20 1 0
github.com/yourorg/devour/cmd/get.go:127.16,128.22 1 0
github.com/yourorg/devour/cmd/get.go:129.26,130.26 1 0
github.com/yourorg/devour/cmd/get.go:131.15,132.21 1 0
github.com/yourorg/devour/cmd/get.go:133.10,134.15 1 0
github.com/yourorg/devour/cmd/init.go:31.13,34.2 2 0
github.com/yourorg/devour/cmd/init.go:36.55,39.19 2 0
github.com/yourorg/devour/cmd/init.go:39.19,41.3 1 0
github.com/yourorg/devour/cmd/init.go:44.2,44.53 1 0
github.com/yourorg/devour/cmd/init.go:44.53,46.3 1 0
github.com/yourorg/devour/cmd/init.go:48.2,51.61 2 0
github.com/yourorg/devour/cmd/init.go:51.61,53.3 1 0
github.com/yourorg/devour/cmd/init.go:56.2,57.71 2 0
github.com/yourorg/devour/cmd/init.go:57.71,59.3 1 0
github.com/yourorg/devour/cmd/init.go:62.2,70.27 3 0
github.com/yourorg/devour/cmd/init.go:70.27,71.48 1 0
github.com/yourorg/devour/cmd/init.go:71.48,73.4 1 0
github.com/yourorg/devour/cmd/init.go:76.2,83.12 7 0
github.com/yourorg/devour/cmd/init.go:86.48,88.12 2 0
github.com/yourorg/devour/cmd/init.go:88.12,90.3 1 0
github.com/yourorg/devour/cmd/init.go:92.2,143.9 1 0
github.com/yourorg/devour/cmd/languages.go:21.13,23.2 1 0
github.com/yourorg/devour/cmd/languages.go:25.60,97.33 5 0
github.com/yourorg/devour/cmd/languages.go:97.33,101.41 4 0
github.com/yourorg/devour/cmd/languages.go:101.41,103.4 1 0
github.com/yourorg/devour/cmd/languages.go:104.3,104.16 1 0
github.com/yourorg/devour/cmd/languages.go:107.2,117.12 10 0
github.com/yourorg/devour/cmd/push.go:32.13,35.2 2 0
github.com/yourorg/devour/cmd/push.go:37.55,40.22 2 0
github.com/yourorg/devour/cmd/push.go:40.22,43.3 1 0
github.com/yourorg/devour/cmd/push.go:45.2,47.23 3 0
github.com/yourorg/devour/cmd/push.go:47.23,49.3 1 0
github.com/yourorg/devour/cmd/push.go:57.2,61.12 4 0
github.com/yourorg/devour/cmd/quality.go:136.13,171.2 22 0
github.com/yourorg/devour/cmd/quality.go:173.62,175.19 2 0
github.com/yourorg/devour/cmd/quality.go:175.19,177.3 1 0
github.com/yourorg/devour/cmd/quality.go:179.2,179.49 1 0
github.com/yourorg/devour/cmd/quality.go:179.49,181.3 1 0
github.com/yourorg/devour/cmd/quality.go:183.2,200.16 6 0
github.com/yourorg/devour/cmd/quality.go:200.16,203.3 2 0
github.com/yourorg/devour/cmd/quality.go:205.2,206.8 2 0
github.com/yourorg/devour/cmd/quality.go:206.8,208.59 2 0
github.com/yourorg/devour/cmd/quality.go:208.59,210.4 1 0
github.com/yourorg/devour/cmd/quality.go:211.8,215.3 3 0
github.com/yourorg/devour/cmd/quality.go:217.2,219.16 3 0
github.com/yourorg/devour/cmd/quality.go:219.16,221.3 1 0
github.com/yourorg/devour/cmd/quality.go:223.2,223.48 1 0
github.com/yourorg/devour/cmd/quality.go:226.64,234.54 5 0
github.com/yourorg/devour/cmd/quality.go:234.54,239.55 2 0
github.com/yourorg/devour/cmd/quality.go:239.55,242.4 2 0
github.com/yourorg/devour/cmd/quality.go:245.2,245.24 1 0
github.com/yourorg/devour/cmd/quality.go:245.24,248.3 2 0
github.com/yourorg/devour/cmd/quality.go:251.2,255.23 3 0
github.com/yourorg/devour/cmd/quality.go:256.14,257.54 1 0
github.com/yourorg/devour/cmd/quality.go:258.10,260.13 2 0
github.com/yourorg/devour/cmd/quality.go:264.62,271.54 4 0
github.com/yourorg/devour/cmd/quality.go:271.54,275.55 2 0
github.com/yourorg/devour/cmd/quality.go:275.55,277.4 1 0
github.com/yourorg/devour/cmd/quality.go:280.2,280.24 1 0
github.com/yourorg/devour/cmd/quality.go:280.24,283.3 2 0
github.com/yourorg/devour/cmd/quality.go:286.2,289.17 3 0
github.com/yourorg/devour/cmd/quality.go:289.17,292.3 2 0
github.com/yourorg/devour/cmd/quality.go:295.2,295.14 1 0
github.com/yourorg/devour/cmd/quality.go:295.14,296.33 1 0
github.com/yourorg/devour/cmd/quality.go:296.33,298.37 1 0
github.com/yourorg/devour/cmd/quality.go:298.37,299.78 1 0
github.com/yourorg/devour/cmd/quality.go:299.78,301.11 2 0
github.com/yourorg/devour/cmd/quality.go:304.4,304.49 1 0
github.com/yourorg/devour/cmd/quality.go:304.49,307.5 2 0
github.com/yourorg/devour/cmd/quality.go:312.2,320.13 8 0
github.com/yourorg/devour/cmd/quality.go:320.13,323.24 3 0
github.com/yourorg/devour/cmd/quality.go:324.27,325.90 1 0
github.com/yourorg/devour/cmd/quality.go:326.27,327.94 1 0
github.com/yourorg/devour/cmd/quality.go:328.27,329.97 1 0
github.com/yourorg/devour/cmd/quality.go:330.27,331.93 1 0
github.com/yourorg/devour/cmd/quality.go:334.3,334.104 1 0
github.com/yourorg/devour/cmd/quality.go:337.2,337.12 1 0
github.com/yourorg/devour/cmd/quality.go:340.65,341.19 1 0
github.com/yourorg/devour/cmd/quality.go:341.19,343.3 1 0
github.com/yourorg/devour/cmd/quality.go:345.2,356.28 4 0
github.com/yourorg/devour/cmd/quality.go:356.28,358.3 1 0
github.com/yourorg/devour/cmd/quality.go:360.2,360.23 1 0
github.com/yourorg/devour/cmd/quality.go:360.23,362.3 1 0
github.com/yourorg/devour/cmd/quality.go:365.2,370.54 4 0
github.com/yourorg/devour/cmd/quality.go:370.54,374.59 2 0
github.com/yourorg/devour/cmd/quality.go:374.59,376.4 1 0
github.com/yourorg/devour/cmd/quality.go:380.2,381.35 2 0
github.com/yourorg/devour/cmd/quality.go:381.35,382.23 1 0
github.com/yourorg/devour/cmd/quality.go:382.23,385.31 3 0
github.com/yourorg/devour/cmd/quality.go:385.31,387.5 1 0
github.com/yourorg/devour/cmd/quality.go:388.4,389.20 2 0
github.com/yourorg/devour/cmd/quality.go:389.20,391.5 1 0
github.com/yourorg/devour/cmd/quality.go:392.4,393.9 2 0
github.com/yourorg/devour/cmd/quality.go:397.2,397.12 1 0
github.com/yourorg/devour/cmd/quality.go:397.12,399.3 1 0
github.com/yourorg/devour/cmd/quality.go:402.2,402.51 1 0
github.com/yourorg/devour/cmd/quality.go:402.51,404.3 1 0
github.com/yourorg/devour/cmd/quality.go:406.2,415.16 3 0
github.com/yourorg/devour/cmd/quality.go:415.16,417.3 1 0
github.com/yourorg/devour/cmd/quality.go:419.2,419.61 1 0
github.com/yourorg/devour/cmd/quality.go:419.61,421.3 1 0
github.com/yourorg/devour/cmd/quality.go:423.2,424.23 2 0
github.com/yourorg/devour/cmd/quality.go:424.23,426.3 1 0
github.com/yourorg/devour/cmd/quality.go:428.2,428.12 1 0
github.com/yourorg/devour/cmd/quality.go:431.72,434.51 2 0
github.com/yourorg/devour/cmd/quality.go:434.51,436.3 1 0
github.com/yourorg/devour/cmd/quality.go:438.2,448.16 4 0
github.com/yourorg/devour/cmd/quality.go:448.16,450.3 1 0
github.com/yourorg/devour/cmd/quality.go:452.2,452.61 1 0
github.com/yourorg/devour/cmd/quality.go:452.61,454.3 1 0
github.com/yourorg/devour/cmd/quality.go:457.2,457.16 1 0
github.com/yourorg/devour/cmd/quality.go:458.14,459.51 1 0
github.com/yourorg/devour/cmd/quality.go:460.10,461.38 1 0
github.com/yourorg/devour/cmd/quality.go:465.61,473.31 7 0
github.com/yourorg/devour/cmd/quality.go:473.31,476.3 2 0
github.com/yourorg/devour/cmd/quality.go:478.2,479.42 2 0
github.com/yourorg/devour/cmd/quality.go:479.42,481.3 1 0
github.com/yourorg/devour/cmd/quality.go:483.2,491.33 3 0
github.com/yourorg/devour/cmd/quality.go:491.33,493.25 2 0
github.com/yourorg/devour/cmd/quality.go:493.25,494.12 1 0
github.com/yourorg/devour/cmd/quality.go:497.3,498.36 2 0
github.com/yourorg/devour/cmd/quality.go:498.36,501.4 1 0
github.com/yourorg/devour/cmd/quality.go:502.3,502.16 1 0
github.com/yourorg/devour/cmd/quality.go:505.2,508.12 3 0
github.com/yourorg/devour/cmd/quality.go:511.61,516.54 4 0
github.com/yourorg/devour/cmd/quality.go:516.54,520.55 2 0
github.com/yourorg/devour/cmd/quality.go:520.55,522.4 1 0
github.com/yourorg/devour/cmd/quality.go:525.2,525.24 1 0
github.com/yourorg/devour/cmd/quality.go:525.24,528.3 2 0
github.com/yourorg/devour/cmd/quality.go:530.2,539.12 5 0
github.com/yourorg/devour/cmd/quality.go:539.12,540.36 1 0
github.com/yourorg/devour/cmd/quality.go:540.36,541.86 1 0
github.com/yourorg/devour/cmd/quality.go:541.86,542.13 1 0
github.com/yourorg/devour/cmd/quality.go:545.4,545.42 1 0
github.com/yourorg/devour/cmd/quality.go:545.42,546.30 1 0
github.com/yourorg/devour/cmd/quality.go:546.30,548.20 2 0
github.com/yourorg/devour/cmd/quality.go:548.20,550.15 2 0
github.com/yourorg/devour/cmd/quality.go:552.6,552.24 1 0
github.com/yourorg/devour/cmd/quality.go:552.24,555.7 2 0
github.com/yourorg/devour/cmd/quality.go:556.6,556.11 1 0
github.com/yourorg/devour/cmd/quality.go:560.8,561.20 1 0
github.com/yourorg/devour/cmd/quality.go:561.20,563.4 1 0
github.com/yourorg/devour/cmd/quality.go:565.3,567.27 3 0
github.com/yourorg/devour/cmd/quality.go:567.27,568.34 1 0
github.com/yourorg/devour/cmd/quality.go:568.34,570.10 2 0
github.com/yourorg/devour/cmd/quality.go:574.3,574.20 1 0
github.com/yourorg/devour/cmd/quality.go:574.20,576.4 1 0
github.com/yourorg/devour/cmd/quality.go:578.3,578.41 1 0
github.com/yourorg/devour/cmd/quality.go:578.41,579.29 1 0
github.com/yourorg/devour/cmd/quality.go:579.29,581.19 2 0
github.com/yourorg/devour/cmd/quality.go:581.19,583.6 1 0
github.com/yourorg/devour/cmd/quality.go:584.5,586.10 3 0
github.com/yourorg/devour/cmd/quality.go:591.2,591.15 1 0
github.com/yourorg/devour/cmd/quality.go:591.15,593.3 1 0
github.com/yourorg/devour/cmd/quality.go:593.8,595.3 1 0
github.com/yourorg/devour/cmd/quality.go:597.2,597.21 1 0
github.com/yourorg/devour/cmd/quality.go:597.21,599.28 2 0
github.com/yourorg/devour/cmd/quality.go:599.28,601.4 1 0
github.com/yourorg/devour/cmd/quality.go:604.2,604.12 1 0
github.com/yourorg/devour/cmd/quality.go:607.64,610.19 2 0
github.com/yourorg/devour/cmd/quality.go:610.19,612.3 1 0
github.com/yourorg/devour/cmd/quality.go:614.2,614.24 1 0
github.com/yourorg/devour/cmd/quality.go:614.24,616.3 1 0
github.com/yourorg/devour/cmd/quality.go:618.2,618.103 1 0
github.com/yourorg/devour/cmd/quality.go:621.48,627.54 4 0
github.com/yourorg/devour/cmd/quality.go:627.54,632.55 2 0
github.com/yourorg/devour/cmd/quality.go:632.55,635.4 2 0
github.com/yourorg/devour/cmd/quality.go:638.2,643.16 5 0
github.com/yourorg/devour/cmd/quality.go:643.16,645.3 1 0
github.com/yourorg/devour/cmd/quality.go:647.2,648.51 2 0
github.com/yourorg/devour/cmd/quality.go:648.51,650.3 1 0
github.com/yourorg/devour/cmd/quality.go:652.2,656.12 4 0
github.com/yourorg/devour/cmd/quality.go:659.67,665.16 4 0
github.com/yourorg/devour/cmd/quality.go:665.16,667.3 1 0
github.com/yourorg/devour/cmd/quality.go:669.2,672.56 2 0
github.com/yourorg/devour/cmd/quality.go:672.56,674.3 1 0
github.com/yourorg/devour/cmd/quality.go:674.8,676.64 2 0
github.com/yourorg/devour/cmd/quality.go:676.64,678.4 1 0
github.com/yourorg/devour/cmd/quality.go:679.3,679.30 1 0
github.com/yourorg/devour/cmd/quality.go:682.2,682.77 1 0
github.com/yourorg/devour/cmd/quality.go:682.77,684.3 1 0
github.com/yourorg/devour/cmd/quality.go:686.2,688.12 2 0
github.com/yourorg/devour/cmd/query.go:31.13,35.2 3 0
github.com/yourorg/devour/cmd/query.go:37.56,39.19 2 0
github.com/yourorg/devour/cmd/query.go:39.19,41.3 1 0
github.com/yourorg/devour/cmd/query.go:43.2,59.12 9 0
github.com/yourorg/devour/cmd/root.go:29.16,30.42 1 0
github.com/yourorg/devour/cmd/root.go:30.42,33.3 2 0
github.com/yourorg/devour/cmd/root.go:36.13,52.2 13 0
github.com/yourorg/devour/cmd/root.go:54.19,55.19 1 0
github.com/yourorg/devour/cmd/root.go:55.19,57.3 1 0
github.com/yourorg/devour/cmd/root.go:57.8,62.3 4 0
github.com/yourorg/devour/cmd/root.go:64.2,67.56 3 0
github.com/yourorg/devour/cmd/root.go:67.56,69.3 1 0
github.com/yourorg/devour/cmd/scrape.go:57.13,63.2 5 0
github.com/yourorg/devour/cmd/scrape.go:65.57,66.25 1 0
github.com/yourorg/devour/cmd/scrape.go:66.25,68.3 1 0
github.com/yourorg/devour/cmd/scrape.go:70.2,70.20 1 0
github.com/yourorg/devour/cmd/scrape.go:70.20,72.3 1 0
github.com/yourorg/devour/cmd/scrape.go:74.2,85.22 4 0
github.com/yourorg/devour/cmd/scrape.go:85.22,87.3 1 0
github.com/yourorg/devour/cmd/scrape.go:89.2,95.14 6 0
github.com/yourorg/devour/cmd/scrape.go:95.14,97.3 1 0
github.com/yourorg/devour/cmd/scrape.go:99.2,109.16 5 0
github.com/yourorg/devour/cmd/scrape.go:109.16,111.3 1 0
github.com/yourorg/devour/cmd/scrape.go:113.2,115.24 2 0
github.com/yourorg/devour/cmd/scrape.go:115.24,117.3 1 0
github.com/yourorg/devour/cmd/scrape.go:119.2,119.56 1 0
github.com/yourorg/devour/cmd/scrape.go:119.56,121.3 1 0
github.com/yourorg/devour/cmd/scrape.go:123.2,123.27 1 0
github.com/yourorg/devour/cmd/scrape.go:123.27,127.33 3 0
github.com/yourorg/devour/cmd/scrape.go:127.33,145.4 4 0
github.com/yourorg/devour/cmd/scrape.go:145.9,148.18 3 0
github.com/yourorg/devour/cmd/scrape.go:148.18,150.5 1 0
github.com/yourorg/devour/cmd/scrape.go:153.3,154.63 2 0
github.com/yourorg/devour/cmd/scrape.go:154.63,156.4 1 0
github.com/yourorg/devour/cmd/scrape.go:158.3,158.53 1 0
github.com/yourorg/devour/cmd/scrape.go:161.2,165.12 4 0
github.com/yourorg/devour/cmd/scrape.go:168.48,170.2 1 0
github.com/yourorg/devour/cmd/scrape.go:172.60,174.16 2 0
github.com/yourorg/devour/cmd/scrape.go:174.16,176.3 1 0
github.com/yourorg/devour/cmd/scrape.go:178.2,181.9 3 0
github.com/yourorg/devour/cmd/scrape.go:182.69,183.34 1 0
github.com/yourorg/devour/cmd/scrape.go:184.56,185.36 1 0
github.com/yourorg/devour/cmd/scrape.go:186.33,187.38 1 0
github.com/yourorg/devour/cmd/scrape.go:188.33,189.36 1 0
github.com/yourorg/devour/cmd/scrape.go:190.32,191.38 1 0
github.com/yourorg/devour/cmd/scrape.go:192.72,193.34 1 0
github.com/yourorg/devour/cmd/scrape.go:194.27,195.37 1 0
github.com/yourorg/devour/cmd/scrape.go:196.27,197.35 1 0
github.com/yourorg/devour/cmd/scrape.go:198.26,199.36 1 0
github.com/yourorg/devour/cmd/scrape.go:200.73,201.38 1 0
github.com/yourorg/devour/cmd/scrape.go:201.38,203.4 1 0
github.com/yourorg/devour/cmd/scrape.go:204.3,204.38 1 0
github.com/yourorg/devour/cmd/scrape.go:205.43,206.42 1 0
github.com/yourorg/devour/cmd/scrape.go:207.34,208.37 1 0
github.com/yourorg/devour/cmd/scrape.go:209.28,210.34 1 0
github.com/yourorg/devour/cmd/scrape.go:211.10,212.31 1 0
github.com/yourorg/devour/cmd/scrape.go:216.43,218.16 2 0
github.com/yourorg/devour/cmd/scrape.go:218.16,220.3 1 0
github.com/yourorg/devour/cmd/scrape.go:222.2,223.20 2 0
github.com/yourorg/devour/cmd/scrape.go:223.20,225.3 1 0
github.com/yourorg/devour/cmd/scrape.go:227.2,227.15 1 0
github.com/yourorg/devour/cmd/scrape.go:230.43,237.20 6 0
github.com/yourorg/devour/cmd/scrape.go:237.20,239.3 1 0
github.com/yourorg/devour/cmd/scrape.go:241.2,241.13 1 0
github.com/yourorg/devour/cmd/serve.go:33.13,37.2 3 0
github.com/yourorg/devour/cmd/serve.go:39.56,40.17 1 0
github.com/yourorg/devour/cmd/serve.go:40.17,48.3 5 0
github.com/yourorg/devour/cmd/serve.go:50.2,60.53 3 0
github.com/yourorg/devour/cmd/status.go:24.57,54.2 16 0
github.com/yourorg/devour/cmd/sync.go:30.13,34.2 3 0
github.com/yourorg/devour/cmd/sync.go:36.55,37.17 1 0
github.com/yourorg/devour/cmd/sync.go:37.17,39.3 1 0
github.com/yourorg/devour/cmd/sync.go:39.8,41.3 1 0
github.com/yourorg/devour/cmd/sync.go:43.2,43.22 1 0
github.com/yourorg/devour/cmd/sync.go:43.22,45.3 1 0
github.com/yourorg/devour/cmd/sync.go:57.2,61.12 4 0
+125
View File
@@ -0,0 +1,125 @@
package cmd
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
)
var demoCmd = &cobra.Command{
Use: "demo",
Short: "Run a demonstration of Devour features",
Long: `Demonstrates Devour's capabilities by scraping sample documentation
and showing the enhanced markdown output.
This command will:
1. Scrape Go's HTTP package documentation
2. Generate enhanced markdown output
3. Display a preview of the results
4. Show statistics about the operation`,
RunE: runDemo,
}
func init() {
rootCmd.AddCommand(demoCmd)
}
func runDemo(cmd *cobra.Command, args []string) error {
fmt.Println("🎬 Devour Feature Demonstration")
fmt.Println("═══════════════════════════════════════════════════════════════")
fmt.Println()
// Create demo directory
demoDir := "devour_demo"
if err := os.MkdirAll(demoDir, 0755); err != nil {
return fmt.Errorf("failed to create demo directory: %w", err)
}
fmt.Println("📍 Step 1: Scraping Go HTTP package documentation...")
fmt.Println(" Command: devour get go http --format markdown --output devour_demo")
fmt.Println()
// Set demo output directory and format
scrapeOutput = demoDir
scrapeFormat = "markdown"
// Use the get command logic to fetch Go HTTP docs
err := runGet(cmd, []string{"go", "http"})
if err != nil {
return fmt.Errorf("demo scrape failed: %w", err)
}
fmt.Println()
fmt.Println("📊 Step 2: Analyzing results...")
// Count files in demo directory
files, err := os.ReadDir(demoDir)
if err != nil {
return fmt.Errorf("failed to read demo directory: %w", err)
}
fmt.Printf(" ✓ Generated %d markdown files\n", len(files))
// Show file sizes
var totalSize int64
for _, file := range files {
if !file.IsDir() {
info, err := file.Info()
if err == nil {
totalSize += info.Size()
}
}
}
fmt.Printf(" ✓ Total size: %d bytes\n", totalSize)
fmt.Println()
fmt.Println("📖 Step 3: Preview of enhanced markdown output...")
// Find and show preview of first markdown file
var firstFile string
for _, file := range files {
if !file.IsDir() && filepath.Ext(file.Name()) == ".md" {
firstFile = filepath.Join(demoDir, file.Name())
break
}
}
if firstFile != "" {
content, err := os.ReadFile(firstFile)
if err == nil {
// Show first 20 lines
lines := strings.Split(string(content), "\n")
fmt.Printf(" From file: %s\n", filepath.Base(firstFile))
fmt.Println(" ─────────────────────────────────────────")
for i, line := range lines {
if i >= 20 {
fmt.Printf(" ... (%d more lines)\n", len(lines)-20)
break
}
fmt.Printf(" %s\n", line)
}
}
}
fmt.Println()
fmt.Println("✨ Demo Complete!")
fmt.Println()
fmt.Println("🚀 What you can do next:")
fmt.Printf(" • View all files: ls -la %s/\n", demoDir)
fmt.Printf(" • Open in editor: code %s/\n", demoDir)
fmt.Println(" • Try other languages: devour get python asyncio")
fmt.Println(" • Use enhanced format: devour get react hooks --format markdown")
fmt.Println(" • See all languages: devour languages")
fmt.Println()
fmt.Println("💡 Pro Tip: The enhanced markdown includes:")
fmt.Println(" • 📋 Document metadata tables")
fmt.Println(" • 📑 Auto-generated table of contents")
fmt.Println(" • 🎨 Visual indicators for important content")
fmt.Println(" • 🔗 Automatic link conversion")
fmt.Println(" • 📚 Structured content sections")
return nil
}
+9
View File
@@ -0,0 +1,9 @@
package main
import (
"github.com/yourorg/devour/cmd"
)
func main() {
cmd.Execute()
}
+136
View File
@@ -0,0 +1,136 @@
package cmd
import (
"fmt"
"strings"
"github.com/spf13/cobra"
)
var getCmd = &cobra.Command{
Use: "get <language> <keyword>",
Short: "Get documentation for a language/framework",
Long: `Quickly fetch documentation for popular languages and frameworks.
This command automatically maps language+keyword combinations to their official documentation sites.
Supported languages:
go, golang - Go documentation (pkg.go.dev)
rust - Rust documentation (docs.rs)
python, py - Python documentation (docs.python.org)
java - Java documentation (docs.oracle.com)
spring - Spring Boot documentation (docs.spring.io)
typescript, ts - TypeScript documentation (typescriptlang.org)
react - React documentation (react.dev)
vue - Vue.js documentation (vuejs.org)
nuxt - Nuxt documentation (nuxt.com)
docker - Docker documentation (docs.docker.com)
cloudflare, cf - Cloudflare documentation (developers.cloudflare.com)
astro - Astro documentation (docs.astro.build)
Examples:
devour get go http # Go HTTP package documentation
devour get python asyncio # Python asyncio module
devour get react hooks # React Hooks documentation
devour get docker compose # Docker Compose docs
devour get rust tokio # Rust Tokio crate`,
Args: cobra.ExactArgs(2),
RunE: runGet,
}
func init() {
// Add flags that can override defaults
getCmd.Flags().StringVarP(&scrapeFormat, "format", "f", "json", "output format (json, markdown)")
getCmd.Flags().StringVarP(&scrapeOutput, "output", "o", "", "output directory (default: devour_data/docs)")
getCmd.Flags().IntVar(&scrapeConcurrency, "concurrency", 10, "parallel scraping workers")
}
func runGet(cmd *cobra.Command, args []string) error {
language := strings.ToLower(args[0])
keyword := strings.ToLower(args[1])
// Map language to base URL and construct full URL
url, err := constructDocURL(language, keyword)
if err != nil {
return err
}
// Set the scrape type based on language
sourceType := mapLanguageToType(language)
// Reuse the existing scrape logic with pre-determined values
scrapeType = string(sourceType)
sourceURL := url
fmt.Printf("Getting docs for: %s %s\n", language, keyword)
fmt.Printf("URL: %s\n", sourceURL)
fmt.Printf("Type: %s\n", sourceType)
fmt.Println()
// Call the existing scrape logic
return runScrape(cmd, []string{sourceURL})
}
func constructDocURL(language, keyword string) (string, error) {
switch language {
case "go", "golang":
return fmt.Sprintf("https://pkg.go.dev/%s", keyword), nil
case "rust":
return fmt.Sprintf("https://docs.rs/%s/latest/%s/", keyword, keyword), nil
case "python", "py":
if keyword == "stdlib" || keyword == "standard" {
return "https://docs.python.org/3/library/", nil
}
return fmt.Sprintf("https://docs.python.org/3/library/%s.html", keyword), nil
case "java":
return fmt.Sprintf("https://docs.oracle.com/javase/8/docs/api/%s.html", keyword), nil
case "spring":
return fmt.Sprintf("https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#%s", keyword), nil
case "typescript", "ts":
return fmt.Sprintf("https://www.typescriptlang.org/docs/handbook/%s.html", keyword), nil
case "react":
return fmt.Sprintf("https://react.dev/reference/react/%s", keyword), nil
case "vue":
return fmt.Sprintf("https://vuejs.org/guide/%s.html", keyword), nil
case "nuxt":
return fmt.Sprintf("https://nuxt.com/docs/guide/%s", keyword), nil
case "docker":
return fmt.Sprintf("https://docs.docker.com/%s", keyword), nil
case "cloudflare", "cf":
return fmt.Sprintf("https://developers.cloudflare.com/%s", keyword), nil
case "astro":
return fmt.Sprintf("https://docs.astro.build/en/guides/%s", keyword), nil
default:
return "", fmt.Errorf("unsupported language: %s. Supported languages: go, rust, python, java, spring, typescript, react, vue, nuxt, docker, cloudflare, astro", language)
}
}
func mapLanguageToType(language string) string {
switch language {
case "go", "golang":
return "godocs"
case "rust":
return "rustdocs"
case "python", "py":
return "pythondocs"
case "java":
return "javadocs"
case "spring":
return "springdocs"
case "typescript", "ts":
return "tsdocs"
case "react":
return "reactdocs"
case "vue":
return "vuedocs"
case "nuxt":
return "nuxtdocs"
case "docker":
return "dockerdocs"
case "cloudflare", "cf":
return "cloudflaredocs"
case "astro":
return "astrodocs"
default:
return "web"
}
}
+144
View File
@@ -0,0 +1,144 @@
package cmd
import (
"fmt"
"os"
"path/filepath"
"github.com/spf13/cobra"
)
var initCmd = &cobra.Command{
Use: "init [path]",
Short: "Initialize Devour for a project",
Long: `Initialize Devour configuration for a project.
Creates a devour.yaml configuration file and the necessary directory
structure for storing documents and indexes.
Examples:
devour init # Initialize in current directory
devour init ./my-project # Initialize in specific directory
devour init --remote # Initialize with remote mode config`,
RunE: runInit,
}
var (
initRemote bool
initForce bool
)
func init() {
initCmd.Flags().BoolVar(&initRemote, "remote", false, "configure for remote mode")
initCmd.Flags().BoolVarP(&initForce, "force", "f", false, "overwrite existing config")
}
func runInit(cmd *cobra.Command, args []string) error {
// Determine target directory
targetDir := "."
if len(args) > 0 {
targetDir = args[0]
}
// Create directory if it doesn't exist
if err := os.MkdirAll(targetDir, 0755); err != nil {
return fmt.Errorf("failed to create directory: %w", err)
}
configPath := filepath.Join(targetDir, "devour.yaml")
// Check if config already exists
if _, err := os.Stat(configPath); err == nil && !initForce {
return fmt.Errorf("config file already exists at %s (use --force to overwrite)", configPath)
}
// Create default config
config := generateDefaultConfig(initRemote)
if err := os.WriteFile(configPath, []byte(config), 0644); err != nil {
return fmt.Errorf("failed to write config: %w", err)
}
// Create data directories
dataDir := filepath.Join(targetDir, "devour_data")
dirs := []string{
filepath.Join(dataDir, "docs"),
filepath.Join(dataDir, "index"),
filepath.Join(dataDir, "metadata"),
filepath.Join(dataDir, "cache"),
}
for _, dir := range dirs {
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("failed to create directory %s: %w", dir, err)
}
}
fmt.Printf("✓ Created config at %s\n", configPath)
fmt.Printf("✓ Created data directories at %s\n", dataDir)
fmt.Println("\nNext steps:")
fmt.Println(" 1. Set OPENAI_API_KEY environment variable")
fmt.Println(" 2. Add sources to devour.yaml")
fmt.Println(" 3. Run 'devour scrape <source>' to index documentation")
return nil
}
func generateDefaultConfig(remote bool) string {
mode := "local"
if remote {
mode = "remote"
}
return fmt.Sprintf(`# Devour Configuration
version: 1
# Storage paths
storage:
docs_dir: ./devour_data/docs
index_dir: ./devour_data/index
metadata_dir: ./devour_data/metadata
# Embedding settings
embeddings:
provider: openai
model: text-embedding-3-small
dimensions: 1536
api_key: ${OPENAI_API_KEY}
batch_size: 100
# Vector database
vector_db:
type: chromem
persist: true
similarity_metric: cosine
# Scraping settings
scraper:
user_agent: "Devour/1.0"
timeout: 30s
retry_count: 3
concurrency: 10
rate_limit: 500ms
max_depth: 3
cache_dir: ./devour_data/cache
# Scheduler
scheduler:
enabled: true
interval: 72h
check_method: hash
# Server settings
server:
mode: %s
port: 8080
host: localhost
# Sources (add your own)
sources: []
# - name: example-docs
# type: url
# url: https://docs.example.com
# include: ["**/*.md", "**/*.html"]
`, mode)
}
+118
View File
@@ -0,0 +1,118 @@
package cmd
import (
"fmt"
"strings"
"github.com/spf13/cobra"
)
var languagesCmd = &cobra.Command{
Use: "languages",
Short: "Show supported languages and their mappings",
Long: `Display all supported languages for the 'devour get' command
along with their base URLs and examples.
This helps you discover what documentation sources are available
and how to reference them quickly.`,
RunE: runLanguages,
}
func init() {
rootCmd.AddCommand(languagesCmd)
}
func runLanguages(cmd *cobra.Command, args []string) error {
fmt.Println("🌐 Devour Supported Languages")
fmt.Println("═══════════════════════════════════════════════════════════════")
fmt.Println()
languages := []struct {
langs []string
url string
examples []string
}{
{
langs: []string{"go", "golang"},
url: "https://pkg.go.dev/{package}",
examples: []string{"devour get go http", "devour get go fmt", "devour get golang json"},
},
{
langs: []string{"rust"},
url: "https://docs.rs/{crate}/latest/{crate}/",
examples: []string{"devour get rust tokio", "devour get rust serde", "devour get rust clap"},
},
{
langs: []string{"python", "py"},
url: "https://docs.python.org/3/library/{module}.html",
examples: []string{"devour get python asyncio", "devour get py requests", "devour get python stdlib"},
},
{
langs: []string{"java"},
url: "https://docs.oracle.com/javase/8/docs/api/{package}.html",
examples: []string{"devour get java string", "devour get java arraylist"},
},
{
langs: []string{"spring"},
url: "https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#{section}",
examples: []string{"devour get spring boot", "devour get spring testing"},
},
{
langs: []string{"typescript", "ts"},
url: "https://www.typescriptlang.org/docs/handbook/{topic}.html",
examples: []string{"devour get typescript interfaces", "devour get ts decorators"},
},
{
langs: []string{"react"},
url: "https://react.dev/reference/react/{feature}",
examples: []string{"devour get react hooks", "devour get react components", "devour get react state"},
},
{
langs: []string{"vue"},
url: "https://vuejs.org/guide/{topic}.html",
examples: []string{"devour get vue components", "devour get vue reactivity"},
},
{
langs: []string{"nuxt"},
url: "https://nuxt.com/docs/guide/{topic}",
examples: []string{"devour get nuxt routing", "devour get nuxt middleware"},
},
{
langs: []string{"docker"},
url: "https://docs.docker.com/{topic}",
examples: []string{"devour get docker compose", "devour get docker build", "devour get docker networking"},
},
{
langs: []string{"cloudflare", "cf"},
url: "https://developers.cloudflare.com/{topic}",
examples: []string{"devour get cloudflare workers", "devour get cf pages", "devour get cloudflare dns"},
},
{
langs: []string{"astro"},
url: "https://docs.astro.build/en/guides/{topic}",
examples: []string{"devour get astro routing", "devour get astro components"},
},
}
for _, lang := range languages {
fmt.Printf("🔷 %s\n", strings.Join(lang.langs, ", "))
fmt.Printf(" URL: %s\n", lang.url)
fmt.Printf(" Examples:\n")
for _, example := range lang.examples {
fmt.Printf(" • %s\n", example)
}
fmt.Println()
}
fmt.Println("💡 Pro Tips:")
fmt.Println(" • Use 'devour get <language> help' for language-specific help")
fmt.Println(" • Add --format markdown for enhanced documentation")
fmt.Println(" • Most languages support common aliases (e.g., py → python)")
fmt.Println()
fmt.Println("🚀 Quick Start:")
fmt.Println(" devour get go http --format markdown")
fmt.Println(" devour get python asyncio")
fmt.Println(" devour get react hooks")
return nil
}
+62
View File
@@ -0,0 +1,62 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var pushCmd = &cobra.Command{
Use: "push <path>",
Short: "Push documents to remote MCP server",
Long: `Push local documents to a remote Devour MCP server.
Useful for:
- Syncing local documentation to a shared server
- Backing up indexed content
- Contributing to a team knowledge base
Examples:
devour push ./docs
devour push ./docs --server http://devour.company.com
devour push ./docs --server http://localhost:8080 --project my-project`,
Args: cobra.ExactArgs(1),
RunE: runPush,
}
var (
pushServer string
pushProject string
)
func init() {
pushCmd.Flags().StringVar(&pushServer, "server", "", "remote Devour server URL")
pushCmd.Flags().StringVarP(&pushProject, "project", "p", "", "project name on remote server")
}
func runPush(cmd *cobra.Command, args []string) error {
path := args[0]
if pushServer == "" {
// Try to get from config
pushServer = "http://localhost:8080"
}
fmt.Printf("📤 Pushing to: %s\n", pushServer)
fmt.Printf(" Path: %s\n", path)
if pushProject != "" {
fmt.Printf(" Project: %s\n", pushProject)
}
// TODO: Implement actual push logic
// 1. Scan path for documents
// 2. Connect to remote server
// 3. Upload documents
// 4. Wait for indexing confirmation
fmt.Println()
fmt.Println("⚠️ Push functionality not yet implemented")
fmt.Println(" Remote server support coming soon")
return nil
}
+736
View File
@@ -0,0 +1,736 @@
package cmd
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
"github.com/spf13/cobra"
"github.com/yourorg/devour/internal/quality"
"github.com/yourorg/devour/internal/quality/detectors"
"github.com/yourorg/devour/internal/quality/plugins"
"github.com/yourorg/devour/internal/quality/plugins/go/fixers"
"github.com/yourorg/devour/internal/quality/review"
"github.com/yourorg/devour/internal/quality/scorecard"
_ "github.com/yourorg/devour/internal/quality/plugins/go"
)
// qualityCmd represents the quality command
var qualityCmd = &cobra.Command{
Use: "quality",
Short: "Code quality analysis and technical debt tracking",
Long: `Analyze code quality issues like complexity, duplication, naming inconsistencies,
and more. Supports multiple languages including Go, Python, TypeScript, Java, and Rust.
Examples:
devour quality scan ./src # Scan current directory
devour quality scan --lang go ./src # Scan with explicit language
devour quality status # Show current status
devour quality next # Show next priority issue
devour quality resolve fixed 123 --note "Refactored" # Mark issue as fixed`,
}
// scanCmd represents the scan subcommand
var scanCmd = &cobra.Command{
Use: "scan [path]",
Short: "Run code quality analysis",
Long: `Scan the given path for code quality issues. Automatically detects language
unless specified with --lang flag.
The scan will detect:
- Complexity issues (nested loops, excessive function calls)
- Code duplication and near-duplicates
- Naming inconsistencies across directories
- Large files and god classes
- Orphaned code and unused exports`,
RunE: runQualityScan,
}
// qualityStatusCmd represents the quality status subcommand
var qualityStatusCmd = &cobra.Command{
Use: "status",
Short: "Show code quality status and scorecard",
Long: `Display the current code quality status including:
- Overall health score and grade
- Findings broken down by type and severity
- Progress metrics and next steps`,
RunE: runQualityStatus,
}
// nextCmd represents the next subcommand
var nextCmd = &cobra.Command{
Use: "next",
Short: "Show the next highest priority issue to fix",
Long: `Display the next highest priority finding based on severity and score.
This helps you focus on the most impactful improvements first.`,
RunE: runQualityNext,
}
// resolveCmd represents the resolve subcommand
var resolveCmd = &cobra.Command{
Use: "resolve <status> <id>",
Short: "Mark a finding as resolved",
Long: `Mark a finding with a specific status:
- fixed: Issue has been resolved
- wontfix: Issue won't be fixed (valid reason required)
- false_positive: Finding is incorrect
- ignore: Temporarily ignore the finding
Examples:
devour quality resolve fixed abc123 --note "Refactored complex function"
devour quality resolve wontfix def456 --note "Legacy code, planned replacement"`,
RunE: runQualityResolve,
}
// Quality flags
var (
qualityPath string
qualityLanguage string
qualityExclude []string
qualityThreshold int
qualityMinLOC int
qualityTargetScore int
qualityFormat string
qualityResetSubjective bool
qualityNoBadge bool
qualityBadgePath string
)
var explain bool
var tier int
var resolveNote string
var attest string
var qualityUseAST bool
var statusNarrative bool
var fixDryRun bool
var fixAll bool
var reviewPrepare bool
var reviewImport string
var fixCmd = &cobra.Command{
Use: "fix [id]",
Short: "Auto-fix code quality issues",
Long: `Automatically fix T1 (auto-fixable) issues.
Examples:
devour quality fix unused_import::file.go::fmt # Fix specific issue
devour quality fix --all --dry-run # Preview all fixes
devour quality fix --all # Fix all auto-fixable issues`,
RunE: runQualityFix,
}
var reviewCmd = &cobra.Command{
Use: "review",
Short: "Generate or import AI review packets",
Long: `Generate a review packet for AI analysis, or import responses.
Examples:
devour quality review --prepare # Generate review packet
devour quality review --import responses.json # Import AI responses`,
RunE: runQualityReview,
}
func init() {
rootCmd.AddCommand(qualityCmd)
qualityCmd.AddCommand(scanCmd, qualityStatusCmd, nextCmd, resolveCmd, fixCmd, reviewCmd)
// Scan flags
scanCmd.Flags().StringVar(&qualityPath, "path", ".", "Path to scan")
scanCmd.Flags().StringVar(&qualityLanguage, "lang", "", "Language (auto-detected if not specified)")
scanCmd.Flags().StringSliceVar(&qualityExclude, "exclude", []string{}, "Exclude patterns")
scanCmd.Flags().IntVar(&qualityThreshold, "threshold", 15, "Minimum score to flag an issue")
scanCmd.Flags().IntVar(&qualityMinLOC, "min-loc", 50, "Minimum lines of code to analyze")
scanCmd.Flags().IntVar(&qualityTargetScore, "target-score", 95, "Target health score")
scanCmd.Flags().StringVar(&qualityFormat, "format", "text", "Output format (text, json)")
scanCmd.Flags().BoolVar(&qualityResetSubjective, "reset-subjective", false, "Reset subjective baseline")
scanCmd.Flags().BoolVar(&qualityNoBadge, "no-badge", false, "Skip badge generation")
scanCmd.Flags().StringVar(&qualityBadgePath, "badge-path", "scorecard.png", "Badge output path")
// Status flags
qualityStatusCmd.Flags().StringVar(&qualityFormat, "format", "text", "Output format (text, json)")
qualityStatusCmd.Flags().BoolVar(&statusNarrative, "narrative", false, "Include narrative analysis")
// Next flags
nextCmd.Flags().BoolVar(&explain, "explain", false, "Show detailed explanation")
nextCmd.Flags().IntVar(&tier, "tier", 0, "Filter by severity tier (1-4)")
// Resolve flags
resolveCmd.Flags().StringVar(&resolveNote, "note", "", "Note explaining the resolution (required)")
resolveCmd.Flags().StringVar(&attest, "attest", "", "Attestation of improvement")
// Fix flags
fixCmd.Flags().BoolVar(&fixDryRun, "dry-run", false, "Show what would be fixed without making changes")
fixCmd.Flags().BoolVar(&fixAll, "all", false, "Fix all auto-fixable issues")
// Review flags
reviewCmd.Flags().BoolVar(&reviewPrepare, "prepare", false, "Generate review packet for AI analysis")
reviewCmd.Flags().StringVar(&reviewImport, "import", "", "Import review responses from file")
}
func runQualityScan(cmd *cobra.Command, args []string) error {
path := qualityPath
if len(args) > 0 {
path = args[0]
}
if _, err := os.Stat(path); os.IsNotExist(err) {
return fmt.Errorf("path does not exist: %s", path)
}
config := &quality.Config{
Path: path,
Language: qualityLanguage,
Exclude: qualityExclude,
Threshold: qualityThreshold,
MinLOC: qualityMinLOC,
TargetScore: qualityTargetScore,
ResetSubjective: qualityResetSubjective,
NoBadge: qualityNoBadge,
BadgePath: qualityBadgePath,
}
scanner := quality.NewScanner(config)
finder := quality.NewDefaultFileFinder()
scanner.SetFileFinder(finder)
lang := qualityLanguage
if lang == "" {
lang = quality.DetectLanguage(path)
fmt.Printf("Auto-detected language: %s\n", lang)
}
plugin, ok := plugins.Get(lang)
if ok {
fmt.Printf("Using %s plugin with AST analysis\n", lang)
for _, detector := range plugin.CreateDetectors(finder) {
scanner.RegisterDetector(detector)
}
} else {
scanner.RegisterDetector(detectors.NewComplexityDetector(finder))
scanner.RegisterDetector(detectors.NewDuplicationDetector(finder))
scanner.RegisterDetector(detectors.NewNamingDetector(finder))
}
ctx := context.Background()
result, err := scanner.Scan(ctx)
if err != nil {
return fmt.Errorf("scan failed: %w", err)
}
return outputScanResult(result, qualityFormat)
}
func runQualityStatus(cmd *cobra.Command, args []string) error {
// Load previous scan results
dataDir := filepath.Join(".", "devour_data", "quality")
statusFile := filepath.Join(dataDir, "status.json")
var findings []quality.Finding
var lastScan time.Time
if data, err := os.ReadFile(statusFile); err == nil {
var status struct {
Findings []quality.Finding `json:"findings"`
Timestamp time.Time `json:"timestamp"`
}
if err := json.Unmarshal(data, &status); err == nil {
findings = status.Findings
lastScan = status.Timestamp
}
}
if len(findings) == 0 {
fmt.Println("No previous scan results found. Run 'devour quality scan' first.")
return nil
}
// Generate scorecard
scorer := quality.NewScorer(qualityTargetScore)
scorecard := scorer.GenerateScorecard(findings, lastScan)
// Output based on format
switch qualityFormat {
case "json":
return json.NewEncoder(os.Stdout).Encode(scorecard)
default:
fmt.Println(scorer.FormatScorecard(scorecard))
return nil
}
}
func runQualityNext(cmd *cobra.Command, args []string) error {
// Load previous scan results
dataDir := filepath.Join(".", "devour_data", "quality")
statusFile := filepath.Join(dataDir, "status.json")
var findings []quality.Finding
if data, err := os.ReadFile(statusFile); err == nil {
var status struct {
Findings []quality.Finding `json:"findings"`
}
if err := json.Unmarshal(data, &status); err == nil {
findings = status.Findings
}
}
if len(findings) == 0 {
fmt.Println("No findings found. Run 'devour quality scan' first.")
return nil
}
// Get next priority finding
scorer := quality.NewScorer(qualityTargetScore)
next := scorer.GetNextPriority(findings)
if next == nil {
fmt.Println("🎉 No open issues to fix!")
return nil
}
// Filter by tier if specified
if tier > 0 {
if int(next.Severity) != tier {
// Find next in specified tier
for _, finding := range findings {
if finding.Status == quality.StatusOpen && int(finding.Severity) == tier {
next = &finding
break
}
}
if next == nil || int(next.Severity) != tier {
fmt.Printf("No open issues found in tier %d.\n", tier)
return nil
}
}
}
// Display finding
fmt.Printf("Next Priority Issue (T%d)\n", int(next.Severity))
fmt.Println("=======================================")
fmt.Printf("File: %s:%d\n", next.File, next.Line)
fmt.Printf("Title: %s\n", next.Title)
fmt.Printf("Score: %d\n", next.Score)
fmt.Printf("ID: %s\n", next.ID)
fmt.Printf("\nDescription:\n%s\n", next.Description)
if explain {
fmt.Printf("\nExplanation:\n")
fmt.Printf("This is a T%d severity issue. ", int(next.Severity))
switch next.Severity {
case quality.SeverityT1:
fmt.Println("T1 issues are typically auto-fixable like unused imports or debug logs.")
case quality.SeverityT2:
fmt.Println("T2 issues require quick manual fixes like unused variables or dead exports.")
case quality.SeverityT3:
fmt.Println("T3 issues need judgment calls like near-duplicates or single-use abstractions.")
case quality.SeverityT4:
fmt.Println("T4 issues require major refactoring like god components or mixed concerns.")
}
fmt.Printf("\nTo fix: devour quality resolve fixed %s --note \"Describe what you fixed\"\n", next.ID)
}
return nil
}
func runQualityResolve(cmd *cobra.Command, args []string) error {
if len(args) < 2 {
return fmt.Errorf("usage: devour quality resolve <status> <id>")
}
status := quality.Status(args[0])
id := args[1]
// Validate status
validStatuses := map[quality.Status]bool{
quality.StatusFixed: true,
quality.StatusWontfix: true,
quality.StatusFalsePositive: true,
quality.StatusIgnored: true,
}
if !validStatuses[status] {
return fmt.Errorf("invalid status: %s", status)
}
if resolveNote == "" {
return fmt.Errorf("--note is required when resolving findings")
}
// Load current findings
dataDir := filepath.Join(".", "devour_data", "quality")
statusFile := filepath.Join(dataDir, "status.json")
var findings []quality.Finding
if data, err := os.ReadFile(statusFile); err == nil {
var statusData struct {
Findings []quality.Finding `json:"findings"`
}
if err := json.Unmarshal(data, &statusData); err == nil {
findings = statusData.Findings
}
}
// Find and update the finding
found := false
for i, finding := range findings {
if finding.ID == id {
findings[i].Status = status
findings[i].UpdatedAt = time.Now()
if finding.Metadata == nil {
findings[i].Metadata = make(map[string]string)
}
findings[i].Metadata["resolution_note"] = resolveNote
if attest != "" {
findings[i].Metadata["attestation"] = attest
}
found = true
break
}
}
if !found {
return fmt.Errorf("finding not found: %s", id)
}
// Save updated findings
if err := os.MkdirAll(dataDir, 0755); err != nil {
return fmt.Errorf("failed to create data directory: %w", err)
}
statusData := struct {
Findings []quality.Finding `json:"findings"`
Timestamp time.Time `json:"timestamp"`
}{
Findings: findings,
Timestamp: time.Now(),
}
data, err := json.MarshalIndent(statusData, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal status: %w", err)
}
if err := os.WriteFile(statusFile, data, 0644); err != nil {
return fmt.Errorf("failed to save status: %w", err)
}
fmt.Printf("Resolved: %s as %s\n", id, status)
if resolveNote != "" {
fmt.Printf("Note: %s\n", resolveNote)
}
return nil
}
func outputScanResult(result *quality.ScanResult, format string) error {
// Save results to data directory
dataDir := filepath.Join(".", "devour_data", "quality")
if err := os.MkdirAll(dataDir, 0755); err != nil {
return fmt.Errorf("failed to create data directory: %w", err)
}
statusFile := filepath.Join(dataDir, "status.json")
statusData := struct {
Findings []quality.Finding `json:"findings"`
Timestamp time.Time `json:"timestamp"`
}{
Findings: result.Findings,
Timestamp: result.Timestamp,
}
data, err := json.MarshalIndent(statusData, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal results: %w", err)
}
if err := os.WriteFile(statusFile, data, 0644); err != nil {
return fmt.Errorf("failed to save results: %w", err)
}
// Generate scorecard badge if not disabled
if !qualityNoBadge && qualityBadgePath != "" {
if err := generateScorecardBadge(result, qualityBadgePath); err != nil {
fmt.Fprintf(os.Stderr, "Warning: failed to generate scorecard badge: %v\n", err)
} else {
fmt.Printf("Scorecard badge generated: %s\n", qualityBadgePath)
}
}
// Output based on format
switch format {
case "json":
return json.NewEncoder(os.Stdout).Encode(result)
default:
return formatScanResultText(result)
}
}
func generateScorecardBadge(result *quality.ScanResult, outputPath string) error {
// Calculate grade from score
scorer := quality.NewScorer(qualityTargetScore)
grade := scorer.GetHealthGrade(result.StrictScore)
// Group findings by type and tier
findByType := make(map[string]int)
findByTier := make(map[string]int)
for _, f := range result.Findings {
findByType[f.Type]++
tierName := fmt.Sprintf("T%d", int(f.Severity))
findByTier[tierName]++
}
// Get project name from current directory
projectName := "devour"
if dir, err := os.Getwd(); err == nil {
projectName = filepath.Base(dir)
}
// Prepare scorecard data
scoreData := &scorecard.ScorecardData{
ProjectName: projectName,
Version: "", // Could be extracted from version info
OverallScore: float64(result.Score),
StrictScore: float64(result.StrictScore),
Grade: grade,
FindingsTotal: len(result.Findings),
FindingsOpen: len(result.Findings), // All findings are open initially
LastScan: result.Timestamp,
FindByType: findByType,
FindByTier: findByTier,
}
return scorecard.Generate(scoreData, outputPath)
}
func formatScanResultText(result *quality.ScanResult) error {
fmt.Println("Code Quality Scan Results")
fmt.Println("=======================================")
fmt.Printf("Files checked: %d\n", result.FilesChecked)
fmt.Printf("Duration: %s\n", result.Duration)
fmt.Printf("Score: %d (strict: %d)\n", result.Score, result.StrictScore)
fmt.Printf("Findings: %d\n\n", len(result.Findings))
if len(result.Findings) == 0 {
fmt.Println("No code quality issues found.")
return nil
}
bySeverity := make(map[quality.Severity][]quality.Finding)
for _, finding := range result.Findings {
bySeverity[finding.Severity] = append(bySeverity[finding.Severity], finding)
}
tiers := []quality.Severity{quality.SeverityT4, quality.SeverityT3, quality.SeverityT2, quality.SeverityT1}
tierNames := map[quality.Severity]string{
quality.SeverityT1: "T1 (Auto-fixable)",
quality.SeverityT2: "T2 (Quick manual)",
quality.SeverityT3: "T3 (Needs judgment)",
quality.SeverityT4: "T4 (Major refactor)",
}
for _, severity := range tiers {
findings := bySeverity[severity]
if len(findings) == 0 {
continue
}
fmt.Printf("[%s] %d issues\n", tierNames[severity], len(findings))
for _, finding := range findings {
fmt.Printf(" - %s:%d - %s (score: %d)\n",
filepath.Base(finding.File), finding.Line, finding.Title, finding.Score)
}
fmt.Println()
}
fmt.Println("Run 'devour quality next' to see the highest priority issue to fix.")
fmt.Println("Run 'devour quality status' for detailed scorecard.")
return nil
}
func runQualityFix(cmd *cobra.Command, args []string) error {
dataDir := filepath.Join(".", "devour_data", "quality")
statusFile := filepath.Join(dataDir, "status.json")
var findings []quality.Finding
if data, err := os.ReadFile(statusFile); err == nil {
var status struct {
Findings []quality.Finding `json:"findings"`
}
if err := json.Unmarshal(data, &status); err == nil {
findings = status.Findings
}
}
if len(findings) == 0 {
fmt.Println("No findings found. Run 'devour quality scan' first.")
return nil
}
availableFixers := []plugins.Fixer{
fixers.NewUnusedImportFixer(),
fixers.NewFormattingFixer(),
}
ctx := context.Background()
fixed := 0
var errors []string
if fixAll {
for _, finding := range findings {
if finding.Status != quality.StatusOpen || finding.Severity != quality.SeverityT1 {
continue
}
for _, fixer := range availableFixers {
if fixer.CanFix(finding) {
result, err := fixer.Fix(ctx, finding, fixDryRun)
if err != nil {
errors = append(errors, fmt.Sprintf("%s: %v", finding.ID, err))
continue
}
if result.Success {
fixed++
fmt.Printf("[OK] %s\n", result.Message)
}
break
}
}
}
} else {
if len(args) < 1 {
return fmt.Errorf("specify a finding ID or use --all")
}
targetID := args[0]
var target *quality.Finding
for i := range findings {
if findings[i].ID == targetID {
target = &findings[i]
break
}
}
if target == nil {
return fmt.Errorf("finding not found: %s", targetID)
}
for _, fixer := range availableFixers {
if fixer.CanFix(*target) {
result, err := fixer.Fix(ctx, *target, fixDryRun)
if err != nil {
return fmt.Errorf("fix failed: %w", err)
}
fmt.Printf("[OK] %s\n", result.Message)
fixed = 1
break
}
}
}
if fixDryRun {
fmt.Printf("\nDry run complete. %d issues would be fixed.\n", fixed)
} else {
fmt.Printf("\nFixed %d issues.\n", fixed)
}
if len(errors) > 0 {
fmt.Printf("\nErrors:\n")
for _, e := range errors {
fmt.Printf(" • %s\n", e)
}
}
return nil
}
func runQualityReview(cmd *cobra.Command, args []string) error {
dataDir := filepath.Join(".", "devour_data")
if reviewPrepare {
return prepareReviewPacket(dataDir)
}
if reviewImport != "" {
return importReviewResponses(dataDir, reviewImport)
}
return fmt.Errorf("use --prepare to generate a review packet or --import <file> to import responses")
}
func prepareReviewPacket(dataDir string) error {
statusFile := filepath.Join(dataDir, "quality", "status.json")
var findings []quality.Finding
var lastScan time.Time
if data, err := os.ReadFile(statusFile); err == nil {
var status struct {
Findings []quality.Finding `json:"findings"`
Timestamp time.Time `json:"timestamp"`
}
if err := json.Unmarshal(data, &status); err == nil {
findings = status.Findings
lastScan = status.Timestamp
}
}
scorer := quality.NewScorer(qualityTargetScore)
scorecard := scorer.GenerateScorecard(findings, lastScan)
gen := review.NewPacketGenerator(dataDir)
packet, err := gen.Generate(findings, scorecard, "go")
if err != nil {
return fmt.Errorf("failed to generate review packet: %w", err)
}
filename := fmt.Sprintf("review-%s.json", time.Now().Format("20060102-150405"))
if err := gen.Save(packet, filename); err != nil {
return fmt.Errorf("failed to save review packet: %w", err)
}
fmt.Printf("Review packet generated: %s/review/%s\n", dataDir, filename)
fmt.Printf("Findings to review: %d\n", len(packet.Findings))
fmt.Printf("Questions: %d\n", len(packet.Questions))
return nil
}
func importReviewResponses(dataDir string, filename string) error {
gen := review.NewPacketGenerator(dataDir)
responses := make(map[string]string)
data, err := os.ReadFile(filename)
if err != nil {
return fmt.Errorf("failed to read responses file: %w", err)
}
var respData struct {
Responses map[string]string `json:"responses"`
}
if err := json.Unmarshal(data, &respData); err == nil {
responses = respData.Responses
} else {
var simpleResponses map[string]string
if err := json.Unmarshal(data, &simpleResponses); err != nil {
return fmt.Errorf("failed to parse responses: %w", err)
}
responses = simpleResponses
}
if err := gen.ImportReview(filepath.Base(filename), responses); err != nil {
return fmt.Errorf("failed to import responses: %w", err)
}
fmt.Printf("Imported %d review responses\n", len(responses))
return nil
}
+60
View File
@@ -0,0 +1,60 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var queryCmd = &cobra.Command{
Use: "query <text>",
Short: "Search indexed documentation",
Long: `Search indexed documentation using semantic similarity.
Returns the most relevant document chunks based on vector similarity
to the query text.
Examples:
devour query "how to authenticate"
devour query "API rate limiting" --limit 10
devour query "deployment" --format json`,
Args: cobra.MinimumNArgs(1),
RunE: runQuery,
}
var (
queryLimit int
queryFormat string
queryThreshold float64
)
func init() {
queryCmd.Flags().IntVarP(&queryLimit, "limit", "l", 5, "maximum number of results")
queryCmd.Flags().StringVarP(&queryFormat, "format", "f", "text", "output format (text, json, markdown)")
queryCmd.Flags().Float64Var(&queryThreshold, "threshold", 0.7, "similarity threshold (0-1)")
}
func runQuery(cmd *cobra.Command, args []string) error {
query := args[0]
if len(args) > 1 {
query = fmt.Sprintf("%s", args)
}
fmt.Printf("Searching: %q\n", query)
fmt.Printf(" Limit: %d\n", queryLimit)
fmt.Printf(" Threshold: %.2f\n", queryThreshold)
fmt.Println()
// TODO: Implement actual query logic
// 1. Generate embedding for query
// 2. Search vector database
// 3. Format and return results
// Placeholder results
fmt.Println("Results:")
fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
fmt.Println("⚠️ Query functionality not yet implemented")
fmt.Println(" Index some documents first with 'devour scrape'")
return nil
}
+119
View File
@@ -0,0 +1,119 @@
package main
import (
"context"
"fmt"
"time"
"github.com/yourorg/devour/internal/scraper"
)
func main() {
fmt.Println("=== Devour Real HTTP Scraping Test ===")
fmt.Println()
config := &scraper.Config{
UserAgent: "Devour/1.0 (Documentation Scraper)",
Timeout: 30 * time.Second,
RetryCount: 3,
RetryDelay: 1 * time.Second,
Concurrency: 10,
}
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
sources := []struct {
name string
st scraper.SourceType
url string
}{
{"Go stdlib net/http", scraper.SourceTypeGoDocs, "https://pkg.go.dev/net/http"},
{"Spring AI MCP", scraper.SourceTypeSpringDocs, "https://docs.spring.io/spring-ai/reference/api/mcp/mcp-overview.html"},
{"React Hooks", scraper.SourceTypeReactDocs, "https://react.dev/reference/react"},
{"Vue Composition API", scraper.SourceTypeVueDocs, "https://vuejs.org/api/"},
{"Cloudflare Docs", scraper.SourceTypeCloudflareDocs, "https://developers.cloudflare.com/"},
}
for _, src := range sources {
fmt.Printf("=== Testing: %s ===\n", src.name)
s := scraper.NewScraper(src.st, config)
if s == nil {
fmt.Printf(" ✗ Scraper not available for type: %s\n\n", src.st)
continue
}
source := &scraper.Source{
Name: src.name,
Type: src.st,
URL: src.url,
}
fmt.Printf(" Fetching: %s\n", src.url)
docs, err := s.Scrape(ctx, source)
if err != nil {
fmt.Printf(" ✗ Error: %v\n\n", err)
continue
}
fmt.Printf(" ✓ Scraped %d documents\n", len(docs))
if len(docs) > 0 {
first := docs[0]
fmt.Printf(" First document:\n")
fmt.Printf(" Title: %s\n", first.Title)
fmt.Printf(" Type: %s\n", first.Type)
if len(first.Content) > 100 {
fmt.Printf(" Content preview: %s...\n", first.Content[:100])
} else {
fmt.Printf(" Content: %s\n", first.Content)
}
}
changed, hash, err := s.DetectChanges(ctx, source, "")
if err != nil {
fmt.Printf(" ✗ Change detection error: %v\n", err)
} else {
fmt.Printf(" ✓ Change detection: changed=%v, hash=%s\n", changed, hash[:16]+"...")
}
fmt.Println()
}
fmt.Println("=== All Source Types ===")
fmt.Println()
fmt.Println("Available scrapers:")
allTypes := []scraper.SourceType{
scraper.SourceTypeWeb,
scraper.SourceTypeGitHub,
scraper.SourceTypeOpenAPI,
scraper.SourceTypeLocal,
scraper.SourceTypeGoDocs,
scraper.SourceTypeRustDocs,
scraper.SourceTypePythonDocs,
scraper.SourceTypeJavaDocs,
scraper.SourceTypeSpringDocs,
scraper.SourceTypeTSDocs,
scraper.SourceTypeReactDocs,
scraper.SourceTypeVueDocs,
scraper.SourceTypeNuxtDocs,
scraper.SourceTypeMCPDocs,
scraper.SourceTypeDockerDocs,
scraper.SourceTypeCloudflareDocs,
scraper.SourceTypeAstroDocs,
}
for _, st := range allTypes {
s := scraper.NewScraper(st, config)
if s != nil {
fmt.Printf(" ✓ %s\n", st)
} else {
fmt.Printf(" ✗ %s (not implemented)\n", st)
}
}
fmt.Println()
fmt.Println("=== Test Complete ===")
}
+92
View File
@@ -0,0 +1,92 @@
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/yourorg/devour/internal/ui"
)
var (
cfgFile string
verbose bool
showLogo bool
)
var rootCmd = &cobra.Command{
Use: "devour",
Short: "Context ingestion and management for AI",
Long: ` ___
.-' '-.
/ O O \
| _ |
\ '-' /
'-.___.-'
Documentation Devourer
Devour scrapes, indexes, and serves documentation from multiple sources
(GitHub repos, OpenAPI specs, web docs, local files) to feed structured
context to AI models for generating accurate, fully working code.
Runs in two modes:
- Local mode: OpenCode skill running entirely on your machine
- Remote mode: MCP server for multi-user/team access`,
Version: "1.0.0",
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is ./devour.yaml)")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "enable verbose logging")
rootCmd.PersistentFlags().BoolVarP(&showLogo, "logo", "l", false, "show the Devour character logo")
rootCmd.AddCommand(initCmd)
rootCmd.AddCommand(scrapeCmd)
rootCmd.AddCommand(getCmd)
rootCmd.AddCommand(languagesCmd)
rootCmd.AddCommand(demoCmd)
rootCmd.AddCommand(serveCmd)
rootCmd.AddCommand(queryCmd)
rootCmd.AddCommand(statusCmd)
rootCmd.AddCommand(syncCmd)
rootCmd.AddCommand(pushCmd)
rootCmd.AddCommand(logoCmd)
}
// logoCmd displays the Devour character
var logoCmd = &cobra.Command{
Use: "logo",
Short: "Display the Devour character logo",
Long: `Display the ASCII art Devour character mascot.`,
Run: func(cmd *cobra.Command, args []string) {
ui.PrintCharacter()
},
}
func initConfig() {
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
viper.AddConfigPath(".")
viper.AddConfigPath("$HOME/.devour")
viper.SetConfigType("yaml")
viper.SetConfigName("devour")
}
viper.AutomaticEnv()
viper.SetEnvPrefix("DEVOUR")
if err := viper.ReadInConfig(); err == nil && verbose {
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
}
}
+242
View File
@@ -0,0 +1,242 @@
package cmd
import (
"context"
"encoding/json"
"fmt"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"github.com/spf13/cobra"
"github.com/yourorg/devour/internal/markdown"
"github.com/yourorg/devour/internal/scraper"
)
var scrapeCmd = &cobra.Command{
Use: "scrape <source>",
Short: "Scrape documentation from a source",
Long: `Scrape and index documentation from various sources.
Supported source types:
- godocs: Go packages (pkg.go.dev)
- rustdocs: Rust crates (docs.rs)
- pythondocs: Python modules (docs.python.org)
- javadocs: Java packages (docs.oracle.com)
- springdocs: Spring Boot (docs.spring.io)
- tsdocs: TypeScript (typescriptlang.org)
- reactdocs: React (react.dev)
- vuedocs: Vue.js (vuejs.org)
- nuxtdocs: Nuxt (nuxt.com)
- mcpdocs: MCP servers (hub.docker.com/mcp)
- dockerdocs: Docker (docs.docker.com)
- cloudflaredocs: Cloudflare (developers.cloudflare.com)
- astrodocs: Astro (docs.astro.build)
- url: Generic web pages
- github: GitHub repositories
Examples:
devour scrape https://pkg.go.dev/net/http --type godocs
devour scrape https://react.dev/reference/react --type reactdocs
devour scrape https://developers.cloudflare.com/ --type cloudflaredocs
devour scrape --sources sources.yaml`,
Args: cobra.MaximumNArgs(1),
RunE: runScrape,
}
var (
scrapeFormat string
scrapeSources string
scrapeOutput string
scrapeConcurrency int
scrapeType string
)
func init() {
scrapeCmd.Flags().StringVarP(&scrapeFormat, "format", "f", "json", "output format (json, markdown)")
scrapeCmd.Flags().StringVarP(&scrapeSources, "sources", "s", "", "YAML file with source definitions")
scrapeCmd.Flags().StringVarP(&scrapeOutput, "output", "o", "", "output directory (default: devour_data/docs)")
scrapeCmd.Flags().IntVar(&scrapeConcurrency, "concurrency", 10, "parallel scraping workers")
scrapeCmd.Flags().StringVarP(&scrapeType, "type", "t", "", "source type (auto-detected if not specified)")
}
func runScrape(cmd *cobra.Command, args []string) error {
if scrapeSources != "" {
return scrapeFromConfig(scrapeSources)
}
if len(args) == 0 {
return fmt.Errorf("source argument required when not using --sources flag")
}
sourceURL := args[0]
config := &scraper.Config{
UserAgent: "Devour/1.0 (Documentation Scraper)",
Timeout: 30 * time.Second,
RetryCount: 3,
RetryDelay: 1 * time.Second,
Concurrency: scrapeConcurrency,
}
sourceType := scraper.SourceType(scrapeType)
if sourceType == "" {
sourceType = detectSourceType(sourceURL)
}
fmt.Printf("Scraping: %s\n", sourceURL)
fmt.Printf(" Type: %s\n", sourceType)
fmt.Printf(" Concurrency: %d\n", scrapeConcurrency)
fmt.Println()
s := scraper.NewScraper(sourceType, config)
if s == nil {
return fmt.Errorf("unsupported source type: %s", sourceType)
}
source := &scraper.Source{
Name: extractName(sourceURL),
Type: sourceType,
URL: sourceURL,
}
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
docs, err := s.Scrape(ctx, source)
if err != nil {
return fmt.Errorf("scraping failed: %w", err)
}
fmt.Printf("✓ Scraped %d documents\n\n", len(docs))
if scrapeOutput == "" {
scrapeOutput = "devour_data/docs"
}
if err := os.MkdirAll(scrapeOutput, 0755); err != nil {
return fmt.Errorf("failed to create output directory: %w", err)
}
for i, doc := range docs {
var filename string
var content []byte
if scrapeFormat == "markdown" {
filename = fmt.Sprintf("%s_%d.md", sanitizeFilename(doc.Title), i)
// Create enhanced markdown document
markdownDoc := &markdown.Document{
ID: doc.ID,
Source: doc.Source,
Type: string(doc.Type),
Title: doc.Title,
Content: doc.Content,
URL: doc.URL,
Metadata: doc.Metadata,
Hash: doc.Hash,
Timestamp: doc.Timestamp,
}
formatter := markdown.NewFormatter()
content = []byte(formatter.FormatWithTOC(markdownDoc))
} else {
filename = fmt.Sprintf("%s_%d.json", sanitizeFilename(doc.Title), i)
content, err = json.MarshalIndent(doc, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal document: %w", err)
}
}
filePath := filepath.Join(scrapeOutput, filename)
if err := os.WriteFile(filePath, content, 0644); err != nil {
return fmt.Errorf("failed to write document: %w", err)
}
fmt.Printf(" 📄 %s (%s)\n", filename, doc.Type)
}
fmt.Printf("\n✓ Scraping complete!\n")
fmt.Printf(" Output: %s\n", scrapeOutput)
fmt.Println(" Run 'devour status' to see indexed documents")
return nil
}
func scrapeFromConfig(configPath string) error {
return fmt.Errorf("scraping from config file not yet implemented")
}
func detectSourceType(sourceURL string) scraper.SourceType {
u, err := url.Parse(sourceURL)
if err != nil {
return scraper.SourceTypeWeb
}
host := u.Host
path := u.Path
switch {
case host == "pkg.go.dev" || strings.HasSuffix(host, "pkg.go.dev"):
return scraper.SourceTypeGoDocs
case host == "docs.rs" || host == "doc.rust-lang.org":
return scraper.SourceTypeRustDocs
case host == "docs.python.org":
return scraper.SourceTypePythonDocs
case host == "docs.oracle.com":
return scraper.SourceTypeJavaDocs
case host == "docs.spring.io":
return scraper.SourceTypeSpringDocs
case host == "www.typescriptlang.org" || host == "typescriptlang.org":
return scraper.SourceTypeTSDocs
case host == "react.dev":
return scraper.SourceTypeReactDocs
case host == "vuejs.org":
return scraper.SourceTypeVueDocs
case host == "nuxt.com":
return scraper.SourceTypeNuxtDocs
case strings.Contains(host, "docker.com") || host == "docs.docker.com":
if strings.Contains(path, "/mcp/") {
return scraper.SourceTypeMCPDocs
}
return scraper.SourceTypeDockerDocs
case host == "developers.cloudflare.com":
return scraper.SourceTypeCloudflareDocs
case host == "docs.astro.build":
return scraper.SourceTypeAstroDocs
case host == "github.com":
return scraper.SourceTypeGitHub
default:
return scraper.SourceTypeWeb
}
}
func extractName(sourceURL string) string {
u, err := url.Parse(sourceURL)
if err != nil {
return "unknown"
}
parts := strings.Split(strings.Trim(u.Path, "/"), "/")
if len(parts) > 0 {
return parts[len(parts)-1]
}
return u.Host
}
func sanitizeFilename(name string) string {
name = strings.ToLower(name)
name = strings.ReplaceAll(name, " ", "_")
name = strings.ReplaceAll(name, "/", "_")
name = strings.ReplaceAll(name, ":", "_")
name = strings.ReplaceAll(name, ".", "_")
if len(name) > 50 {
name = name[:50]
}
return name
}
+61
View File
@@ -0,0 +1,61 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var serveCmd = &cobra.Command{
Use: "serve",
Short: "Start the MCP server",
Long: `Start the Devour MCP server.
In local mode (default), the server communicates via stdio, making it
suitable for use as an OpenCode skill.
In remote mode (--remote flag), the server listens on HTTP and exposes
a REST API for multi-user access.
Examples:
devour serve # Local mode (stdio)
devour serve --remote # Remote mode on default port
devour serve --remote --port 3000`,
RunE: runServe,
}
var (
serveRemote bool
servePort int
serveHost string
)
func init() {
serveCmd.Flags().BoolVar(&serveRemote, "remote", false, "run as remote HTTP server")
serveCmd.Flags().IntVarP(&servePort, "port", "p", 8080, "HTTP port (remote mode only)")
serveCmd.Flags().StringVar(&serveHost, "host", "localhost", "HTTP host (remote mode only)")
}
func runServe(cmd *cobra.Command, args []string) error {
if serveRemote {
fmt.Printf("🚀 Starting Devour server in remote mode\n")
fmt.Printf(" Host: %s\n", serveHost)
fmt.Printf(" Port: %d\n", servePort)
fmt.Printf(" URL: http://%s:%d\n", serveHost, servePort)
// TODO: Start HTTP MCP server
return fmt.Errorf("remote mode not yet implemented")
}
fmt.Println("🚀 Starting Devour server in local mode (stdio)")
fmt.Println(" Communicating via JSON-RPC over stdin/stdout")
// TODO: Start stdio MCP server
// Should handle JSON-RPC messages for:
// - devour_query
// - devour_add
// - devour_status
// - devour_sync
return fmt.Errorf("local mode not yet implemented")
}
+61
View File
@@ -0,0 +1,61 @@
package cmd
import (
"fmt"
"time"
"github.com/spf13/cobra"
"github.com/yourorg/devour/internal/ui"
)
var statusCmd = &cobra.Command{
Use: "status",
Short: "Show index status and statistics",
Long: `Display the current status of the Devour index.
Shows:
- Index health
- Document count
- Last update time
- Source status
- Storage usage`,
RunE: runStatus,
}
func runStatus(cmd *cobra.Command, args []string) error {
// Print the small character mascot
ui.PrintCharacterSmall()
fmt.Println()
ui.PrintHeader("Devour Status")
// TODO: Implement actual status check
// Check:
// - Index existence and health
// - Document count
// - Vector count
// - Last sync time
// - Source status
// Placeholder status
ui.PrintKeyValue("Index Health", "⚠️ Not initialized")
ui.PrintKeyValue("Documents", "0 indexed")
ui.PrintKeyValue("Chunks", "0 total")
ui.PrintKeyValue("Vector Dimension", "1536")
ui.PrintKeyValue("Last Updated", "Never")
ui.PrintKeyValue("Storage Used", "0 MB")
fmt.Println()
ui.PrintSection("Sources")
ui.PrintInfo(" None configured")
fmt.Println()
ui.PrintSection("Next Steps")
fmt.Println(" 1. Run 'devour init' to initialize")
fmt.Println(" 2. Run 'devour scrape <source>' to index documents")
// Show when check happened
fmt.Printf("\nStatus as of: %s\n", time.Now().Format(time.RFC3339))
return nil
}
+62
View File
@@ -0,0 +1,62 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var syncCmd = &cobra.Command{
Use: "sync",
Short: "Synchronize with configured sources",
Long: `Fetch updates from all configured sources.
Checks each source for changes (using hash or timestamp comparison)
and updates the index accordingly.
Examples:
devour sync # Sync all sources
devour sync --source my-docs # Sync specific source
devour sync --rebuild # Full rebuild`,
RunE: runSync,
}
var (
syncSource string
syncRebuild bool
syncForce bool
)
func init() {
syncCmd.Flags().StringVarP(&syncSource, "source", "s", "", "sync specific source only")
syncCmd.Flags().BoolVar(&syncRebuild, "rebuild", false, "rebuild entire index")
syncCmd.Flags().BoolVarP(&syncForce, "force", "f", false, "force sync even if no changes detected")
}
func runSync(cmd *cobra.Command, args []string) error {
if syncRebuild {
fmt.Println("🔄 Rebuilding index from all sources...")
} else {
fmt.Println("🔄 Syncing with configured sources...")
}
if syncSource != "" {
fmt.Printf(" Source: %s\n", syncSource)
}
// TODO: Implement actual sync logic
// 1. Load sources from config
// 2. For each source:
// a. Check for changes (hash/timestamp)
// b. If changes detected or --force:
// - Scrape updated content
// - Re-generate embeddings
// - Update index
// 3. Update metadata
fmt.Println()
fmt.Println("⚠️ Sync functionality not yet implemented")
fmt.Println(" Configure sources in devour.yaml first")
return nil
}