project-tiny-context-harness 0.8.11 → 0.8.13

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 (395) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +514 -472
  3. package/assets/README.md +803 -749
  4. package/assets/README.zh-CN.md +468 -431
  5. package/assets/agents/.gitkeep +1 -1
  6. package/assets/agents/AGENTS_CORE.md +22 -24
  7. package/assets/agents/long-task-implementation.toml +15 -0
  8. package/assets/context_templates/architecture.md +18 -18
  9. package/assets/context_templates/area.md +30 -30
  10. package/assets/context_templates/context.toml +24 -24
  11. package/assets/context_templates/deployment.md +35 -35
  12. package/assets/context_templates/global.md +56 -56
  13. package/assets/context_templates/product-surface-contract.md +70 -70
  14. package/assets/context_templates/screen-contract.md +189 -189
  15. package/assets/context_templates/verification.md +25 -25
  16. package/assets/github/.gitkeep +1 -1
  17. package/assets/github/harness.yml +39 -39
  18. package/assets/make/.gitkeep +1 -1
  19. package/assets/make/ty-context.mk +48 -48
  20. package/assets/skills/context_development_engineer/SKILL.md +72 -185
  21. package/assets/skills/context_full_project_export/SKILL.md +28 -28
  22. package/assets/skills/context_harness_upgrade/SKILL.md +60 -60
  23. package/assets/skills/context_product_plan/SKILL.md +35 -101
  24. package/assets/skills/context_surface_contract/SKILL.md +194 -191
  25. package/assets/skills/context_uiux_design/SKILL.md +49 -172
  26. package/assets/skills/design-resource-authoring/SKILL.md +54 -88
  27. package/assets/skills/design-resource-authoring/references/downstream-handoff.md +51 -163
  28. package/assets/skills/design-resource-authoring/references/formal-selected-web-app-handoff.md +96 -0
  29. package/assets/skills/design-resource-authoring/references/open-design-provider.md +140 -120
  30. package/assets/skills/design-resource-authoring/references/recovery-and-writeback.md +199 -0
  31. package/assets/skills/design-resource-authoring/references/resource-selection.md +158 -181
  32. package/assets/skills/design-system-authoring/SKILL.md +57 -57
  33. package/assets/skills/design-system-authoring/agents/openai.yaml +6 -6
  34. package/assets/skills/design-system-authoring/references/authority-adoption.md +48 -48
  35. package/assets/skills/design-system-authoring/references/open-design-design-system-provider.md +110 -110
  36. package/assets/skills/long-task-workflow/SKILL.md +71 -106
  37. package/assets/skills/long-task-workflow/agents/openai.yaml +1 -1
  38. package/assets/skills/long-task-workflow/references/authority-lifecycle.md +71 -69
  39. package/assets/skills/long-task-workflow/references/contract-authoring.md +110 -101
  40. package/assets/skills/long-task-workflow/references/evidence-design.md +99 -78
  41. package/assets/skills/long-task-workflow/references/source-authoring.md +90 -90
  42. package/assets/tools/validate_context.py +442 -442
  43. package/dist/commands/design-resource-recovery.d.ts +1 -0
  44. package/dist/commands/design-resource-recovery.js +152 -0
  45. package/dist/commands/design-resource.js +29 -3
  46. package/dist/commands/enable.js +2 -0
  47. package/dist/commands/index.js +2 -2
  48. package/dist/commands/long-task-revision.js +8 -7
  49. package/dist/commands/sync.js +2 -0
  50. package/dist/index.d.ts +3 -3
  51. package/dist/index.js +2 -0
  52. package/dist/lib/compact-authoring-support.d.ts +22 -0
  53. package/dist/lib/compact-authoring-support.js +143 -0
  54. package/dist/lib/compact-shared-structure-authoring.d.ts +5 -0
  55. package/dist/lib/compact-shared-structure-authoring.js +9 -0
  56. package/dist/lib/compact-shared-structure-types.d.ts +61 -0
  57. package/dist/lib/compact-shared-structure-types.js +1 -0
  58. package/dist/lib/compact-shared-structure-validation.d.ts +2 -0
  59. package/dist/lib/compact-shared-structure-validation.js +159 -0
  60. package/dist/lib/compact-shared-structures.d.ts +3 -0
  61. package/dist/lib/compact-shared-structures.js +204 -0
  62. package/dist/lib/design-resource-fact-enums.d.ts +4 -0
  63. package/dist/lib/design-resource-fact-enums.js +12 -0
  64. package/dist/lib/design-resource-fact-manifest-shape-evidence.js +2 -2
  65. package/dist/lib/design-resource-fact-policy.d.ts +2 -2
  66. package/dist/lib/design-resource-fact-types.d.ts +2 -2
  67. package/dist/lib/design-resource-handoff-bundle-draft.js +4 -2
  68. package/dist/lib/design-resource-handoff-input-types.d.ts +3 -0
  69. package/dist/lib/design-resource-handoff-parser.d.ts +2 -2
  70. package/dist/lib/design-resource-handoff-parser.js +2 -2
  71. package/dist/lib/design-resource-handoff-shape.d.ts +2 -0
  72. package/dist/lib/design-resource-handoff-shape.js +10 -0
  73. package/dist/lib/design-resource-handoff-snapshot.js +4 -0
  74. package/dist/lib/design-resource-handoff-validation.d.ts +4 -1
  75. package/dist/lib/design-resource-handoff-validation.js +11 -0
  76. package/dist/lib/design-resource-handoff-web-dependency-validation.d.ts +4 -2
  77. package/dist/lib/design-resource-reconciliation-codec.d.ts +2 -0
  78. package/dist/lib/design-resource-reconciliation-codec.js +174 -0
  79. package/dist/lib/design-resource-reconciliation-types.d.ts +62 -0
  80. package/dist/lib/design-resource-reconciliation-types.js +1 -0
  81. package/dist/lib/design-resource-reconciliation.d.ts +3 -0
  82. package/dist/lib/design-resource-reconciliation.js +224 -0
  83. package/dist/lib/design-resource-recovery-authority-policy.d.ts +8 -0
  84. package/dist/lib/design-resource-recovery-authority-policy.js +123 -0
  85. package/dist/lib/design-resource-recovery-catalog-resources.d.ts +6 -0
  86. package/dist/lib/design-resource-recovery-catalog-resources.js +121 -0
  87. package/dist/lib/design-resource-recovery-catalog-shape.d.ts +3 -0
  88. package/dist/lib/design-resource-recovery-catalog-shape.js +104 -0
  89. package/dist/lib/design-resource-recovery-catalog.d.ts +9 -0
  90. package/dist/lib/design-resource-recovery-catalog.js +103 -0
  91. package/dist/lib/design-resource-recovery-cleanup.d.ts +2 -0
  92. package/dist/lib/design-resource-recovery-cleanup.js +11 -0
  93. package/dist/lib/design-resource-recovery-codec-primitives.d.ts +19 -0
  94. package/dist/lib/design-resource-recovery-codec-primitives.js +92 -0
  95. package/dist/lib/design-resource-recovery-codec.d.ts +4 -0
  96. package/dist/lib/design-resource-recovery-codec.js +93 -0
  97. package/dist/lib/design-resource-recovery-current.d.ts +9 -0
  98. package/dist/lib/design-resource-recovery-current.js +41 -0
  99. package/dist/lib/design-resource-recovery-delta-policy.d.ts +5 -0
  100. package/dist/lib/design-resource-recovery-delta-policy.js +108 -0
  101. package/dist/lib/design-resource-recovery-files.d.ts +37 -0
  102. package/dist/lib/design-resource-recovery-files.js +204 -0
  103. package/dist/lib/design-resource-recovery-final-disposition-shape.d.ts +2 -0
  104. package/dist/lib/design-resource-recovery-final-disposition-shape.js +47 -0
  105. package/dist/lib/design-resource-recovery-patch-types.d.ts +54 -0
  106. package/dist/lib/design-resource-recovery-patch-types.js +1 -0
  107. package/dist/lib/design-resource-recovery-replay.d.ts +7 -0
  108. package/dist/lib/design-resource-recovery-replay.js +112 -0
  109. package/dist/lib/design-resource-recovery-repository-bindings.d.ts +9 -0
  110. package/dist/lib/design-resource-recovery-repository-bindings.js +49 -0
  111. package/dist/lib/design-resource-recovery-schema.d.ts +5 -0
  112. package/dist/lib/design-resource-recovery-schema.js +5 -0
  113. package/dist/lib/design-resource-recovery-shape.d.ts +10 -0
  114. package/dist/lib/design-resource-recovery-shape.js +184 -0
  115. package/dist/lib/design-resource-recovery-source-authority.d.ts +5 -0
  116. package/dist/lib/design-resource-recovery-source-authority.js +219 -0
  117. package/dist/lib/design-resource-recovery-text.d.ts +17 -0
  118. package/dist/lib/design-resource-recovery-text.js +186 -0
  119. package/dist/lib/design-resource-recovery-types.d.ts +197 -0
  120. package/dist/lib/design-resource-recovery-types.js +1 -0
  121. package/dist/lib/design-resource-recovery-writeback-policy.d.ts +5 -0
  122. package/dist/lib/design-resource-recovery-writeback-policy.js +326 -0
  123. package/dist/lib/design-resource-recovery-writeback-shape.d.ts +6 -0
  124. package/dist/lib/design-resource-recovery-writeback-shape.js +155 -0
  125. package/dist/lib/design-resource-recovery.d.ts +44 -0
  126. package/dist/lib/design-resource-recovery.js +216 -0
  127. package/dist/lib/design-resource-symbolic-applicability-authority.d.ts +7 -0
  128. package/dist/lib/design-resource-symbolic-applicability-authority.js +19 -0
  129. package/dist/lib/design-resource-symbolic-applicability-policy.d.ts +12 -0
  130. package/dist/lib/design-resource-symbolic-applicability-policy.js +79 -0
  131. package/dist/lib/design-resource-symbolic-applicability-profiles.d.ts +8 -0
  132. package/dist/lib/design-resource-symbolic-applicability-profiles.js +55 -0
  133. package/dist/lib/design-resource-symbolic-applicability-shape.d.ts +2 -0
  134. package/dist/lib/design-resource-symbolic-applicability-shape.js +76 -0
  135. package/dist/lib/design-resource-symbolic-applicability-validation.d.ts +7 -0
  136. package/dist/lib/design-resource-symbolic-applicability-validation.js +128 -0
  137. package/dist/lib/design-resource-symbolic-compilation.d.ts +3 -0
  138. package/dist/lib/design-resource-symbolic-compilation.js +13 -0
  139. package/dist/lib/design-resource-symbolic-denotation.d.ts +3 -0
  140. package/dist/lib/design-resource-symbolic-denotation.js +167 -0
  141. package/dist/lib/design-resource-symbolic-disposition-validation.d.ts +4 -0
  142. package/dist/lib/design-resource-symbolic-disposition-validation.js +28 -0
  143. package/dist/lib/design-resource-symbolic-fact-policy.d.ts +12 -0
  144. package/dist/lib/design-resource-symbolic-fact-policy.js +12 -0
  145. package/dist/lib/design-resource-symbolic-fact-shape.d.ts +3 -0
  146. package/dist/lib/design-resource-symbolic-fact-shape.js +138 -0
  147. package/dist/lib/design-resource-symbolic-fact-types.d.ts +213 -0
  148. package/dist/lib/design-resource-symbolic-fact-types.js +1 -0
  149. package/dist/lib/design-resource-symbolic-fact-validation.d.ts +3 -0
  150. package/dist/lib/design-resource-symbolic-fact-validation.js +86 -0
  151. package/dist/lib/design-resource-symbolic-indexes.d.ts +23 -0
  152. package/dist/lib/design-resource-symbolic-indexes.js +54 -0
  153. package/dist/lib/design-resource-symbolic-manifest-shape.d.ts +3 -0
  154. package/dist/lib/design-resource-symbolic-manifest-shape.js +155 -0
  155. package/dist/lib/design-resource-symbolic-manifest-validation.d.ts +6 -0
  156. package/dist/lib/design-resource-symbolic-manifest-validation.js +132 -0
  157. package/dist/lib/design-resource-symbolic-noninterference-artifact.d.ts +33 -0
  158. package/dist/lib/design-resource-symbolic-noninterference-artifact.js +110 -0
  159. package/dist/lib/design-resource-symbolic-noninterference-equivalence.d.ts +3 -0
  160. package/dist/lib/design-resource-symbolic-noninterference-equivalence.js +77 -0
  161. package/dist/lib/design-resource-symbolic-noninterference-recompute.d.ts +4 -0
  162. package/dist/lib/design-resource-symbolic-noninterference-recompute.js +146 -0
  163. package/dist/lib/design-resource-symbolic-noninterference-scope.d.ts +7 -0
  164. package/dist/lib/design-resource-symbolic-noninterference-scope.js +49 -0
  165. package/dist/lib/design-resource-symbolic-noninterference-shape.d.ts +2 -0
  166. package/dist/lib/design-resource-symbolic-noninterference-shape.js +152 -0
  167. package/dist/lib/design-resource-symbolic-noninterference-types.d.ts +103 -0
  168. package/dist/lib/design-resource-symbolic-noninterference-types.js +1 -0
  169. package/dist/lib/design-resource-symbolic-noninterference-validation.d.ts +5 -0
  170. package/dist/lib/design-resource-symbolic-noninterference-validation.js +62 -0
  171. package/dist/lib/design-resource-symbolic-oracle-fingerprint.d.ts +1 -0
  172. package/dist/lib/design-resource-symbolic-oracle-fingerprint.js +47 -0
  173. package/dist/lib/design-resource-symbolic-predicate-shape.d.ts +6 -0
  174. package/dist/lib/design-resource-symbolic-predicate-shape.js +123 -0
  175. package/dist/lib/design-resource-symbolic-production-closure.d.ts +4 -0
  176. package/dist/lib/design-resource-symbolic-production-closure.js +144 -0
  177. package/dist/lib/design-resource-symbolic-proof-authority-validation.d.ts +10 -0
  178. package/dist/lib/design-resource-symbolic-proof-authority-validation.js +68 -0
  179. package/dist/lib/design-resource-symbolic-proof-validation.d.ts +10 -0
  180. package/dist/lib/design-resource-symbolic-proof-validation.js +89 -0
  181. package/dist/lib/design-resource-symbolic-region-validation.d.ts +19 -0
  182. package/dist/lib/design-resource-symbolic-region-validation.js +146 -0
  183. package/dist/lib/design-resource-symbolic-resource-validation.d.ts +5 -0
  184. package/dist/lib/design-resource-symbolic-resource-validation.js +90 -0
  185. package/dist/lib/design-resource-symbolic-rule-shape.d.ts +27 -0
  186. package/dist/lib/design-resource-symbolic-rule-shape.js +158 -0
  187. package/dist/lib/design-resource-symbolic-safety-validation.d.ts +6 -0
  188. package/dist/lib/design-resource-symbolic-safety-validation.js +29 -0
  189. package/dist/lib/design-resource-symbolic-source-ir-evaluation.d.ts +19 -0
  190. package/dist/lib/design-resource-symbolic-source-ir-evaluation.js +74 -0
  191. package/dist/lib/design-resource-symbolic-source-ir-evidence.d.ts +44 -0
  192. package/dist/lib/design-resource-symbolic-source-ir-evidence.js +88 -0
  193. package/dist/lib/design-resource-symbolic-source-ir-oracle.d.ts +13 -0
  194. package/dist/lib/design-resource-symbolic-source-ir-oracle.js +96 -0
  195. package/dist/lib/design-resource-symbolic-source-ir-proof.d.ts +14 -0
  196. package/dist/lib/design-resource-symbolic-source-ir-proof.js +98 -0
  197. package/dist/lib/design-resource-symbolic-source-ir-shape.d.ts +2 -0
  198. package/dist/lib/design-resource-symbolic-source-ir-shape.js +38 -0
  199. package/dist/lib/design-resource-symbolic-source-ir-types.d.ts +17 -0
  200. package/dist/lib/design-resource-symbolic-source-ir-types.js +2 -0
  201. package/dist/lib/design-resource-symbolic-static-dependency-validation.d.ts +2 -0
  202. package/dist/lib/design-resource-symbolic-static-dependency-validation.js +83 -0
  203. package/dist/lib/design-resource-symbolic-structural-closure-validation.d.ts +6 -0
  204. package/dist/lib/design-resource-symbolic-structural-closure-validation.js +79 -0
  205. package/dist/lib/design-resource-symbolic-validation-support.d.ts +29 -0
  206. package/dist/lib/design-resource-symbolic-validation-support.js +111 -0
  207. package/dist/lib/design-resource-v1-capacity-header.d.ts +8 -0
  208. package/dist/lib/design-resource-v1-capacity-header.js +231 -0
  209. package/dist/lib/design-resource-v1-capacity.d.ts +5 -0
  210. package/dist/lib/design-resource-v1-capacity.js +45 -0
  211. package/dist/lib/init.js +1 -0
  212. package/dist/lib/long-task-acceptance-shape.js +8 -2
  213. package/dist/lib/long-task-activation-validation.js +13 -3
  214. package/dist/lib/long-task-admitted-observation-records.d.ts +25 -0
  215. package/dist/lib/long-task-admitted-observation-records.js +40 -0
  216. package/dist/lib/long-task-admitted-observation.d.ts +93 -0
  217. package/dist/lib/long-task-admitted-observation.js +201 -0
  218. package/dist/lib/long-task-artifacts.d.ts +1 -0
  219. package/dist/lib/long-task-artifacts.js +1 -0
  220. package/dist/lib/long-task-authority-material-diff.js +1 -0
  221. package/dist/lib/long-task-authority-policy.d.ts +5 -0
  222. package/dist/lib/long-task-authority-policy.js +5 -0
  223. package/dist/lib/long-task-authority-revision-details.js +3 -1
  224. package/dist/lib/long-task-authority.js +2 -1
  225. package/dist/lib/long-task-check-execution-policy.js +5 -0
  226. package/dist/lib/long-task-check-runner.d.ts +2 -2
  227. package/dist/lib/long-task-check-runner.js +440 -23
  228. package/dist/lib/long-task-claims.d.ts +18 -0
  229. package/dist/lib/long-task-claims.js +35 -0
  230. package/dist/lib/long-task-codex-agent-profile.d.ts +42 -0
  231. package/dist/lib/long-task-codex-agent-profile.js +276 -0
  232. package/dist/lib/long-task-compact-authoring-projections.d.ts +12 -0
  233. package/dist/lib/long-task-compact-authoring-projections.js +67 -0
  234. package/dist/lib/long-task-compact-authoring.d.ts +3 -0
  235. package/dist/lib/long-task-compact-authoring.js +173 -0
  236. package/dist/lib/long-task-compact-carrier.d.ts +7 -0
  237. package/dist/lib/long-task-compact-carrier.js +132 -0
  238. package/dist/lib/long-task-compact-parser.d.ts +14 -0
  239. package/dist/lib/long-task-compact-parser.js +123 -0
  240. package/dist/lib/long-task-compact-primitives.d.ts +22 -0
  241. package/dist/lib/long-task-compact-primitives.js +132 -0
  242. package/dist/lib/long-task-compact-projections.d.ts +4 -0
  243. package/dist/lib/long-task-compact-projections.js +153 -0
  244. package/dist/lib/long-task-compact-structure-targets.d.ts +2 -0
  245. package/dist/lib/long-task-compact-structure-targets.js +91 -0
  246. package/dist/lib/long-task-conformance-policy.js +3 -1
  247. package/dist/lib/long-task-counterfactual-claim-policy.d.ts +3 -4
  248. package/dist/lib/long-task-counterfactual-claim-policy.js +11 -3
  249. package/dist/lib/long-task-counterfactual-sandbox.d.ts +1 -1
  250. package/dist/lib/long-task-counterfactual-sandbox.js +8 -5
  251. package/dist/lib/long-task-counterfactual-types.d.ts +2 -0
  252. package/dist/lib/long-task-delivery-compiler.js +2 -0
  253. package/dist/lib/long-task-delivery-parser.d.ts +1 -0
  254. package/dist/lib/long-task-delivery-parser.js +38 -6
  255. package/dist/lib/long-task-design-resource-handoff.d.ts +2 -1
  256. package/dist/lib/long-task-design-resource-handoff.js +71 -11
  257. package/dist/lib/long-task-design-resource-method-binding.d.ts +8 -1
  258. package/dist/lib/long-task-design-resource-method-binding.js +150 -0
  259. package/dist/lib/long-task-evidence-capability-codec.js +256 -34
  260. package/dist/lib/long-task-evidence-capability-policy.d.ts +3 -2
  261. package/dist/lib/long-task-evidence-capability-policy.js +142 -8
  262. package/dist/lib/long-task-evidence-capability-runtime.d.ts +5 -2
  263. package/dist/lib/long-task-evidence-capability-runtime.js +420 -68
  264. package/dist/lib/long-task-evidence-capability-types.d.ts +70 -2
  265. package/dist/lib/long-task-evidence-sensitivity-policy.d.ts +9 -0
  266. package/dist/lib/long-task-evidence-sensitivity-policy.js +31 -1
  267. package/dist/lib/long-task-evidence-v2.d.ts +4 -4
  268. package/dist/lib/long-task-evidence-v2.js +322 -44
  269. package/dist/lib/long-task-exact-comparison.d.ts +16 -0
  270. package/dist/lib/long-task-exact-comparison.js +28 -0
  271. package/dist/lib/long-task-execution-observation.d.ts +24 -0
  272. package/dist/lib/long-task-execution-observation.js +332 -0
  273. package/dist/lib/long-task-explain-acceptance-link.d.ts +4 -0
  274. package/dist/lib/long-task-explain-acceptance-link.js +4 -0
  275. package/dist/lib/long-task-explain-claim-links.d.ts +2 -0
  276. package/dist/lib/long-task-explain-claim-links.js +2 -0
  277. package/dist/lib/long-task-explain-source-links.d.ts +6 -0
  278. package/dist/lib/long-task-final-v2.js +7 -4
  279. package/dist/lib/long-task-hook-install.d.ts +19 -2
  280. package/dist/lib/long-task-hook-install.js +288 -91
  281. package/dist/lib/long-task-json-pointer-observation.d.ts +59 -0
  282. package/dist/lib/long-task-json-pointer-observation.js +184 -0
  283. package/dist/lib/long-task-observation-artifact.d.ts +8 -0
  284. package/dist/lib/long-task-observation-artifact.js +58 -0
  285. package/dist/lib/long-task-observation-authority.d.ts +17 -0
  286. package/dist/lib/long-task-observation-authority.js +348 -0
  287. package/dist/lib/long-task-paths.js +1 -1
  288. package/dist/lib/long-task-playwright-capability-records.d.ts +2 -0
  289. package/dist/lib/long-task-playwright-capability-records.js +74 -2
  290. package/dist/lib/long-task-process-observation.d.ts +6 -0
  291. package/dist/lib/long-task-process-observation.js +117 -0
  292. package/dist/lib/long-task-process-runtime-closure.d.ts +13 -0
  293. package/dist/lib/long-task-process-runtime-closure.js +175 -0
  294. package/dist/lib/long-task-progress.js +2 -0
  295. package/dist/lib/long-task-protected-files.d.ts +1 -1
  296. package/dist/lib/long-task-protected-files.js +1 -30
  297. package/dist/lib/long-task-risk.js +8 -3
  298. package/dist/lib/long-task-root-shape.js +10 -1
  299. package/dist/lib/long-task-runner-environment.d.ts +1 -1
  300. package/dist/lib/long-task-runner-environment.js +4 -4
  301. package/dist/lib/long-task-runner-freeze.d.ts +4 -2
  302. package/dist/lib/long-task-runner-freeze.js +130 -28
  303. package/dist/lib/long-task-runtime-types.d.ts +111 -0
  304. package/dist/lib/long-task-scoped-binding.d.ts +13 -0
  305. package/dist/lib/long-task-scoped-binding.js +11 -0
  306. package/dist/lib/long-task-semantic-contract-types.d.ts +2 -1
  307. package/dist/lib/long-task-semantic-fact-binding-types.d.ts +8 -0
  308. package/dist/lib/long-task-semantic-fact-closure.js +4 -1
  309. package/dist/lib/long-task-semantic-fact-contract-closure.d.ts +1 -1
  310. package/dist/lib/long-task-semantic-fact-contract-closure.js +18 -6
  311. package/dist/lib/long-task-semantic-fact-contract-facts.d.ts +1 -1
  312. package/dist/lib/long-task-semantic-fact-contract-facts.js +4 -1
  313. package/dist/lib/long-task-semantic-fact-contract-proofs.d.ts +1 -1
  314. package/dist/lib/long-task-semantic-fact-contract-proofs.js +12 -3
  315. package/dist/lib/long-task-semantic-fact-evidence.d.ts +8 -4
  316. package/dist/lib/long-task-semantic-fact-evidence.js +103 -43
  317. package/dist/lib/long-task-semantic-fact-shape.js +17 -6
  318. package/dist/lib/long-task-shape-primitives.d.ts +1 -1
  319. package/dist/lib/long-task-shape-primitives.js +1 -0
  320. package/dist/lib/long-task-source-claim-validation.js +4 -1
  321. package/dist/lib/long-task-source-item-parser.d.ts +2 -2
  322. package/dist/lib/long-task-source-item-parser.js +20 -11
  323. package/dist/lib/long-task-source-target-continuity.d.ts +2 -2
  324. package/dist/lib/long-task-source-target-continuity.js +35 -2
  325. package/dist/lib/long-task-source-target-index.d.ts +3 -0
  326. package/dist/lib/long-task-source-target-index.js +19 -0
  327. package/dist/lib/long-task-stage-policy.js +10 -4
  328. package/dist/lib/long-task-static-observation-freeze.d.ts +83 -0
  329. package/dist/lib/long-task-static-observation-freeze.js +428 -0
  330. package/dist/lib/long-task-status-projection.js +5 -1
  331. package/dist/lib/long-task-target-policy.d.ts +1 -1
  332. package/dist/lib/long-task-target-policy.js +3 -0
  333. package/dist/lib/long-task-ui-design-policy.js +64 -4
  334. package/dist/lib/long-task-ui-surface-shape.js +195 -0
  335. package/dist/lib/long-task-ui-surface-types.d.ts +53 -0
  336. package/dist/lib/long-task-verifier-dependency-closure.js +7 -3
  337. package/dist/lib/long-task-verifier-v2.js +70 -9
  338. package/dist/lib/long-task-worker-selection.d.ts +2 -0
  339. package/dist/lib/long-task-worker-selection.js +7 -0
  340. package/dist/lib/long-task-workspace-scope.js +3 -1
  341. package/dist/lib/long-task-workspace.d.ts +1 -1
  342. package/dist/lib/long-task-workspace.js +12 -13
  343. package/dist/lib/migrations.js +58 -0
  344. package/dist/lib/profiles.js +0 -1
  345. package/dist/lib/repository-path-safety.d.ts +14 -0
  346. package/dist/lib/repository-path-safety.js +126 -0
  347. package/dist/lib/semantic-fact-compact-authoring.d.ts +2 -0
  348. package/dist/lib/semantic-fact-compact-authoring.js +160 -0
  349. package/dist/lib/semantic-fact-compact-capacity.d.ts +9 -0
  350. package/dist/lib/semantic-fact-compact-capacity.js +40 -0
  351. package/dist/lib/semantic-fact-compact-carrier.d.ts +16 -0
  352. package/dist/lib/semantic-fact-compact-carrier.js +146 -0
  353. package/dist/lib/semantic-fact-compact-parser.d.ts +17 -0
  354. package/dist/lib/semantic-fact-compact-parser.js +168 -0
  355. package/dist/lib/semantic-fact-compact-revision.d.ts +11 -0
  356. package/dist/lib/semantic-fact-compact-revision.js +64 -0
  357. package/dist/lib/semantic-fact-compact-support.d.ts +23 -0
  358. package/dist/lib/semantic-fact-compact-support.js +159 -0
  359. package/dist/lib/semantic-fact-policy-census.d.ts +1 -0
  360. package/dist/lib/semantic-fact-policy-census.js +43 -0
  361. package/dist/lib/semantic-fact-proof-shape.d.ts +1 -1
  362. package/dist/lib/semantic-fact-property-shape.d.ts +1 -1
  363. package/dist/lib/semantic-fact-source-parser.d.ts +5 -0
  364. package/dist/lib/semantic-fact-source-parser.js +47 -6
  365. package/dist/lib/structural-closure-cost.d.ts +79 -0
  366. package/dist/lib/structural-closure-cost.js +91 -0
  367. package/dist/lib/symbolic-denotation-dag-builder.d.ts +25 -0
  368. package/dist/lib/symbolic-denotation-dag-builder.js +176 -0
  369. package/dist/lib/symbolic-denotation-domain-validation.d.ts +3 -0
  370. package/dist/lib/symbolic-denotation-domain-validation.js +56 -0
  371. package/dist/lib/symbolic-denotation-engine.d.ts +32 -0
  372. package/dist/lib/symbolic-denotation-engine.js +140 -0
  373. package/dist/lib/symbolic-denotation-public.d.ts +11 -0
  374. package/dist/lib/symbolic-denotation-public.js +12 -0
  375. package/dist/lib/symbolic-denotation-runtime.d.ts +20 -0
  376. package/dist/lib/symbolic-denotation-runtime.js +90 -0
  377. package/dist/lib/symbolic-denotation-support.d.ts +11 -0
  378. package/dist/lib/symbolic-denotation-support.js +49 -0
  379. package/dist/lib/symbolic-denotation-types.d.ts +133 -0
  380. package/dist/lib/symbolic-denotation-types.js +18 -0
  381. package/dist/lib/symbolic-denotation-validation.d.ts +6 -0
  382. package/dist/lib/symbolic-denotation-validation.js +126 -0
  383. package/dist/lib/sync-engine.d.ts +1 -0
  384. package/dist/lib/sync-engine.js +4 -1
  385. package/dist/lib/upgrade.js +1 -0
  386. package/dist/long-task-hook.js +40 -1
  387. package/dist/public-types.d.ts +8 -0
  388. package/dist/public-types.js +1 -0
  389. package/dist/schemas/design-resource-symbolic-noninterference-artifact-v2.schema.json +289 -0
  390. package/dist/schemas/design-resource-symbolic-source-ir-v1.schema.json +130 -0
  391. package/dist/schemas/long-task-delivery-v2/long-task-delivery-v2.schema.json +435 -5
  392. package/migrations/README.md +92 -15
  393. package/package.json +2 -2
  394. package/source-mappings.yaml +28 -25
  395. package/assets/skills/source-plan-authoring/SKILL.md +0 -14
@@ -1,442 +1,442 @@
1
- #!/usr/bin/env python3
2
- """Standalone Minimal Context validator installed with the portable profile."""
3
-
4
- from pathlib import Path, PureWindowsPath
5
- import json
6
- import re
7
- import subprocess
8
- import sys
9
-
10
- try:
11
- import tomllib
12
- except ImportError: # Python 3.10 compatibility; Node is already required by ty-context.
13
- tomllib = None
14
-
15
-
16
- ROOT = Path.cwd().resolve()
17
- CONTEXT_ROOT = ROOT / "project_context"
18
-
19
- GLOBAL_CHECKS = [
20
- ("project goal", ["project goal", "项目目标", "目标"]),
21
- ("boundaries", ["non-goals", "boundaries", "非目标", "边界"]),
22
- ("design rationale", ["design rationale", "设计思路", "设计原因"]),
23
- ("architecture context", ["architecture context", "架构上下文", "architecture.md"]),
24
- ("verification entry points", ["verification entry", "验证入口", "测试入口"]),
25
- ("current state", ["current state", "当前状态"]),
26
- ("next safe action", ["next safe action", "下一步安全动作"]),
27
- ("context index", ["context index", "module index", "上下文索引", "模块索引"]),
28
- ]
29
-
30
- ARCHITECTURE_CHECKS = [
31
- ("system boundary", ["system boundary", "系统边界", "边界"]),
32
- ("component map", ["component map", "组件", "模块关系"]),
33
- ("data or control flow", ["data / control flow", "data flow", "control flow", "数据流", "控制流"]),
34
- ("design rationale", ["design rationale", "设计思路", "设计原因"]),
35
- ("verification implications", ["verification implications", "验证影响", "验证入口"]),
36
- ]
37
-
38
- VALID_ROLES = {
39
- "global",
40
- "architecture",
41
- "area",
42
- "domain",
43
- "subdomain",
44
- "foundation",
45
- "archive",
46
- "contract",
47
- "verification",
48
- "deployment",
49
- "implementation-index",
50
- "decision-rationale",
51
- }
52
- ROLE_ALIASES = {
53
- "implementation_index": "implementation-index",
54
- "decision_rationale": "decision-rationale",
55
- }
56
- READ_POLICIES = {"default", "always", "optional", "on-demand", "never-default"}
57
- TOP_LEVEL_FIELDS = {"areas", "context"}
58
- AREA_FIELDS = {"id", "root", "context", "kind", "default", "forbidden_runtime_dependencies"}
59
- CONTEXT_FIELDS = {"path", "role", "read_when", "read_policy", "triggers", "default_children"}
60
- CONFIG_CANDIDATES = [
61
- ".agent/config.yaml",
62
- ".codex/config.yaml",
63
- ".harness/config.yaml",
64
- ".claude/config.yaml",
65
- ".cursor/config.yaml",
66
- ".cline/config.yaml",
67
- ".roo/config.yaml",
68
- ".gemini/config.yaml",
69
- ]
70
- FAKE_VERIFICATION_PATTERNS = [
71
- re.compile(r"\btests?\s+(?:pass(?:ed|es)?|green)\b", re.I),
72
- re.compile(r"\bverified\b", re.I),
73
- re.compile(r"\bdeployed\s+successfully\b", re.I),
74
- re.compile(r"\bvalidation\s+passed\b", re.I),
75
- re.compile(r"测试(?:已)?通过"),
76
- re.compile(r"验证(?:已)?通过"),
77
- re.compile(r"部署(?:已)?成功"),
78
- ]
79
- PLACEHOLDER = re.compile(r"^(?:todo|tbd|placeholder|coming soon|待补充|占位|暂无)[.!。…\s-]*$", re.I)
80
-
81
-
82
- def has_any(text, terms):
83
- lower = text.lower()
84
- return any(term.lower() in lower for term in terms)
85
-
86
-
87
- def normalize_role(value):
88
- role = ROLE_ALIASES.get(value.strip().lower(), value.strip().lower())
89
- return role if role in VALID_ROLES else None
90
-
91
-
92
- def normalize_context_path(value):
93
- return value.replace("\\", "/").removeprefix("./")
94
-
95
-
96
- def parse_front_matter(text):
97
- lines = text.splitlines()
98
- if not lines or lines[0].strip() != "---":
99
- return {}
100
- result = {}
101
- for line in lines[1:]:
102
- if line.strip() == "---":
103
- return result
104
- match = re.match(r"^([A-Za-z0-9_-]+):\s*(.+?)\s*$", line.strip())
105
- if match:
106
- result[match.group(1)] = match.group(2).strip().strip("\"'")
107
- return result
108
-
109
-
110
- def table_lines(content, table):
111
- return [
112
- index
113
- for index, line in enumerate(content.splitlines(), start=1)
114
- if line.strip() == f"[[{table}]]"
115
- ]
116
-
117
-
118
- def parse_manifest(path, errors):
119
- content = path.read_text(encoding="utf-8")
120
- try:
121
- parsed = parse_toml(content)
122
- except (ValueError, RuntimeError) as error:
123
- errors.append(f"project_context/context.toml is not valid TOML: {error}")
124
- return {"areas": [], "context": []}
125
- for key in parsed:
126
- if key not in TOP_LEVEL_FIELDS:
127
- errors.append(f"project_context/context.toml has unknown top-level field {key}")
128
- manifest = {}
129
- for table, allowed in (("areas", AREA_FIELDS), ("context", CONTEXT_FIELDS)):
130
- entries = parsed.get(table, [])
131
- if not isinstance(entries, list) or any(not isinstance(entry, dict) for entry in entries):
132
- errors.append(f"project_context/context.toml field [[{table}]] must be an array of TOML tables")
133
- entries = []
134
- lines = table_lines(content, table)
135
- manifest[table] = []
136
- for index, raw in enumerate(entries):
137
- entry = dict(raw)
138
- entry["line"] = lines[index] if index < len(lines) else 1
139
- for key in raw:
140
- if key not in allowed:
141
- errors.append(f"project_context/context.toml line {entry['line']} has unknown field {key}")
142
- manifest[table].append(entry)
143
- return manifest
144
-
145
-
146
- def parse_toml(content):
147
- if tomllib is not None:
148
- try:
149
- return tomllib.loads(content)
150
- except tomllib.TOMLDecodeError as error:
151
- raise ValueError(str(error)) from error
152
- script = "import('smol-toml').then(({parse})=>{let s='';process.stdin.setEncoding('utf8');process.stdin.on('data',c=>s+=c);process.stdin.on('end',()=>process.stdout.write(JSON.stringify(parse(s))))}).catch(e=>{console.error(e.message);process.exit(1)})"
153
- result = subprocess.run(
154
- ["node", "--input-type=module", "--eval", script],
155
- input=content,
156
- text=True,
157
- capture_output=True,
158
- cwd=ROOT,
159
- check=False,
160
- )
161
- if result.returncode != 0:
162
- raise RuntimeError(f"formal TOML parser unavailable: {result.stderr.strip()}")
163
- return json.loads(result.stdout)
164
-
165
-
166
- def required_string(entry, key, errors):
167
- value = entry.get(key)
168
- if not isinstance(value, str) or not value.strip():
169
- errors.append(f"project_context/context.toml line {entry['line']} must include non-empty string field {key}")
170
- return None
171
- return value
172
-
173
-
174
- def optional_string(entry, key, errors):
175
- value = entry.get(key)
176
- if value is None:
177
- return None
178
- if not isinstance(value, str) or not value.strip():
179
- errors.append(f"project_context/context.toml line {entry['line']} field {key} must be a non-empty string")
180
- return None
181
- return value
182
-
183
-
184
- def string_array(entry, key, errors):
185
- value = entry.get(key, [])
186
- if not isinstance(value, list) or any(not isinstance(item, str) or not item.strip() for item in value):
187
- errors.append(f"project_context/context.toml line {entry['line']} {key} must be an array of non-empty strings")
188
- return []
189
- if len(value) != len(set(value)):
190
- errors.append(f"project_context/context.toml line {entry['line']} {key} must not contain duplicates")
191
- return value
192
-
193
-
194
- def is_relative_to(target, root):
195
- try:
196
- target.relative_to(root)
197
- return True
198
- except ValueError:
199
- return False
200
-
201
-
202
- def safe_manifest_path(raw, allowed_root, label, expected, errors):
203
- normalized = raw.replace("\\", "/")
204
- if Path(raw).is_absolute() or PureWindowsPath(raw).is_absolute() or ".." in normalized.split("/"):
205
- errors.append(f"project_context/context.toml {label} path must be relative and must not contain '..': {raw}")
206
- return None
207
- target = ROOT.joinpath(*normalized.split("/"))
208
- if not target.exists():
209
- errors.append(f"project_context/context.toml references missing {expected}: {normalized}")
210
- return None
211
- resolved_root = allowed_root.resolve()
212
- resolved_target = target.resolve()
213
- if not is_relative_to(resolved_target, resolved_root):
214
- errors.append(f"project_context/context.toml {label} resolves outside its allowed root: {raw}")
215
- return None
216
- if expected == "context file" and not resolved_target.is_file():
217
- errors.append(f"project_context/context.toml {label} must reference a regular file: {raw}")
218
- return None
219
- if expected == "area root" and not resolved_target.is_dir():
220
- errors.append(f"project_context/context.toml {label} must reference a directory: {raw}")
221
- return None
222
- return resolved_target
223
-
224
-
225
- def validate_manifest(errors):
226
- manifest_path = CONTEXT_ROOT / "context.toml"
227
- roles = {}
228
- policies = {}
229
- if not manifest_path.exists():
230
- if schema_requires_manifest():
231
- errors.append("missing project_context/context.toml; run ty-context upgrade to create the Schema v4 Context graph manifest")
232
- return roles, policies
233
- manifest = parse_manifest(manifest_path, errors)
234
- areas = manifest["areas"]
235
- contexts = manifest["context"]
236
- if not areas:
237
- errors.append("project_context/context.toml must declare at least one [[areas]] entry")
238
- defaults = [entry for entry in areas if entry.get("default") is True]
239
- if areas and len(defaults) != 1:
240
- errors.append(f"project_context/context.toml must mark exactly one [[areas]] entry with default = true; found {len(defaults)}")
241
- area_ids = set()
242
- registered_paths = set()
243
-
244
- for area in areas:
245
- area_id = required_string(area, "id", errors)
246
- area_root = required_string(area, "root", errors)
247
- context_path = required_string(area, "context", errors)
248
- optional_string(area, "kind", errors)
249
- if "default" in area and not isinstance(area["default"], bool):
250
- errors.append(f"project_context/context.toml line {area['line']} default must be a boolean")
251
- string_array(area, "forbidden_runtime_dependencies", errors)
252
- if area_id:
253
- if area_id in area_ids:
254
- errors.append(f"project_context/context.toml has duplicate area id: {area_id}")
255
- area_ids.add(area_id)
256
- if area_root:
257
- safe_manifest_path(area_root, ROOT, f"area {area_id or area['line']} root", "area root", errors)
258
- if context_path:
259
- register_context_path(context_path, "area", None, f"area {area_id or area['line']}", registered_paths, roles, policies, errors)
260
-
261
- for entry in contexts:
262
- context_path = required_string(entry, "path", errors)
263
- raw_role = required_string(entry, "role", errors)
264
- optional_string(entry, "read_when", errors)
265
- read_policy = optional_string(entry, "read_policy", errors)
266
- string_array(entry, "triggers", errors)
267
- string_array(entry, "default_children", errors)
268
- role = normalize_role(raw_role) if raw_role else None
269
- if raw_role and role is None:
270
- errors.append(f"project_context/context.toml line {entry['line']} has unsupported context role: {raw_role}")
271
- if read_policy and read_policy not in READ_POLICIES:
272
- errors.append(f"project_context/context.toml line {entry['line']} has unsupported read_policy: {read_policy}")
273
- if context_path and role:
274
- register_context_path(context_path, role, read_policy, f"context {context_path}", registered_paths, roles, policies, errors)
275
-
276
- for entry in contexts:
277
- for child in entry.get("default_children", []):
278
- normalized = normalize_context_path(child)
279
- if normalized not in registered_paths:
280
- errors.append(f"project_context/context.toml line {entry['line']} default_children references unregistered Context path: {child}")
281
- return roles, policies
282
-
283
-
284
- def register_context_path(raw, role, read_policy, label, registered, roles, policies, errors):
285
- normalized = normalize_context_path(raw)
286
- if normalized in registered:
287
- errors.append(f"project_context/context.toml has duplicate Context path: {normalized}")
288
- return
289
- registered.add(normalized)
290
- if not normalized.startswith("project_context/") or not normalized.endswith(".md"):
291
- errors.append(f"project_context/context.toml {label} must reference a markdown file under project_context/: {raw}")
292
- return
293
- if safe_manifest_path(raw, CONTEXT_ROOT, label, "context file", errors) is None:
294
- return
295
- roles[normalized] = role
296
- if read_policy:
297
- policies[normalized] = read_policy
298
-
299
-
300
- def schema_requires_manifest():
301
- schema_version = "4"
302
- for candidate in CONFIG_CANDIDATES:
303
- path = ROOT / candidate
304
- if not path.exists():
305
- continue
306
- match = re.search(r'schema_version:\s*["\']?([^"\'\s]+)', path.read_text(encoding="utf-8"))
307
- if match:
308
- schema_version = match.group(1)
309
- break
310
- try:
311
- return int(schema_version.split(".", 1)[0]) >= 4
312
- except ValueError:
313
- return True
314
-
315
-
316
- def validate_checks(rel, text, checks, errors):
317
- for label, terms in checks:
318
- if not has_any(text, terms):
319
- errors.append(f"{rel} must include {label}")
320
-
321
-
322
- def section_bodies(text):
323
- headings = list(re.finditer(r"^(#{2,6})\s+(.+?)\s*$", text, re.M))
324
- for index, match in enumerate(headings):
325
- end = headings[index + 1].start() if index + 1 < len(headings) else len(text)
326
- yield match.group(2), text[match.end():end].strip()
327
-
328
-
329
- def validate_no_fake_verification(rel, text, errors):
330
- for heading, body in section_bodies(text):
331
- if not re.search(r"verification|tests?|deployment|验证|测试|部署", heading, re.I):
332
- continue
333
- if any(pattern.search(body) for pattern in FAKE_VERIFICATION_PATTERNS):
334
- errors.append(f"{rel} must list verification entry points, not claim tests were already executed")
335
- return
336
-
337
-
338
- def validate_minimum_recoverability(rel, text, errors):
339
- if not re.search(r"^#\s+\S.+$", text, re.M):
340
- errors.append(f"{rel} must contain a non-empty level-one title")
341
- body = re.sub(r"^---\s*\n[\s\S]*?\n---\s*\n?", "", text, count=1)
342
- facts = []
343
- for line in body.splitlines():
344
- value = line.strip()
345
- if not value or value.startswith(("#", "```", "<!--")):
346
- continue
347
- value = re.sub(r"^[-*+>]\s*", "", value).strip()
348
- if len(value) >= 8 and not PLACEHOLDER.fullmatch(value):
349
- facts.append(value)
350
- if not facts:
351
- errors.append(f"{rel} must contain at least one concrete fact paragraph and cannot be only TODO or placeholder text")
352
-
353
-
354
- def validate_role_recoverability(rel, text, role, errors):
355
- if role == "verification" and not re.search(r"`[^`\r\n]+`|(?:npm|pnpm|yarn|make|node|python|pytest|cargo|go)\s+\S+|(?:^|\s)(?:\./|[\w.-]+/)[\w./-]+", text, re.I | re.M):
356
- errors.append(f"{rel} verification Context must contain a command, repository path or repeatable entry point")
357
- if role == "implementation-index":
358
- candidates = [match.group(1) for match in re.finditer(r"`([^`]+)`", text) if ("/" in match.group(1) or "\\" in match.group(1)) and " " not in match.group(1)]
359
- if not any(repository_path_exists(candidate) for candidate in candidates):
360
- errors.append(f"{rel} implementation-index Context must contain at least one existing repository path")
361
- if role == "contract" and not re.search(r"producer|consumer|input|output|schema|compatibility|constraint|must|shall|禁止|必须|输入|输出|兼容", text, re.I):
362
- errors.append(f"{rel} contract Context must state at least one constraint or input/output semantic")
363
- if role == "decision-rationale":
364
- has_decision = re.search(r"^##\s+(?:(?:Decision|Current Design Choice|Design Choice)(?:\b|\s|$)|决定(?:\s|$)|当前设计选择(?:\s|$))", text, re.I | re.M)
365
- has_reason = re.search(r"^##\s+(?:(?:Reason|Rationale|Why)(?:\b|\s|$)|原因(?:\s|$)|理由(?:\s|$)|为什么(?:\s|$))", text, re.I | re.M)
366
- if not has_decision or not has_reason:
367
- errors.append(f"{rel} decision-rationale Context must contain both a Decision and a Reason")
368
-
369
-
370
- def repository_path_exists(candidate):
371
- clean = candidate.rstrip(".:,;").replace("\\", "/")
372
- if Path(clean).is_absolute() or PureWindowsPath(clean).is_absolute() or ".." in clean.split("/"):
373
- return False
374
- target = ROOT.joinpath(*clean.split("/"))
375
- return target.exists() and is_relative_to(target.resolve(), ROOT)
376
-
377
-
378
- def validate_context_file(rel, text, role, errors):
379
- validate_no_fake_verification(rel, text, errors)
380
- validate_minimum_recoverability(rel, text, errors)
381
- if role:
382
- validate_role_recoverability(rel, text, role, errors)
383
-
384
-
385
- def main():
386
- errors = []
387
- warnings = []
388
- global_path = CONTEXT_ROOT / "global.md"
389
- architecture_path = CONTEXT_ROOT / "architecture.md"
390
- if not global_path.exists():
391
- errors.append("missing project_context/global.md")
392
- else:
393
- text = global_path.read_text(encoding="utf-8")
394
- validate_checks("project_context/global.md", text, GLOBAL_CHECKS, errors)
395
- validate_context_file("project_context/global.md", text, "global", errors)
396
- if not architecture_path.exists():
397
- errors.append("missing project_context/architecture.md")
398
- else:
399
- text = architecture_path.read_text(encoding="utf-8")
400
- validate_checks("project_context/architecture.md", text, ARCHITECTURE_CHECKS, errors)
401
- validate_context_file("project_context/architecture.md", text, "architecture", errors)
402
-
403
- manifest_roles, manifest_policies = validate_manifest(errors)
404
- checked = 0
405
- context_files = sorted(CONTEXT_ROOT.rglob("*.md")) if CONTEXT_ROOT.exists() else []
406
- for path in context_files:
407
- rel = path.relative_to(ROOT).as_posix()
408
- if rel in {"project_context/global.md", "project_context/architecture.md"}:
409
- continue
410
- text = path.read_text(encoding="utf-8")
411
- front_matter = parse_front_matter(text)
412
- declared_role = front_matter.get("context_role")
413
- front_role = normalize_role(declared_role) if declared_role else None
414
- if declared_role and front_role is None:
415
- errors.append(f"{rel} has unsupported context_role: {declared_role}")
416
- front_policy = front_matter.get("read_policy")
417
- if front_policy and front_policy not in READ_POLICIES:
418
- errors.append(f"{rel} has unsupported read_policy: {front_policy}")
419
- manifest_role = manifest_roles.get(rel)
420
- manifest_policy = manifest_policies.get(rel)
421
- if manifest_role and front_role and manifest_role != front_role:
422
- errors.append(f"{rel} front matter context_role {front_role} does not match manifest role {manifest_role}")
423
- if manifest_policy and front_policy and manifest_policy != front_policy:
424
- errors.append(f"{rel} front matter read_policy {front_policy} does not match manifest read_policy {manifest_policy}")
425
- if not manifest_role:
426
- warnings.append(f"{rel} is an unregistered Context Markdown file; add it to project_context/context.toml or move it out of project_context/**")
427
- role = manifest_role or front_role
428
- validate_context_file(rel, text, role, errors)
429
- checked += 1
430
-
431
- for warning in warnings:
432
- print(f"warning: {warning}", file=sys.stderr)
433
- if errors:
434
- for error in errors:
435
- print(f"error: {error}", file=sys.stderr)
436
- return 1
437
- print(f"Context OK: {2 + checked} context file(s)")
438
- return 0
439
-
440
-
441
- if __name__ == "__main__":
442
- raise SystemExit(main())
1
+ #!/usr/bin/env python3
2
+ """Standalone Minimal Context validator installed with the portable profile."""
3
+
4
+ from pathlib import Path, PureWindowsPath
5
+ import json
6
+ import re
7
+ import subprocess
8
+ import sys
9
+
10
+ try:
11
+ import tomllib
12
+ except ImportError: # Python 3.10 compatibility; Node is already required by ty-context.
13
+ tomllib = None
14
+
15
+
16
+ ROOT = Path.cwd().resolve()
17
+ CONTEXT_ROOT = ROOT / "project_context"
18
+
19
+ GLOBAL_CHECKS = [
20
+ ("project goal", ["project goal", "项目目标", "目标"]),
21
+ ("boundaries", ["non-goals", "boundaries", "非目标", "边界"]),
22
+ ("design rationale", ["design rationale", "设计思路", "设计原因"]),
23
+ ("architecture context", ["architecture context", "架构上下文", "architecture.md"]),
24
+ ("verification entry points", ["verification entry", "验证入口", "测试入口"]),
25
+ ("current state", ["current state", "当前状态"]),
26
+ ("next safe action", ["next safe action", "下一步安全动作"]),
27
+ ("context index", ["context index", "module index", "上下文索引", "模块索引"]),
28
+ ]
29
+
30
+ ARCHITECTURE_CHECKS = [
31
+ ("system boundary", ["system boundary", "系统边界", "边界"]),
32
+ ("component map", ["component map", "组件", "模块关系"]),
33
+ ("data or control flow", ["data / control flow", "data flow", "control flow", "数据流", "控制流"]),
34
+ ("design rationale", ["design rationale", "设计思路", "设计原因"]),
35
+ ("verification implications", ["verification implications", "验证影响", "验证入口"]),
36
+ ]
37
+
38
+ VALID_ROLES = {
39
+ "global",
40
+ "architecture",
41
+ "area",
42
+ "domain",
43
+ "subdomain",
44
+ "foundation",
45
+ "archive",
46
+ "contract",
47
+ "verification",
48
+ "deployment",
49
+ "implementation-index",
50
+ "decision-rationale",
51
+ }
52
+ ROLE_ALIASES = {
53
+ "implementation_index": "implementation-index",
54
+ "decision_rationale": "decision-rationale",
55
+ }
56
+ READ_POLICIES = {"default", "always", "optional", "on-demand", "never-default"}
57
+ TOP_LEVEL_FIELDS = {"areas", "context"}
58
+ AREA_FIELDS = {"id", "root", "context", "kind", "default", "forbidden_runtime_dependencies"}
59
+ CONTEXT_FIELDS = {"path", "role", "read_when", "read_policy", "triggers", "default_children"}
60
+ CONFIG_CANDIDATES = [
61
+ ".agent/config.yaml",
62
+ ".codex/config.yaml",
63
+ ".harness/config.yaml",
64
+ ".claude/config.yaml",
65
+ ".cursor/config.yaml",
66
+ ".cline/config.yaml",
67
+ ".roo/config.yaml",
68
+ ".gemini/config.yaml",
69
+ ]
70
+ FAKE_VERIFICATION_PATTERNS = [
71
+ re.compile(r"\btests?\s+(?:pass(?:ed|es)?|green)\b", re.I),
72
+ re.compile(r"\bverified\b", re.I),
73
+ re.compile(r"\bdeployed\s+successfully\b", re.I),
74
+ re.compile(r"\bvalidation\s+passed\b", re.I),
75
+ re.compile(r"测试(?:已)?通过"),
76
+ re.compile(r"验证(?:已)?通过"),
77
+ re.compile(r"部署(?:已)?成功"),
78
+ ]
79
+ PLACEHOLDER = re.compile(r"^(?:todo|tbd|placeholder|coming soon|待补充|占位|暂无)[.!。…\s-]*$", re.I)
80
+
81
+
82
+ def has_any(text, terms):
83
+ lower = text.lower()
84
+ return any(term.lower() in lower for term in terms)
85
+
86
+
87
+ def normalize_role(value):
88
+ role = ROLE_ALIASES.get(value.strip().lower(), value.strip().lower())
89
+ return role if role in VALID_ROLES else None
90
+
91
+
92
+ def normalize_context_path(value):
93
+ return value.replace("\\", "/").removeprefix("./")
94
+
95
+
96
+ def parse_front_matter(text):
97
+ lines = text.splitlines()
98
+ if not lines or lines[0].strip() != "---":
99
+ return {}
100
+ result = {}
101
+ for line in lines[1:]:
102
+ if line.strip() == "---":
103
+ return result
104
+ match = re.match(r"^([A-Za-z0-9_-]+):\s*(.+?)\s*$", line.strip())
105
+ if match:
106
+ result[match.group(1)] = match.group(2).strip().strip("\"'")
107
+ return result
108
+
109
+
110
+ def table_lines(content, table):
111
+ return [
112
+ index
113
+ for index, line in enumerate(content.splitlines(), start=1)
114
+ if line.strip() == f"[[{table}]]"
115
+ ]
116
+
117
+
118
+ def parse_manifest(path, errors):
119
+ content = path.read_text(encoding="utf-8")
120
+ try:
121
+ parsed = parse_toml(content)
122
+ except (ValueError, RuntimeError) as error:
123
+ errors.append(f"project_context/context.toml is not valid TOML: {error}")
124
+ return {"areas": [], "context": []}
125
+ for key in parsed:
126
+ if key not in TOP_LEVEL_FIELDS:
127
+ errors.append(f"project_context/context.toml has unknown top-level field {key}")
128
+ manifest = {}
129
+ for table, allowed in (("areas", AREA_FIELDS), ("context", CONTEXT_FIELDS)):
130
+ entries = parsed.get(table, [])
131
+ if not isinstance(entries, list) or any(not isinstance(entry, dict) for entry in entries):
132
+ errors.append(f"project_context/context.toml field [[{table}]] must be an array of TOML tables")
133
+ entries = []
134
+ lines = table_lines(content, table)
135
+ manifest[table] = []
136
+ for index, raw in enumerate(entries):
137
+ entry = dict(raw)
138
+ entry["line"] = lines[index] if index < len(lines) else 1
139
+ for key in raw:
140
+ if key not in allowed:
141
+ errors.append(f"project_context/context.toml line {entry['line']} has unknown field {key}")
142
+ manifest[table].append(entry)
143
+ return manifest
144
+
145
+
146
+ def parse_toml(content):
147
+ if tomllib is not None:
148
+ try:
149
+ return tomllib.loads(content)
150
+ except tomllib.TOMLDecodeError as error:
151
+ raise ValueError(str(error)) from error
152
+ script = "import('smol-toml').then(({parse})=>{let s='';process.stdin.setEncoding('utf8');process.stdin.on('data',c=>s+=c);process.stdin.on('end',()=>process.stdout.write(JSON.stringify(parse(s))))}).catch(e=>{console.error(e.message);process.exit(1)})"
153
+ result = subprocess.run(
154
+ ["node", "--input-type=module", "--eval", script],
155
+ input=content,
156
+ text=True,
157
+ capture_output=True,
158
+ cwd=ROOT,
159
+ check=False,
160
+ )
161
+ if result.returncode != 0:
162
+ raise RuntimeError(f"formal TOML parser unavailable: {result.stderr.strip()}")
163
+ return json.loads(result.stdout)
164
+
165
+
166
+ def required_string(entry, key, errors):
167
+ value = entry.get(key)
168
+ if not isinstance(value, str) or not value.strip():
169
+ errors.append(f"project_context/context.toml line {entry['line']} must include non-empty string field {key}")
170
+ return None
171
+ return value
172
+
173
+
174
+ def optional_string(entry, key, errors):
175
+ value = entry.get(key)
176
+ if value is None:
177
+ return None
178
+ if not isinstance(value, str) or not value.strip():
179
+ errors.append(f"project_context/context.toml line {entry['line']} field {key} must be a non-empty string")
180
+ return None
181
+ return value
182
+
183
+
184
+ def string_array(entry, key, errors):
185
+ value = entry.get(key, [])
186
+ if not isinstance(value, list) or any(not isinstance(item, str) or not item.strip() for item in value):
187
+ errors.append(f"project_context/context.toml line {entry['line']} {key} must be an array of non-empty strings")
188
+ return []
189
+ if len(value) != len(set(value)):
190
+ errors.append(f"project_context/context.toml line {entry['line']} {key} must not contain duplicates")
191
+ return value
192
+
193
+
194
+ def is_relative_to(target, root):
195
+ try:
196
+ target.relative_to(root)
197
+ return True
198
+ except ValueError:
199
+ return False
200
+
201
+
202
+ def safe_manifest_path(raw, allowed_root, label, expected, errors):
203
+ normalized = raw.replace("\\", "/")
204
+ if Path(raw).is_absolute() or PureWindowsPath(raw).is_absolute() or ".." in normalized.split("/"):
205
+ errors.append(f"project_context/context.toml {label} path must be relative and must not contain '..': {raw}")
206
+ return None
207
+ target = ROOT.joinpath(*normalized.split("/"))
208
+ if not target.exists():
209
+ errors.append(f"project_context/context.toml references missing {expected}: {normalized}")
210
+ return None
211
+ resolved_root = allowed_root.resolve()
212
+ resolved_target = target.resolve()
213
+ if not is_relative_to(resolved_target, resolved_root):
214
+ errors.append(f"project_context/context.toml {label} resolves outside its allowed root: {raw}")
215
+ return None
216
+ if expected == "context file" and not resolved_target.is_file():
217
+ errors.append(f"project_context/context.toml {label} must reference a regular file: {raw}")
218
+ return None
219
+ if expected == "area root" and not resolved_target.is_dir():
220
+ errors.append(f"project_context/context.toml {label} must reference a directory: {raw}")
221
+ return None
222
+ return resolved_target
223
+
224
+
225
+ def validate_manifest(errors):
226
+ manifest_path = CONTEXT_ROOT / "context.toml"
227
+ roles = {}
228
+ policies = {}
229
+ if not manifest_path.exists():
230
+ if schema_requires_manifest():
231
+ errors.append("missing project_context/context.toml; run ty-context upgrade to create the Schema v4 Context graph manifest")
232
+ return roles, policies
233
+ manifest = parse_manifest(manifest_path, errors)
234
+ areas = manifest["areas"]
235
+ contexts = manifest["context"]
236
+ if not areas:
237
+ errors.append("project_context/context.toml must declare at least one [[areas]] entry")
238
+ defaults = [entry for entry in areas if entry.get("default") is True]
239
+ if areas and len(defaults) != 1:
240
+ errors.append(f"project_context/context.toml must mark exactly one [[areas]] entry with default = true; found {len(defaults)}")
241
+ area_ids = set()
242
+ registered_paths = set()
243
+
244
+ for area in areas:
245
+ area_id = required_string(area, "id", errors)
246
+ area_root = required_string(area, "root", errors)
247
+ context_path = required_string(area, "context", errors)
248
+ optional_string(area, "kind", errors)
249
+ if "default" in area and not isinstance(area["default"], bool):
250
+ errors.append(f"project_context/context.toml line {area['line']} default must be a boolean")
251
+ string_array(area, "forbidden_runtime_dependencies", errors)
252
+ if area_id:
253
+ if area_id in area_ids:
254
+ errors.append(f"project_context/context.toml has duplicate area id: {area_id}")
255
+ area_ids.add(area_id)
256
+ if area_root:
257
+ safe_manifest_path(area_root, ROOT, f"area {area_id or area['line']} root", "area root", errors)
258
+ if context_path:
259
+ register_context_path(context_path, "area", None, f"area {area_id or area['line']}", registered_paths, roles, policies, errors)
260
+
261
+ for entry in contexts:
262
+ context_path = required_string(entry, "path", errors)
263
+ raw_role = required_string(entry, "role", errors)
264
+ optional_string(entry, "read_when", errors)
265
+ read_policy = optional_string(entry, "read_policy", errors)
266
+ string_array(entry, "triggers", errors)
267
+ string_array(entry, "default_children", errors)
268
+ role = normalize_role(raw_role) if raw_role else None
269
+ if raw_role and role is None:
270
+ errors.append(f"project_context/context.toml line {entry['line']} has unsupported context role: {raw_role}")
271
+ if read_policy and read_policy not in READ_POLICIES:
272
+ errors.append(f"project_context/context.toml line {entry['line']} has unsupported read_policy: {read_policy}")
273
+ if context_path and role:
274
+ register_context_path(context_path, role, read_policy, f"context {context_path}", registered_paths, roles, policies, errors)
275
+
276
+ for entry in contexts:
277
+ for child in entry.get("default_children", []):
278
+ normalized = normalize_context_path(child)
279
+ if normalized not in registered_paths:
280
+ errors.append(f"project_context/context.toml line {entry['line']} default_children references unregistered Context path: {child}")
281
+ return roles, policies
282
+
283
+
284
+ def register_context_path(raw, role, read_policy, label, registered, roles, policies, errors):
285
+ normalized = normalize_context_path(raw)
286
+ if normalized in registered:
287
+ errors.append(f"project_context/context.toml has duplicate Context path: {normalized}")
288
+ return
289
+ registered.add(normalized)
290
+ if not normalized.startswith("project_context/") or not normalized.endswith(".md"):
291
+ errors.append(f"project_context/context.toml {label} must reference a markdown file under project_context/: {raw}")
292
+ return
293
+ if safe_manifest_path(raw, CONTEXT_ROOT, label, "context file", errors) is None:
294
+ return
295
+ roles[normalized] = role
296
+ if read_policy:
297
+ policies[normalized] = read_policy
298
+
299
+
300
+ def schema_requires_manifest():
301
+ schema_version = "4"
302
+ for candidate in CONFIG_CANDIDATES:
303
+ path = ROOT / candidate
304
+ if not path.exists():
305
+ continue
306
+ match = re.search(r'schema_version:\s*["\']?([^"\'\s]+)', path.read_text(encoding="utf-8"))
307
+ if match:
308
+ schema_version = match.group(1)
309
+ break
310
+ try:
311
+ return int(schema_version.split(".", 1)[0]) >= 4
312
+ except ValueError:
313
+ return True
314
+
315
+
316
+ def validate_checks(rel, text, checks, errors):
317
+ for label, terms in checks:
318
+ if not has_any(text, terms):
319
+ errors.append(f"{rel} must include {label}")
320
+
321
+
322
+ def section_bodies(text):
323
+ headings = list(re.finditer(r"^(#{2,6})\s+(.+?)\s*$", text, re.M))
324
+ for index, match in enumerate(headings):
325
+ end = headings[index + 1].start() if index + 1 < len(headings) else len(text)
326
+ yield match.group(2), text[match.end():end].strip()
327
+
328
+
329
+ def validate_no_fake_verification(rel, text, errors):
330
+ for heading, body in section_bodies(text):
331
+ if not re.search(r"verification|tests?|deployment|验证|测试|部署", heading, re.I):
332
+ continue
333
+ if any(pattern.search(body) for pattern in FAKE_VERIFICATION_PATTERNS):
334
+ errors.append(f"{rel} must list verification entry points, not claim tests were already executed")
335
+ return
336
+
337
+
338
+ def validate_minimum_recoverability(rel, text, errors):
339
+ if not re.search(r"^#\s+\S.+$", text, re.M):
340
+ errors.append(f"{rel} must contain a non-empty level-one title")
341
+ body = re.sub(r"^---\s*\n[\s\S]*?\n---\s*\n?", "", text, count=1)
342
+ facts = []
343
+ for line in body.splitlines():
344
+ value = line.strip()
345
+ if not value or value.startswith(("#", "```", "<!--")):
346
+ continue
347
+ value = re.sub(r"^[-*+>]\s*", "", value).strip()
348
+ if len(value) >= 8 and not PLACEHOLDER.fullmatch(value):
349
+ facts.append(value)
350
+ if not facts:
351
+ errors.append(f"{rel} must contain at least one concrete fact paragraph and cannot be only TODO or placeholder text")
352
+
353
+
354
+ def validate_role_recoverability(rel, text, role, errors):
355
+ if role == "verification" and not re.search(r"`[^`\r\n]+`|(?:npm|pnpm|yarn|make|node|python|pytest|cargo|go)\s+\S+|(?:^|\s)(?:\./|[\w.-]+/)[\w./-]+", text, re.I | re.M):
356
+ errors.append(f"{rel} verification Context must contain a command, repository path or repeatable entry point")
357
+ if role == "implementation-index":
358
+ candidates = [match.group(1) for match in re.finditer(r"`([^`]+)`", text) if ("/" in match.group(1) or "\\" in match.group(1)) and " " not in match.group(1)]
359
+ if not any(repository_path_exists(candidate) for candidate in candidates):
360
+ errors.append(f"{rel} implementation-index Context must contain at least one existing repository path")
361
+ if role == "contract" and not re.search(r"producer|consumer|input|output|schema|compatibility|constraint|must|shall|禁止|必须|输入|输出|兼容", text, re.I):
362
+ errors.append(f"{rel} contract Context must state at least one constraint or input/output semantic")
363
+ if role == "decision-rationale":
364
+ has_decision = re.search(r"^##\s+(?:(?:Decision|Current Design Choice|Design Choice)(?:\b|\s|$)|决定(?:\s|$)|当前设计选择(?:\s|$))", text, re.I | re.M)
365
+ has_reason = re.search(r"^##\s+(?:(?:Reason|Rationale|Why)(?:\b|\s|$)|原因(?:\s|$)|理由(?:\s|$)|为什么(?:\s|$))", text, re.I | re.M)
366
+ if not has_decision or not has_reason:
367
+ errors.append(f"{rel} decision-rationale Context must contain both a Decision and a Reason")
368
+
369
+
370
+ def repository_path_exists(candidate):
371
+ clean = candidate.rstrip(".:,;").replace("\\", "/")
372
+ if Path(clean).is_absolute() or PureWindowsPath(clean).is_absolute() or ".." in clean.split("/"):
373
+ return False
374
+ target = ROOT.joinpath(*clean.split("/"))
375
+ return target.exists() and is_relative_to(target.resolve(), ROOT)
376
+
377
+
378
+ def validate_context_file(rel, text, role, errors):
379
+ validate_no_fake_verification(rel, text, errors)
380
+ validate_minimum_recoverability(rel, text, errors)
381
+ if role:
382
+ validate_role_recoverability(rel, text, role, errors)
383
+
384
+
385
+ def main():
386
+ errors = []
387
+ warnings = []
388
+ global_path = CONTEXT_ROOT / "global.md"
389
+ architecture_path = CONTEXT_ROOT / "architecture.md"
390
+ if not global_path.exists():
391
+ errors.append("missing project_context/global.md")
392
+ else:
393
+ text = global_path.read_text(encoding="utf-8")
394
+ validate_checks("project_context/global.md", text, GLOBAL_CHECKS, errors)
395
+ validate_context_file("project_context/global.md", text, "global", errors)
396
+ if not architecture_path.exists():
397
+ errors.append("missing project_context/architecture.md")
398
+ else:
399
+ text = architecture_path.read_text(encoding="utf-8")
400
+ validate_checks("project_context/architecture.md", text, ARCHITECTURE_CHECKS, errors)
401
+ validate_context_file("project_context/architecture.md", text, "architecture", errors)
402
+
403
+ manifest_roles, manifest_policies = validate_manifest(errors)
404
+ checked = 0
405
+ context_files = sorted(CONTEXT_ROOT.rglob("*.md")) if CONTEXT_ROOT.exists() else []
406
+ for path in context_files:
407
+ rel = path.relative_to(ROOT).as_posix()
408
+ if rel in {"project_context/global.md", "project_context/architecture.md"}:
409
+ continue
410
+ text = path.read_text(encoding="utf-8")
411
+ front_matter = parse_front_matter(text)
412
+ declared_role = front_matter.get("context_role")
413
+ front_role = normalize_role(declared_role) if declared_role else None
414
+ if declared_role and front_role is None:
415
+ errors.append(f"{rel} has unsupported context_role: {declared_role}")
416
+ front_policy = front_matter.get("read_policy")
417
+ if front_policy and front_policy not in READ_POLICIES:
418
+ errors.append(f"{rel} has unsupported read_policy: {front_policy}")
419
+ manifest_role = manifest_roles.get(rel)
420
+ manifest_policy = manifest_policies.get(rel)
421
+ if manifest_role and front_role and manifest_role != front_role:
422
+ errors.append(f"{rel} front matter context_role {front_role} does not match manifest role {manifest_role}")
423
+ if manifest_policy and front_policy and manifest_policy != front_policy:
424
+ errors.append(f"{rel} front matter read_policy {front_policy} does not match manifest read_policy {manifest_policy}")
425
+ if not manifest_role:
426
+ warnings.append(f"{rel} is an unregistered Context Markdown file; add it to project_context/context.toml or move it out of project_context/**")
427
+ role = manifest_role or front_role
428
+ validate_context_file(rel, text, role, errors)
429
+ checked += 1
430
+
431
+ for warning in warnings:
432
+ print(f"warning: {warning}", file=sys.stderr)
433
+ if errors:
434
+ for error in errors:
435
+ print(f"error: {error}", file=sys.stderr)
436
+ return 1
437
+ print(f"Context OK: {2 + checked} context file(s)")
438
+ return 0
439
+
440
+
441
+ if __name__ == "__main__":
442
+ raise SystemExit(main())