@dommaker/harness 0.19.0 → 1.0.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 (348) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/bin/harness.js +136 -557
  3. package/dist/cli/commands/check.d.ts.map +1 -1
  4. package/dist/cli/commands/check.js +2 -13
  5. package/dist/cli/commands/check.js.map +1 -1
  6. package/dist/cli/commands/definitions.d.ts +90 -0
  7. package/dist/cli/commands/definitions.d.ts.map +1 -0
  8. package/dist/cli/commands/definitions.js +519 -0
  9. package/dist/cli/commands/definitions.js.map +1 -0
  10. package/dist/cli/commands/knowledge.js.map +1 -1
  11. package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts +3 -25
  12. package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts.map +1 -1
  13. package/dist/cli/commands/sync-docs/capabilities-syncer.js +3 -93
  14. package/dist/cli/commands/sync-docs/capabilities-syncer.js.map +1 -1
  15. package/dist/cli/commands/sync-docs/index.d.ts +1 -1
  16. package/dist/cli/commands/sync-docs/index.d.ts.map +1 -1
  17. package/dist/cli/commands/sync-docs/index.js +4 -4
  18. package/dist/cli/commands/sync-docs/index.js.map +1 -1
  19. package/dist/context/index.d.ts +1 -5
  20. package/dist/context/index.d.ts.map +1 -1
  21. package/dist/context/index.js +1 -5
  22. package/dist/context/index.js.map +1 -1
  23. package/dist/context/knowledge-injector.d.ts +3 -3
  24. package/dist/context/knowledge-injector.d.ts.map +1 -1
  25. package/dist/context/knowledge-injector.js +1 -1
  26. package/dist/context/session-manager.d.ts +1 -12
  27. package/dist/context/session-manager.d.ts.map +1 -1
  28. package/dist/context/session-manager.js +0 -63
  29. package/dist/context/session-manager.js.map +1 -1
  30. package/dist/context/types.d.ts +1 -39
  31. package/dist/context/types.d.ts.map +1 -1
  32. package/dist/context/types.js +2 -15
  33. package/dist/context/types.js.map +1 -1
  34. package/dist/core/constraints/agent-prompt-renderer.d.ts +1 -1
  35. package/dist/core/constraints/agent-prompt-renderer.js +1 -1
  36. package/dist/core/constraints/capabilities-parser.d.ts +17 -2
  37. package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
  38. package/dist/core/constraints/capabilities-parser.js +84 -2
  39. package/dist/core/constraints/capabilities-parser.js.map +1 -1
  40. package/dist/core/constraints/checker.d.ts +3 -17
  41. package/dist/core/constraints/checker.d.ts.map +1 -1
  42. package/dist/core/constraints/checker.js +7 -47
  43. package/dist/core/constraints/checker.js.map +1 -1
  44. package/dist/core/constraints/definitions.d.ts +0 -7
  45. package/dist/core/constraints/definitions.d.ts.map +1 -1
  46. package/dist/core/constraints/definitions.js +1 -8
  47. package/dist/core/constraints/definitions.js.map +1 -1
  48. package/dist/core/constraints/doc-freshness/runner.d.ts +0 -2
  49. package/dist/core/constraints/doc-freshness/runner.d.ts.map +1 -1
  50. package/dist/core/constraints/doc-freshness/runner.js.map +1 -1
  51. package/dist/core/constraints/index.d.ts +1 -1
  52. package/dist/core/constraints/index.d.ts.map +1 -1
  53. package/dist/core/constraints/index.js +2 -3
  54. package/dist/core/constraints/index.js.map +1 -1
  55. package/dist/core/constraints/interceptor.d.ts.map +1 -1
  56. package/dist/core/constraints/interceptor.js +1 -5
  57. package/dist/core/constraints/interceptor.js.map +1 -1
  58. package/dist/core/effective-constraints.d.ts.map +1 -1
  59. package/dist/core/effective-constraints.js +0 -1
  60. package/dist/core/effective-constraints.js.map +1 -1
  61. package/dist/core/project-config-loader.d.ts.map +1 -1
  62. package/dist/core/project-config-loader.js +1 -8
  63. package/dist/core/project-config-loader.js.map +1 -1
  64. package/dist/dashboard/stats.d.ts.map +1 -1
  65. package/dist/dashboard/stats.js.map +1 -1
  66. package/dist/dashboard/types.d.ts +2 -2
  67. package/dist/dashboard/types.d.ts.map +1 -1
  68. package/dist/failure/constraint-handler.d.ts.map +1 -1
  69. package/dist/failure/constraint-handler.js +0 -2
  70. package/dist/failure/constraint-handler.js.map +1 -1
  71. package/dist/gates/acceptance.d.ts +9 -1
  72. package/dist/gates/acceptance.d.ts.map +1 -1
  73. package/dist/gates/acceptance.js +21 -0
  74. package/dist/gates/acceptance.js.map +1 -1
  75. package/dist/gates/checker-gate.d.ts +23 -0
  76. package/dist/gates/checker-gate.d.ts.map +1 -0
  77. package/dist/gates/checker-gate.js +53 -0
  78. package/dist/gates/checker-gate.js.map +1 -0
  79. package/dist/gates/command.d.ts +9 -2
  80. package/dist/gates/command.d.ts.map +1 -1
  81. package/dist/gates/command.js +10 -0
  82. package/dist/gates/command.js.map +1 -1
  83. package/dist/gates/contract.d.ts +8 -2
  84. package/dist/gates/contract.d.ts.map +1 -1
  85. package/dist/gates/contract.js +9 -0
  86. package/dist/gates/contract.js.map +1 -1
  87. package/dist/gates/decision.d.ts +17 -0
  88. package/dist/gates/decision.d.ts.map +1 -0
  89. package/dist/gates/decision.js +25 -0
  90. package/dist/gates/decision.js.map +1 -0
  91. package/dist/gates/definitions.d.ts +63 -0
  92. package/dist/gates/definitions.d.ts.map +1 -0
  93. package/dist/gates/definitions.js +160 -0
  94. package/dist/gates/definitions.js.map +1 -0
  95. package/dist/gates/effective-gates.d.ts +30 -0
  96. package/dist/gates/effective-gates.d.ts.map +1 -0
  97. package/dist/gates/effective-gates.js +85 -0
  98. package/dist/gates/effective-gates.js.map +1 -0
  99. package/dist/gates/index.d.ts +25 -1
  100. package/dist/gates/index.d.ts.map +1 -1
  101. package/dist/gates/index.js +37 -1
  102. package/dist/gates/index.js.map +1 -1
  103. package/dist/gates/performance.d.ts +8 -2
  104. package/dist/gates/performance.d.ts.map +1 -1
  105. package/dist/gates/performance.js +9 -0
  106. package/dist/gates/performance.js.map +1 -1
  107. package/dist/gates/registry.d.ts +32 -0
  108. package/dist/gates/registry.d.ts.map +1 -0
  109. package/dist/gates/registry.js +90 -0
  110. package/dist/gates/registry.js.map +1 -0
  111. package/dist/gates/review.d.ts +8 -2
  112. package/dist/gates/review.d.ts.map +1 -1
  113. package/dist/gates/review.js +9 -0
  114. package/dist/gates/review.js.map +1 -1
  115. package/dist/gates/runner.d.ts +30 -0
  116. package/dist/gates/runner.d.ts.map +1 -0
  117. package/dist/gates/runner.js +38 -0
  118. package/dist/gates/runner.js.map +1 -0
  119. package/dist/gates/security.d.ts +8 -2
  120. package/dist/gates/security.d.ts.map +1 -1
  121. package/dist/gates/security.js +9 -0
  122. package/dist/gates/security.js.map +1 -1
  123. package/dist/gates/types.d.ts +41 -1
  124. package/dist/gates/types.d.ts.map +1 -1
  125. package/dist/hooks/bootstrap.d.ts.map +1 -1
  126. package/dist/hooks/bootstrap.js +0 -2
  127. package/dist/hooks/bootstrap.js.map +1 -1
  128. package/dist/hooks/config.d.ts +26 -0
  129. package/dist/hooks/config.d.ts.map +1 -0
  130. package/dist/hooks/config.js +30 -0
  131. package/dist/hooks/config.js.map +1 -0
  132. package/dist/hooks/index.d.ts +2 -1
  133. package/dist/hooks/index.d.ts.map +1 -1
  134. package/dist/hooks/index.js +4 -1
  135. package/dist/hooks/index.js.map +1 -1
  136. package/dist/hooks/registry.d.ts +18 -1
  137. package/dist/hooks/registry.d.ts.map +1 -1
  138. package/dist/hooks/registry.js +45 -0
  139. package/dist/hooks/registry.js.map +1 -1
  140. package/dist/hooks/types.d.ts +20 -0
  141. package/dist/hooks/types.d.ts.map +1 -1
  142. package/dist/index.d.ts +4 -8
  143. package/dist/index.d.ts.map +1 -1
  144. package/dist/index.js +5 -20
  145. package/dist/index.js.map +1 -1
  146. package/dist/knowledge/import.d.ts +2 -2
  147. package/dist/knowledge/import.d.ts.map +1 -1
  148. package/dist/knowledge/ingest.js.map +1 -1
  149. package/dist/knowledge/lifecycle-hooks.d.ts +2 -2
  150. package/dist/knowledge/lifecycle-hooks.d.ts.map +1 -1
  151. package/dist/knowledge/types.d.ts +5 -7
  152. package/dist/knowledge/types.d.ts.map +1 -1
  153. package/dist/knowledge/types.js +1 -1
  154. package/dist/knowledge/types.js.map +1 -1
  155. package/dist/monitoring/constraint-doctor.d.ts +4 -22
  156. package/dist/monitoring/constraint-doctor.d.ts.map +1 -1
  157. package/dist/monitoring/constraint-doctor.js +7 -94
  158. package/dist/monitoring/constraint-doctor.js.map +1 -1
  159. package/dist/presets/standard.d.ts +1 -1
  160. package/dist/presets/standard.d.ts.map +1 -1
  161. package/dist/presets/standard.js +1 -2
  162. package/dist/presets/standard.js.map +1 -1
  163. package/dist/spec/annotation-checker.d.ts.map +1 -1
  164. package/dist/spec/annotation-checker.js +6 -26
  165. package/dist/spec/annotation-checker.js.map +1 -1
  166. package/dist/tools/index.d.ts +0 -4
  167. package/dist/tools/index.d.ts.map +1 -1
  168. package/dist/tools/index.js +0 -4
  169. package/dist/tools/index.js.map +1 -1
  170. package/dist/types/constraint.d.ts +1 -12
  171. package/dist/types/constraint.d.ts.map +1 -1
  172. package/dist/types/constraint.js +0 -3
  173. package/dist/types/constraint.js.map +1 -1
  174. package/dist/types/project-config.d.ts +0 -2
  175. package/dist/types/project-config.d.ts.map +1 -1
  176. package/dist/utils/file-walk.d.ts +2 -2
  177. package/dist/utils/file-walk.js +2 -2
  178. package/package.json +6 -1
  179. package/src/CONTEXT.md +14 -0
  180. package/src/__tests__/checker-extra.test.ts +2 -59
  181. package/src/__tests__/constraint-doctor.test.ts +1 -140
  182. package/src/__tests__/constraint-handler.test.ts +0 -4
  183. package/src/__tests__/project-config-loader-extra.test.ts +0 -20
  184. package/src/__tests__/project-config-loader.test.ts +0 -1
  185. package/src/cli/commands/CONTEXT.md +15 -7
  186. package/src/cli/commands/__tests__/check-drift.test.ts +0 -3
  187. package/src/cli/commands/__tests__/check-skip-output.test.ts +0 -5
  188. package/src/cli/commands/__tests__/check.test.ts +7 -75
  189. package/src/cli/commands/__tests__/registry.test.ts +201 -0
  190. package/src/cli/commands/check.ts +2 -14
  191. package/src/cli/commands/definitions.ts +593 -0
  192. package/src/cli/commands/knowledge.ts +3 -3
  193. package/src/cli/commands/sync-docs/capabilities-syncer.ts +4 -110
  194. package/src/cli/commands/sync-docs/index.ts +8 -5
  195. package/src/context/CONTEXT.md +2 -7
  196. package/src/context/__tests__/session-manager.test.ts +0 -91
  197. package/src/context/index.ts +1 -5
  198. package/src/context/knowledge-injector.ts +3 -3
  199. package/src/context/session-manager.ts +0 -74
  200. package/src/context/types.ts +2 -63
  201. package/src/core/constraints/__tests__/interceptor.test.ts +0 -2
  202. package/src/core/constraints/agent-prompt-renderer.ts +1 -1
  203. package/src/core/constraints/capabilities-parser.ts +95 -2
  204. package/src/core/constraints/checker.ts +8 -53
  205. package/src/core/constraints/definitions.ts +0 -8
  206. package/src/core/constraints/doc-freshness/runner.ts +1 -2
  207. package/src/core/constraints/index.ts +1 -2
  208. package/src/core/constraints/interceptor.ts +1 -6
  209. package/src/core/effective-constraints.ts +0 -1
  210. package/src/core/project-config-loader.ts +1 -7
  211. package/src/dashboard/stats.ts +2 -2
  212. package/src/dashboard/types.ts +2 -2
  213. package/src/failure/constraint-handler.ts +0 -2
  214. package/src/gates/CONTEXT.md +16 -13
  215. package/src/gates/__tests__/checker-gate.test.ts +45 -0
  216. package/src/gates/__tests__/contract.test.ts +73 -0
  217. package/src/gates/__tests__/effective-gates.test.ts +82 -0
  218. package/src/gates/__tests__/gate-interface.test.ts +202 -0
  219. package/src/gates/__tests__/registry.test.ts +87 -0
  220. package/src/gates/__tests__/runner.test.ts +81 -0
  221. package/src/gates/acceptance.ts +24 -1
  222. package/src/gates/checker-gate.ts +54 -0
  223. package/src/gates/command.ts +13 -2
  224. package/src/gates/contract.ts +12 -2
  225. package/src/gates/decision.ts +27 -0
  226. package/src/gates/definitions.ts +207 -0
  227. package/src/gates/effective-gates.ts +106 -0
  228. package/src/gates/index.ts +38 -0
  229. package/src/gates/performance.ts +12 -2
  230. package/src/gates/registry.ts +107 -0
  231. package/src/gates/review.ts +12 -2
  232. package/src/gates/runner.ts +57 -0
  233. package/src/gates/security.ts +12 -2
  234. package/src/gates/types.ts +48 -1
  235. package/src/hooks/CONTEXT.md +12 -2
  236. package/src/hooks/__tests__/bootstrap.test.ts +0 -1
  237. package/src/hooks/__tests__/config.test.ts +19 -0
  238. package/src/hooks/__tests__/pipeline.test.ts +79 -0
  239. package/src/hooks/__tests__/registry.test.ts +67 -0
  240. package/src/hooks/bootstrap.ts +0 -2
  241. package/src/hooks/config.ts +29 -0
  242. package/src/hooks/index.ts +2 -1
  243. package/src/hooks/registry.ts +60 -1
  244. package/src/hooks/types.ts +21 -0
  245. package/src/index.ts +5 -22
  246. package/src/knowledge/import.ts +3 -3
  247. package/src/knowledge/ingest.ts +4 -4
  248. package/src/knowledge/lifecycle-hooks.ts +3 -3
  249. package/src/knowledge/types.ts +5 -8
  250. package/src/monitoring/constraint-doctor.ts +7 -106
  251. package/src/presets/__tests__/standard.test.ts +0 -2
  252. package/src/presets/standard.ts +1 -2
  253. package/src/spec/__tests__/annotation-checker.test.ts +9 -3
  254. package/src/spec/annotation-checker.ts +7 -29
  255. package/src/tools/CONTEXT.md +7 -14
  256. package/src/tools/index.ts +0 -4
  257. package/src/types/constraint.ts +1 -14
  258. package/src/types/project-config.ts +0 -3
  259. package/src/utils/file-walk.ts +2 -2
  260. package/dist/architecture/constraint-engine.d.ts +0 -67
  261. package/dist/architecture/constraint-engine.d.ts.map +0 -1
  262. package/dist/architecture/constraint-engine.js +0 -295
  263. package/dist/architecture/constraint-engine.js.map +0 -1
  264. package/dist/architecture/cross-project-checker.d.ts +0 -54
  265. package/dist/architecture/cross-project-checker.d.ts.map +0 -1
  266. package/dist/architecture/cross-project-checker.js +0 -467
  267. package/dist/architecture/cross-project-checker.js.map +0 -1
  268. package/dist/cli/commands/index.d.ts +0 -30
  269. package/dist/cli/commands/index.d.ts.map +0 -1
  270. package/dist/cli/commands/index.js +0 -91
  271. package/dist/cli/commands/index.js.map +0 -1
  272. package/dist/context/file-budget.d.ts +0 -40
  273. package/dist/context/file-budget.d.ts.map +0 -1
  274. package/dist/context/file-budget.js +0 -147
  275. package/dist/context/file-budget.js.map +0 -1
  276. package/dist/context/progressive-loader.d.ts +0 -109
  277. package/dist/context/progressive-loader.d.ts.map +0 -1
  278. package/dist/context/progressive-loader.js +0 -166
  279. package/dist/context/progressive-loader.js.map +0 -1
  280. package/dist/context/token-pipeline.d.ts +0 -55
  281. package/dist/context/token-pipeline.d.ts.map +0 -1
  282. package/dist/context/token-pipeline.js +0 -220
  283. package/dist/context/token-pipeline.js.map +0 -1
  284. package/dist/context/tool-output-budget.d.ts +0 -59
  285. package/dist/context/tool-output-budget.d.ts.map +0 -1
  286. package/dist/context/tool-output-budget.js +0 -177
  287. package/dist/context/tool-output-budget.js.map +0 -1
  288. package/dist/core/constraints/default-executors.d.ts +0 -10
  289. package/dist/core/constraints/default-executors.d.ts.map +0 -1
  290. package/dist/core/constraints/default-executors.js +0 -120
  291. package/dist/core/constraints/default-executors.js.map +0 -1
  292. package/dist/llm/adapter.d.ts +0 -36
  293. package/dist/llm/adapter.d.ts.map +0 -1
  294. package/dist/llm/adapter.js +0 -118
  295. package/dist/llm/adapter.js.map +0 -1
  296. package/dist/llm/index.d.ts +0 -3
  297. package/dist/llm/index.d.ts.map +0 -1
  298. package/dist/llm/index.js +0 -19
  299. package/dist/llm/index.js.map +0 -1
  300. package/dist/llm/types.d.ts +0 -62
  301. package/dist/llm/types.d.ts.map +0 -1
  302. package/dist/llm/types.js +0 -8
  303. package/dist/llm/types.js.map +0 -1
  304. package/dist/tools/core/index.d.ts +0 -13
  305. package/dist/tools/core/index.d.ts.map +0 -1
  306. package/dist/tools/core/index.js +0 -198
  307. package/dist/tools/core/index.js.map +0 -1
  308. package/dist/tools/loader.d.ts +0 -21
  309. package/dist/tools/loader.d.ts.map +0 -1
  310. package/dist/tools/loader.js +0 -199
  311. package/dist/tools/loader.js.map +0 -1
  312. package/dist/tools/registry.d.ts +0 -59
  313. package/dist/tools/registry.d.ts.map +0 -1
  314. package/dist/tools/registry.js +0 -115
  315. package/dist/tools/registry.js.map +0 -1
  316. package/dist/tools/types.d.ts +0 -38
  317. package/dist/tools/types.d.ts.map +0 -1
  318. package/dist/tools/types.js +0 -6
  319. package/dist/tools/types.js.map +0 -1
  320. package/src/__tests__/architecture-engine.test.ts +0 -619
  321. package/src/__tests__/context/progressive-loader.test.ts +0 -131
  322. package/src/__tests__/cross-project-checker.test.ts +0 -318
  323. package/src/architecture/CONTEXT.md +0 -22
  324. package/src/architecture/constraint-engine.ts +0 -351
  325. package/src/architecture/cross-project-checker.ts +0 -545
  326. package/src/cli/commands/index.ts +0 -30
  327. package/src/context/__tests__/file-budget.test.ts +0 -142
  328. package/src/context/__tests__/progressive-loader.test.ts +0 -264
  329. package/src/context/__tests__/token-pipeline.test.ts +0 -178
  330. package/src/context/__tests__/tool-output-budget.test.ts +0 -95
  331. package/src/context/file-budget.ts +0 -142
  332. package/src/context/progressive-loader.ts +0 -260
  333. package/src/context/token-pipeline.ts +0 -265
  334. package/src/context/tool-output-budget.ts +0 -178
  335. package/src/core/constraints/__tests__/default-executors.test.ts +0 -57
  336. package/src/core/constraints/default-executors.ts +0 -92
  337. package/src/llm/CONTEXT.md +0 -21
  338. package/src/llm/__tests__/adapter.test.ts +0 -172
  339. package/src/llm/adapter.ts +0 -149
  340. package/src/llm/index.ts +0 -2
  341. package/src/llm/types.ts +0 -81
  342. package/src/tools/__tests__/loader.test.ts +0 -252
  343. package/src/tools/__tests__/registry.test.ts +0 -157
  344. package/src/tools/core/__tests__/core.test.ts +0 -122
  345. package/src/tools/core/index.ts +0 -201
  346. package/src/tools/loader.ts +0 -216
  347. package/src/tools/registry.ts +0 -128
  348. package/src/tools/types.ts +0 -46
@@ -1,142 +0,0 @@
1
- /**
2
- * 文件读取预算
3
- *
4
- * 按行数、字节数、Token 数三重限制读取文件
5
- * 超限时生成 continuationHint 提示 offset
6
- */
7
-
8
- import * as fs from 'fs';
9
- import { TokenEstimator } from './token-budget';
10
- import type { FileBudgetConfig } from './types';
11
- import { DEFAULT_FILE_BUDGET } from './types';
12
-
13
- export interface FileCheckResult {
14
- allowed: boolean;
15
- reason?: string;
16
- lineCount?: number;
17
- byteCount?: number;
18
- }
19
-
20
- export interface FileReadResult {
21
- content: string;
22
- truncated: boolean;
23
- linesRead: number;
24
- bytesRead: number;
25
- tokensEstimate: number;
26
- continuationHint?: string;
27
- }
28
-
29
- export class FileBudget {
30
- private config: FileBudgetConfig;
31
-
32
- constructor(config?: Partial<FileBudgetConfig>) {
33
- this.config = { ...DEFAULT_FILE_BUDGET, ...config };
34
- }
35
-
36
- /**
37
- * 检查文件是否在预算内(stat 检查)
38
- */
39
- checkFile(filePath: string): FileCheckResult {
40
- try {
41
- const stat = fs.statSync(filePath);
42
-
43
- if (stat.size > this.config.maxBytes) {
44
- return {
45
- allowed: false,
46
- reason: `文件大小 ${(stat.size / 1024).toFixed(1)}KB 超过限制 ${(this.config.maxBytes / 1024).toFixed(1)}KB`,
47
- byteCount: stat.size,
48
- };
49
- }
50
-
51
- return { allowed: true, byteCount: stat.size };
52
- } catch (error) {
53
- return {
54
- allowed: false,
55
- reason: `无法读取文件: ${(error as Error).message}`,
56
- };
57
- }
58
- }
59
-
60
- /**
61
- * 按预算读取文件
62
- *
63
- * 三重限制:行数 → 字节数 → Token 数
64
- */
65
- readWithBudget(filePath: string, config?: Partial<FileBudgetConfig>): FileReadResult {
66
- const effectiveConfig = { ...this.config, ...config };
67
-
68
- try {
69
- const content = fs.readFileSync(filePath, 'utf-8');
70
- const lines = content.split('\n');
71
-
72
- let truncated = false;
73
- let selectedLines = lines;
74
- let continuationHint: string | undefined;
75
-
76
- // 限制 1: 行数
77
- if (lines.length > effectiveConfig.maxLines) {
78
- selectedLines = lines.slice(0, effectiveConfig.maxLines);
79
- truncated = true;
80
-
81
- if (effectiveConfig.continuationHint) {
82
- continuationHint = `文件被截断。已读取 ${effectiveConfig.maxLines} 行,共 ${lines.length} 行。使用 offset=${effectiveConfig.maxLines} 继续读取。`;
83
- }
84
- }
85
-
86
- let result = selectedLines.join('\n');
87
-
88
- // 限制 2: 字节数
89
- const byteCount = Buffer.byteLength(result, 'utf-8');
90
- if (byteCount > effectiveConfig.maxBytes) {
91
- const encoder = new TextEncoder();
92
- const bytes = encoder.encode(result);
93
- const truncatedBytes = bytes.slice(0, effectiveConfig.maxBytes);
94
- result = new TextDecoder().decode(truncatedBytes);
95
- truncated = true;
96
-
97
- if (!continuationHint && effectiveConfig.continuationHint) {
98
- continuationHint = `文件内容被截断。已读取 ${effectiveConfig.maxBytes} 字节。`;
99
- }
100
- }
101
-
102
- // 限制 3: Token 数
103
- const tokensEstimate = TokenEstimator.estimateText(result);
104
- if (tokensEstimate > effectiveConfig.maxTokenEstimate) {
105
- // 按比例截断
106
- const ratio = effectiveConfig.maxTokenEstimate / tokensEstimate;
107
- const charLimit = Math.floor(result.length * ratio);
108
- result = result.slice(0, charLimit);
109
- truncated = true;
110
-
111
- if (!continuationHint && effectiveConfig.continuationHint) {
112
- continuationHint = `文件内容被截断。已读取约 ${effectiveConfig.maxTokenEstimate} tokens。`;
113
- }
114
- }
115
-
116
- return {
117
- content: result,
118
- truncated,
119
- linesRead: selectedLines.length,
120
- bytesRead: Buffer.byteLength(result, 'utf-8'),
121
- tokensEstimate: TokenEstimator.estimateText(result),
122
- continuationHint,
123
- };
124
- } catch (error) {
125
- return {
126
- content: '',
127
- truncated: false,
128
- linesRead: 0,
129
- bytesRead: 0,
130
- tokensEstimate: 0,
131
- continuationHint: `无法读取文件: ${(error as Error).message}`,
132
- };
133
- }
134
- }
135
-
136
- /**
137
- * 获取当前配置
138
- */
139
- getConfig(): FileBudgetConfig {
140
- return { ...this.config };
141
- }
142
- }
@@ -1,260 +0,0 @@
1
- import { delay } from '../utils/exec';
2
-
3
- /**
4
- * @spec HZ-002
5
- * @implements HZ-002-C1
6
- * @acceptance AC-001-1, AC-001-4
7
- *
8
- * Progressive Content Loader
9
- *
10
- * 通用内容分片加载能力
11
- * - 支持大数据集分片加载
12
- * - 支持 Token 预算管理
13
- * - 零业务逻辑,可被任意系统使用
14
- */
15
-
16
- export interface ChunkLoadOptions<T = any> {
17
- /** 每片大小 */
18
- chunkSize: number;
19
- /** 每片加载回调 */
20
- onChunk?: (chunk: T[], index: number) => Promise<void> | void;
21
- /** 进度回调 (loaded, total) */
22
- onProgress?: (loaded: number, total: number) => void;
23
- /** 是否并行加载 */
24
- parallel?: boolean;
25
- /** 延迟(毫秒),用于节流 */
26
- delay?: number;
27
- }
28
-
29
- export interface TokenBudgetOptions<T = any> {
30
- /** Token 预算 */
31
- budget: number;
32
- /** Token 估算函数 */
33
- estimator: (item: T) => number;
34
- /** 预算超限策略 */
35
- onBudgetExceeded?: 'truncate' | 'error' | 'skip';
36
- /** 最小保留项数(即使超预算) */
37
- minItems?: number;
38
- }
39
-
40
- export interface LoadResult<T> {
41
- /** 加载的项 */
42
- items: T[];
43
- /** 实际消耗的 Token */
44
- tokensUsed: number;
45
- /** 是否被截断 */
46
- truncated: boolean;
47
- /** 跳过的项数 */
48
- skipped: number;
49
- }
50
-
51
- /**
52
- * 渐进式内容加载器
53
- *
54
- * 提供通用的内容分片加载和 Token 预算管理能力
55
- */
56
- export class ProgressiveLoader {
57
- /**
58
- * 分片加载
59
- *
60
- * 将大数据集分片加载,避免一次性加载全部内容
61
- *
62
- * @example
63
- * ```typescript
64
- * const loader = new ProgressiveLoader();
65
- * const largeData = Array.from({ length: 1000 }, (_, i) => ({ id: i }));
66
- *
67
- * await loader.loadInChunks(largeData, {
68
- * chunkSize: 100,
69
- * onChunk: async (chunk, index) => {
70
- * console.log(`Loaded chunk ${index}: ${chunk.length} items`);
71
- * },
72
- * onProgress: (loaded, total) => {
73
- * console.log(`Progress: ${loaded}/${total}`);
74
- * }
75
- * });
76
- * ```
77
- */
78
- async loadInChunks<T>(
79
- items: T[],
80
- options: ChunkLoadOptions<T>
81
- ): Promise<void> {
82
- const { chunkSize, onChunk, onProgress, parallel = false, delay: throttleDelay = 0 } = options;
83
- const total = items.length;
84
- const chunks: T[][] = [];
85
-
86
- // 分片
87
- for (let i = 0; i < total; i += chunkSize) {
88
- chunks.push(items.slice(i, i + chunkSize));
89
- }
90
-
91
- // 加载
92
- if (parallel) {
93
- // 并行加载
94
- await Promise.all(
95
- chunks.map(async (chunk, index) => {
96
- if (throttleDelay > 0) {
97
- await delay(throttleDelay * index);
98
- }
99
- await this.processChunk(chunk, index, onChunk, onProgress, total);
100
- })
101
- );
102
- } else {
103
- // 串行加载
104
- for (let i = 0; i < chunks.length; i++) {
105
- if (throttleDelay > 0 && i > 0) {
106
- await delay(throttleDelay);
107
- }
108
- await this.processChunk(chunks[i], i, onChunk, onProgress, total);
109
- }
110
- }
111
- }
112
-
113
- /**
114
- * Token 预算加载
115
- *
116
- * 在 Token 预算范围内加载内容,超预算时按策略处理
117
- *
118
- * @example
119
- * ```typescript
120
- * const loader = new ProgressiveLoader();
121
- * const items = [
122
- * { content: 'short text' },
123
- * { content: 'long text '.repeat(100) }
124
- * ];
125
- *
126
- * const result = await loader.loadWithBudget(items, {
127
- * budget: 1000,
128
- * estimator: (item) => item.content.length,
129
- * onBudgetExceeded: 'truncate'
130
- * });
131
- *
132
- * console.log(`Loaded ${result.items.length} items, used ${result.tokensUsed} tokens`);
133
- * ```
134
- */
135
- async loadWithBudget<T>(
136
- items: T[],
137
- options: TokenBudgetOptions<T>
138
- ): Promise<LoadResult<T>> {
139
- const {
140
- budget,
141
- estimator,
142
- onBudgetExceeded = 'truncate',
143
- minItems = 1
144
- } = options;
145
-
146
- const result: LoadResult<T> = {
147
- items: [],
148
- tokensUsed: 0,
149
- truncated: false,
150
- skipped: 0
151
- };
152
-
153
- let currentBudget = budget;
154
-
155
- for (const item of items) {
156
- const itemTokens = estimator(item);
157
-
158
- // 检查预算
159
- if (itemTokens > currentBudget) {
160
- // 预算超限
161
- if (result.items.length >= minItems) {
162
- // 已达到最小项数,按策略处理
163
- switch (onBudgetExceeded) {
164
- case 'truncate':
165
- result.truncated = true;
166
- return result;
167
- case 'error':
168
- throw new Error(
169
- `Token budget exceeded: need ${itemTokens}, remaining ${currentBudget}`
170
- );
171
- case 'skip':
172
- result.skipped++;
173
- continue;
174
- }
175
- }
176
- // 未达到最小项数,强制添加(可能超预算)
177
- }
178
-
179
- // 添加项
180
- result.items.push(item);
181
- result.tokensUsed += itemTokens;
182
- currentBudget -= itemTokens;
183
- }
184
-
185
- return result;
186
- }
187
-
188
- /**
189
- * 创建流式加载器
190
- *
191
- * 支持大文件/大内容的流式分片加载
192
- */
193
- async *createStream<T>(
194
- source: AsyncIterable<T>,
195
- chunkSize: number
196
- ): AsyncIterable<T[]> {
197
- let chunk: T[] = [];
198
-
199
- for await (const item of source) {
200
- chunk.push(item);
201
-
202
- if (chunk.length >= chunkSize) {
203
- yield chunk;
204
- chunk = [];
205
- }
206
- }
207
-
208
- // yield remaining items
209
- if (chunk.length > 0) {
210
- yield chunk;
211
- }
212
- }
213
-
214
- /**
215
- * 批量处理(带并发控制)
216
- */
217
- async processBatch<T, R>(
218
- items: T[],
219
- processor: (item: T) => Promise<R>,
220
- concurrency: number = 5
221
- ): Promise<R[]> {
222
- const results = new Array<R>(items.length);
223
- let nextIndex = 0;
224
-
225
- const worker = async (): Promise<void> => {
226
- while (nextIndex < items.length) {
227
- const index = nextIndex++;
228
- results[index] = await processor(items[index]);
229
- }
230
- };
231
-
232
- const workers = Array.from(
233
- { length: Math.min(concurrency, items.length) },
234
- () => worker()
235
- );
236
- await Promise.all(workers);
237
- return results;
238
- }
239
-
240
- private async processChunk<T>(
241
- chunk: T[],
242
- index: number,
243
- onChunk: ((chunk: T[], index: number) => Promise<void> | void) | undefined,
244
- onProgress: ((loaded: number, total: number) => void) | undefined,
245
- total: number
246
- ): Promise<void> {
247
- if (onChunk) {
248
- await onChunk(chunk, index);
249
- }
250
-
251
- if (onProgress) {
252
- const loaded = Math.min((index + 1) * chunk.length, total);
253
- onProgress(loaded, total);
254
- }
255
- }
256
-
257
- }
258
-
259
- // 默认实例
260
- export const progressiveLoader = new ProgressiveLoader();
@@ -1,265 +0,0 @@
1
- /**
2
- * Token 流水线
3
- *
4
- * 五步:收集 → 排序 → 压缩 → 预算 → 组装
5
- * 每次 LLM 调用前运行
6
- */
7
-
8
- import { TokenEstimator, TokenBudget } from './token-budget';
9
- import type {
10
- ContextSource,
11
- TokenBudgetAllocation,
12
- PipelineInput,
13
- PipelineOutput,
14
- ContextUsageSnapshot,
15
- } from './types';
16
-
17
- interface CollectedGroup {
18
- priority: number;
19
- sources: ContextSource[];
20
- }
21
-
22
- export class TokenPipeline {
23
- /**
24
- * 完整流水线:收集 → 排序 → 压缩 → 预算 → 组装
25
- */
26
- run(input: PipelineInput): PipelineOutput {
27
- const { sources, budget } = input;
28
-
29
- // Step 1: 收集 — 按 priority 分组
30
- const collected = this.collect(sources);
31
-
32
- // Step 2: 排序 — 按 P1→P6 排序
33
- const sorted = this.sort(collected);
34
-
35
- // Step 3: 压缩 — 对 P3-P5 应用预算裁剪
36
- const { compressed, dropped } = this.compress(sorted, budget);
37
-
38
- // Step 4: 预算 — 按分配裁剪
39
- const budgeted = this.applyBudget(compressed, budget);
40
-
41
- // Step 5: 组装 — 拼装最终 prompt
42
- const prompt = this.assemble(budgeted);
43
-
44
- // 生成快照
45
- const snapshot = this.buildSnapshot(budgeted, dropped);
46
-
47
- return { prompt, snapshot, dropped };
48
- }
49
-
50
- /**
51
- * Step 1: 收集 — 从 sources 按 priority 分组
52
- */
53
- collect(sources: ContextSource[]): CollectedGroup[] {
54
- const groups = new Map<number, ContextSource[]>();
55
-
56
- for (const source of sources) {
57
- const existing = groups.get(source.priority) || [];
58
- existing.push(source);
59
- groups.set(source.priority, existing);
60
- }
61
-
62
- return Array.from(groups.entries())
63
- .map(([priority, sources]) => ({ priority, sources }))
64
- .sort((a, b) => a.priority - b.priority);
65
- }
66
-
67
- /**
68
- * Step 2: 排序 — 按 P1→P6,组内保持原序
69
- */
70
- sort(groups: CollectedGroup[]): ContextSource[] {
71
- const sorted: ContextSource[] = [];
72
-
73
- for (const group of groups) {
74
- sorted.push(...group.sources);
75
- }
76
-
77
- return sorted;
78
- }
79
-
80
- /**
81
- * Step 3: 压缩 — 对 P3-P5 应用 token 预算裁剪
82
- *
83
- * P1(系统提示词)和 P6(用户消息)不压缩
84
- * P2(工具定义)按需懒加载,这里不处理
85
- */
86
- compress(sorted: ContextSource[], budget: TokenBudgetAllocation): {
87
- compressed: ContextSource[];
88
- dropped: Array<{ type: string; id: string; reason: string }>;
89
- } {
90
- const compressed: ContextSource[] = [];
91
- const dropped: Array<{ type: string; id: string; reason: string }> = [];
92
-
93
- // 各段预算
94
- const budgetMap: Record<number, number> = {
95
- 1: budget.systemPrompt,
96
- 2: budget.toolDefinitions,
97
- 3: budget.knowledge,
98
- 4: budget.notes,
99
- 5: budget.history,
100
- 6: Infinity, // 用户消息不限
101
- };
102
-
103
- // 按优先级分组处理
104
- const byPriority = new Map<number, ContextSource[]>();
105
- for (const item of sorted) {
106
- const existing = byPriority.get(item.priority) || [];
107
- existing.push(item);
108
- byPriority.set(item.priority, existing);
109
- }
110
-
111
- for (const [priority, items] of byPriority) {
112
- const sectionBudget = budgetMap[priority] || budget.history;
113
-
114
- if (priority === 1 || priority === 6) {
115
- // P1/P6 不压缩
116
- compressed.push(...items);
117
- continue;
118
- }
119
-
120
- // 按 token 预算裁剪
121
- let usedTokens = 0;
122
- for (const item of items) {
123
- const tokens = TokenEstimator.estimateText(item.content);
124
-
125
- if (usedTokens + tokens <= sectionBudget) {
126
- compressed.push(item);
127
- usedTokens += tokens;
128
- } else {
129
- // 尝试截断
130
- const remaining = sectionBudget - usedTokens;
131
- if (remaining > 50) {
132
- const ratio = remaining / tokens;
133
- const truncatedContent = item.content.slice(0, Math.floor(item.content.length * ratio));
134
- compressed.push({ ...item, content: truncatedContent });
135
- dropped.push({
136
- type: item.type,
137
- id: item.id,
138
- reason: `截断: ${tokens} → ${Math.floor(tokens * ratio)} tokens`,
139
- });
140
- } else {
141
- dropped.push({
142
- type: item.type,
143
- id: item.id,
144
- reason: `超出预算: 需要 ${tokens} tokens,剩余 ${remaining}`,
145
- });
146
- }
147
- usedTokens += tokens; // 仍然计入已用
148
- }
149
- }
150
- }
151
-
152
- return { compressed, dropped };
153
- }
154
-
155
- /**
156
- * Step 4: 预算 — 按 TokenBudgetAllocation 分配各段
157
- */
158
- applyBudget(compressed: ContextSource[], budget: TokenBudgetAllocation): ContextSource[] {
159
- const tokenBudget = new TokenBudget(budget.total);
160
- const result: ContextSource[] = [];
161
-
162
- for (const item of compressed) {
163
- const tokens = TokenEstimator.estimateText(item.content);
164
-
165
- if (tokenBudget.canAfford(tokens)) {
166
- tokenBudget.consume(tokens);
167
- result.push(item);
168
- } else if (item.priority === 1 || item.priority === 6) {
169
- // P1/P6 强制保留
170
- tokenBudget.forceConsume(tokens);
171
- result.push(item);
172
- }
173
- // 其他优先级的超预算项被丢弃
174
- }
175
-
176
- return result;
177
- }
178
-
179
- /**
180
- * Step 5: 组装 — 拼装最终 prompt
181
- *
182
- * P1 开头 + P6 结尾,中间 P2-P5
183
- */
184
- assemble(budgeted: ContextSource[]): string {
185
- const sections: Record<number, string[]> = {
186
- 1: [], 2: [], 3: [], 4: [], 5: [], 6: [],
187
- };
188
-
189
- for (const item of budgeted) {
190
- sections[item.priority] = sections[item.priority] || [];
191
- sections[item.priority].push(item.content);
192
- }
193
-
194
- const parts: string[] = [];
195
-
196
- // P1: 系统提示词(开头)
197
- if (sections[1].length > 0) {
198
- parts.push(sections[1].join('\n'));
199
- }
200
-
201
- // P2-P5: 中间部分
202
- for (const priority of [2, 3, 4, 5]) {
203
- if (sections[priority].length > 0) {
204
- parts.push(sections[priority].join('\n'));
205
- }
206
- }
207
-
208
- // P6: 用户消息(结尾)
209
- if (sections[6].length > 0) {
210
- parts.push(sections[6].join('\n'));
211
- }
212
-
213
- return parts.join('\n\n');
214
- }
215
-
216
- /**
217
- * 构建上下文使用快照
218
- */
219
- private buildSnapshot(budgeted: ContextSource[], dropped: Array<{ type: string; id: string; reason: string }>): ContextUsageSnapshot {
220
- const breakdown = {
221
- systemPrompt: 0,
222
- messages: 0,
223
- toolOutputs: 0,
224
- knowledge: 0,
225
- other: 0,
226
- };
227
-
228
- for (const item of budgeted) {
229
- const tokens = TokenEstimator.estimateText(item.content);
230
-
231
- switch (item.type) {
232
- case 'system_prompt':
233
- breakdown.systemPrompt += tokens;
234
- break;
235
- case 'user_message':
236
- case 'session_event':
237
- breakdown.messages += tokens;
238
- break;
239
- case 'tool_output':
240
- case 'tool_definition':
241
- breakdown.toolOutputs += tokens;
242
- break;
243
- case 'knowledge':
244
- breakdown.knowledge += tokens;
245
- break;
246
- default:
247
- breakdown.other += tokens;
248
- }
249
- }
250
-
251
- return {
252
- timestamp: new Date().toISOString(),
253
- totalTokens: breakdown.systemPrompt + breakdown.messages + breakdown.toolOutputs + breakdown.knowledge + breakdown.other,
254
- breakdown,
255
- truncatedItems: dropped.filter(d => d.reason.startsWith('截断')).map(d => ({
256
- type: d.type,
257
- id: d.id,
258
- originalTokens: 0,
259
- keptTokens: 0,
260
- })),
261
- offloadedItems: [],
262
- compactionTriggered: false,
263
- };
264
- }
265
- }