project-tiny-context-harness 0.2.85 → 0.6.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 (361) hide show
  1. package/README.md +163 -428
  2. package/assets/README.md +283 -455
  3. package/assets/README.zh-CN.md +199 -123
  4. package/assets/agents/AGENTS_CORE.md +54 -67
  5. package/assets/context_templates/context.toml +3 -0
  6. package/assets/context_templates/global.md +6 -6
  7. package/assets/github/harness.yml +2 -2
  8. package/assets/skills/context_development_engineer/SKILL.md +91 -107
  9. package/assets/skills/context_product_plan/SKILL.md +18 -28
  10. package/assets/skills/context_surface_contract/SKILL.md +35 -38
  11. package/assets/skills/context_uiux_design/SKILL.md +31 -30
  12. package/assets/skills/long-task-workflow/SKILL.md +76 -0
  13. package/assets/skills/long-task-workflow/agents/openai.yaml +4 -0
  14. package/assets/skills/long-task-workflow/references/authority-lifecycle.md +41 -0
  15. package/assets/skills/long-task-workflow/references/contract-authoring.md +62 -0
  16. package/assets/skills/long-task-workflow/references/evidence-design.md +45 -0
  17. package/assets/skills/normal-long-task/SKILL.md +12 -489
  18. package/assets/skills/source-plan-authoring/SKILL.md +379 -0
  19. package/assets/tools/validate_context.py +293 -127
  20. package/dist/cli.js +3 -1
  21. package/dist/commands/check-modularity.js +33 -10
  22. package/dist/commands/composite-campaign.d.ts +1 -0
  23. package/dist/commands/composite-campaign.js +8 -0
  24. package/dist/commands/composite-long-task.d.ts +1 -6
  25. package/dist/commands/composite-long-task.js +7 -163
  26. package/dist/commands/delivery-set.d.ts +1 -0
  27. package/dist/commands/delivery-set.js +8 -0
  28. package/dist/commands/disable.d.ts +1 -0
  29. package/dist/commands/disable.js +15 -0
  30. package/dist/commands/enable.d.ts +1 -0
  31. package/dist/commands/enable.js +14 -0
  32. package/dist/commands/export-context-args.js +13 -4
  33. package/dist/commands/export-context.js +18 -7
  34. package/dist/commands/index.js +42 -30
  35. package/dist/commands/init.js +12 -6
  36. package/dist/commands/long-task-authoring.d.ts +3 -0
  37. package/dist/commands/long-task-authoring.js +92 -0
  38. package/dist/commands/long-task-explain.d.ts +1 -0
  39. package/dist/commands/long-task-explain.js +52 -0
  40. package/dist/commands/long-task.d.ts +1 -0
  41. package/dist/commands/long-task.js +226 -0
  42. package/dist/commands/package-source.js +2 -2
  43. package/dist/commands/upgrade.js +10 -10
  44. package/dist/index.d.ts +11 -1
  45. package/dist/index.js +8 -0
  46. package/dist/lib/config.js +34 -8
  47. package/dist/lib/context-default-footprint.d.ts +16 -0
  48. package/dist/lib/context-default-footprint.js +107 -0
  49. package/dist/lib/context-export.js +105 -41
  50. package/dist/lib/context-graph-snapshot.d.ts +18 -0
  51. package/dist/lib/context-graph-snapshot.js +171 -0
  52. package/dist/lib/context-manifest-schema.d.ts +28 -0
  53. package/dist/lib/context-manifest-schema.js +150 -0
  54. package/dist/lib/context-manifest.js +28 -20
  55. package/dist/lib/context-templates.js +5 -5
  56. package/dist/lib/design-md.js +1 -1
  57. package/dist/lib/doctor.js +54 -1
  58. package/dist/lib/harness-root.js +25 -8
  59. package/dist/lib/init.js +6 -3
  60. package/dist/lib/legacy-managed-scan.js +23 -7
  61. package/dist/lib/legacy-sdlc-migration.js +21 -11
  62. package/dist/lib/long-task-acceptance-reference.d.ts +22 -0
  63. package/dist/lib/long-task-acceptance-reference.js +46 -0
  64. package/dist/lib/long-task-acceptance-shape.d.ts +5 -0
  65. package/dist/lib/long-task-acceptance-shape.js +131 -0
  66. package/dist/lib/long-task-activation-validation.d.ts +21 -0
  67. package/dist/lib/long-task-activation-validation.js +136 -0
  68. package/dist/lib/long-task-artifacts.d.ts +5 -0
  69. package/dist/lib/long-task-artifacts.js +34 -0
  70. package/dist/lib/long-task-assertions-v2.d.ts +22 -0
  71. package/dist/lib/long-task-assertions-v2.js +212 -0
  72. package/dist/lib/long-task-authoring-authority-preview.d.ts +5 -0
  73. package/dist/lib/long-task-authoring-authority-preview.js +85 -0
  74. package/dist/lib/long-task-authoring-preflight-diagnostics.d.ts +8 -0
  75. package/dist/lib/long-task-authoring-preflight-diagnostics.js +179 -0
  76. package/dist/lib/long-task-authoring-preflight-repair-order.d.ts +8 -0
  77. package/dist/lib/long-task-authoring-preflight-repair-order.js +95 -0
  78. package/dist/lib/long-task-authoring-preflight-types.d.ts +46 -0
  79. package/dist/lib/long-task-authoring-preflight-types.js +38 -0
  80. package/dist/lib/long-task-authoring-preflight.d.ts +3 -0
  81. package/dist/lib/long-task-authoring-preflight.js +44 -0
  82. package/dist/lib/long-task-authority-material-diff.d.ts +17 -0
  83. package/dist/lib/long-task-authority-material-diff.js +152 -0
  84. package/dist/lib/long-task-authority-materials.d.ts +7 -0
  85. package/dist/lib/long-task-authority-materials.js +83 -0
  86. package/dist/lib/long-task-authority-policy.d.ts +168 -0
  87. package/dist/lib/long-task-authority-policy.js +181 -0
  88. package/dist/lib/long-task-authority-revision-details.d.ts +19 -0
  89. package/dist/lib/long-task-authority-revision-details.js +192 -0
  90. package/dist/lib/long-task-authority-revision-enforcement.d.ts +3 -0
  91. package/dist/lib/long-task-authority-revision-enforcement.js +41 -0
  92. package/dist/lib/long-task-authority-revision-types.d.ts +50 -0
  93. package/dist/lib/long-task-authority-revision-types.js +1 -0
  94. package/dist/lib/long-task-authority-revision.d.ts +4 -0
  95. package/dist/lib/long-task-authority-revision.js +188 -0
  96. package/dist/lib/long-task-authority-transition-policy.d.ts +62 -0
  97. package/dist/lib/long-task-authority-transition-policy.js +62 -0
  98. package/dist/lib/long-task-authority-types.d.ts +96 -0
  99. package/dist/lib/long-task-authority-types.js +1 -0
  100. package/dist/lib/long-task-authority.d.ts +7 -0
  101. package/dist/lib/long-task-authority.js +142 -0
  102. package/dist/lib/long-task-boundary-check.d.ts +15 -0
  103. package/dist/lib/long-task-boundary-check.js +9 -0
  104. package/dist/lib/long-task-check-evidence-decoder.d.ts +3 -0
  105. package/dist/lib/long-task-check-evidence-decoder.js +74 -0
  106. package/dist/lib/long-task-check-execution-policy.d.ts +18 -0
  107. package/dist/lib/long-task-check-execution-policy.js +36 -0
  108. package/dist/lib/long-task-check-runner.d.ts +2 -0
  109. package/dist/lib/long-task-check-runner.js +181 -0
  110. package/dist/lib/long-task-check-shape.d.ts +2 -0
  111. package/dist/lib/long-task-check-shape.js +136 -0
  112. package/dist/lib/long-task-claim-definitions.d.ts +7 -0
  113. package/dist/lib/long-task-claim-definitions.js +78 -0
  114. package/dist/lib/long-task-claim-proof-policy.d.ts +4 -0
  115. package/dist/lib/long-task-claim-proof-policy.js +57 -0
  116. package/dist/lib/long-task-claims.d.ts +10 -0
  117. package/dist/lib/long-task-claims.js +187 -0
  118. package/dist/lib/long-task-context-authority-topology.d.ts +7 -0
  119. package/dist/lib/long-task-context-authority-topology.js +37 -0
  120. package/dist/lib/long-task-context-authority.d.ts +14 -0
  121. package/dist/lib/long-task-context-authority.js +66 -0
  122. package/dist/lib/long-task-contract-types.d.ts +203 -0
  123. package/dist/lib/long-task-contract-types.js +1 -0
  124. package/dist/lib/long-task-counterfactual-claim-policy.d.ts +10 -0
  125. package/dist/lib/long-task-counterfactual-claim-policy.js +57 -0
  126. package/dist/lib/long-task-counterfactual-sandbox.d.ts +6 -0
  127. package/dist/lib/long-task-counterfactual-sandbox.js +87 -0
  128. package/dist/lib/long-task-counterfactual-types.d.ts +24 -0
  129. package/dist/lib/long-task-counterfactual-types.js +1 -0
  130. package/dist/lib/long-task-delivery-compiler.d.ts +10 -0
  131. package/dist/lib/long-task-delivery-compiler.js +129 -0
  132. package/dist/lib/long-task-delivery-parser.d.ts +13 -0
  133. package/dist/lib/long-task-delivery-parser.js +90 -0
  134. package/dist/lib/long-task-delivery-preflight.d.ts +5 -0
  135. package/dist/lib/long-task-delivery-preflight.js +151 -0
  136. package/dist/lib/long-task-delivery-shape.d.ts +5 -0
  137. package/dist/lib/long-task-delivery-shape.js +5 -0
  138. package/dist/lib/long-task-delivery-types.d.ts +8 -0
  139. package/dist/lib/long-task-delivery-types.js +8 -0
  140. package/dist/lib/long-task-delivery-validation.d.ts +3 -0
  141. package/dist/lib/long-task-delivery-validation.js +175 -0
  142. package/dist/lib/long-task-evidence-adapter-policy.d.ts +4 -0
  143. package/dist/lib/long-task-evidence-adapter-policy.js +15 -0
  144. package/dist/lib/long-task-evidence-adapter-types.d.ts +1 -0
  145. package/dist/lib/long-task-evidence-adapter-types.js +1 -0
  146. package/dist/lib/long-task-evidence-findings.d.ts +5 -0
  147. package/dist/lib/long-task-evidence-findings.js +113 -0
  148. package/dist/lib/long-task-evidence-sensitivity-policy.d.ts +4 -0
  149. package/dist/lib/long-task-evidence-sensitivity-policy.js +98 -0
  150. package/dist/lib/long-task-evidence-v2.d.ts +5 -0
  151. package/dist/lib/long-task-evidence-v2.js +226 -0
  152. package/dist/lib/long-task-explain-acceptance-link.d.ts +46 -0
  153. package/dist/lib/long-task-explain-acceptance-link.js +96 -0
  154. package/dist/lib/long-task-explain-claim-links.d.ts +45 -0
  155. package/dist/lib/long-task-explain-claim-links.js +88 -0
  156. package/dist/lib/long-task-explain-source-links.d.ts +93 -0
  157. package/dist/lib/long-task-explain-source-links.js +51 -0
  158. package/dist/lib/long-task-final-v2.d.ts +2 -0
  159. package/dist/lib/long-task-final-v2.js +124 -0
  160. package/dist/lib/long-task-finding-context.d.ts +3 -0
  161. package/dist/lib/long-task-finding-context.js +63 -0
  162. package/dist/lib/long-task-freshness.d.ts +3 -0
  163. package/dist/lib/long-task-freshness.js +80 -0
  164. package/dist/lib/long-task-hook-install.d.ts +9 -0
  165. package/dist/lib/long-task-hook-install.js +184 -0
  166. package/dist/lib/long-task-hook-preflight.d.ts +7 -0
  167. package/dist/lib/long-task-hook-preflight.js +58 -0
  168. package/dist/lib/long-task-observation-ownership.d.ts +2 -0
  169. package/dist/lib/long-task-observation-ownership.js +22 -0
  170. package/dist/lib/long-task-outcome-parser.d.ts +4 -0
  171. package/dist/lib/long-task-outcome-parser.js +100 -0
  172. package/dist/lib/long-task-paths.d.ts +49 -0
  173. package/dist/lib/long-task-paths.js +418 -0
  174. package/dist/lib/long-task-playwright-counterfactual-policy.d.ts +7 -0
  175. package/dist/lib/long-task-playwright-counterfactual-policy.js +125 -0
  176. package/dist/lib/long-task-playwright-evidence.d.ts +6 -0
  177. package/dist/lib/long-task-playwright-evidence.js +244 -0
  178. package/dist/lib/long-task-product-shape.d.ts +6 -0
  179. package/dist/lib/long-task-product-shape.js +73 -0
  180. package/dist/lib/long-task-progress.d.ts +4 -0
  181. package/dist/lib/long-task-progress.js +114 -0
  182. package/dist/lib/long-task-protected-files.d.ts +1 -0
  183. package/dist/lib/long-task-protected-files.js +30 -0
  184. package/dist/lib/long-task-required-proof-surfaces.d.ts +2 -0
  185. package/dist/lib/long-task-required-proof-surfaces.js +13 -0
  186. package/dist/lib/long-task-requirement-shape.d.ts +2 -0
  187. package/dist/lib/long-task-requirement-shape.js +18 -0
  188. package/dist/lib/long-task-risk-surfaces.d.ts +2 -0
  189. package/dist/lib/long-task-risk-surfaces.js +60 -0
  190. package/dist/lib/long-task-risk-types.d.ts +5 -0
  191. package/dist/lib/long-task-risk-types.js +12 -0
  192. package/dist/lib/long-task-risk.d.ts +9 -0
  193. package/dist/lib/long-task-risk.js +132 -0
  194. package/dist/lib/long-task-root-shape.d.ts +4 -0
  195. package/dist/lib/long-task-root-shape.js +86 -0
  196. package/dist/lib/long-task-runner-environment.d.ts +7 -0
  197. package/dist/lib/long-task-runner-environment.js +57 -0
  198. package/dist/lib/long-task-runner-files.d.ts +3 -0
  199. package/dist/lib/long-task-runner-files.js +39 -0
  200. package/dist/lib/long-task-runner-freeze.d.ts +2 -0
  201. package/dist/lib/long-task-runner-freeze.js +168 -0
  202. package/dist/lib/long-task-runtime-types.d.ts +214 -0
  203. package/dist/lib/long-task-runtime-types.js +1 -0
  204. package/dist/lib/long-task-shape-primitives.d.ts +17 -0
  205. package/dist/lib/long-task-shape-primitives.js +81 -0
  206. package/dist/lib/long-task-source-authority-types.d.ts +13 -0
  207. package/dist/lib/long-task-source-authority-types.js +1 -0
  208. package/dist/lib/long-task-source-claim-validation.d.ts +5 -0
  209. package/dist/lib/long-task-source-claim-validation.js +96 -0
  210. package/dist/lib/long-task-source-continuity.d.ts +4 -0
  211. package/dist/lib/long-task-source-continuity.js +57 -0
  212. package/dist/lib/long-task-source-inventory.d.ts +2 -0
  213. package/dist/lib/long-task-source-inventory.js +21 -0
  214. package/dist/lib/long-task-source-item-parser.d.ts +3 -0
  215. package/dist/lib/long-task-source-item-parser.js +112 -0
  216. package/dist/lib/long-task-source-shape.d.ts +2 -0
  217. package/dist/lib/long-task-source-shape.js +56 -0
  218. package/dist/lib/long-task-source-target-continuity.d.ts +4 -0
  219. package/dist/lib/long-task-source-target-continuity.js +80 -0
  220. package/dist/lib/long-task-source-target-index.d.ts +12 -0
  221. package/dist/lib/long-task-source-target-index.js +76 -0
  222. package/dist/lib/long-task-source-validation.d.ts +3 -0
  223. package/dist/lib/long-task-source-validation.js +46 -0
  224. package/dist/lib/long-task-state.d.ts +91 -0
  225. package/dist/lib/long-task-state.js +611 -0
  226. package/dist/lib/long-task-status-projection.d.ts +21 -0
  227. package/dist/lib/long-task-status-projection.js +116 -0
  228. package/dist/lib/long-task-status-v2.d.ts +37 -0
  229. package/dist/lib/long-task-status-v2.js +306 -0
  230. package/dist/lib/long-task-technical-shape.d.ts +3 -0
  231. package/dist/lib/long-task-technical-shape.js +44 -0
  232. package/dist/lib/long-task-verifier-authority.d.ts +12 -0
  233. package/dist/lib/long-task-verifier-authority.js +47 -0
  234. package/dist/lib/long-task-verifier-identity.d.ts +2 -0
  235. package/dist/lib/long-task-verifier-identity.js +55 -0
  236. package/dist/lib/long-task-verifier-v2.d.ts +13 -0
  237. package/dist/lib/long-task-verifier-v2.js +266 -0
  238. package/dist/lib/long-task-workspace-runtime-types.d.ts +23 -0
  239. package/dist/lib/long-task-workspace-runtime-types.js +1 -0
  240. package/dist/lib/long-task-workspace.d.ts +25 -0
  241. package/dist/lib/long-task-workspace.js +366 -0
  242. package/dist/lib/managed-file.js +20 -5
  243. package/dist/lib/migrations.js +156 -32
  244. package/dist/lib/modularity.d.ts +27 -1
  245. package/dist/lib/modularity.js +499 -23
  246. package/dist/lib/package-json-config.js +14 -5
  247. package/dist/lib/package-source.js +4 -2
  248. package/dist/lib/profiles.d.ts +13 -0
  249. package/dist/lib/profiles.js +66 -0
  250. package/dist/lib/schema-guard.js +4 -2
  251. package/dist/lib/source-files.js +14 -7
  252. package/dist/lib/source-pack-classify.js +91 -19
  253. package/dist/lib/source-pack-config.js +25 -8
  254. package/dist/lib/source-pack-export.js +84 -25
  255. package/dist/lib/source-pack-manifest.js +18 -6
  256. package/dist/lib/source-pack-records.js +35 -11
  257. package/dist/lib/source-pack-render.js +47 -18
  258. package/dist/lib/stable-json.d.ts +2 -0
  259. package/dist/lib/stable-json.js +21 -0
  260. package/dist/lib/strict-codec.d.ts +4 -0
  261. package/dist/lib/strict-codec.js +65 -0
  262. package/dist/lib/sync-engine.js +76 -24
  263. package/dist/lib/types.d.ts +8 -1
  264. package/dist/lib/upgrade.js +4 -3
  265. package/dist/lib/validators.js +218 -191
  266. package/dist/long-task-hook.d.ts +2 -0
  267. package/dist/long-task-hook.js +55 -0
  268. package/dist/schemas/long-task-delivery-v2/long-task-delivery-v2.schema.json +340 -0
  269. package/dist/schemas/long-task-delivery-v2/long-task-outcomes-v2.schema.json +18 -0
  270. package/migrations/README.md +8 -3
  271. package/package.json +17 -6
  272. package/source-mappings.yaml +0 -3
  273. package/assets/protected-harness-baseline.json +0 -20
  274. package/assets/skills/composite-long-task-workflow/SKILL.md +0 -245
  275. package/assets/skills/composite-long-task-workflow/assets/execution-binding.template.md +0 -62
  276. package/assets/skills/composite-long-task-workflow/assets/goal-objective.template.md +0 -17
  277. package/assets/skills/composite-long-task-workflow/references/composite-long-task-workflow-protocol.md +0 -682
  278. package/dist/commands/superpowers.d.ts +0 -1
  279. package/dist/commands/superpowers.js +0 -8
  280. package/dist/lib/composite-long-task-renderer.d.ts +0 -12
  281. package/dist/lib/composite-long-task-renderer.js +0 -109
  282. package/dist/lib/plan-acceptance-artifacts.d.ts +0 -1
  283. package/dist/lib/plan-acceptance-artifacts.js +0 -220
  284. package/dist/lib/plan-acceptance-evidence.d.ts +0 -2
  285. package/dist/lib/plan-acceptance-evidence.js +0 -108
  286. package/dist/lib/plan-acceptance-json.d.ts +0 -15
  287. package/dist/lib/plan-acceptance-json.js +0 -133
  288. package/dist/lib/plan-acceptance-validator.d.ts +0 -2
  289. package/dist/lib/plan-acceptance-validator.js +0 -209
  290. package/dist/lib/plan-contract-validator.d.ts +0 -2
  291. package/dist/lib/plan-contract-validator.js +0 -127
  292. package/dist/lib/plan-validator-common.d.ts +0 -24
  293. package/dist/lib/plan-validator-common.js +0 -196
  294. package/dist/lib/superpowers-task-ac010.d.ts +0 -6
  295. package/dist/lib/superpowers-task-ac010.js +0 -26
  296. package/dist/lib/superpowers-task-assertion-normalizers.d.ts +0 -3
  297. package/dist/lib/superpowers-task-assertion-normalizers.js +0 -74
  298. package/dist/lib/superpowers-task-assertions.d.ts +0 -20
  299. package/dist/lib/superpowers-task-assertions.js +0 -257
  300. package/dist/lib/superpowers-task-attempt.d.ts +0 -4
  301. package/dist/lib/superpowers-task-attempt.js +0 -102
  302. package/dist/lib/superpowers-task-command-run-correlation.d.ts +0 -8
  303. package/dist/lib/superpowers-task-command-run-correlation.js +0 -103
  304. package/dist/lib/superpowers-task-command-specs.d.ts +0 -3
  305. package/dist/lib/superpowers-task-command-specs.js +0 -52
  306. package/dist/lib/superpowers-task-compile-diagnostics.d.ts +0 -5
  307. package/dist/lib/superpowers-task-compile-diagnostics.js +0 -20
  308. package/dist/lib/superpowers-task-compile-guards.d.ts +0 -2
  309. package/dist/lib/superpowers-task-compile-guards.js +0 -66
  310. package/dist/lib/superpowers-task-compile.d.ts +0 -6
  311. package/dist/lib/superpowers-task-compile.js +0 -133
  312. package/dist/lib/superpowers-task-completion-output.d.ts +0 -91
  313. package/dist/lib/superpowers-task-completion-output.js +0 -374
  314. package/dist/lib/superpowers-task-conformance.d.ts +0 -2
  315. package/dist/lib/superpowers-task-conformance.js +0 -24
  316. package/dist/lib/superpowers-task-contradictions.d.ts +0 -6
  317. package/dist/lib/superpowers-task-contradictions.js +0 -126
  318. package/dist/lib/superpowers-task-current-evidence.d.ts +0 -3
  319. package/dist/lib/superpowers-task-current-evidence.js +0 -176
  320. package/dist/lib/superpowers-task-delivery.d.ts +0 -4
  321. package/dist/lib/superpowers-task-delivery.js +0 -96
  322. package/dist/lib/superpowers-task-derive.d.ts +0 -14
  323. package/dist/lib/superpowers-task-derive.js +0 -393
  324. package/dist/lib/superpowers-task-events.d.ts +0 -1
  325. package/dist/lib/superpowers-task-events.js +0 -13
  326. package/dist/lib/superpowers-task-evidence-kernel.d.ts +0 -19
  327. package/dist/lib/superpowers-task-evidence-kernel.js +0 -347
  328. package/dist/lib/superpowers-task-evidence-records.d.ts +0 -2
  329. package/dist/lib/superpowers-task-evidence-records.js +0 -55
  330. package/dist/lib/superpowers-task-evidence.d.ts +0 -10
  331. package/dist/lib/superpowers-task-evidence.js +0 -147
  332. package/dist/lib/superpowers-task-fields.d.ts +0 -22
  333. package/dist/lib/superpowers-task-fields.js +0 -276
  334. package/dist/lib/superpowers-task-final-card.d.ts +0 -3
  335. package/dist/lib/superpowers-task-final-card.js +0 -35
  336. package/dist/lib/superpowers-task-gates.d.ts +0 -12
  337. package/dist/lib/superpowers-task-gates.js +0 -267
  338. package/dist/lib/superpowers-task-harness-drift.d.ts +0 -11
  339. package/dist/lib/superpowers-task-harness-drift.js +0 -90
  340. package/dist/lib/superpowers-task-next-slices.d.ts +0 -1
  341. package/dist/lib/superpowers-task-next-slices.js +0 -12
  342. package/dist/lib/superpowers-task-protected-baseline.d.ts +0 -10
  343. package/dist/lib/superpowers-task-protected-baseline.js +0 -66
  344. package/dist/lib/superpowers-task-source-compile.d.ts +0 -5
  345. package/dist/lib/superpowers-task-source-compile.js +0 -226
  346. package/dist/lib/superpowers-task-source-parser.d.ts +0 -23
  347. package/dist/lib/superpowers-task-source-parser.js +0 -218
  348. package/dist/lib/superpowers-task-state-schema.d.ts +0 -460
  349. package/dist/lib/superpowers-task-state-schema.js +0 -66
  350. package/dist/lib/superpowers-task-state-shape.d.ts +0 -3
  351. package/dist/lib/superpowers-task-state-shape.js +0 -50
  352. package/dist/lib/superpowers-task-state.d.ts +0 -16
  353. package/dist/lib/superpowers-task-state.js +0 -246
  354. package/dist/lib/superpowers-task-status.d.ts +0 -2
  355. package/dist/lib/superpowers-task-status.js +0 -24
  356. package/dist/lib/superpowers-task-under-specified.d.ts +0 -7
  357. package/dist/lib/superpowers-task-under-specified.js +0 -61
  358. package/dist/lib/superpowers-task-unregistered-evidence.d.ts +0 -11
  359. package/dist/lib/superpowers-task-unregistered-evidence.js +0 -72
  360. package/dist/lib/superpowers-task-validator.d.ts +0 -5
  361. package/dist/lib/superpowers-task-validator.js +0 -293
@@ -8,28 +8,40 @@ const execFileAsync = promisify(execFile);
8
8
  const GIT_MAX_BUFFER = 16 * 1024 * 1024;
9
9
  const DEFAULT_LINE_LIMIT = 300;
10
10
  const DEFAULT_MODULARITY_POLICY = "scoped_waivers";
11
- const MODULARITY_POLICIES = new Set([DEFAULT_MODULARITY_POLICY, "strict_except_generated"]);
11
+ const MODULARITY_POLICIES = new Set([
12
+ DEFAULT_MODULARITY_POLICY,
13
+ "strict_except_generated",
14
+ ]);
15
+ const COMPLEXITY_LIMITS = {
16
+ functionStatements: 80,
17
+ branchComplexity: 25,
18
+ exports: 24,
19
+ stateTransitions: 24,
20
+ responsibilities: 6,
21
+ };
12
22
  const GENERATED_FILE_PATTERNS = [
13
23
  /^\s*(?:\/\/|#|--|;)\s*@generated\b/im,
14
24
  /^\s*(?:\/\/|#|--|;)\s*code generated .*do not edit\.?\s*$/im,
15
25
  /^\s*(?:\/\/|#|--|;)\s*do not edit\.?\s*$/im,
16
26
  /^\s*(?:\/\/|#|--|;|<!--)\s*<auto-generated\b/im,
17
27
  /^\s*(?:\/\/|#|--|;)\s*this file was generated\b/im,
18
- /^\s*(?:\/\/|#|--|;)\s*generated by\b/im
28
+ /^\s*(?:\/\/|#|--|;)\s*generated by\b/im,
19
29
  ];
20
30
  const WAIVER_CATEGORIES = new Set([
21
31
  "generated",
22
32
  "third_party_reference",
23
33
  "legacy_migration",
24
34
  "aggregate_styles",
25
- "fixture_snapshot"
35
+ "fixture_snapshot",
26
36
  ]);
27
37
  export async function runModularityCheck(projectRoot, options) {
28
38
  const config = await readConfig(projectRoot);
29
- const { limit: configuredLimit, waiverValues, errors: configErrors } = validateModularityConfig(config.modularity);
39
+ const { limit: configuredLimit, waiverValues, errors: configErrors, } = validateModularityConfig(config.modularity);
30
40
  const { waivers, errors: waiverErrors } = validateWaivers(projectRoot, waiverValues);
31
41
  const limit = options.limit ?? configuredLimit ?? DEFAULT_LINE_LIMIT;
42
+ const waiverTargetErrors = await validateWaiverTargets(projectRoot, waivers, limit);
32
43
  const candidates = new Set();
44
+ const baselineRef = options.base ?? (options.touched ? "HEAD" : undefined);
33
45
  for (const file of options.files ?? []) {
34
46
  candidates.add(normalizeExplicitPath(projectRoot, file));
35
47
  }
@@ -49,20 +61,149 @@ export async function runModularityCheck(projectRoot, options) {
49
61
  continue;
50
62
  }
51
63
  const absolutePath = path.join(projectRoot, ...relativePath.split("/"));
52
- if (!(await isRegularFile(absolutePath)) || (await isLikelyGeneratedFile(absolutePath))) {
64
+ if (!(await isRegularFile(absolutePath)) ||
65
+ (await isLikelyGeneratedFile(absolutePath))) {
53
66
  continue;
54
67
  }
55
- const lines = countPhysicalLines(await fs.readFile(absolutePath, "utf8"));
68
+ const content = await fs.readFile(absolutePath, "utf8");
69
+ const lines = countPhysicalLines(content);
70
+ const metrics = analyzeModularity(content, relativePath);
71
+ const risks = modularityRisks(lines, limit, metrics);
72
+ const baselineContent = baselineRef
73
+ ? await gitFileAtRef(projectRoot, baselineRef, relativePath)
74
+ : undefined;
75
+ const baseline = baselineContent
76
+ ? analyzeFile(baselineContent, limit, relativePath)
77
+ : undefined;
78
+ const regressions = modularityRegressions({ lines, metrics, risks }, baseline);
56
79
  const waiver = waivers.get(relativePath);
57
- files.push({ relativePath, lines, overLimit: lines > limit, waived: waiver });
80
+ files.push({
81
+ relativePath,
82
+ lines,
83
+ metrics,
84
+ risks,
85
+ regressions,
86
+ overLimit: risks.length > 0,
87
+ regressed: regressions.length > 0,
88
+ baseline,
89
+ waived: waiver,
90
+ });
58
91
  }
59
92
  const warnings = files
60
- .filter((file) => file.overLimit && !file.waived)
61
- .map((file) => `${file.relativePath}: ${file.lines} physical lines exceeds limit ${limit}`);
93
+ .filter((file) => file.regressed && !file.waived)
94
+ .map((file) => `${file.relativePath}: ${file.regressions.join("; ")}`);
62
95
  const waivedWarnings = files
63
- .filter((file) => file.overLimit && file.waived)
64
- .map((file) => `${file.relativePath}: ${file.lines} physical lines exceeds limit ${limit} but is waived as ${file.waived?.category}`);
65
- return { limit, files, warnings, waivedWarnings, errors: [...configErrors, ...waiverErrors] };
96
+ .filter((file) => file.regressed && file.waived)
97
+ .map((file) => `${file.relativePath}: ${file.regressions.join("; ")} but is waived by ${file.waived?.owner} until ${file.waived?.expiryCondition}`);
98
+ return {
99
+ limit,
100
+ files,
101
+ warnings,
102
+ waivedWarnings,
103
+ errors: [...configErrors, ...waiverErrors, ...waiverTargetErrors],
104
+ };
105
+ }
106
+ function analyzeFile(content, limit, relativePath) {
107
+ const lines = countPhysicalLines(content);
108
+ const metrics = analyzeModularity(content, relativePath);
109
+ return { lines, metrics, risks: modularityRisks(lines, limit, metrics) };
110
+ }
111
+ function modularityRegressions(current, baseline) {
112
+ if (!baseline) {
113
+ return current.risks;
114
+ }
115
+ const checks = [
116
+ {
117
+ current: current.metrics.maxFunctionStatements,
118
+ baseline: baseline.metrics.maxFunctionStatements,
119
+ limit: COMPLEXITY_LIMITS.functionStatements,
120
+ message: `${current.metrics.maxFunctionStatements} statements in one function exceeds limit ${COMPLEXITY_LIMITS.functionStatements}${formatMetricLocation(current.metrics.maxFunctionStatementsLocation)}`,
121
+ },
122
+ {
123
+ current: current.metrics.maxBranchComplexity,
124
+ baseline: baseline.metrics.maxBranchComplexity,
125
+ limit: COMPLEXITY_LIMITS.branchComplexity,
126
+ message: `${current.metrics.maxBranchComplexity} branch complexity exceeds limit ${COMPLEXITY_LIMITS.branchComplexity}${formatMetricLocation(current.metrics.maxBranchComplexityLocation)}`,
127
+ },
128
+ {
129
+ current: current.metrics.exports,
130
+ baseline: baseline.metrics.exports,
131
+ limit: COMPLEXITY_LIMITS.exports,
132
+ message: `${current.metrics.exports} exports exceeds limit ${COMPLEXITY_LIMITS.exports}`,
133
+ },
134
+ {
135
+ current: current.metrics.stateTransitions,
136
+ baseline: baseline.metrics.stateTransitions,
137
+ limit: COMPLEXITY_LIMITS.stateTransitions,
138
+ message: `${current.metrics.stateTransitions} state transitions exceeds limit ${COMPLEXITY_LIMITS.stateTransitions}`,
139
+ },
140
+ {
141
+ current: current.metrics.responsibilities.length,
142
+ baseline: baseline.metrics.responsibilities.length,
143
+ limit: COMPLEXITY_LIMITS.responsibilities,
144
+ message: `${current.metrics.responsibilities.length} module responsibilities exceeds limit ${COMPLEXITY_LIMITS.responsibilities}`,
145
+ },
146
+ ];
147
+ return checks
148
+ .filter((check) => check.current > check.limit && check.current > check.baseline)
149
+ .map((check) => check.message);
150
+ }
151
+ export function analyzeModularity(content, relativePath = "") {
152
+ const code = sanitizeCode(content);
153
+ const bodies = relativePath.toLowerCase().endsWith(".py")
154
+ ? pythonFunctionBodies(code)
155
+ : functionBodies(code);
156
+ const analyzedBodies = bodies.map((body) => ({
157
+ ...body,
158
+ statements: statementCount(body.body),
159
+ branches: branchComplexity(body.body),
160
+ }));
161
+ const statementPeak = maxMetricBody(analyzedBodies, "statements");
162
+ const branchPeak = maxMetricBody(analyzedBodies, "branches");
163
+ return {
164
+ maxFunctionStatements: statementPeak?.statements ?? 0,
165
+ ...(statementPeak
166
+ ? {
167
+ maxFunctionStatementsLocation: {
168
+ symbol: statementPeak.symbol,
169
+ line: statementPeak.line,
170
+ },
171
+ }
172
+ : {}),
173
+ maxBranchComplexity: branchPeak?.branches ?? 0,
174
+ ...(branchPeak
175
+ ? {
176
+ maxBranchComplexityLocation: {
177
+ symbol: branchPeak.symbol,
178
+ line: branchPeak.line,
179
+ },
180
+ }
181
+ : {}),
182
+ exports: exportCount(code),
183
+ stateTransitions: stateTransitionCount(code),
184
+ responsibilities: inferResponsibilities(code),
185
+ };
186
+ }
187
+ function modularityRisks(lines, lineLimit, metrics) {
188
+ const risks = [];
189
+ if (lines > lineLimit)
190
+ risks.push(`${lines} physical lines exceeds limit ${lineLimit}`);
191
+ if (metrics.maxFunctionStatements > COMPLEXITY_LIMITS.functionStatements) {
192
+ risks.push(`${metrics.maxFunctionStatements} statements in one function exceeds limit ${COMPLEXITY_LIMITS.functionStatements}${formatMetricLocation(metrics.maxFunctionStatementsLocation)}`);
193
+ }
194
+ if (metrics.maxBranchComplexity > COMPLEXITY_LIMITS.branchComplexity) {
195
+ risks.push(`${metrics.maxBranchComplexity} branch complexity exceeds limit ${COMPLEXITY_LIMITS.branchComplexity}${formatMetricLocation(metrics.maxBranchComplexityLocation)}`);
196
+ }
197
+ if (metrics.exports > COMPLEXITY_LIMITS.exports) {
198
+ risks.push(`${metrics.exports} exports exceeds limit ${COMPLEXITY_LIMITS.exports}`);
199
+ }
200
+ if (metrics.stateTransitions > COMPLEXITY_LIMITS.stateTransitions) {
201
+ risks.push(`${metrics.stateTransitions} state transitions exceeds limit ${COMPLEXITY_LIMITS.stateTransitions}`);
202
+ }
203
+ if (metrics.responsibilities.length > COMPLEXITY_LIMITS.responsibilities) {
204
+ risks.push(`${metrics.responsibilities.length} module responsibilities exceeds limit ${COMPLEXITY_LIMITS.responsibilities}`);
205
+ }
206
+ return risks;
66
207
  }
67
208
  export function countPhysicalLines(content) {
68
209
  if (content.length === 0) {
@@ -74,12 +215,277 @@ export function countPhysicalLines(content) {
74
215
  }
75
216
  return lines.length;
76
217
  }
218
+ function sanitizeCode(content) {
219
+ const cursor = {
220
+ state: "code",
221
+ result: "",
222
+ index: 0,
223
+ regexCharacterClass: false,
224
+ };
225
+ while (cursor.index < content.length) {
226
+ if (cursor.state === "code")
227
+ scanCode(content, cursor);
228
+ else if (cursor.state === "line-comment")
229
+ scanLineComment(content, cursor);
230
+ else if (cursor.state === "block-comment")
231
+ scanBlockComment(content, cursor);
232
+ else if (cursor.state === "regex")
233
+ scanRegex(content, cursor);
234
+ else
235
+ scanQuoted(content, cursor);
236
+ cursor.index += 1;
237
+ }
238
+ return cursor.result;
239
+ }
240
+ function scanCode(content, cursor) {
241
+ const char = content[cursor.index];
242
+ const next = content[cursor.index + 1];
243
+ if (char === "/" && (next === "/" || next === "*")) {
244
+ cursor.state = next === "/" ? "line-comment" : "block-comment";
245
+ cursor.result += " ";
246
+ cursor.index += 1;
247
+ return;
248
+ }
249
+ if (char === "/" && startsRegexLiteral(cursor.result)) {
250
+ cursor.state = "regex";
251
+ cursor.regexCharacterClass = false;
252
+ cursor.result += " ";
253
+ return;
254
+ }
255
+ const quoteState = quoteStateFor(char);
256
+ if (quoteState) {
257
+ cursor.state = quoteState;
258
+ cursor.result += " ";
259
+ return;
260
+ }
261
+ cursor.result += char;
262
+ }
263
+ function scanLineComment(content, cursor) {
264
+ const char = content[cursor.index];
265
+ if (isLineBreak(char))
266
+ cursor.state = "code";
267
+ cursor.result += isLineBreak(char) ? char : " ";
268
+ }
269
+ function scanBlockComment(content, cursor) {
270
+ const char = content[cursor.index];
271
+ if (char === "*" && content[cursor.index + 1] === "/") {
272
+ cursor.state = "code";
273
+ cursor.result += " ";
274
+ cursor.index += 1;
275
+ return;
276
+ }
277
+ cursor.result += isLineBreak(char) ? char : " ";
278
+ }
279
+ function scanRegex(content, cursor) {
280
+ const char = content[cursor.index];
281
+ if (char === "\\") {
282
+ cursor.result += " ";
283
+ cursor.index += 1;
284
+ return;
285
+ }
286
+ if (char === "[")
287
+ cursor.regexCharacterClass = true;
288
+ if (char === "]")
289
+ cursor.regexCharacterClass = false;
290
+ if (char === "/" && !cursor.regexCharacterClass)
291
+ cursor.state = "code";
292
+ cursor.result += isLineBreak(char) ? char : " ";
293
+ }
294
+ function scanQuoted(content, cursor) {
295
+ const char = content[cursor.index];
296
+ if (char === "\\") {
297
+ cursor.result += " ";
298
+ cursor.index += 1;
299
+ return;
300
+ }
301
+ if (char === delimiterFor(cursor.state))
302
+ cursor.state = "code";
303
+ cursor.result += isLineBreak(char) ? char : " ";
304
+ }
305
+ function quoteStateFor(char) {
306
+ if (char === "'")
307
+ return "single";
308
+ if (char === '"')
309
+ return "double";
310
+ if (char === "`")
311
+ return "template";
312
+ return undefined;
313
+ }
314
+ function delimiterFor(state) {
315
+ if (state === "single")
316
+ return "'";
317
+ if (state === "double")
318
+ return '"';
319
+ return "`";
320
+ }
321
+ function isLineBreak(char) {
322
+ return char === "\n" || char === "\r";
323
+ }
324
+ function startsRegexLiteral(result) {
325
+ const trimmed = result.trimEnd();
326
+ if (!trimmed)
327
+ return true;
328
+ const previous = trimmed.at(-1) ?? "";
329
+ if (/[=(:,!&|?;\[{]/u.test(previous))
330
+ return true;
331
+ return /\b(?:return|throw|case|delete|void|typeof|instanceof|in|of)$/u.test(trimmed);
332
+ }
333
+ function functionBodies(code) {
334
+ const starts = new Map();
335
+ const add = (openingBrace, symbol) => {
336
+ const existing = starts.get(openingBrace);
337
+ if (!existing || existing === "<anonymous>")
338
+ starts.set(openingBrace, symbol);
339
+ };
340
+ for (const match of code.matchAll(/\bfunction\s*([A-Za-z_$][\w$]*)?[^{};]*\{/g)) {
341
+ const openingBrace = (match.index ?? 0) + match[0].lastIndexOf("{");
342
+ add(openingBrace, match[1] || "<anonymous>");
343
+ }
344
+ for (const match of code.matchAll(/=>\s*\{/g)) {
345
+ const openingBrace = (match.index ?? 0) + match[0].lastIndexOf("{");
346
+ add(openingBrace, inferArrowSymbol(code, match.index ?? 0));
347
+ }
348
+ for (const match of code.matchAll(/\b(?:async\s+)?(?:get\s+|set\s+)?([A-Za-z_$][\w$]*)\s*\([^;{}]*\)\s*(?::\s*[^={]+)?\{/g)) {
349
+ const prefix = match[0].trimStart();
350
+ if (/^(?:if|for|while|switch|catch|with)\b/u.test(prefix))
351
+ continue;
352
+ const openingBrace = (match.index ?? 0) + match[0].lastIndexOf("{");
353
+ add(openingBrace, match[1] || "<anonymous>");
354
+ }
355
+ return [...starts.entries()]
356
+ .sort(([left], [right]) => left - right)
357
+ .map(([openingBrace, symbol]) => {
358
+ const body = balancedBody(code, openingBrace);
359
+ return body === undefined
360
+ ? undefined
361
+ : {
362
+ body,
363
+ symbol,
364
+ line: lineNumberAt(code, openingBrace),
365
+ };
366
+ })
367
+ .filter((body) => body !== undefined);
368
+ }
369
+ function pythonFunctionBodies(code) {
370
+ const lines = code.split(/\r\n|\n|\r/u);
371
+ const bodies = [];
372
+ for (let index = 0; index < lines.length; index += 1) {
373
+ const match = /^(\s*)(?:async\s+)?def\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/u.exec(lines[index]);
374
+ if (!match)
375
+ continue;
376
+ const indentation = indentationWidth(match[1]);
377
+ const body = [];
378
+ for (let cursor = index + 1; cursor < lines.length; cursor += 1) {
379
+ const line = lines[cursor];
380
+ if (!line.trim()) {
381
+ body.push(line);
382
+ continue;
383
+ }
384
+ const leading = /^\s*/u.exec(line)?.[0] ?? "";
385
+ if (indentationWidth(leading) <= indentation)
386
+ break;
387
+ body.push(line);
388
+ }
389
+ bodies.push({ body: body.join("\n"), symbol: match[2], line: index + 1 });
390
+ }
391
+ return bodies;
392
+ }
393
+ function inferArrowSymbol(code, arrowIndex) {
394
+ const prefix = code.slice(Math.max(0, arrowIndex - 240), arrowIndex);
395
+ const assignment = /(?:\b(?:const|let|var)\s+|\b)([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*$/u.exec(prefix);
396
+ if (assignment?.[1])
397
+ return assignment[1];
398
+ const property = /([A-Za-z_$][\w$]*)\s*:\s*(?:async\s*)?(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*$/u.exec(prefix);
399
+ return property?.[1] ?? "<anonymous>";
400
+ }
401
+ function lineNumberAt(code, index) {
402
+ return code.slice(0, index).split(/\r\n|\n|\r/u).length;
403
+ }
404
+ function maxMetricBody(bodies, metric) {
405
+ let selected;
406
+ for (const body of bodies) {
407
+ if (!selected || body[metric] > selected[metric])
408
+ selected = body;
409
+ }
410
+ return selected;
411
+ }
412
+ function formatMetricLocation(location) {
413
+ return location
414
+ ? ` in function ${location.symbol} at line ${location.line}`
415
+ : "";
416
+ }
417
+ function indentationWidth(value) {
418
+ return [...value].reduce((width, character) => width + (character === "\t" ? 4 : 1), 0);
419
+ }
420
+ function balancedBody(code, openingBrace) {
421
+ let depth = 0;
422
+ for (let index = openingBrace; index < code.length; index += 1) {
423
+ if (code[index] === "{")
424
+ depth += 1;
425
+ if (code[index] === "}")
426
+ depth -= 1;
427
+ if (depth === 0)
428
+ return code.slice(openingBrace + 1, index);
429
+ }
430
+ return undefined;
431
+ }
432
+ function statementCount(body) {
433
+ const semicolons = body.match(/;/g)?.length ?? 0;
434
+ const structural = body.match(/\b(?:if|for|while|switch|case|catch|return|throw|break|continue)\b/g)?.length ?? 0;
435
+ return semicolons + structural;
436
+ }
437
+ function branchComplexity(body) {
438
+ const branches = body.match(/\b(?:if|for|while|case|catch)\b|\?|&&|\|\||\?\?/g)?.length ?? 0;
439
+ return 1 + branches;
440
+ }
441
+ function exportCount(code) {
442
+ let count = code.match(/\bexport\s+(?:default\s+)?(?:async\s+)?(?:function|class|const|let|var|interface|type|enum|namespace)\b/g)?.length ?? 0;
443
+ for (const match of code.matchAll(/\bexport\s*\{([^}]*)\}/g)) {
444
+ count += match[1].split(",").filter((item) => item.trim()).length;
445
+ }
446
+ return count;
447
+ }
448
+ function stateTransitionCount(code) {
449
+ return (code.match(/\.(?:status|state|phase|workflow_status|campaign_status)\s*=|\b(?:transition|setState|setStatus|advanceState|mutateCampaign)[A-Za-z0-9_$]*\s*\(/g)?.length ?? 0);
450
+ }
451
+ function inferResponsibilities(code) {
452
+ const patterns = [
453
+ [
454
+ "filesystem",
455
+ /\b(?:readFile|writeFile|mkdir|rm|rename|readdir|stat|realpath)\s*\(/,
456
+ ],
457
+ ["process-or-git", /\b(?:spawn|execFile|runGit|process\.)/],
458
+ ["network", /\b(?:fetch|http\.|https\.|WebSocket|net\.)/],
459
+ ["parsing", /\b(?:parse|decode|deserialize)[A-Za-z0-9_$]*\s*\(/],
460
+ ["validation", /\b(?:assert|validate|reject)[A-Za-z0-9_$]*\s*\(/],
461
+ [
462
+ "state-machine",
463
+ /\.(?:status|state|phase)\s*=|\btransition[A-Za-z0-9_$]*\s*\(/,
464
+ ],
465
+ ["rendering", /\b(?:render|stringify|format)[A-Za-z0-9_$]*\s*\(/],
466
+ [
467
+ "persistence",
468
+ /\b(?:atomic|transaction|journal|lease|checkpoint)[A-Za-z0-9_$]*\b/i,
469
+ ],
470
+ ["cli", /\b(?:process\.argv|console\.|parseArgs|helpText)\b/],
471
+ ];
472
+ return patterns
473
+ .filter(([, pattern]) => pattern.test(code))
474
+ .map(([name]) => name);
475
+ }
77
476
  async function gitTouchedFiles(projectRoot) {
78
477
  let result;
79
478
  try {
80
- result = await execFileAsync("git", ["-C", projectRoot, "status", "--porcelain", "-z"], {
479
+ result = await execFileAsync("git", [
480
+ "-C",
481
+ projectRoot,
482
+ "status",
483
+ "--porcelain=v1",
484
+ "-z",
485
+ "--untracked-files=all",
486
+ ], {
81
487
  encoding: "utf8",
82
- maxBuffer: GIT_MAX_BUFFER
488
+ maxBuffer: GIT_MAX_BUFFER,
83
489
  });
84
490
  }
85
491
  catch (error) {
@@ -106,10 +512,28 @@ async function gitTouchedFiles(projectRoot) {
106
512
  async function gitDiffFiles(projectRoot, base) {
107
513
  const result = await execFileAsync("git", ["-C", projectRoot, "diff", "--name-only", "-z", base, "--"], {
108
514
  encoding: "utf8",
109
- maxBuffer: GIT_MAX_BUFFER
515
+ maxBuffer: GIT_MAX_BUFFER,
110
516
  });
111
517
  return result.stdout.split("\0").filter(Boolean);
112
518
  }
519
+ async function gitFileAtRef(projectRoot, ref, relativePath) {
520
+ try {
521
+ const result = await execFileAsync("git", ["-C", projectRoot, "show", `${ref}:${relativePath}`], {
522
+ encoding: "utf8",
523
+ maxBuffer: GIT_MAX_BUFFER,
524
+ });
525
+ return result.stdout;
526
+ }
527
+ catch (error) {
528
+ const stderr = error && typeof error === "object" && "stderr" in error
529
+ ? String(error.stderr)
530
+ : "";
531
+ if (/does not exist in|exists on disk, but not in|path .* not in/i.test(stderr)) {
532
+ return undefined;
533
+ }
534
+ throw error;
535
+ }
536
+ }
113
537
  function isNotGitRepositoryError(error) {
114
538
  if (!error || typeof error !== "object") {
115
539
  return false;
@@ -118,9 +542,13 @@ function isNotGitRepositoryError(error) {
118
542
  return /not a git repository/i.test(stderr);
119
543
  }
120
544
  function normalizeExplicitPath(projectRoot, value) {
121
- const absolute = path.isAbsolute(value) ? path.resolve(value) : path.resolve(projectRoot, value);
545
+ const absolute = path.isAbsolute(value)
546
+ ? path.resolve(value)
547
+ : path.resolve(projectRoot, value);
122
548
  const relative = toPosix(path.relative(projectRoot, absolute));
123
- if (relative.startsWith("../") || relative === ".." || path.isAbsolute(relative)) {
549
+ if (relative.startsWith("../") ||
550
+ relative === ".." ||
551
+ path.isAbsolute(relative)) {
124
552
  throw new Error(`check-modularity --file must stay inside the project root: ${value}`);
125
553
  }
126
554
  return normalizeGitPath(relative);
@@ -133,7 +561,9 @@ function validateModularityConfig(modularity) {
133
561
  if (modularity === undefined) {
134
562
  return { errors };
135
563
  }
136
- if (!modularity || typeof modularity !== "object" || Array.isArray(modularity)) {
564
+ if (!modularity ||
565
+ typeof modularity !== "object" ||
566
+ Array.isArray(modularity)) {
137
567
  errors.push("<harnessRoot>/config.yaml modularity must be an object");
138
568
  return { errors };
139
569
  }
@@ -149,7 +579,8 @@ function validateModularityConfig(modularity) {
149
579
  }
150
580
  }
151
581
  if (value.policy !== undefined) {
152
- if (typeof value.policy !== "string" || !MODULARITY_POLICIES.has(value.policy)) {
582
+ if (typeof value.policy !== "string" ||
583
+ !MODULARITY_POLICIES.has(value.policy)) {
153
584
  errors.push("<harnessRoot>/config.yaml modularity.policy must be one of scoped_waivers, strict_except_generated");
154
585
  }
155
586
  else {
@@ -159,7 +590,11 @@ function validateModularityConfig(modularity) {
159
590
  if (policy === "strict_except_generated" && value.waivers !== undefined) {
160
591
  errors.push("<harnessRoot>/config.yaml modularity.waivers is not allowed when modularity.policy is strict_except_generated");
161
592
  }
162
- return { limit, waiverValues: policy === "scoped_waivers" ? value.waivers : undefined, errors };
593
+ return {
594
+ limit,
595
+ waiverValues: policy === "scoped_waivers" ? value.waivers : undefined,
596
+ errors,
597
+ };
163
598
  }
164
599
  function validateWaivers(projectRoot, waiverValues) {
165
600
  const waivers = new Map();
@@ -179,9 +614,23 @@ function validateWaivers(projectRoot, waiverValues) {
179
614
  }
180
615
  const relativePath = requiredWaiverString(waiver, "path", label, errors);
181
616
  const category = requiredWaiverString(waiver, "category", label, errors);
617
+ const owner = requiredWaiverString(waiver, "owner", label, errors);
618
+ const introducedAt = requiredWaiverString(waiver, "introduced_at", label, errors);
182
619
  const reason = requiredWaiverString(waiver, "reason", label, errors);
183
- const futureSplitBoundary = requiredWaiverString(waiver, "future_split_boundary", label, errors);
184
- if (!relativePath || !category || !reason || !futureSplitBoundary) {
620
+ const trackingIssue = requiredWaiverString(waiver, "tracking_issue", label, errors);
621
+ const expiryCondition = requiredWaiverString(waiver, "expiry_condition", label, errors);
622
+ if (!relativePath ||
623
+ !category ||
624
+ !owner ||
625
+ !introducedAt ||
626
+ !reason ||
627
+ !trackingIssue ||
628
+ !expiryCondition) {
629
+ continue;
630
+ }
631
+ if (!/^\d{4}-\d{2}-\d{2}$/u.test(introducedAt) ||
632
+ Number.isNaN(Date.parse(`${introducedAt}T00:00:00Z`))) {
633
+ errors.push(`${label}.introduced_at must be an ISO YYYY-MM-DD date`);
185
634
  continue;
186
635
  }
187
636
  if (!WAIVER_CATEGORIES.has(category)) {
@@ -204,12 +653,39 @@ function validateWaivers(projectRoot, waiverValues) {
204
653
  waivers.set(normalized, {
205
654
  relativePath: normalized,
206
655
  category,
656
+ owner,
657
+ introducedAt,
207
658
  reason,
208
- futureSplitBoundary
659
+ trackingIssue,
660
+ expiryCondition,
209
661
  });
210
662
  }
211
663
  return { waivers, errors };
212
664
  }
665
+ async function validateWaiverTargets(projectRoot, waivers, limit) {
666
+ const errors = [];
667
+ for (const waiver of waivers.values()) {
668
+ const label = `<harnessRoot>/config.yaml modularity waiver for ${waiver.relativePath}`;
669
+ if (!shouldIncludeCodeFile(waiver.relativePath)) {
670
+ errors.push(`${label} does not target a supported handwritten source file`);
671
+ continue;
672
+ }
673
+ const target = path.join(projectRoot, ...waiver.relativePath.split("/"));
674
+ if (!(await isRegularFile(target))) {
675
+ errors.push(`${label} references a missing source file`);
676
+ continue;
677
+ }
678
+ if (await isLikelyGeneratedFile(target)) {
679
+ errors.push(`${label} is unnecessary because generated files are excluded`);
680
+ continue;
681
+ }
682
+ const analyzed = analyzeFile(await fs.readFile(target, "utf8"), limit, waiver.relativePath);
683
+ if (analyzed.risks.length === 0) {
684
+ errors.push(`${label} is unnecessary because the source does not currently exceed a modularity threshold`);
685
+ }
686
+ }
687
+ return errors;
688
+ }
213
689
  function requiredWaiverString(waiver, field, label, errors) {
214
690
  const value = waiver[field];
215
691
  if (typeof value !== "string" || value.trim().length === 0) {
@@ -12,19 +12,28 @@ export async function packageHarnessRoot(projectRoot) {
12
12
  return undefined;
13
13
  }
14
14
  const value = config.harnessFolderName;
15
- return typeof value === "string" && value.trim() ? normalizeHarnessFolderName(value) : undefined;
15
+ return typeof value === "string" && value.trim()
16
+ ? normalizeHarnessFolderName(value)
17
+ : undefined;
16
18
  }
17
19
  export async function writePackageHarnessRoot(projectRoot, folderName) {
18
20
  const normalized = normalizeHarnessFolderName(folderName);
19
21
  const packagePath = path.join(projectRoot, "package.json");
20
- const packageJson = (await pathExists(packagePath)) ? parsePackageJson(await readText(packagePath)) : {};
22
+ const packageJson = (await pathExists(packagePath))
23
+ ? parsePackageJson(await readText(packagePath))
24
+ : {};
21
25
  const existingConfig = packageJson.tyContext;
22
- const nextConfig = existingConfig && typeof existingConfig === "object" && !Array.isArray(existingConfig)
23
- ? { ...existingConfig, harnessFolderName: normalized }
26
+ const nextConfig = existingConfig &&
27
+ typeof existingConfig === "object" &&
28
+ !Array.isArray(existingConfig)
29
+ ? {
30
+ ...existingConfig,
31
+ harnessFolderName: normalized,
32
+ }
24
33
  : { harnessFolderName: normalized };
25
34
  const next = {
26
35
  ...packageJson,
27
- tyContext: nextConfig
36
+ tyContext: nextConfig,
28
37
  };
29
38
  return writeTextIfChanged(packagePath, `${JSON.stringify(next, null, 2)}\n`);
30
39
  }
@@ -1,7 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { promises as fs } from "node:fs";
3
3
  import path from "node:path";
4
- import { ensureDir, listFiles, pathExists, readText, writeTextIfChanged } from "./fs.js";
4
+ import { ensureDir, listFiles, pathExists, readText, writeTextIfChanged, } from "./fs.js";
5
5
  import { AGENTS_BLOCK_MARKERS } from "./managed-file.js";
6
6
  import { SOURCE_MAPPINGS_PATH } from "./paths.js";
7
7
  import { parseYaml } from "./yaml.js";
@@ -29,7 +29,9 @@ export async function checkSource(projectRoot) {
29
29
  for (const item of expected) {
30
30
  sourceHashes.set(item.relative, hash(item.content));
31
31
  const targetFile = path.join(target, item.relative);
32
- const existing = (await pathExists(targetFile)) ? await readText(targetFile) : "";
32
+ const existing = (await pathExists(targetFile))
33
+ ? await readText(targetFile)
34
+ : "";
33
35
  if (hash(existing) !== hash(item.content)) {
34
36
  drift.push(`${mapping.target}/${item.relative}`);
35
37
  }