@amaster.ai/pi-lark 0.1.2-beta.42 → 0.1.2-beta.44

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.
Files changed (35) hide show
  1. package/package.json +3 -3
  2. package/skills/lark-approval/references/lark-approval-initiate.md +2 -5
  3. package/skills/lark-approval/references/lark-approval-instances-initiated.md +6 -0
  4. package/skills/lark-approval/references/lark-approval-tasks-query.md +9 -0
  5. package/skills/lark-approval/references/lark-approval-tasks-rollback.md +8 -2
  6. package/skills/lark-apps/SKILL.md +16 -10
  7. package/skills/lark-apps/references/lark-apps-access-scope-set.md +1 -1
  8. package/skills/lark-apps/references/lark-apps-db-execute.md +185 -1
  9. package/skills/lark-apps/references/lark-apps-db.md +1 -1
  10. package/skills/lark-apps/references/lark-apps-role.md +133 -0
  11. package/skills/lark-base/SKILL.md +6 -2
  12. package/skills/lark-base/references/dashboard-block-data-config.md +28 -2
  13. package/skills/lark-base/references/lark-base-cell-value.md +9 -4
  14. package/skills/lark-base/references/lark-base-dashboard.md +11 -2
  15. package/skills/lark-base/references/lark-base-data-query.md +9 -7
  16. package/skills/lark-base/references/lark-base-field-create.md +4 -2
  17. package/skills/lark-base/references/lark-base-field-json.md +52 -15
  18. package/skills/lark-base/references/lark-base-field-update.md +4 -2
  19. package/skills/lark-base/references/lark-base-view-set-filter.md +3 -1
  20. package/skills/lark-drive/SKILL.md +4 -2
  21. package/skills/lark-drive/references/lark-drive-delete.md +23 -11
  22. package/skills/lark-drive/references/lark-drive-move.md +5 -3
  23. package/skills/lark-drive/references/lark-drive-task-result.md +58 -5
  24. package/skills/lark-event/SKILL.md +2 -1
  25. package/skills/lark-event/references/lark-event-approval.md +170 -0
  26. package/skills/lark-slides/references/slides_xml_schema_definition.xml +7 -2
  27. package/skills/lark-slides/references/xml-format-guide.md +15 -0
  28. package/skills/lark-slides/scripts/xml_text_overlap_lint.py +264 -6
  29. package/skills/lark-slides/scripts/xml_text_overlap_lint_test.py +348 -6
  30. package/skills/lark-vc/SKILL.md +6 -3
  31. package/skills/lark-vc/references/vc-domain-boundaries.md +9 -1
  32. package/skills/lark-vc-agent/SKILL.md +1 -1
  33. package/skills/lark-wiki/SKILL.md +4 -2
  34. package/skills/lark-wiki/references/lark-wiki-move-to-drive.md +122 -0
  35. package/skills/lark-wiki/references/lark-wiki-move.md +5 -3
@@ -2,7 +2,12 @@
2
2
  # SPDX-License-Identifier: MIT
3
3
  from __future__ import annotations
4
4
 
5
+ import json
6
+ import subprocess
7
+ import sys
8
+ import tempfile
5
9
  import unittest
10
+ from pathlib import Path
6
11
 
7
12
  import xml_text_overlap_lint
8
13
 
@@ -212,7 +217,8 @@ class XmlTextOverlapLintTest(unittest.TestCase):
212
217
  )
213
218
  self.assertEqual(result["slide_size"], {"width": 960, "height": 540})
214
219
  self.assertEqual(result["summary"]["slide_count"], 1)
215
- self.assertEqual(result["summary"]["error_count"], 0)
220
+ self.assertEqual(result["summary"]["error_count"], 1)
221
+ self.assertEqual(result["slides"][0]["issues"][0]["code"], "shape_out_of_canvas")
216
222
 
217
223
  def test_lint_xml_preserves_presentation_canvas_and_slide_order(self) -> None:
218
224
  result = xml_text_overlap_lint.lint_xml(
@@ -596,7 +602,7 @@ class XmlTextOverlapLintTest(unittest.TestCase):
596
602
  self.assertEqual(result["slides"][0]["issues"][0]["code"], "bbox_overlap")
597
603
  self.assertEqual(result["slides"][0]["issues"][0]["elements"], ["source", "target"])
598
604
 
599
- def test_lint_xml_does_not_check_bounds_or_text_height(self) -> None:
605
+ def test_lint_xml_reports_text_out_of_canvas_but_not_text_height(self) -> None:
600
606
  result = xml_text_overlap_lint.lint_xml(
601
607
  """
602
608
  <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
@@ -613,8 +619,11 @@ class XmlTextOverlapLintTest(unittest.TestCase):
613
619
  </presentation>
614
620
  """
615
621
  )
616
- self.assertEqual(result["summary"]["error_count"], 0)
622
+ issue = result["slides"][0]["issues"][0]
623
+ self.assertEqual(result["summary"]["error_count"], 1)
617
624
  self.assertEqual(result["summary"]["warning_count"], 0)
625
+ self.assertEqual(issue["code"], "shape_out_of_canvas")
626
+ self.assertEqual(issue["overflow"], {"left": 0, "top": 0, "right": 160, "bottom": 40})
618
627
 
619
628
  def test_lint_xml_allows_template_style_bleed_and_text_over_images(self) -> None:
620
629
  result = xml_text_overlap_lint.lint_xml(
@@ -669,7 +678,7 @@ class XmlTextOverlapLintTest(unittest.TestCase):
669
678
  self.assertEqual(elements[1]["fontSize"], 28)
670
679
  self.assertEqual(elements[1]["text"], "Growth & scale\nFocused execution")
671
680
 
672
- def test_lint_xml_does_not_check_small_out_of_bounds_elements(self) -> None:
681
+ def test_lint_xml_allows_small_out_of_bounds_images(self) -> None:
673
682
  result = xml_text_overlap_lint.lint_xml(
674
683
  """
675
684
  <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
@@ -683,7 +692,7 @@ class XmlTextOverlapLintTest(unittest.TestCase):
683
692
  )
684
693
  self.assertEqual(result["summary"]["error_count"], 0)
685
694
 
686
- def test_lint_xml_ignores_obviously_misplaced_large_visuals(self) -> None:
695
+ def test_lint_xml_allows_out_of_canvas_images(self) -> None:
687
696
  result = xml_text_overlap_lint.lint_xml(
688
697
  """
689
698
  <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
@@ -698,7 +707,7 @@ class XmlTextOverlapLintTest(unittest.TestCase):
698
707
  )
699
708
  self.assertEqual(result["summary"]["error_count"], 0)
700
709
 
701
- def test_lint_xml_allows_reasonable_large_visual_bleed(self) -> None:
710
+ def test_lint_xml_allows_full_bleed_images(self) -> None:
702
711
  result = xml_text_overlap_lint.lint_xml(
703
712
  """
704
713
  <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
@@ -712,6 +721,339 @@ class XmlTextOverlapLintTest(unittest.TestCase):
712
721
  )
713
722
  self.assertEqual(result["summary"]["error_count"], 0)
714
723
 
724
+ def test_lint_xml_reports_text_and_chart_out_of_canvas(self) -> None:
725
+ result = xml_text_overlap_lint.lint_xml(
726
+ """
727
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
728
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
729
+ <data>
730
+ <shape id="outside-shape" type="text" topLeftX="-10" topLeftY="40" width="50" height="50"/>
731
+ <img id="outside-img" src="token" topLeftX="120" topLeftY="-20" width="50" height="50"/>
732
+ <chart id="outside-chart" topLeftX="900" topLeftY="100" width="100" height="100"/>
733
+ <whiteboard id="outside-whiteboard" topLeftX="100" topLeftY="500" width="100" height="100"/>
734
+ </data>
735
+ </slide>
736
+ </presentation>
737
+ """
738
+ )
739
+ issues = result["slides"][0]["issues"]
740
+ self.assertEqual(result["summary"]["error_count"], 2)
741
+ self.assertEqual(
742
+ [(issue["code"], issue["elements"], issue["overflow"]) for issue in issues],
743
+ [
744
+ ("shape_out_of_canvas", ["outside-shape"], {"left": 10, "top": 0, "right": 0, "bottom": 0}),
745
+ ("chart_out_of_canvas", ["outside-chart"], {"left": 0, "top": 0, "right": 40, "bottom": 0}),
746
+ ],
747
+ )
748
+
749
+ def test_lint_xml_uses_rotated_text_and_chart_bounds_for_canvas_validation(self) -> None:
750
+ result = xml_text_overlap_lint.lint_xml(
751
+ """
752
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
753
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
754
+ <data>
755
+ <shape id="rotated-text" type="text" topLeftX="0" topLeftY="0" width="100" height="100" rotation="45"/>
756
+ <chart id="rotated-chart" topLeftX="860" topLeftY="200" width="100" height="100" rotation="45"/>
757
+ </data>
758
+ </slide>
759
+ </presentation>
760
+ """
761
+ )
762
+ issues_by_element = {issue["elements"][0]: issue for issue in result["slides"][0]["issues"]}
763
+ self.assertEqual(result["summary"]["error_count"], 2)
764
+ self.assertEqual(issues_by_element["rotated-text"]["code"], "shape_out_of_canvas")
765
+ self.assertAlmostEqual(issues_by_element["rotated-text"]["overflow"]["left"], 20.710678, places=5)
766
+ self.assertAlmostEqual(issues_by_element["rotated-text"]["overflow"]["top"], 20.710678, places=5)
767
+ self.assertEqual(issues_by_element["rotated-chart"]["code"], "chart_out_of_canvas")
768
+ self.assertAlmostEqual(issues_by_element["rotated-chart"]["overflow"]["right"], 20.710678, places=5)
769
+
770
+ def test_lint_xml_treats_non_finite_rotations_as_zero(self) -> None:
771
+ result = xml_text_overlap_lint.lint_xml(
772
+ """
773
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
774
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
775
+ <data>
776
+ <shape id="infinite" type="text" topLeftX="-10" topLeftY="0" width="20" height="20" rotation="inf"/>
777
+ <shape id="negative-infinite" type="text" topLeftX="0" topLeftY="-10" width="20" height="20" rotation="-inf"/>
778
+ <chart id="not-a-number" topLeftX="950" topLeftY="0" width="20" height="20" rotation="nan"/>
779
+ </data>
780
+ </slide>
781
+ </presentation>
782
+ """
783
+ )
784
+ issues_by_element = {issue["elements"][0]: issue for issue in result["slides"][0]["issues"]}
785
+ self.assertEqual(result["summary"]["error_count"], 3)
786
+ self.assertEqual(issues_by_element["infinite"]["overflow"], {"left": 10, "top": 0, "right": 0, "bottom": 0})
787
+ self.assertEqual(issues_by_element["negative-infinite"]["overflow"], {"left": 0, "top": 10, "right": 0, "bottom": 0})
788
+ self.assertEqual(issues_by_element["not-a-number"]["overflow"], {"left": 0, "top": 0, "right": 10, "bottom": 0})
789
+
790
+ def test_lint_xml_reports_table_bottom_overflow_from_declared_bounds(self) -> None:
791
+ result = xml_text_overlap_lint.lint_xml(
792
+ """
793
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
794
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
795
+ <data>
796
+ <table id="score-table" topLeftX="54" topLeftY="238" width="414" height="385">
797
+ <tr><td><content><p>Score</p></content></td></tr>
798
+ </table>
799
+ </data>
800
+ </slide>
801
+ </presentation>
802
+ """
803
+ )
804
+ issue = result["slides"][0]["issues"][0]
805
+ self.assertEqual(result["summary"]["error_count"], 1)
806
+ self.assertEqual(issue["code"], "table_out_of_canvas")
807
+ self.assertEqual(issue["elements"], ["score-table"])
808
+ self.assertEqual(issue["overflow"], {"left": 0, "top": 0, "right": 0, "bottom": 83})
809
+ self.assertEqual(issue["bbox"], {"x": 54, "y": 238, "width": 414, "height": 385})
810
+
811
+ def test_lint_xml_reports_table_right_overflow_from_declared_bounds(self) -> None:
812
+ result = xml_text_overlap_lint.lint_xml(
813
+ """
814
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
815
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
816
+ <data>
817
+ <table id="wide-table" topLeftX="850" topLeftY="80" width="180" height="120">
818
+ <tr><td><content><p>Score</p></content></td></tr>
819
+ </table>
820
+ </data>
821
+ </slide>
822
+ </presentation>
823
+ """
824
+ )
825
+ issue = result["slides"][0]["issues"][0]
826
+ self.assertEqual(result["summary"]["error_count"], 1)
827
+ self.assertEqual(issue["code"], "table_out_of_canvas")
828
+ self.assertEqual(issue["overflow"], {"left": 0, "top": 0, "right": 70, "bottom": 0})
829
+
830
+ def test_lint_xml_allows_table_with_declared_bounds_inside_canvas(self) -> None:
831
+ result = xml_text_overlap_lint.lint_xml(
832
+ """
833
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
834
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
835
+ <data>
836
+ <table id="inside-table" topLeftX="40" topLeftY="120" width="880" height="360">
837
+ <tr><td><content><p>Score</p></content></td></tr>
838
+ </table>
839
+ </data>
840
+ </slide>
841
+ </presentation>
842
+ """
843
+ )
844
+ self.assertEqual(result["summary"]["error_count"], 0)
845
+ self.assertEqual(result["summary"]["warning_count"], 0)
846
+
847
+ def test_lint_xml_reports_resolved_table_bounds_when_declared_sizes_are_missing(self) -> None:
848
+ result = xml_text_overlap_lint.lint_xml(
849
+ """
850
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
851
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
852
+ <data>
853
+ <table id="implicit-size-table" topLeftX="850" topLeftY="480">
854
+ <colgroup><col/><col/></colgroup>
855
+ <tr><td/><td/></tr>
856
+ <tr><td/><td/></tr>
857
+ </table>
858
+ </data>
859
+ </slide>
860
+ </presentation>
861
+ """
862
+ )
863
+ issue = result["slides"][0]["issues"][0]
864
+ self.assertEqual(result["summary"]["error_count"], 1)
865
+ self.assertEqual(issue["code"], "table_out_of_canvas")
866
+ self.assertEqual(issue["bbox"], {"x": 850, "y": 480, "width": 220, "height": 74})
867
+ self.assertEqual(issue["overflow"], {"left": 0, "top": 0, "right": 110, "bottom": 14})
868
+
869
+ def test_lint_xml_uses_resolved_table_bounds_for_canvas_validation(self) -> None:
870
+ result = xml_text_overlap_lint.lint_xml(
871
+ """
872
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
873
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
874
+ <data>
875
+ <table id="resolved-overflow-table" topLeftX="800" topLeftY="80" width="100" height="40">
876
+ <colgroup><col width="100"/><col width="100"/></colgroup>
877
+ <tr height="40"><td/><td/></tr>
878
+ </table>
879
+ </data>
880
+ </slide>
881
+ </presentation>
882
+ """
883
+ )
884
+ issues = result["slides"][0]["issues"]
885
+ canvas_issue = next(issue for issue in issues if issue["code"] == "table_out_of_canvas")
886
+ mismatch_issue = next(issue for issue in issues if issue["code"] == "table_resolved_size_mismatch")
887
+ self.assertEqual(result["summary"]["error_count"], 1)
888
+ self.assertEqual(canvas_issue["bbox"], {"x": 800, "y": 80, "width": 200, "height": 40})
889
+ self.assertEqual(canvas_issue["overflow"]["right"], 40)
890
+ self.assertEqual(mismatch_issue["dimension"], "width")
891
+ self.assertEqual(mismatch_issue["resolved_size"], canvas_issue["bbox"]["width"])
892
+
893
+ def test_lint_xml_uses_the_same_anonymous_table_id_for_all_table_diagnostics(self) -> None:
894
+ result = xml_text_overlap_lint.lint_xml(
895
+ """
896
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
897
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
898
+ <data>
899
+ <shape id="title" type="text" topLeftX="40" topLeftY="40" width="200" height="40"/>
900
+ <img id="logo" src="token" topLeftX="40" topLeftY="100" width="40" height="40"/>
901
+ <table topLeftX="900" topLeftY="80" width="100" height="40">
902
+ <colgroup><col width="100"/><col width="100"/></colgroup>
903
+ <tr height="40"><td/><td/></tr>
904
+ </table>
905
+ </data>
906
+ </slide>
907
+ </presentation>
908
+ """
909
+ )
910
+ issues = result["slides"][0]["issues"]
911
+ canvas_issue = next(issue for issue in issues if issue["code"] == "table_out_of_canvas")
912
+ mismatch_issue = next(issue for issue in issues if issue["code"] == "table_resolved_size_mismatch")
913
+ self.assertEqual(canvas_issue["elements"], ["table-3"])
914
+ self.assertEqual(mismatch_issue["elements"], ["table-3"])
915
+
916
+ def test_lint_xml_reports_info_when_table_target_size_resolves_larger_than_declared(self) -> None:
917
+ result = xml_text_overlap_lint.lint_xml(
918
+ """
919
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
920
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
921
+ <data>
922
+ <table id="size-mismatch" topLeftX="40" topLeftY="120" width="200" height="80">
923
+ <colgroup><col span="2" width="100"/><col width="50"/></colgroup>
924
+ <tr height="40"><td/><td/><td/></tr>
925
+ <tr height="60"><td/><td/><td/></tr>
926
+ </table>
927
+ </data>
928
+ </slide>
929
+ </presentation>
930
+ """
931
+ )
932
+ issues_by_dimension = {issue["dimension"]: issue for issue in result["slides"][0]["issues"]}
933
+ self.assertEqual(result["summary"]["error_count"], 0)
934
+ self.assertEqual(result["summary"]["warning_count"], 0)
935
+ self.assertEqual(result["summary"]["info_count"], 2)
936
+ self.assertEqual(issues_by_dimension["width"]["level"], "info")
937
+ self.assertEqual(issues_by_dimension["width"]["code"], "table_resolved_size_mismatch")
938
+ self.assertEqual(issues_by_dimension["width"]["resolved_sizes"], [100, 100, 50])
939
+ self.assertEqual(issues_by_dimension["width"]["resolved_size"], 250)
940
+ self.assertEqual(issues_by_dimension["height"]["resolved_sizes"], [40, 60])
941
+ self.assertEqual(issues_by_dimension["height"]["resolved_size"], 100)
942
+
943
+ def test_lint_xml_does_not_report_info_when_table_target_size_is_resolved_exactly(self) -> None:
944
+ result = xml_text_overlap_lint.lint_xml(
945
+ """
946
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
947
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
948
+ <data>
949
+ <table id="size-match" topLeftX="40" topLeftY="120" width="300" height="100">
950
+ <colgroup><col width="100"/><col/></colgroup>
951
+ <tr height="40"><td/><td/></tr>
952
+ <tr><td/><td/></tr>
953
+ </table>
954
+ </data>
955
+ </slide>
956
+ </presentation>
957
+ """
958
+ )
959
+ self.assertEqual(result["summary"]["error_count"], 0)
960
+ self.assertEqual(result["summary"]["warning_count"], 0)
961
+ self.assertEqual(result["summary"]["info_count"], 0)
962
+ self.assertEqual(result["slides"][0]["issues"], [])
963
+
964
+ def test_lint_xml_keeps_resolved_table_sizes_positive_when_target_is_too_small(self) -> None:
965
+ result = xml_text_overlap_lint.lint_xml(
966
+ """
967
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
968
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
969
+ <data>
970
+ <table id="narrow-table" topLeftX="40" topLeftY="120" width="1">
971
+ <colgroup><col/><col/></colgroup>
972
+ <tr><td/><td/></tr>
973
+ </table>
974
+ </data>
975
+ </slide>
976
+ </presentation>
977
+ """
978
+ )
979
+ issue = result["slides"][0]["issues"][0]
980
+ self.assertEqual(issue["dimension"], "width")
981
+ self.assertEqual(issue["resolved_sizes"], [1, 1])
982
+ self.assertEqual(issue["resolved_size"], 2)
983
+
984
+ def test_fill_last_size_gap_preserves_target_when_positive_sizes_are_possible(self) -> None:
985
+ final_sizes = xml_text_overlap_lint.fill_last_size_gap([10, 10], 3)
986
+ self.assertEqual(final_sizes, [2, 1])
987
+ self.assertEqual(sum(final_sizes), 3)
988
+
989
+ def test_cli_reports_table_layout_size_info_for_weighted_min_layout_cases(self) -> None:
990
+ cases = {
991
+ "target-exact": (
992
+ """
993
+ <table topLeftX="40" topLeftY="120" width="360" height="150">
994
+ <colgroup><col width="100"/><col width="200"/></colgroup>
995
+ <tr height="40"><td/><td/></tr><tr height="60"><td/><td/></tr>
996
+ </table>
997
+ """,
998
+ 0,
999
+ ),
1000
+ "declared-size-exceeds-target": (
1001
+ """
1002
+ <table topLeftX="40" topLeftY="120" width="200" height="80">
1003
+ <colgroup><col span="2" width="100"/><col width="50"/></colgroup>
1004
+ <tr height="40"><td/><td/><td/></tr><tr height="60"><td/><td/><td/></tr>
1005
+ </table>
1006
+ """,
1007
+ 2,
1008
+ ),
1009
+ "remaining-space-insufficient": (
1010
+ """
1011
+ <table topLeftX="40" topLeftY="120" width="80" height="30">
1012
+ <colgroup><col width="80"/><col/></colgroup>
1013
+ <tr height="40"><td/><td/></tr><tr><td/><td/></tr>
1014
+ </table>
1015
+ """,
1016
+ 2,
1017
+ ),
1018
+ "no-target-size": (
1019
+ """
1020
+ <table topLeftX="40" topLeftY="120">
1021
+ <colgroup><col width="80"/><col/></colgroup>
1022
+ <tr height="40"><td/><td/></tr><tr><td/><td/></tr>
1023
+ </table>
1024
+ """,
1025
+ 0,
1026
+ ),
1027
+ }
1028
+ script_path = Path(xml_text_overlap_lint.__file__).resolve()
1029
+ with tempfile.TemporaryDirectory() as temp_dir:
1030
+ for name, (table_xml, expected_info_count) in cases.items():
1031
+ with self.subTest(case=name):
1032
+ input_path = Path(temp_dir) / f"{name}.xml"
1033
+ input_path.write_text(
1034
+ f"""
1035
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
1036
+ <slide xmlns="http://www.larkoffice.com/sml/2.0"><data>{table_xml}</data></slide>
1037
+ </presentation>
1038
+ """,
1039
+ encoding="utf-8",
1040
+ )
1041
+ completed = subprocess.run(
1042
+ [sys.executable, str(script_path), "--input", str(input_path)],
1043
+ capture_output=True,
1044
+ check=False,
1045
+ text=True,
1046
+ )
1047
+ result = json.loads(completed.stdout)
1048
+ self.assertEqual(completed.returncode, 0, completed.stderr)
1049
+ self.assertEqual(result["summary"]["error_count"], 0)
1050
+ self.assertEqual(result["summary"]["warning_count"], 0)
1051
+ self.assertEqual(result["summary"]["info_count"], expected_info_count)
1052
+ self.assertTrue(
1053
+ all(issue["level"] == "info" for issue in result["slides"][0]["issues"]),
1054
+ result["slides"][0]["issues"],
1055
+ )
1056
+
715
1057
  def test_lint_xml_warns_for_whiteboard_external_boundary_overlap(self) -> None:
716
1058
  result = xml_text_overlap_lint.lint_xml(
717
1059
  """
@@ -56,7 +56,7 @@ lark-cli vc +search --query "站会" --start <start_time> --end <end_time>
56
56
 
57
57
  - **视频会议(Meeting)**:飞书视频会议实例,通过 meeting_id 标识。已结束的会议支持通过关键词、时间段、参会人、组织者、会议室等条件搜索(见 `+search`)。
58
58
  - **会议纪要(Note)**:视频会议结束后生成的结构化文档,通过 `note_id` 标识,包含纪要文档(总结、待办)和逐字稿文档。`note_display_type` 区分**普通纪要(`normal`)**和 **unified 纪要**;已知 `note_id` 的直查与 unified 原始记录请用 [lark-note](../lark-note/SKILL.md)。
59
- - **妙记(Minutes)**:来源于飞书视频会议的录制产物或用户上传的音视频文件,支持视频/音频的转写,包含总结、待办、章节和文字记录,通过 minute_token 标识。
59
+ - **妙记(Minutes)**:来源于飞书视频会议的录制产物或用户上传的音视频文件,支持视频/音频的转写,包含总结、待办、章节和文字记录,通过 minute_token 标识。妙记带有**原始会议录制视频**,会后**不会自动授权给参会人**,需管理员授权或参会人主动申请;而智能纪要及其逐字稿会后自动授权给参会人。
60
60
  - **纪要文档(MainDoc)**:AI 智能纪要的主文档,包含 AI 生成的总结和待办,对应 `note_doc_token`。
61
61
  - **用户会议纪要(MeetingNotes)**:用户主动绑定到日程的纪要文档,对应 `meeting_note`。需先通过 [`calendar +meeting`](../lark-calendar/references/lark-calendar-meeting.md) 由 `event_id` 获取。
62
62
  - **逐字稿(VerbatimDoc)**:会议的逐句文字记录,包含说话人和时间戳。
@@ -65,12 +65,15 @@ lark-cli vc +search --query "站会" --start <start_time> --end <end_time>
65
65
 
66
66
  | 用户意图 | 必须读取的产物 | 禁止 |
67
67
  |---------|-------------|------|
68
- | 提炼/总结/重新总结/整理会议内容/回顾会议 | 为降低 token 消耗,非必须不得获取 AI 纪要。必须使用原始对话记录(按下方逐字稿路由取得)或妙记文字记录(Transcript),基于原始对话独立分析 | 禁止直接搬运 AI 纪要(`note_doc_token`)的总结作为最终输出 |
69
- | 查看待办/章节 | AI 纪要(`note_doc_token`)或妙记产物 — AI 待办更友好(含提出人和负责人),章节按话题划分更结构化 | — |
68
+ | 提炼/总结/重新总结/整理会议内容/回顾会议 | 为降低 token 消耗,非必须不得获取 AI 纪要。必须使用原始对话记录(按下方逐字稿路由取得),基于原始对话独立分析。两类产物都存在且用户未指定时,默认用智能纪要的逐字稿;用户明确要妙记时才用妙记文字记录(Transcript) | 禁止直接搬运 AI 纪要(`note_doc_token`)的总结作为最终输出 |
69
+ | 查看待办/章节 | 默认 AI 纪要(`note_doc_token`);仅存在妙记或用户明确要妙记时用妙记产物 — AI 待办更友好(含提出人和负责人),章节按话题划分更结构化 | — |
70
70
  | 查看纪要链接/文档地址 | 仅返回文档链接,无需读取内容 | — |
71
71
  | 直接看 AI 总结结果 | AI 纪要(`note_doc_token`) | — |
72
72
  | 谁说了什么/完整发言记录 | 原始对话记录(按下方逐字稿路由取得) | — |
73
73
 
74
+ > **智能纪要 vs 妙记的选择规则**(总结/待办/逐字稿等重复产物通用):只存在一类 → 用存在的那类;两类都存在且用户明确指定(如"看妙记逐字稿")→ **语义指向哪个走哪个,不要改道**;两类都存在但用户未指定 → **默认智能纪要及其逐字稿**(会后自动授权给参会人,访问门槛低于含原始录制视频、需申请授权的妙记)。完整说明见 [`references/vc-domain-boundaries.md`](references/vc-domain-boundaries.md) 的「产物选择决策」。
75
+
76
+
74
77
  > **逐字稿路由**:先用 `vc +detail` 拿到 `note_id`,再 [`note +detail`](../lark-note/SKILL.md) 看 `note_display_type`,**不要只看 `verbatim_doc_token` 是否为空**。具体路由以 [lark-note](../lark-note/SKILL.md) 的 `note_display_type` 规则为准。
75
78
  >
76
79
  > **为什么"提炼/总结"必须从原始对话记录出发?** AI 纪要是模型对会议的二次压缩,可能遗漏讨论细节、争论过程和隐含决策。用户要求"提炼"或"重新总结"时,期望的是基于原始对话的独立分析,而非对 AI 产物的重新排版。
@@ -30,6 +30,8 @@
30
30
  | 逐字稿 | `verbatim_doc_token` | 飞书文档 | 完整的逐句发言记录(含说话人、时间戳)— **仅 `note_display_type=normal` 时是可读的独立文档**;`unified` 纪要的逐字稿用 `note +transcript --note-id <note_id>` 拉取(见下方 [Note 域](#note-域)) |
31
31
  | 共享文档 | `shared_doc_token` | 飞书文档 | 会中投屏共享的文档信息 |
32
32
 
33
+ > **授权特性**:智能纪要总结文档及其逐字稿文档(总结文档尾部会挂逐字稿链接与会中投屏共享文档链接)在会后**自动授权给参会人**,参会人通常可直接读取,无需额外申请。
34
+
33
35
  此外,还存在**用户会议纪要(MeetingNotes)**,对应 `meeting_note` 字段。这是用户主动绑定到日程的纪要文档,通常用于会前记录会议相关内容,与智能纪要文档相互独立。仅通过 [`calendar +meeting --event-ids`](../../lark-calendar/references/lark-calendar-meeting.md) 路径返回。
34
36
 
35
37
  #### 链路二:开启「录制」
@@ -43,6 +45,8 @@
43
45
  | Chapter(章节) | 按讨论话题划分的核心内容摘要 |
44
46
  | Transcript(文字记录) | 整场会议最原始的逐人发言记录 |
45
47
 
48
+ > **授权特性**:妙记带有**原始会议录制视频**,会后**不会自动授权给参会人**,需管理员主动授权或参会人主动申请后才能读取(含其 Summary/Todo/Chapter/Transcript 等产物)。因此当同一场会议既有智能纪要又有妙记时,参会人访问**智能纪要及其逐字稿**的门槛通常低于妙记。
49
+
46
50
  #### 两条链路的独立性
47
51
 
48
52
  - 智能纪要(AI 总结链路)和妙记(录制链路)**相互独立、互不影响**。
@@ -54,7 +58,11 @@
54
58
  > - **用户要求"提炼/总结/重新总结/整理/回顾"会议内容时** → **内容总结必须从逐字稿/文字记录出发,基于原始对话独立分析**。禁止直接搬运 AI 纪要的总结作为最终输出——那只是对 AI 产物的重新排版,不是独立提炼。
55
59
  > - **用户要求查看待办或章节时** → **应参考 AI 产物的待办和章节**,因为 AI 产物的待办更友好(包含提出人和负责人),章节按话题划分更结构化。
56
60
  > - **用户只想直接看 AI 总结结果** → 使用 AI 产物的总结。
57
- > - **链路优先级**:如果用户没有明确偏好,对于重复的内容(如智能总结、待办),**优先查询智能纪要(Note),不存在时再降级到妙记(Minutes)**。
61
+ > - **智能纪要 vs 妙记的选择规则**(适用于总结、待办、逐字稿等重复产物,含逐字稿/原始记录):
62
+ > - **只存在一类产物** → 用存在的那一类。
63
+ > - **两类都存在、用户明确指定了其中一类**(如"看妙记的逐字稿""用妙记总结")→ **语义指向哪个就走哪个链路,不要自作主张改道**。
64
+ > - **两类都存在、用户未指定** → **默认用智能纪要及其逐字稿**(智能纪要及逐字稿会后自动授权给参会人,访问门槛更低;妙记含原始录制视频、不自动授权,需申请)。
65
+
58
66
 
59
67
  #### 逐字稿与文字记录的格式
60
68
 
@@ -92,7 +92,7 @@ metadata:
92
92
  ### 3. 发送会中文本或会中表情(写操作)
93
93
 
94
94
  1. 用户明确要求在当前进行中的会议里发送提示、说明、会中表情,或反馈“听不到 / 看不到 / 声音清楚 / 效果不错”时,用 `+meeting-message-send`。
95
- 2. 输入是长数字 `meeting_id`,不是 9 位会议号。若用户只给 9 位会议号,先按当前身份执行 `+meeting-list-active` 并按 `meeting_no` 匹配,匹配到唯一会议后再发送;不要为了发消息自动入会。
95
+ 2. 输入是长数字 `meeting_id`,不是 9 位会议号。若用户只给 9 位会议号,先按当前身份执行 `+meeting-list-active` 并按 `meeting_no` 匹配,匹配到唯一会议后再发送;不要为了发消息自动入会。发消息只需 `meeting_id`,不要先查 `+detail`。
96
96
  3. 身份必须延续:`meeting_id` 来自用户身份发现,就继续 `--as user`;来自应用身份发现或应用机器人入会,就继续 `--as bot`。
97
97
  4. 文本消息使用 `--text`;会中表情 / 反馈使用 `--emoji-type`。`--emoji-type` 必须从 reference 里的完整列表中选择,大小写敏感。
98
98
  5. 支持普通 Feishu reaction emoji(如 `LOVE`、`SMILE`、`THUMBSUP`)和 4 个 VC 反馈 key(`VC_CanNotSee`、`VC_NoSound`、`VC_LooksGood`、`VC_SoundsClear`)。
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: lark-wiki
3
- version: 1.0.2
3
+ version: 1.0.3
4
4
  description: "飞书知识库:管理知识空间、空间成员和文档节点。创建和查询知识空间、查看和管理空间成员、管理节点层级结构、在知识库中组织文档和快捷方式。当用户需要在知识库中查找或创建文档、浏览知识空间结构、查看或管理空间成员、移动或复制节点时使用。当用户给出 doubao.com 的 /wiki/ URL/token 时,也应直接使用本 skill,不要因为域名不是飞书而回退到 WebFetch;路由依据是 URL 路径模式和 token,而不是域名。不负责:上传文件到知识库节点下(走 lark-drive)、编辑文档/表格/Base 内容(走 lark-doc / lark-sheets / lark-base)。"
5
5
  metadata:
6
6
  requires:
@@ -25,6 +25,7 @@ metadata:
25
25
  ## 快速决策
26
26
 
27
27
  - 用户要**整理 / 盘点 / 归类 / 重构知识库、个人文档库、文档库目录或 Wiki 节点结构**,或要生成整理方案、目标目录树、移动计划时,不要只使用 Wiki 节点 API。必须先阅读 [`../lark-drive/references/lark-drive-workflow.md`](../lark-drive/references/lark-drive-workflow.md),再按其中 `Workflow Registry` 进入 [`knowledge_organize`](../lark-drive/references/lark-drive-workflow-knowledge-organize.md) workflow;该 workflow 负责 Drive / Wiki / 个人文档库的统一入口解析、资源盘点、分类计划、写前确认和结果验证。
28
+ - 用户要把**已有 Wiki 节点移出知识库,放到 Drive 文件夹或“我的空间”根目录**:使用 `wiki +move-to-drive`,不要使用 `wiki +move` 或 `drive +move`。这是会改变节点归属和权限继承的写操作,执行前确认源节点与目标位置。
28
29
  - 用户给的是知识库 URL(`.../wiki/<token>`),且后续要查成员/加成员/删成员:先调用 `lark-cli wiki spaces get_node --params '{"token":"<wiki_token>"}'` 获取 `space_id`,后续成员接口统一使用 `space_id`。
29
30
  - 用户要**删除**知识空间(`wiki +delete-space`)但只给了名称或 URL:**不能**把名称 / URL 原样传给 `--space-id`,必须先解析出真实 `space_id`。解析方式:
30
31
  - URL(`.../wiki/<token>`):`lark-cli wiki spaces get_node --params '{"token":"<wiki_token>"}' --format json`,读 `data.node.space_id`。
@@ -49,6 +50,7 @@ Shortcut 是对常用操作的高级封装(`lark-cli wiki +<verb> [flags]`)
49
50
  | Shortcut | 说明 |
50
51
  |----------|------|
51
52
  | [`+move`](references/lark-wiki-move.md) | Move a wiki node, or move a Drive document into Wiki |
53
+ | [`+move-to-drive`](references/lark-wiki-move-to-drive.md) | Move a wiki node to a Drive folder and poll the async task |
52
54
  | [`+node-create`](references/lark-wiki-node-create.md) | Create a wiki node with automatic space resolution |
53
55
  | [`+delete-space`](references/lark-wiki-delete-space.md) | Delete a wiki space, polling the async delete task when needed |
54
56
  | [`+space-list`](references/lark-wiki-space-list.md) | List all wiki spaces accessible to the caller |
@@ -76,7 +78,7 @@ Shortcut 是对常用操作的高级封装(`lark-cli wiki +<verb> [flags]`)
76
78
  - `我的文档库` / `My Document Library` / `我的知识库` / `个人知识库` / `my_library` 都应视为 **Wiki personal library**,不是 Drive 根目录
77
79
  - 处理这类目标时,先解析 `my_library` 对应的真实 `space_id`,再执行 `wiki +move`、`wiki +node-create` 或其他 Wiki 写操作
78
80
  - 不要因为缺少显式 `space_id` 就退化成 `drive +move`
79
- - 如果用户明确说的是 Drive 文件夹、云空间(云盘/云存储)根目录、`我的空间`,才进入 Drive 域处理
81
+ - 如果用户明确说的是 Drive 文件夹、云空间(云盘/云存储)根目录、`我的空间`,再按源对象分流:源对象是 Wiki 节点时用 `wiki +move-to-drive`,源对象已在 Drive 时用 `drive +move`
80
82
 
81
83
  ## API Resources
82
84
 
@@ -0,0 +1,122 @@
1
+ # wiki +move-to-drive
2
+
3
+ > **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
4
+
5
+ 将已有 Wiki 节点移出知识库,并放到指定 Drive 文件夹;省略目标文件夹时放到当前调用身份的“我的空间”根目录。该操作始终创建异步任务,shortcut 会自动有限轮询。
6
+
7
+ ## 何时使用
8
+
9
+ | 源对象 | 目标位置 | 命令 |
10
+ |--------|----------|------|
11
+ | Wiki 节点 | Wiki 空间或 Wiki 父节点 | `wiki +move` |
12
+ | Drive 文档 | Wiki 空间或 Wiki 父节点 | `wiki +move` |
13
+ | Wiki 节点 | Drive 文件夹或“我的空间”根目录 | `wiki +move-to-drive` |
14
+ | Drive 文件 / 文件夹 | Drive 文件夹或根目录 | `drive +move` |
15
+
16
+ `--node-token` 必须是 Wiki 节点 token,不是底层文档的 `obj_token`。无法判断时,先执行 `wiki +node-get --node-token <URL_OR_TOKEN>`。
17
+
18
+ ## 命令
19
+
20
+ ```bash
21
+ # 移到指定 Drive 文件夹
22
+ lark-cli wiki +move-to-drive \
23
+ --node-token <WIKI_NODE_TOKEN> \
24
+ --folder-token <TARGET_FOLDER_TOKEN> \
25
+ --as user
26
+
27
+ # 移到当前调用身份的“我的空间”根目录
28
+ lark-cli wiki +move-to-drive \
29
+ --node-token <WIKI_NODE_TOKEN> \
30
+ --as user
31
+
32
+ # 预览提交任务和轮询任务两步请求
33
+ lark-cli wiki +move-to-drive \
34
+ --node-token <WIKI_NODE_TOKEN> \
35
+ --folder-token <TARGET_FOLDER_TOKEN> \
36
+ --dry-run
37
+ ```
38
+
39
+ ## 参数
40
+
41
+ | 参数 | 必填 | 说明 |
42
+ |------|------|------|
43
+ | `--node-token` | 是 | 要移出知识库的 Wiki 节点 token |
44
+ | `--folder-token` | 否 | 目标 Drive 文件夹 token;省略时移动到当前调用身份的“我的空间”根目录 |
45
+
46
+ ## 异步协议与续跑
47
+
48
+ shortcut 会按以下协议执行:
49
+
50
+ 1. `POST /open-apis/wiki/v2/nodes/{node_token}/move_wiki_to_docs`,取得完整、不可拆分的 `task_id`。
51
+ 2. `GET /open-apis/wiki/v2/tasks/{task_id}?task_type=move_wiki_to_docs`。
52
+ 3. 读取 `data.task.move_wiki_to_docs_result`:`status=1` 表示处理中,`status=0` 表示成功,`status=-1` 表示失败。
53
+
54
+ 任务查询必须使用 `task_type=move_wiki_to_docs`、`move_wiki_to_docs_result` 和数值状态;不要回退到其他 task type、result 字段或字符串状态。
55
+
56
+ - 最多轮询 30 次,每次间隔 2 秒。
57
+ - 轮询窗口内成功时返回 `ready=true`,并尽可能返回 `obj_token`、`obj_type` 和 `url`。
58
+ - 仍在处理中时返回 `ready=false`、`timed_out=true`、完整 `task_id` 和 `next_command`;超时不代表任务失败。
59
+ - 任务进入失败态时返回结构化错误。
60
+ - `task_id` 是服务端签名的 opaque ID,可能包含多个连字符;必须原样保存,不能自行切分。
61
+ - 续跑必须保持和初始移动相同的 `--profile` 与 `--as user|bot` 身份,否则可能收到权限错误;shortcut 返回的 `next_command` 会保留两者。
62
+
63
+ 手动续跑命令:
64
+
65
+ ```bash
66
+ lark-cli drive +task_result \
67
+ --scenario wiki_move_to_drive \
68
+ --task-id <COMPLETE_TASK_ID> \
69
+ --as user
70
+ ```
71
+
72
+ ## 典型返回
73
+
74
+ 成功:
75
+
76
+ ```json
77
+ {
78
+ "node_token": "wikcnXXX",
79
+ "folder_token": "fldcnXXX",
80
+ "task_id": "<OPAQUE_TASK_ID>",
81
+ "ready": true,
82
+ "failed": false,
83
+ "status": 0,
84
+ "status_msg": "success",
85
+ "obj_token": "doxcnXXX",
86
+ "obj_type": "docx",
87
+ "url": "https://example.feishu.cn/docx/doxcnXXX"
88
+ }
89
+ ```
90
+
91
+ 轮询窗口超时:
92
+
93
+ ```json
94
+ {
95
+ "node_token": "wikcnXXX",
96
+ "folder_token": "",
97
+ "task_id": "<OPAQUE_TASK_ID>",
98
+ "ready": false,
99
+ "failed": false,
100
+ "status": 1,
101
+ "status_msg": "processing",
102
+ "timed_out": true,
103
+ "next_command": "lark-cli drive +task_result --scenario wiki_move_to_drive --task-id <OPAQUE_TASK_ID> --as user"
104
+ }
105
+ ```
106
+
107
+ ## 权限与影响
108
+
109
+ - CLI 写操作预检查使用 `space:document:move`,任务轮询使用 `wiki:space:read`。
110
+ - 调用方必须能移动源 Wiki 节点并写入目标 Drive 文件夹。
111
+ - 成功后源节点会从 Wiki 树中消失,目标文档改用 Drive 目标位置的权限模型;原 Wiki 层级继承权限不再保留。
112
+ - 省略 `--folder-token` 时,“根目录”属于当前 `--as` 身份,user 与 bot 的可见资源范围可能不同。
113
+
114
+ > [!CAUTION]
115
+ > 这是会改变文档归属和权限继承的**写入操作**。执行前必须确认源 Wiki 节点、目标 Drive 位置和调用身份。
116
+
117
+ ## 参考
118
+
119
+ - [lark-wiki](../SKILL.md) -- 知识库全部命令
120
+ - [wiki +move](lark-wiki-move.md) -- Wiki 内移动与 Drive 文档迁入 Wiki
121
+ - [drive +task_result](../../lark-drive/references/lark-drive-task-result.md) -- 超时后的任务续跑
122
+ - [lark-shared](../../lark-shared/SKILL.md) -- 认证和全局参数