kcode-pi 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/README.md +358 -0
  2. package/dist/cli/kcode.d.ts +15 -0
  3. package/dist/cli/kcode.js +153 -0
  4. package/dist/cli/main.d.ts +2 -0
  5. package/dist/cli/main.js +7 -0
  6. package/docs/KCODE_DISTRIBUTION.md +91 -0
  7. package/extensions/kingdee-harness.ts +180 -0
  8. package/extensions/kingdee-header.ts +122 -0
  9. package/extensions/kingdee-tools.ts +379 -0
  10. package/knowledge/.backup/v1.0.0/version.json +10 -0
  11. package/knowledge/cangqiong/product-notes.md +15 -0
  12. package/knowledge/common/business-flows.md +115 -0
  13. package/knowledge/common/config-guides.md +110 -0
  14. package/knowledge/common/error-patterns.md +170 -0
  15. package/knowledge/common/implementation.md +144 -0
  16. package/knowledge/cosmic/hard-constraints.md +38 -0
  17. package/knowledge/cosmic/ksql-datafix.md +34 -0
  18. package/knowledge/cosmic/platform-baseline.md +32 -0
  19. package/knowledge/cosmic/plugin-decision-matrix.md +40 -0
  20. package/knowledge/cosmic/review-checklist.md +40 -0
  21. package/knowledge/cosmic/unittest.md +35 -0
  22. package/knowledge/enterprise/api-reference.md +186 -0
  23. package/knowledge/enterprise/code-patterns.md +217 -0
  24. package/knowledge/enterprise/plugin-lifecycle.md +188 -0
  25. package/knowledge/enterprise/tables.json +159 -0
  26. package/knowledge/flagship/api-reference.md +237 -0
  27. package/knowledge/flagship/code-patterns.md +246 -0
  28. package/knowledge/flagship/cosmic-platform-note.md +15 -0
  29. package/knowledge/flagship/plugin-lifecycle.md +248 -0
  30. package/knowledge/flagship/tables.json +159 -0
  31. package/knowledge/version.json +10 -0
  32. package/knowledge/xinghan/product-notes.md +15 -0
  33. package/package.json +71 -0
  34. package/prompts/kd-discuss.md +11 -0
  35. package/prompts/kd-execute.md +12 -0
  36. package/prompts/kd-plan.md +12 -0
  37. package/prompts/kd-ship.md +12 -0
  38. package/prompts/kd-spec.md +12 -0
  39. package/prompts/kd-verify.md +12 -0
  40. package/skills/kd-check/SKILL.md +26 -0
  41. package/skills/kd-cosmic-dev/SKILL.md +82 -0
  42. package/skills/kd-cosmic-review/SKILL.md +90 -0
  43. package/skills/kd-cosmic-unittest/SKILL.md +92 -0
  44. package/skills/kd-debug/SKILL.md +30 -0
  45. package/skills/kd-discuss/SKILL.md +24 -0
  46. package/skills/kd-execute/SKILL.md +22 -0
  47. package/skills/kd-gen/SKILL.md +34 -0
  48. package/skills/kd-ksql/SKILL.md +86 -0
  49. package/skills/kd-plan/SKILL.md +24 -0
  50. package/skills/kd-ship/SKILL.md +22 -0
  51. package/skills/kd-spec/SKILL.md +24 -0
  52. package/skills/kd-verify/SKILL.md +22 -0
  53. package/themes/kcode-dark.json +81 -0
  54. package/vendor/kingdee-skills/cosmic-unittest/SKILL.md +788 -0
  55. package/vendor/kingdee-skills/cosmic-unittest/author-cache.json +5 -0
  56. package/vendor/kingdee-skills/cosmic-unittest/cosmic-unittest-skill-overview.html +746 -0
  57. package/vendor/kingdee-skills/cosmic-unittest/examples/business-test.md +205 -0
  58. package/vendor/kingdee-skills/cosmic-unittest/examples/common-test.md +257 -0
  59. package/vendor/kingdee-skills/cosmic-unittest/examples/formplugin-test.md +560 -0
  60. package/vendor/kingdee-skills/cosmic-unittest/examples/op-plugin-test.md +231 -0
  61. package/vendor/kingdee-skills/cosmic-unittest/examples/validator-test.md +232 -0
  62. package/vendor/kingdee-skills/cosmic-unittest/patterns/business-helper.md +184 -0
  63. package/vendor/kingdee-skills/cosmic-unittest/patterns/common-module.md +355 -0
  64. package/vendor/kingdee-skills/cosmic-unittest/patterns/convert-plugin.md +130 -0
  65. package/vendor/kingdee-skills/cosmic-unittest/patterns/formplugin.md +235 -0
  66. package/vendor/kingdee-skills/cosmic-unittest/patterns/op-plugin.md +226 -0
  67. package/vendor/kingdee-skills/cosmic-unittest/patterns/validator.md +206 -0
  68. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/SKILL.md +674 -0
  69. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/advanced-scenario-checklist.md +307 -0
  70. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/algox-performance-checklist.md +129 -0
  71. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/coding-standard-checklist.md +491 -0
  72. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/cosmic-api-checklist.md +285 -0
  73. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/data-access-checklist.md +261 -0
  74. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/data-transaction-checklist.md +390 -0
  75. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/domain-logic-checklist.md +295 -0
  76. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/form-plugin-checklist.md +508 -0
  77. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/infra-checklist.md +254 -0
  78. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/ksql-checklist.md +305 -0
  79. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/lifecycle-checklist.md +298 -0
  80. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/operation-plugin-checklist.md +442 -0
  81. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/test-mock-checklist.md +120 -0
  82. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/ui-performance-checklist.md +320 -0
  83. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/scripts/pattern-matcher.py +336 -0
  84. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/scripts/review-score-calculator.py +121 -0
  85. package/vendor/kingdee-skills/ok-cosmic/CHANGELOG.md +295 -0
  86. package/vendor/kingdee-skills/ok-cosmic/README.md +460 -0
  87. package/vendor/kingdee-skills/ok-cosmic/SKILL.md +287 -0
  88. package/vendor/kingdee-skills/ok-cosmic/agents/openai.yaml +17 -0
  89. package/vendor/kingdee-skills/ok-cosmic/assets/BatchImportPluginTemplate.java +93 -0
  90. package/vendor/kingdee-skills/ok-cosmic/assets/BillPlugInTemplate.java +156 -0
  91. package/vendor/kingdee-skills/ok-cosmic/assets/ConvertPlugInTemplate.java +255 -0
  92. package/vendor/kingdee-skills/ok-cosmic/assets/FormPluginTemplate.java +597 -0
  93. package/vendor/kingdee-skills/ok-cosmic/assets/IWorkflowPluginTemplate.java +91 -0
  94. package/vendor/kingdee-skills/ok-cosmic/assets/ListPluginTemplate.java +194 -0
  95. package/vendor/kingdee-skills/ok-cosmic/assets/OpPluginTemplate.java +201 -0
  96. package/vendor/kingdee-skills/ok-cosmic/assets/OpenApiControllerTemplate.java +103 -0
  97. package/vendor/kingdee-skills/ok-cosmic/assets/PrintPluginTemplate.java +95 -0
  98. package/vendor/kingdee-skills/ok-cosmic/assets/ReportFormPluginTemplate.java +257 -0
  99. package/vendor/kingdee-skills/ok-cosmic/assets/ReportListDataPluginTemplate.java +70 -0
  100. package/vendor/kingdee-skills/ok-cosmic/assets/StandardTreeListPluginTemplate.java +130 -0
  101. package/vendor/kingdee-skills/ok-cosmic/assets/TaskTemplate.java +80 -0
  102. package/vendor/kingdee-skills/ok-cosmic/assets/TreeListPluginTemplate.java +152 -0
  103. package/vendor/kingdee-skills/ok-cosmic/assets/WriteBackPlugInTemplate.java +286 -0
  104. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/attachment/AttachmentUploadBindSample.java +93 -0
  105. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/botp/BotpTracePushSample.java +168 -0
  106. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/botp/SampleConvertPlugin.java +223 -0
  107. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/cache/SampleCacheUsage.java +218 -0
  108. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/concurrent/SampleThreadPoolBatch.java +156 -0
  109. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/data/DynamicObjectCrudSample.java +205 -0
  110. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/data/DynamicObjectOpsSample.java +100 -0
  111. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/BeforeOperationConfirmSample.java +217 -0
  112. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/ConfirmDialogSample.java +131 -0
  113. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/EntryRowCalculateSample.java +116 -0
  114. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/F7FilterSample.java +134 -0
  115. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/GetAndSetValueSample.java +176 -0
  116. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/HyperlinkJumpSample.java +124 -0
  117. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/OpenBillModalSample.java +253 -0
  118. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/ReturnParentDataSample.java +295 -0
  119. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/TreeControlSample.java +140 -0
  120. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/ViewControlOpsSample.java +132 -0
  121. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/list/ListPluginBasicSample.java +170 -0
  122. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/list/ListPreOpenFilterSample.java +68 -0
  123. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/message/MessageNotifySample.java +95 -0
  124. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/mq/SampleMQConsumer.java +198 -0
  125. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/mq/sample_mq.xml +15 -0
  126. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/operation/OpAddValidatorsSample.java +137 -0
  127. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/operation/OperationOptionBridgeSample.java +228 -0
  128. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/package-info.java +19 -0
  129. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/query/BaseDataQuerySample.java +194 -0
  130. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/query/BatchQuerySample.java +368 -0
  131. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/query/DataSetQueryStatSample.java +131 -0
  132. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/report/SampleReportFormPlugin.java +179 -0
  133. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/report/SampleReportListDataPlugin.java +616 -0
  134. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/snippets-guide.md +64 -0
  135. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/task/ScheduleTaskSample.java +160 -0
  136. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/workflow/SampleWorkflowPlugin.java +302 -0
  137. package/vendor/kingdee-skills/ok-cosmic/manifest.json +78 -0
  138. package/vendor/kingdee-skills/ok-cosmic/ok-cosmic-intro.html +903 -0
  139. package/vendor/kingdee-skills/ok-cosmic/references/adv/attachment-api.md +114 -0
  140. package/vendor/kingdee-skills/ok-cosmic/references/adv/botp-convert.md +98 -0
  141. package/vendor/kingdee-skills/ok-cosmic/references/adv/dynamic-object.md +113 -0
  142. package/vendor/kingdee-skills/ok-cosmic/references/adv/entity-metadata.md +123 -0
  143. package/vendor/kingdee-skills/ok-cosmic/references/adv/event-lifecycle.md +184 -0
  144. package/vendor/kingdee-skills/ok-cosmic/references/adv/flex-prop.md +114 -0
  145. package/vendor/kingdee-skills/ok-cosmic/references/adv/form-utils.md +133 -0
  146. package/vendor/kingdee-skills/ok-cosmic/references/adv/operate-chain.md +159 -0
  147. package/vendor/kingdee-skills/ok-cosmic/references/adv/plugin-base.md +218 -0
  148. package/vendor/kingdee-skills/ok-cosmic/references/adv/query-dataset.md +149 -0
  149. package/vendor/kingdee-skills/ok-cosmic/references/adv/request-context.md +88 -0
  150. package/vendor/kingdee-skills/ok-cosmic/references/adv/view-handler.md +157 -0
  151. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-bill.md +76 -0
  152. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-botp.md +70 -0
  153. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-form.md +165 -0
  154. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-import.md +69 -0
  155. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-list.md +227 -0
  156. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-openapi.md +112 -0
  157. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-operation.md +135 -0
  158. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-print.md +65 -0
  159. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-report-data.md +64 -0
  160. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-report-form.md +90 -0
  161. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-task.md +62 -0
  162. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-tree-list.md +71 -0
  163. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-workflow.md +82 -0
  164. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-writeback.md +71 -0
  165. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-algo.md +67 -0
  166. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-cache.md +63 -0
  167. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-dynamic-model-svc.md +82 -0
  168. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-dynamic-object.md +70 -0
  169. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-entity-model.md +61 -0
  170. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-exception.md +64 -0
  171. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-file.md +63 -0
  172. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-id.md +47 -0
  173. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-lock.md +61 -0
  174. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-log.md +63 -0
  175. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-network-control.md +70 -0
  176. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-orm-access.md +78 -0
  177. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-request-context.md +62 -0
  178. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-threadpool.md +63 -0
  179. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-tx.md +64 -0
  180. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-utils.md +67 -0
  181. package/vendor/kingdee-skills/ok-cosmic/requirements.txt +2 -0
  182. package/vendor/kingdee-skills/ok-cosmic/rules/a-layer-rules.json +24 -0
  183. package/vendor/kingdee-skills/ok-cosmic/rules/anti-patterns.md +48 -0
  184. package/vendor/kingdee-skills/ok-cosmic/rules/cheat-sheet.md +256 -0
  185. package/vendor/kingdee-skills/ok-cosmic/rules/coding-preferences.md +140 -0
  186. package/vendor/kingdee-skills/ok-cosmic/rules/constraints.md +61 -0
  187. package/vendor/kingdee-skills/ok-cosmic/rules/decision-matrix.md +222 -0
  188. package/vendor/kingdee-skills/ok-cosmic/rules/intent-routing.md +94 -0
  189. package/vendor/kingdee-skills/ok-cosmic/rules/platform-baseline.md +69 -0
  190. package/vendor/kingdee-skills/ok-cosmic/rules/post-check.md +109 -0
  191. package/vendor/kingdee-skills/ok-cosmic/scripts/config_loader.py +204 -0
  192. package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-api-knowledge.py +910 -0
  193. package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-basedata-query.py +359 -0
  194. package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-config-check.py +181 -0
  195. package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-extpoints-query.py +389 -0
  196. package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-form-metadata.py +856 -0
  197. package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-post-check.py +262 -0
  198. package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-post-lint.py +293 -0
  199. package/vendor/kingdee-skills/ok-cosmic/scripts/lint/__init__.py +2 -0
  200. package/vendor/kingdee-skills/ok-cosmic/scripts/lint/base.py +393 -0
  201. package/vendor/kingdee-skills/ok-cosmic/scripts/lint/resource_check.py +176 -0
  202. package/vendor/kingdee-skills/ok-cosmic/scripts/lint/scene_check.py +375 -0
  203. package/vendor/kingdee-skills/ok-cosmic/scripts/lint/style_check.py +434 -0
  204. package/vendor/kingdee-skills/ok-cosmic/scripts/lint/verify_check.py +36 -0
  205. package/vendor/kingdee-skills/ok-cosmic/scripts/route_client.py +186 -0
  206. package/vendor/kingdee-skills/ok-cosmic/scripts/script_utils.py +40 -0
  207. package/vendor/kingdee-skills/ok-cosmic/scripts/sqlite_cache.py +142 -0
  208. package/vendor/kingdee-skills/ok-cosmic/setup/cuslib/kd-cd-cosmic-commons.jar +0 -0
  209. package/vendor/kingdee-skills/ok-cosmic/setup/cuslib/kd-cd-cosmic-features.jar +0 -0
  210. package/vendor/kingdee-skills/ok-cosmic/setup/ok-cosmic-docs.db +0 -0
  211. package/vendor/kingdee-skills/ok-cosmic/setup/ok-cosmic.json +13 -0
  212. package/vendor/kingdee-skills/ok-cosmic/setup/setup-mac.sh +18 -0
  213. package/vendor/kingdee-skills/ok-cosmic/setup/setup-windows.bat +53 -0
  214. package/vendor/kingdee-skills/ok-cosmic/setup/setup.jar +0 -0
  215. package/vendor/kingdee-skills/ok-ksql/SKILL.md +81 -0
  216. package/vendor/kingdee-skills/ok-ksql/agents/openai.yaml +7 -0
  217. package/vendor/kingdee-skills/ok-ksql/manifest.json +14 -0
  218. package/vendor/kingdee-skills/ok-ksql/references/ksql-datafix.md +452 -0
  219. package/vendor/kingdee-skills/ok-ksql/scripts/ksql_lint.py +363 -0
@@ -0,0 +1,363 @@
1
+ #!/usr/bin/env python3
2
+ """Static checks for ok-ksql generated PostgreSQL data-fix SQL files.
3
+
4
+ The linter is intentionally dependency-free and conservative: it catches the
5
+ high-risk patterns required by the ok-ksql skill, but it is not a full SQL
6
+ parser. Fix reported ERROR items before delivery. WARN items document style
7
+ preferences that may be acceptable only with an explicit reason.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import re
14
+ import sys
15
+ from dataclasses import dataclass
16
+ from pathlib import Path
17
+ from typing import Iterable, Iterator, Sequence
18
+
19
+
20
+ TIMESTAMP_RE = re.compile(r"\b\d{12}\b")
21
+ BACKUP_TABLE_RE = re.compile(r"\bbak_[a-zA-Z0-9_]+_(\d{12})\b", re.IGNORECASE)
22
+ FILE_TS_RE = re.compile(r"ksql_[^/\\]*_(\d{12})\.txt$", re.IGNORECASE)
23
+ HEADER_TS_RE = re.compile(r"备份表时间戳[::]\s*(\d{12})")
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class Finding:
28
+ severity: str
29
+ line: int
30
+ message: str
31
+
32
+ def format(self, path: Path) -> str:
33
+ return f"{path}:{self.line}: {self.severity}: {self.message}"
34
+
35
+
36
+ @dataclass(frozen=True)
37
+ class Statement:
38
+ text: str
39
+ line: int
40
+
41
+
42
+ def strip_comments_and_literals(sql: str) -> str:
43
+ """Return SQL with comments and quoted literals replaced by spaces.
44
+
45
+ This keeps line numbers stable while avoiding false positives from words
46
+ appearing inside comments or strings.
47
+ """
48
+
49
+ out: list[str] = []
50
+ i = 0
51
+ n = len(sql)
52
+ state = "normal"
53
+ dollar_tag = ""
54
+
55
+ while i < n:
56
+ ch = sql[i]
57
+ nxt = sql[i + 1] if i + 1 < n else ""
58
+
59
+ if state == "normal":
60
+ if ch == "-" and nxt == "-":
61
+ out.extend([" ", " "])
62
+ i += 2
63
+ state = "line_comment"
64
+ continue
65
+ if ch == "/" and nxt == "*":
66
+ out.extend([" ", " "])
67
+ i += 2
68
+ state = "block_comment"
69
+ continue
70
+ if ch == "'":
71
+ out.append(" ")
72
+ i += 1
73
+ state = "single_quote"
74
+ continue
75
+ if ch == '"':
76
+ out.append(" ")
77
+ i += 1
78
+ state = "double_quote"
79
+ continue
80
+ if ch == "$":
81
+ match = re.match(r"\$[A-Za-z_][A-Za-z0-9_]*\$|\$\$", sql[i:])
82
+ if match:
83
+ dollar_tag = match.group(0)
84
+ out.extend(" " * len(dollar_tag))
85
+ i += len(dollar_tag)
86
+ state = "dollar_quote"
87
+ continue
88
+ out.append(ch)
89
+ i += 1
90
+ continue
91
+
92
+ if state == "line_comment":
93
+ out.append("\n" if ch == "\n" else " ")
94
+ i += 1
95
+ if ch == "\n":
96
+ state = "normal"
97
+ continue
98
+
99
+ if state == "block_comment":
100
+ if ch == "*" and nxt == "/":
101
+ out.extend([" ", " "])
102
+ i += 2
103
+ state = "normal"
104
+ else:
105
+ out.append("\n" if ch == "\n" else " ")
106
+ i += 1
107
+ continue
108
+
109
+ if state == "single_quote":
110
+ if ch == "'" and nxt == "'":
111
+ out.extend([" ", " "])
112
+ i += 2
113
+ else:
114
+ out.append("\n" if ch == "\n" else " ")
115
+ i += 1
116
+ if ch == "'":
117
+ state = "normal"
118
+ continue
119
+
120
+ if state == "double_quote":
121
+ if ch == '"' and nxt == '"':
122
+ out.extend([" ", " "])
123
+ i += 2
124
+ else:
125
+ out.append("\n" if ch == "\n" else " ")
126
+ i += 1
127
+ if ch == '"':
128
+ state = "normal"
129
+ continue
130
+
131
+ if state == "dollar_quote":
132
+ if sql.startswith(dollar_tag, i):
133
+ out.extend(" " * len(dollar_tag))
134
+ i += len(dollar_tag)
135
+ state = "normal"
136
+ dollar_tag = ""
137
+ else:
138
+ out.append("\n" if ch == "\n" else " ")
139
+ i += 1
140
+ continue
141
+
142
+ return "".join(out)
143
+
144
+
145
+ def iter_statements(masked_sql: str) -> Iterator[Statement]:
146
+ start = 0
147
+ start_line = 1
148
+ line = 1
149
+
150
+ for idx, ch in enumerate(masked_sql):
151
+ if ch == ";":
152
+ text = masked_sql[start : idx + 1]
153
+ if text.strip():
154
+ yield Statement(text=text, line=start_line)
155
+ start = idx + 1
156
+ start_line = line
157
+ if ch == "\n":
158
+ line += 1
159
+ if not masked_sql[start:idx].strip():
160
+ start_line = line
161
+
162
+ tail = masked_sql[start:]
163
+ if tail.strip():
164
+ yield Statement(text=tail, line=start_line)
165
+
166
+
167
+ def line_of_offset(text: str, base_line: int, offset: int) -> int:
168
+ return base_line + text[:offset].count("\n")
169
+
170
+
171
+ def has_token(statement: str, token: str) -> bool:
172
+ return re.search(rf"\b{re.escape(token)}\b", statement, re.IGNORECASE) is not None
173
+
174
+
175
+ def lint_statement(stmt: Statement) -> list[Finding]:
176
+ findings: list[Finding] = []
177
+ compact = " ".join(stmt.text.split())
178
+ if not compact:
179
+ return findings
180
+
181
+ first = re.match(r"^\s*(\w+)", compact)
182
+ first_word = first.group(1).upper() if first else ""
183
+
184
+ if first_word in {"UPDATE", "DELETE"} and not has_token(stmt.text, "WHERE"):
185
+ findings.append(
186
+ Finding(
187
+ "ERROR",
188
+ stmt.line,
189
+ f"{first_word} 语句缺少 WHERE,禁止生成无范围更新/删除。",
190
+ )
191
+ )
192
+
193
+ select_star_matches = list(re.finditer(r"\bSELECT\s+\*", stmt.text, re.IGNORECASE))
194
+ if select_star_matches:
195
+ is_backup = re.search(
196
+ r"\bSELECT\s+\*\s+INTO\s+bak_[a-zA-Z0-9_]+_\d{12}\s+FROM\b",
197
+ stmt.text,
198
+ re.IGNORECASE,
199
+ )
200
+ if is_backup:
201
+ if has_token(stmt.text, "WHERE"):
202
+ findings.append(
203
+ Finding(
204
+ "ERROR",
205
+ stmt.line,
206
+ "备份语句必须整表备份,SELECT * INTO bak_... 不允许带 WHERE。",
207
+ )
208
+ )
209
+ else:
210
+ for match in select_star_matches:
211
+ findings.append(
212
+ Finding(
213
+ "ERROR",
214
+ line_of_offset(stmt.text, stmt.line, match.start()),
215
+ "查询/验证语句禁止 SELECT *;只有整表备份 SELECT * INTO bak_... 例外。",
216
+ )
217
+ )
218
+
219
+ if re.search(r"\bSELECT\s+\*\s+INTO\b", stmt.text, re.IGNORECASE) and not re.search(
220
+ r"\bSELECT\s+\*\s+INTO\s+bak_[a-zA-Z0-9_]+_\d{12}\s+FROM\b",
221
+ stmt.text,
222
+ re.IGNORECASE,
223
+ ):
224
+ findings.append(
225
+ Finding(
226
+ "ERROR",
227
+ stmt.line,
228
+ "备份表名必须形如 bak_<原表或业务缩写>_<yyyyMMddHHmm>。",
229
+ )
230
+ )
231
+
232
+ for match in re.finditer(r"\bEXISTS\b", stmt.text, re.IGNORECASE):
233
+ findings.append(
234
+ Finding(
235
+ "WARN",
236
+ line_of_offset(stmt.text, stmt.line, match.start()),
237
+ "SQL 可读性偏好:成员关系/半连接默认使用 IN,只有 IN 改变语义时才保留 EXISTS 并说明原因。",
238
+ )
239
+ )
240
+
241
+ if re.search(r"\bUPDATE\b.+\bJOIN\b", compact, re.IGNORECASE):
242
+ findings.append(
243
+ Finding(
244
+ "WARN",
245
+ stmt.line,
246
+ "PostgreSQL 多表更新优先使用 UPDATE ... FROM ... WHERE ...,不要使用 MySQL 风格 UPDATE ... JOIN。",
247
+ )
248
+ )
249
+
250
+ if re.search(r"=\s*NULL\b|\bNULL\s*=", stmt.text, re.IGNORECASE):
251
+ findings.append(
252
+ Finding(
253
+ "ERROR",
254
+ stmt.line,
255
+ "NULL 判断必须使用 IS NULL / IS NOT NULL,不能使用 = NULL。",
256
+ )
257
+ )
258
+
259
+ if re.search(r"<>|!=", stmt.text) and has_token(stmt.text, "NULL"):
260
+ findings.append(
261
+ Finding(
262
+ "WARN",
263
+ stmt.line,
264
+ "涉及 NULL 的不等比较需确认语义;PostgreSQL 可优先使用 IS DISTINCT FROM。",
265
+ )
266
+ )
267
+
268
+ return findings
269
+
270
+
271
+ def lint_timestamps(path: Path, raw_sql: str) -> list[Finding]:
272
+ findings: list[Finding] = []
273
+ backup_timestamps = set(BACKUP_TABLE_RE.findall(raw_sql))
274
+
275
+ if len(backup_timestamps) > 1:
276
+ findings.append(
277
+ Finding(
278
+ "ERROR",
279
+ 1,
280
+ "同一 SQL 文件中出现多个备份表时间戳;桌面文件、备份表和文件头时间戳必须一致。",
281
+ )
282
+ )
283
+
284
+ file_match = FILE_TS_RE.search(path.name)
285
+ if file_match and backup_timestamps and file_match.group(1) not in backup_timestamps:
286
+ findings.append(
287
+ Finding(
288
+ "ERROR",
289
+ 1,
290
+ f"文件名时间戳 {file_match.group(1)} 与备份表时间戳 {', '.join(sorted(backup_timestamps))} 不一致。",
291
+ )
292
+ )
293
+
294
+ header_matches = list(HEADER_TS_RE.finditer(raw_sql))
295
+ header_timestamps = {m.group(1) for m in header_matches}
296
+ if len(header_timestamps) > 1:
297
+ findings.append(Finding("ERROR", 1, "文件头出现多个不同的备份表时间戳。"))
298
+ if header_timestamps and backup_timestamps and header_timestamps != backup_timestamps:
299
+ findings.append(
300
+ Finding(
301
+ "ERROR",
302
+ 1,
303
+ f"文件头时间戳 {', '.join(sorted(header_timestamps))} 与备份表时间戳 {', '.join(sorted(backup_timestamps))} 不一致。",
304
+ )
305
+ )
306
+
307
+ return findings
308
+
309
+
310
+ def lint_file(path: Path) -> list[Finding]:
311
+ raw_sql = path.read_text(encoding="utf-8")
312
+ masked_sql = strip_comments_and_literals(raw_sql)
313
+
314
+ findings: list[Finding] = []
315
+ findings.extend(lint_timestamps(path, raw_sql))
316
+ for stmt in iter_statements(masked_sql):
317
+ findings.extend(lint_statement(stmt))
318
+ return sorted(findings, key=lambda f: (f.line, f.severity, f.message))
319
+
320
+
321
+ def parse_args(argv: Sequence[str]) -> argparse.Namespace:
322
+ parser = argparse.ArgumentParser(
323
+ description="Lint ok-ksql generated PostgreSQL data-fix SQL files."
324
+ )
325
+ parser.add_argument("paths", nargs="+", type=Path, help="SQL/.txt files to lint")
326
+ parser.add_argument(
327
+ "--strict",
328
+ action="store_true",
329
+ help="treat WARN findings as failures",
330
+ )
331
+ return parser.parse_args(argv)
332
+
333
+
334
+ def main(argv: Sequence[str] | None = None) -> int:
335
+ args = parse_args(sys.argv[1:] if argv is None else argv)
336
+ all_findings: list[tuple[Path, Finding]] = []
337
+
338
+ for path in args.paths:
339
+ if not path.exists():
340
+ print(f"{path}:1: ERROR: 文件不存在。", file=sys.stderr)
341
+ return 2
342
+ if path.is_dir():
343
+ print(f"{path}:1: ERROR: 请输入 SQL/.txt 文件,不能是目录。", file=sys.stderr)
344
+ return 2
345
+ for finding in lint_file(path):
346
+ all_findings.append((path, finding))
347
+
348
+ for path, finding in all_findings:
349
+ print(finding.format(path))
350
+
351
+ error_count = sum(1 for _, f in all_findings if f.severity == "ERROR")
352
+ warn_count = sum(1 for _, f in all_findings if f.severity == "WARN")
353
+ print(f"SUMMARY: {error_count} error(s), {warn_count} warning(s)")
354
+
355
+ if error_count:
356
+ return 1
357
+ if args.strict and warn_count:
358
+ return 1
359
+ return 0
360
+
361
+
362
+ if __name__ == "__main__":
363
+ raise SystemExit(main())