@rosetears/aili-pi 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (531) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +109 -0
  3. package/THIRD_PARTY_NOTICES.md +64 -0
  4. package/extensions/index.ts +6 -0
  5. package/install.sh +5 -0
  6. package/manifests/adapter-evidence.json +45 -0
  7. package/manifests/capabilities.json +124 -0
  8. package/manifests/live-verification.json +26 -0
  9. package/manifests/provenance.json +71 -0
  10. package/manifests/roles.json +474 -0
  11. package/manifests/sbom.json +7797 -0
  12. package/manifests/skill-compatibility.json +8100 -0
  13. package/manifests/subagent-provenance.json +37 -0
  14. package/package.json +91 -0
  15. package/prompts/build.md +8 -0
  16. package/prompts/define.md +8 -0
  17. package/prompts/ideate.md +8 -0
  18. package/prompts/local-review.md +8 -0
  19. package/prompts/ship.md +8 -0
  20. package/roles/agent-evaluator.md +47 -0
  21. package/roles/ai-regression-scout.md +47 -0
  22. package/roles/browser-qa-runner.md +47 -0
  23. package/roles/code-reviewer.md +47 -0
  24. package/roles/code-scout.md +47 -0
  25. package/roles/convergence-reviewer.md +55 -0
  26. package/roles/doc-researcher.md +47 -0
  27. package/roles/e2e-artifact-runner.md +47 -0
  28. package/roles/implementer.md +47 -0
  29. package/roles/opensource-sanitizer.md +47 -0
  30. package/roles/plan-auditor.md +47 -0
  31. package/roles/pr-test-analyzer.md +47 -0
  32. package/roles/security-auditor.md +47 -0
  33. package/roles/silent-failure-reviewer.md +47 -0
  34. package/roles/spec-miner.md +47 -0
  35. package/roles/test-coverage-reviewer.md +47 -0
  36. package/roles/test-engineer.md +48 -0
  37. package/roles/web-performance-auditor.md +47 -0
  38. package/roles/web-researcher.md +47 -0
  39. package/scripts/apply-adapter-evidence.ts +73 -0
  40. package/scripts/bootstrap.sh +180 -0
  41. package/scripts/generate-provenance.ts +143 -0
  42. package/scripts/local-package-e2e.ts +51 -0
  43. package/scripts/sync-roles.ts +213 -0
  44. package/scripts/sync-skills.ts +356 -0
  45. package/scripts/validate-runtime.ts +11 -0
  46. package/skills/academic-paper-review/SKILL.md +81 -0
  47. package/skills/agents-md-initialization/SKILL.md +121 -0
  48. package/skills/agents-md-initialization/references/agents-template.md +83 -0
  49. package/skills/agents-md-initialization/references/agents_md.py +215 -0
  50. package/skills/ai-regression-scout/SKILL.md +24 -0
  51. package/skills/aili-delivery-flow/SKILL.md +125 -0
  52. package/skills/aili-delivery-flow/references/artifact-contracts.md +138 -0
  53. package/skills/aili-delivery-flow/references/backend-routing.md +40 -0
  54. package/skills/aili-delivery-flow/references/build-execution-loop.md +113 -0
  55. package/skills/aili-delivery-flow/references/build-goal-mode.md +3 -0
  56. package/skills/aili-delivery-flow/references/direct-vs-delegated-work.md +38 -0
  57. package/skills/aili-delivery-flow/references/implementation-packages.md +54 -0
  58. package/skills/aili-delivery-flow/references/lifecycle.md +127 -0
  59. package/skills/aili-delivery-flow/references/protocols/acceptance-test-plan.md +18 -0
  60. package/skills/aili-delivery-flow/references/protocols/alignment-questionnaire.md +10 -0
  61. package/skills/aili-delivery-flow/references/protocols/closeout-report.md +101 -0
  62. package/skills/aili-delivery-flow/references/protocols/compact-evidence-pack.md +46 -0
  63. package/skills/aili-delivery-flow/references/protocols/idea-brief.md +10 -0
  64. package/skills/aili-delivery-flow/references/protocols/implementation-package.md +53 -0
  65. package/skills/aili-delivery-flow/references/protocols/research-evidence-pack.md +19 -0
  66. package/skills/aili-delivery-flow/references/protocols/review-report.md +7 -0
  67. package/skills/aili-delivery-flow/references/protocols/spec-draft.md +12 -0
  68. package/skills/aili-delivery-flow/references/protocols/subagent-result.md +60 -0
  69. package/skills/aili-delivery-flow/references/protocols/subagent-task-packet.md +29 -0
  70. package/skills/aili-delivery-flow/references/protocols/worktree-context.md +109 -0
  71. package/skills/aili-delivery-flow/references/questionnaire-policy.md +62 -0
  72. package/skills/aili-delivery-flow/references/review-repair-loop.md +16 -0
  73. package/skills/aili-delivery-flow/references/test-document-policy.md +35 -0
  74. package/skills/android-native-dev/SKILL.md +722 -0
  75. package/skills/android-native-dev/references/accessibility.md +209 -0
  76. package/skills/android-native-dev/references/adaptive-screens.md +231 -0
  77. package/skills/android-native-dev/references/design-style-guide.md +365 -0
  78. package/skills/android-native-dev/references/functional-requirements.md +229 -0
  79. package/skills/android-native-dev/references/motion-system.md +203 -0
  80. package/skills/android-native-dev/references/performance-stability.md +223 -0
  81. package/skills/android-native-dev/references/privacy-security.md +244 -0
  82. package/skills/android-native-dev/references/testing.md +554 -0
  83. package/skills/android-native-dev/references/visual-design.md +246 -0
  84. package/skills/api-and-interface-design/SKILL.md +318 -0
  85. package/skills/browser-qa/SKILL.md +32 -0
  86. package/skills/browser-testing-with-devtools/SKILL.md +323 -0
  87. package/skills/build-failure-repair/SKILL.md +49 -0
  88. package/skills/chart-visualization/SKILL.md +73 -0
  89. package/skills/ci-cd-and-automation/SKILL.md +353 -0
  90. package/skills/code-review-and-quality/SKILL.md +375 -0
  91. package/skills/code-review-quality-gates/SKILL.md +132 -0
  92. package/skills/code-simplification/SKILL.md +371 -0
  93. package/skills/comment-accuracy-review/SKILL.md +37 -0
  94. package/skills/consulting-analysis/SKILL.md +69 -0
  95. package/skills/context-engineering/SKILL.md +375 -0
  96. package/skills/coverage-review/SKILL.md +24 -0
  97. package/skills/data-analysis/SKILL.md +74 -0
  98. package/skills/deprecation-and-migration/SKILL.md +222 -0
  99. package/skills/documentation-and-adrs/SKILL.md +330 -0
  100. package/skills/e2e-artifact-handling/SKILL.md +30 -0
  101. package/skills/evidence-scoped-retrospective/SKILL.md +156 -0
  102. package/skills/explain-by-allegory/SKILL.md +99 -0
  103. package/skills/flutter-dev/SKILL.md +162 -0
  104. package/skills/flutter-dev/references/animations.md +497 -0
  105. package/skills/flutter-dev/references/bloc-state.md +281 -0
  106. package/skills/flutter-dev/references/forms.md +656 -0
  107. package/skills/flutter-dev/references/gorouter-navigation.md +257 -0
  108. package/skills/flutter-dev/references/localization.md +510 -0
  109. package/skills/flutter-dev/references/networking.md +566 -0
  110. package/skills/flutter-dev/references/performance.md +305 -0
  111. package/skills/flutter-dev/references/platform-specific.md +417 -0
  112. package/skills/flutter-dev/references/project-structure.md +273 -0
  113. package/skills/flutter-dev/references/riverpod-state.md +232 -0
  114. package/skills/flutter-dev/references/testing.md +364 -0
  115. package/skills/flutter-dev/references/widget-patterns.md +233 -0
  116. package/skills/frontend-dev/SKILL.md +468 -0
  117. package/skills/frontend-dev/canvas-fonts/ArsenalSC-OFL.txt +93 -0
  118. package/skills/frontend-dev/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
  119. package/skills/frontend-dev/canvas-fonts/BigShoulders-Bold.ttf +0 -0
  120. package/skills/frontend-dev/canvas-fonts/BigShoulders-OFL.txt +93 -0
  121. package/skills/frontend-dev/canvas-fonts/BigShoulders-Regular.ttf +0 -0
  122. package/skills/frontend-dev/canvas-fonts/Boldonse-OFL.txt +93 -0
  123. package/skills/frontend-dev/canvas-fonts/Boldonse-Regular.ttf +0 -0
  124. package/skills/frontend-dev/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
  125. package/skills/frontend-dev/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
  126. package/skills/frontend-dev/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
  127. package/skills/frontend-dev/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
  128. package/skills/frontend-dev/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
  129. package/skills/frontend-dev/canvas-fonts/CrimsonPro-OFL.txt +93 -0
  130. package/skills/frontend-dev/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
  131. package/skills/frontend-dev/canvas-fonts/DMMono-OFL.txt +93 -0
  132. package/skills/frontend-dev/canvas-fonts/DMMono-Regular.ttf +0 -0
  133. package/skills/frontend-dev/canvas-fonts/EricaOne-OFL.txt +94 -0
  134. package/skills/frontend-dev/canvas-fonts/EricaOne-Regular.ttf +0 -0
  135. package/skills/frontend-dev/canvas-fonts/GeistMono-Bold.ttf +0 -0
  136. package/skills/frontend-dev/canvas-fonts/GeistMono-OFL.txt +93 -0
  137. package/skills/frontend-dev/canvas-fonts/GeistMono-Regular.ttf +0 -0
  138. package/skills/frontend-dev/canvas-fonts/Gloock-OFL.txt +93 -0
  139. package/skills/frontend-dev/canvas-fonts/Gloock-Regular.ttf +0 -0
  140. package/skills/frontend-dev/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
  141. package/skills/frontend-dev/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
  142. package/skills/frontend-dev/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
  143. package/skills/frontend-dev/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
  144. package/skills/frontend-dev/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
  145. package/skills/frontend-dev/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
  146. package/skills/frontend-dev/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
  147. package/skills/frontend-dev/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
  148. package/skills/frontend-dev/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
  149. package/skills/frontend-dev/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
  150. package/skills/frontend-dev/canvas-fonts/InstrumentSans-OFL.txt +93 -0
  151. package/skills/frontend-dev/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
  152. package/skills/frontend-dev/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
  153. package/skills/frontend-dev/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
  154. package/skills/frontend-dev/canvas-fonts/Italiana-OFL.txt +93 -0
  155. package/skills/frontend-dev/canvas-fonts/Italiana-Regular.ttf +0 -0
  156. package/skills/frontend-dev/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
  157. package/skills/frontend-dev/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
  158. package/skills/frontend-dev/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
  159. package/skills/frontend-dev/canvas-fonts/Jura-Light.ttf +0 -0
  160. package/skills/frontend-dev/canvas-fonts/Jura-Medium.ttf +0 -0
  161. package/skills/frontend-dev/canvas-fonts/Jura-OFL.txt +93 -0
  162. package/skills/frontend-dev/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
  163. package/skills/frontend-dev/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
  164. package/skills/frontend-dev/canvas-fonts/Lora-Bold.ttf +0 -0
  165. package/skills/frontend-dev/canvas-fonts/Lora-BoldItalic.ttf +0 -0
  166. package/skills/frontend-dev/canvas-fonts/Lora-Italic.ttf +0 -0
  167. package/skills/frontend-dev/canvas-fonts/Lora-OFL.txt +93 -0
  168. package/skills/frontend-dev/canvas-fonts/Lora-Regular.ttf +0 -0
  169. package/skills/frontend-dev/canvas-fonts/NationalPark-Bold.ttf +0 -0
  170. package/skills/frontend-dev/canvas-fonts/NationalPark-OFL.txt +93 -0
  171. package/skills/frontend-dev/canvas-fonts/NationalPark-Regular.ttf +0 -0
  172. package/skills/frontend-dev/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
  173. package/skills/frontend-dev/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
  174. package/skills/frontend-dev/canvas-fonts/Outfit-Bold.ttf +0 -0
  175. package/skills/frontend-dev/canvas-fonts/Outfit-OFL.txt +93 -0
  176. package/skills/frontend-dev/canvas-fonts/Outfit-Regular.ttf +0 -0
  177. package/skills/frontend-dev/canvas-fonts/PixelifySans-Medium.ttf +0 -0
  178. package/skills/frontend-dev/canvas-fonts/PixelifySans-OFL.txt +93 -0
  179. package/skills/frontend-dev/canvas-fonts/PoiretOne-OFL.txt +93 -0
  180. package/skills/frontend-dev/canvas-fonts/PoiretOne-Regular.ttf +0 -0
  181. package/skills/frontend-dev/canvas-fonts/RedHatMono-Bold.ttf +0 -0
  182. package/skills/frontend-dev/canvas-fonts/RedHatMono-OFL.txt +93 -0
  183. package/skills/frontend-dev/canvas-fonts/RedHatMono-Regular.ttf +0 -0
  184. package/skills/frontend-dev/canvas-fonts/Silkscreen-OFL.txt +93 -0
  185. package/skills/frontend-dev/canvas-fonts/Silkscreen-Regular.ttf +0 -0
  186. package/skills/frontend-dev/canvas-fonts/SmoochSans-Medium.ttf +0 -0
  187. package/skills/frontend-dev/canvas-fonts/SmoochSans-OFL.txt +93 -0
  188. package/skills/frontend-dev/canvas-fonts/Tektur-Medium.ttf +0 -0
  189. package/skills/frontend-dev/canvas-fonts/Tektur-OFL.txt +93 -0
  190. package/skills/frontend-dev/canvas-fonts/Tektur-Regular.ttf +0 -0
  191. package/skills/frontend-dev/canvas-fonts/WorkSans-Bold.ttf +0 -0
  192. package/skills/frontend-dev/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
  193. package/skills/frontend-dev/canvas-fonts/WorkSans-Italic.ttf +0 -0
  194. package/skills/frontend-dev/canvas-fonts/WorkSans-OFL.txt +93 -0
  195. package/skills/frontend-dev/canvas-fonts/WorkSans-Regular.ttf +0 -0
  196. package/skills/frontend-dev/canvas-fonts/YoungSerif-OFL.txt +93 -0
  197. package/skills/frontend-dev/canvas-fonts/YoungSerif-Regular.ttf +0 -0
  198. package/skills/frontend-dev/references/asset-prompt-guide.md +43 -0
  199. package/skills/frontend-dev/references/env-setup.md +33 -0
  200. package/skills/frontend-dev/references/minimax-cli-reference.md +133 -0
  201. package/skills/frontend-dev/references/minimax-image-guide.md +65 -0
  202. package/skills/frontend-dev/references/minimax-music-guide.md +216 -0
  203. package/skills/frontend-dev/references/minimax-tts-guide.md +78 -0
  204. package/skills/frontend-dev/references/minimax-video-guide.md +82 -0
  205. package/skills/frontend-dev/references/minimax-voice-catalog.md +685 -0
  206. package/skills/frontend-dev/references/motion-recipes.md +407 -0
  207. package/skills/frontend-dev/references/troubleshooting.md +85 -0
  208. package/skills/frontend-dev/scripts/minimax_image.py +137 -0
  209. package/skills/frontend-dev/scripts/minimax_music.py +157 -0
  210. package/skills/frontend-dev/scripts/minimax_tts.py +127 -0
  211. package/skills/frontend-dev/scripts/minimax_video.py +187 -0
  212. package/skills/frontend-dev/templates/generator_template.js +223 -0
  213. package/skills/frontend-dev/templates/viewer.html +599 -0
  214. package/skills/frontend-ui-engineering/SKILL.md +367 -0
  215. package/skills/fullstack-dev/SKILL.md +819 -0
  216. package/skills/fullstack-dev/references/api-design.md +444 -0
  217. package/skills/fullstack-dev/references/auth-flow.md +165 -0
  218. package/skills/fullstack-dev/references/db-schema.md +706 -0
  219. package/skills/fullstack-dev/references/django-best-practices.md +466 -0
  220. package/skills/fullstack-dev/references/environment-management.md +78 -0
  221. package/skills/fullstack-dev/references/release-checklist.md +278 -0
  222. package/skills/fullstack-dev/references/technology-selection.md +254 -0
  223. package/skills/fullstack-dev/references/testing-strategy.md +404 -0
  224. package/skills/git-workflow-and-versioning/SKILL.md +446 -0
  225. package/skills/github-evidence-triage/SKILL.md +115 -0
  226. package/skills/harness-evolution/SKILL.md +59 -0
  227. package/skills/harness-evolution/references/activation-matrix.md +13 -0
  228. package/skills/harness-evolution/references/approval-policy.md +20 -0
  229. package/skills/harness-evolution/references/change-report-template.md +33 -0
  230. package/skills/harness-evolution/references/component-taxonomy.md +19 -0
  231. package/skills/harness-evolution/references/verdict-policy.md +13 -0
  232. package/skills/harness-issue-triage/SKILL.md +72 -0
  233. package/skills/harness-issue-triage/references/component-diagnosis.md +25 -0
  234. package/skills/harness-issue-triage/references/triage-report-template.md +34 -0
  235. package/skills/harness-optimization-audit/SKILL.md +125 -0
  236. package/skills/idea-refine/SKILL.md +191 -0
  237. package/skills/idea-refine/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/LICENSE +21 -0
  238. package/skills/idea-refine/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/NOTICE.md +9 -0
  239. package/skills/idea-refine/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/idea-refine/SKILL.upstream.md +178 -0
  240. package/skills/idea-refine/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/idea-refine/examples.md +238 -0
  241. package/skills/idea-refine/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/idea-refine/frameworks.md +99 -0
  242. package/skills/idea-refine/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/idea-refine/refinement-criteria.md +113 -0
  243. package/skills/idea-refine/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/idea-refine/scripts/idea-refine.upstream.sh +15 -0
  244. package/skills/incremental-implementation/SKILL.md +252 -0
  245. package/skills/ios-application-dev/SKILL.md +212 -0
  246. package/skills/ios-application-dev/references/accessibility.md +259 -0
  247. package/skills/ios-application-dev/references/graphics-animation.md +350 -0
  248. package/skills/ios-application-dev/references/layout-system.md +199 -0
  249. package/skills/ios-application-dev/references/metal-shader.md +178 -0
  250. package/skills/ios-application-dev/references/navigation-patterns.md +175 -0
  251. package/skills/ios-application-dev/references/swift-coding-standards.md +757 -0
  252. package/skills/ios-application-dev/references/swiftui-design-guidelines.md +1167 -0
  253. package/skills/ios-application-dev/references/system-integration.md +401 -0
  254. package/skills/ios-application-dev/references/uikit-components.md +297 -0
  255. package/skills/local-review-gate/SKILL.md +207 -0
  256. package/skills/local-review-gate/references/addyosmani-code-review-rubric.md +33 -0
  257. package/skills/local-review-gate/references/codex-github-compatibility.md +26 -0
  258. package/skills/local-review-gate/references/ecc-code-review-adaptation.md +40 -0
  259. package/skills/local-review-gate/references/graphify-local-review.md +33 -0
  260. package/skills/local-review-gate/references/orchestration-adaptation.md +51 -0
  261. package/skills/local-review-gate/references/review-repair-lane-adaptation.md +46 -0
  262. package/skills/local-review-gate/references/upstream-provenance.md +61 -0
  263. package/skills/mature-project-pattern-research/SKILL.md +155 -0
  264. package/skills/mature-project-pattern-research/references/research-rubric.md +45 -0
  265. package/skills/minimax-docx/LICENSE +21 -0
  266. package/skills/minimax-docx/SKILL.md +273 -0
  267. package/skills/minimax-docx/assets/styles/academic_styles.xml +250 -0
  268. package/skills/minimax-docx/assets/styles/corporate_styles.xml +284 -0
  269. package/skills/minimax-docx/assets/styles/default_styles.xml +449 -0
  270. package/skills/minimax-docx/assets/xsd/aesthetic-rules.xsd +470 -0
  271. package/skills/minimax-docx/assets/xsd/business-rules.xsd +130 -0
  272. package/skills/minimax-docx/assets/xsd/common-types.xsd +159 -0
  273. package/skills/minimax-docx/assets/xsd/wml-subset.xsd +589 -0
  274. package/skills/minimax-docx/references/cjk_typography.md +357 -0
  275. package/skills/minimax-docx/references/cjk_university_template_guide.md +184 -0
  276. package/skills/minimax-docx/references/comments_guide.md +191 -0
  277. package/skills/minimax-docx/references/design_good_bad_examples.md +829 -0
  278. package/skills/minimax-docx/references/design_principles.md +819 -0
  279. package/skills/minimax-docx/references/openxml_element_order.md +308 -0
  280. package/skills/minimax-docx/references/openxml_encyclopedia_part1.md +4061 -0
  281. package/skills/minimax-docx/references/openxml_encyclopedia_part2.md +2820 -0
  282. package/skills/minimax-docx/references/openxml_encyclopedia_part3.md +3381 -0
  283. package/skills/minimax-docx/references/openxml_namespaces.md +82 -0
  284. package/skills/minimax-docx/references/openxml_units.md +72 -0
  285. package/skills/minimax-docx/references/scenario_a_create.md +284 -0
  286. package/skills/minimax-docx/references/scenario_b_edit_content.md +295 -0
  287. package/skills/minimax-docx/references/scenario_c_apply_template.md +456 -0
  288. package/skills/minimax-docx/references/track_changes_guide.md +200 -0
  289. package/skills/minimax-docx/references/troubleshooting.md +506 -0
  290. package/skills/minimax-docx/references/typography_guide.md +294 -0
  291. package/skills/minimax-docx/references/xsd_validation_guide.md +158 -0
  292. package/skills/minimax-docx/scripts/doc_to_docx.sh +40 -0
  293. package/skills/minimax-docx/scripts/docx_preview.sh +37 -0
  294. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Cli/MiniMaxAIDocx.Cli.csproj +19 -0
  295. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Cli/Program.cs +18 -0
  296. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/AnalyzeCommand.cs +147 -0
  297. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/ApplyTemplateCommand.cs +322 -0
  298. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/CreateCommand.cs +324 -0
  299. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/DiffCommand.cs +155 -0
  300. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/EditContentCommand.cs +487 -0
  301. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/FixOrderCommand.cs +108 -0
  302. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/MergeRunsCommand.cs +122 -0
  303. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/ValidateCommand.cs +107 -0
  304. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/MiniMaxAIDocx.Core.csproj +15 -0
  305. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/CommentSynchronizer.cs +169 -0
  306. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/ElementOrder.cs +80 -0
  307. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/NamespaceConstants.cs +42 -0
  308. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/RunMerger.cs +81 -0
  309. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/StyleAnalyzer.cs +81 -0
  310. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/TrackChangesHelper.cs +99 -0
  311. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/UnitConverter.cs +23 -0
  312. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples.cs +1832 -0
  313. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples_Batch1.cs +910 -0
  314. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples_Batch2.cs +999 -0
  315. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples_Batch3.cs +1048 -0
  316. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples_Batch4.cs +1038 -0
  317. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/CharacterFormattingSamples.cs +1020 -0
  318. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/DocumentCreationSamples.cs +1121 -0
  319. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/FieldAndTocSamples.cs +624 -0
  320. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/FootnoteAndCommentSamples.cs +675 -0
  321. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/HeaderFooterSamples.cs +838 -0
  322. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/ImageSamples.cs +917 -0
  323. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/ListAndNumberingSamples.cs +826 -0
  324. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/ParagraphFormattingSamples.cs +1199 -0
  325. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/StyleSystemSamples.cs +1487 -0
  326. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/TableSamples.cs +1163 -0
  327. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/TrackChangesSamples.cs +595 -0
  328. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Typography/CjkHelper.cs +39 -0
  329. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Typography/FontDefaults.cs +24 -0
  330. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Typography/PageSizes.cs +20 -0
  331. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Validation/BusinessRuleValidator.cs +224 -0
  332. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Validation/GateCheckValidator.cs +148 -0
  333. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Validation/ValidationResult.cs +23 -0
  334. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Validation/XsdValidator.cs +69 -0
  335. package/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.slnx +4 -0
  336. package/skills/minimax-docx/scripts/env_check.sh +196 -0
  337. package/skills/minimax-docx/scripts/setup.ps1 +274 -0
  338. package/skills/minimax-docx/scripts/setup.sh +504 -0
  339. package/skills/minimax-pdf/README.md +222 -0
  340. package/skills/minimax-pdf/SKILL.md +218 -0
  341. package/skills/minimax-pdf/design/design.md +381 -0
  342. package/skills/minimax-pdf/scripts/cover.py +1579 -0
  343. package/skills/minimax-pdf/scripts/fill_inspect.py +200 -0
  344. package/skills/minimax-pdf/scripts/fill_write.py +242 -0
  345. package/skills/minimax-pdf/scripts/make.sh +491 -0
  346. package/skills/minimax-pdf/scripts/merge.py +112 -0
  347. package/skills/minimax-pdf/scripts/palette.py +521 -0
  348. package/skills/minimax-pdf/scripts/reformat_parse.py +374 -0
  349. package/skills/minimax-pdf/scripts/render_body.py +1052 -0
  350. package/skills/minimax-pdf/scripts/render_cover.js +111 -0
  351. package/skills/minimax-xlsx/SKILL.md +156 -0
  352. package/skills/minimax-xlsx/references/create.md +691 -0
  353. package/skills/minimax-xlsx/references/edit.md +684 -0
  354. package/skills/minimax-xlsx/references/fix.md +37 -0
  355. package/skills/minimax-xlsx/references/format.md +768 -0
  356. package/skills/minimax-xlsx/references/ooxml-cheatsheet.md +231 -0
  357. package/skills/minimax-xlsx/references/read-analyze.md +97 -0
  358. package/skills/minimax-xlsx/references/validate.md +772 -0
  359. package/skills/minimax-xlsx/scripts/formula_check.py +422 -0
  360. package/skills/minimax-xlsx/scripts/libreoffice_recalc.py +248 -0
  361. package/skills/minimax-xlsx/scripts/shared_strings_builder.py +163 -0
  362. package/skills/minimax-xlsx/scripts/style_audit.py +575 -0
  363. package/skills/minimax-xlsx/scripts/xlsx_add_column.py +395 -0
  364. package/skills/minimax-xlsx/scripts/xlsx_insert_row.py +274 -0
  365. package/skills/minimax-xlsx/scripts/xlsx_pack.py +87 -0
  366. package/skills/minimax-xlsx/scripts/xlsx_reader.py +362 -0
  367. package/skills/minimax-xlsx/scripts/xlsx_shift_rows.py +396 -0
  368. package/skills/minimax-xlsx/scripts/xlsx_unpack.py +130 -0
  369. package/skills/minimax-xlsx/templates/minimal_xlsx/[Content_Types].xml +9 -0
  370. package/skills/minimax-xlsx/templates/minimal_xlsx/_rels/.rels +6 -0
  371. package/skills/minimax-xlsx/templates/minimal_xlsx/xl/_rels/workbook.xml.rels +19 -0
  372. package/skills/minimax-xlsx/templates/minimal_xlsx/xl/sharedStrings.xml +33 -0
  373. package/skills/minimax-xlsx/templates/minimal_xlsx/xl/styles.xml +160 -0
  374. package/skills/minimax-xlsx/templates/minimal_xlsx/xl/workbook.xml +30 -0
  375. package/skills/minimax-xlsx/templates/minimal_xlsx/xl/worksheets/sheet1.xml +70 -0
  376. package/skills/newsletter-generation/SKILL.md +71 -0
  377. package/skills/oss-release-readiness/SKILL.md +47 -0
  378. package/skills/parallel-subagent-dispatch/SKILL.md +58 -0
  379. package/skills/performance-optimization/SKILL.md +364 -0
  380. package/skills/planning-and-task-breakdown/SKILL.md +290 -0
  381. package/skills/pptx-generator/SKILL.md +275 -0
  382. package/skills/pptx-generator/references/design-system.md +392 -0
  383. package/skills/pptx-generator/references/editing.md +162 -0
  384. package/skills/pptx-generator/references/pitfalls.md +112 -0
  385. package/skills/pptx-generator/references/pptxgenjs.md +420 -0
  386. package/skills/pptx-generator/references/slide-types.md +413 -0
  387. package/skills/pr-test-analysis/SKILL.md +24 -0
  388. package/skills/react-native-dev/SKILL.md +180 -0
  389. package/skills/react-native-dev/references/animations.md +254 -0
  390. package/skills/react-native-dev/references/components.md +124 -0
  391. package/skills/react-native-dev/references/engineering.md +527 -0
  392. package/skills/react-native-dev/references/forms.md +300 -0
  393. package/skills/react-native-dev/references/native-capabilities.md +163 -0
  394. package/skills/react-native-dev/references/navigation.md +271 -0
  395. package/skills/react-native-dev/references/networking.md +346 -0
  396. package/skills/react-native-dev/references/performance.md +215 -0
  397. package/skills/react-native-dev/references/state-management.md +230 -0
  398. package/skills/react-native-dev/references/styling.md +117 -0
  399. package/skills/react-native-dev/references/testing.md +341 -0
  400. package/skills/requirements-grilling/SKILL.md +447 -0
  401. package/skills/requirements-grilling/references/ADR-FORMAT.md +46 -0
  402. package/skills/requirements-grilling/references/CONTEXT-FORMAT.md +41 -0
  403. package/skills/requirements-grilling/references/INTERVIEW-PACKET-FORMAT.md +62 -0
  404. package/skills/requirements-grilling/references/MIT-LICENSE-MATT-POCOCK.md +25 -0
  405. package/skills/requirements-grilling/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/LICENSE +21 -0
  406. package/skills/requirements-grilling/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/NOTICE.md +9 -0
  407. package/skills/requirements-grilling/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/interview-me/SKILL.upstream.md +225 -0
  408. package/skills/requirements-grilling/references/upstream/mattpocock-skills/9603c1cc8118d08bc1b3bf34cf714f62178dea3b/LICENSE +21 -0
  409. package/skills/requirements-grilling/references/upstream/mattpocock-skills/9603c1cc8118d08bc1b3bf34cf714f62178dea3b/NOTICE.md +12 -0
  410. package/skills/requirements-grilling/references/upstream/mattpocock-skills/9603c1cc8118d08bc1b3bf34cf714f62178dea3b/batch-grill-me/SKILL.upstream.md +15 -0
  411. package/skills/requirements-grilling/references/upstream/mattpocock-skills/9603c1cc8118d08bc1b3bf34cf714f62178dea3b/grill-me/SKILL.upstream.md +7 -0
  412. package/skills/requirements-grilling/references/upstream/mattpocock-skills/9603c1cc8118d08bc1b3bf34cf714f62178dea3b/grilling/SKILL.upstream.md +12 -0
  413. package/skills/review-pipeline/SKILL.md +38 -0
  414. package/skills/rose-memory/SKILL.md +210 -0
  415. package/skills/rose-memory/references/README.md +48 -0
  416. package/skills/rose-memory/references/memory_cli.py +2384 -0
  417. package/skills/rose-memory/references/schema.sql +315 -0
  418. package/skills/security-and-hardening/SKILL.md +369 -0
  419. package/skills/session-handoff/SKILL.md +88 -0
  420. package/skills/session-handoff/references/upstream/mattpocock-skills/391a2701dd948f94f56a39f7533f8eea9a859c87/LICENSE +21 -0
  421. package/skills/session-handoff/references/upstream/mattpocock-skills/391a2701dd948f94f56a39f7533f8eea9a859c87/NOTICE.md +9 -0
  422. package/skills/session-handoff/references/upstream/mattpocock-skills/391a2701dd948f94f56a39f7533f8eea9a859c87/productivity/handoff/SKILL.upstream.md +16 -0
  423. package/skills/session-handoff/scripts/session_handoff.py +764 -0
  424. package/skills/shader-dev/SKILL.md +316 -0
  425. package/skills/shader-dev/reference/ambient-occlusion.md +382 -0
  426. package/skills/shader-dev/reference/analytic-ray-tracing.md +651 -0
  427. package/skills/shader-dev/reference/anti-aliasing.md +71 -0
  428. package/skills/shader-dev/reference/atmospheric-scattering.md +571 -0
  429. package/skills/shader-dev/reference/camera-effects.md +80 -0
  430. package/skills/shader-dev/reference/cellular-automata.md +635 -0
  431. package/skills/shader-dev/reference/color-palette.md +481 -0
  432. package/skills/shader-dev/reference/csg-boolean-operations.md +466 -0
  433. package/skills/shader-dev/reference/domain-repetition.md +436 -0
  434. package/skills/shader-dev/reference/domain-warping.md +419 -0
  435. package/skills/shader-dev/reference/fluid-simulation.md +425 -0
  436. package/skills/shader-dev/reference/fractal-rendering.md +525 -0
  437. package/skills/shader-dev/reference/lighting-model.md +639 -0
  438. package/skills/shader-dev/reference/matrix-transform.md +535 -0
  439. package/skills/shader-dev/reference/multipass-buffer.md +571 -0
  440. package/skills/shader-dev/reference/normal-estimation.md +418 -0
  441. package/skills/shader-dev/reference/particle-system.md +589 -0
  442. package/skills/shader-dev/reference/path-tracing-gi.md +602 -0
  443. package/skills/shader-dev/reference/polar-uv-manipulation.md +521 -0
  444. package/skills/shader-dev/reference/post-processing.md +375 -0
  445. package/skills/shader-dev/reference/procedural-2d-pattern.md +439 -0
  446. package/skills/shader-dev/reference/procedural-noise.md +551 -0
  447. package/skills/shader-dev/reference/ray-marching.md +396 -0
  448. package/skills/shader-dev/reference/sdf-2d.md +724 -0
  449. package/skills/shader-dev/reference/sdf-3d.md +805 -0
  450. package/skills/shader-dev/reference/sdf-tricks.md +63 -0
  451. package/skills/shader-dev/reference/shadow-techniques.md +476 -0
  452. package/skills/shader-dev/reference/simulation-physics.md +644 -0
  453. package/skills/shader-dev/reference/sound-synthesis.md +578 -0
  454. package/skills/shader-dev/reference/terrain-rendering.md +839 -0
  455. package/skills/shader-dev/reference/texture-mapping-advanced.md +87 -0
  456. package/skills/shader-dev/reference/texture-sampling.md +553 -0
  457. package/skills/shader-dev/reference/volumetric-rendering.md +608 -0
  458. package/skills/shader-dev/reference/voronoi-cellular-noise.md +486 -0
  459. package/skills/shader-dev/reference/voxel-rendering.md +701 -0
  460. package/skills/shader-dev/reference/water-ocean.md +445 -0
  461. package/skills/shader-dev/reference/webgl-pitfalls.md +41 -0
  462. package/skills/shader-dev/techniques/ambient-occlusion.md +364 -0
  463. package/skills/shader-dev/techniques/analytic-ray-tracing.md +542 -0
  464. package/skills/shader-dev/techniques/anti-aliasing.md +124 -0
  465. package/skills/shader-dev/techniques/atmospheric-scattering.md +522 -0
  466. package/skills/shader-dev/techniques/camera-effects.md +115 -0
  467. package/skills/shader-dev/techniques/cellular-automata.md +531 -0
  468. package/skills/shader-dev/techniques/color-palette.md +380 -0
  469. package/skills/shader-dev/techniques/csg-boolean-operations.md +491 -0
  470. package/skills/shader-dev/techniques/domain-repetition.md +333 -0
  471. package/skills/shader-dev/techniques/domain-warping.md +414 -0
  472. package/skills/shader-dev/techniques/fluid-simulation.md +1175 -0
  473. package/skills/shader-dev/techniques/fractal-rendering.md +436 -0
  474. package/skills/shader-dev/techniques/lighting-model.md +527 -0
  475. package/skills/shader-dev/techniques/matrix-transform.md +455 -0
  476. package/skills/shader-dev/techniques/multipass-buffer.md +922 -0
  477. package/skills/shader-dev/techniques/normal-estimation.md +318 -0
  478. package/skills/shader-dev/techniques/particle-system.md +1203 -0
  479. package/skills/shader-dev/techniques/path-tracing-gi.md +623 -0
  480. package/skills/shader-dev/techniques/polar-uv-manipulation.md +373 -0
  481. package/skills/shader-dev/techniques/post-processing.md +788 -0
  482. package/skills/shader-dev/techniques/procedural-2d-pattern.md +346 -0
  483. package/skills/shader-dev/techniques/procedural-noise.md +554 -0
  484. package/skills/shader-dev/techniques/ray-marching.md +467 -0
  485. package/skills/shader-dev/techniques/sdf-2d.md +631 -0
  486. package/skills/shader-dev/techniques/sdf-3d.md +589 -0
  487. package/skills/shader-dev/techniques/sdf-tricks.md +100 -0
  488. package/skills/shader-dev/techniques/shadow-techniques.md +776 -0
  489. package/skills/shader-dev/techniques/simulation-physics.md +1542 -0
  490. package/skills/shader-dev/techniques/sound-synthesis.md +490 -0
  491. package/skills/shader-dev/techniques/terrain-rendering.md +408 -0
  492. package/skills/shader-dev/techniques/texture-mapping-advanced.md +121 -0
  493. package/skills/shader-dev/techniques/texture-sampling.md +382 -0
  494. package/skills/shader-dev/techniques/volumetric-rendering.md +375 -0
  495. package/skills/shader-dev/techniques/voronoi-cellular-noise.md +458 -0
  496. package/skills/shader-dev/techniques/voxel-rendering.md +985 -0
  497. package/skills/shader-dev/techniques/water-ocean.md +490 -0
  498. package/skills/shader-dev/techniques/webgl-pitfalls.md +170 -0
  499. package/skills/shipping-and-launch/SKILL.md +334 -0
  500. package/skills/silent-failure-hunting/SKILL.md +24 -0
  501. package/skills/source-driven-development/SKILL.md +223 -0
  502. package/skills/spec-driven-development/SKILL.md +235 -0
  503. package/skills/spec-driven-development/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/LICENSE +21 -0
  504. package/skills/spec-driven-development/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/NOTICE.md +9 -0
  505. package/skills/spec-driven-development/references/upstream/addyosmani-agent-skills/6bcfeb9dae52b11eaad23511acc165109746dbc3/spec-driven-development/SKILL.upstream.md +206 -0
  506. package/skills/strategy-stress-test/SKILL.md +185 -0
  507. package/skills/systematic-literature-review/SKILL.md +77 -0
  508. package/skills/test-document-generator/SKILL.md +152 -0
  509. package/skills/test-document-generator/references/test-document-template.md +45 -0
  510. package/skills/test-driven-development/SKILL.md +399 -0
  511. package/skills/write-skills/GLOSSARY.md +113 -0
  512. package/skills/write-skills/SKILL.md +133 -0
  513. package/skills/write-skills/references/upstream/mattpocock-skills/391a2701dd948f94f56a39f7533f8eea9a859c87/LICENSE +21 -0
  514. package/skills/write-skills/references/upstream/mattpocock-skills/391a2701dd948f94f56a39f7533f8eea9a859c87/NOTICE.md +9 -0
  515. package/skills/write-skills/references/upstream/mattpocock-skills/391a2701dd948f94f56a39f7533f8eea9a859c87/productivity/writing-great-skills/GLOSSARY.md +201 -0
  516. package/skills/write-skills/references/upstream/mattpocock-skills/391a2701dd948f94f56a39f7533f8eea9a859c87/productivity/writing-great-skills/SKILL.upstream.md +83 -0
  517. package/src/runtime/child-guard.ts +70 -0
  518. package/src/runtime/conflicts.ts +25 -0
  519. package/src/runtime/contracts.ts +9 -0
  520. package/src/runtime/doctor.ts +134 -0
  521. package/src/runtime/global-resources.ts +152 -0
  522. package/src/runtime/index.ts +23 -0
  523. package/src/runtime/lifecycle.ts +19 -0
  524. package/src/runtime/native-integrations.ts +44 -0
  525. package/src/runtime/path-boundaries.ts +69 -0
  526. package/src/runtime/registry.ts +213 -0
  527. package/src/runtime/roles.ts +64 -0
  528. package/src/runtime/rose-context.ts +24 -0
  529. package/src/runtime/subagents.ts +426 -0
  530. package/templates/APPEND_SYSTEM.md +12 -0
  531. package/upstream/aili-workflows.lock.json +3226 -0
@@ -0,0 +1,764 @@
1
+ #!/usr/bin/env python3
2
+ """Task-scoped immutable session handoff history using only the Python stdlib."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ from datetime import datetime, timezone
8
+ import hashlib
9
+ import json
10
+ import os
11
+ from pathlib import Path
12
+ import re
13
+ import stat
14
+ import sys
15
+ import unicodedata
16
+ import uuid
17
+ from typing import Any, Callable
18
+
19
+
20
+ SCHEMA_VERSION = "1.0"
21
+ MAX_SNAPSHOT_BYTES = 64 * 1024
22
+ MAX_FRONTMATTER_BYTES = 8 * 1024
23
+ SNAPSHOT_NAME = re.compile(r"^(\d{8}T\d{6}Z)--([a-z0-9]+(?:-[a-z0-9]+)*)(?:--(\d{2,}))?\.md$")
24
+ LANGUAGE = re.compile(r"^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{2,8})*$")
25
+ FRONTMATTER_FIELDS = (
26
+ "schema_version",
27
+ "snapshot_id",
28
+ "task_root",
29
+ "status",
30
+ "created_at",
31
+ "finalized_at",
32
+ "language",
33
+ "continues_from",
34
+ "continues_from_sha256",
35
+ "content_sha256",
36
+ )
37
+ CORE_SECTIONS = (
38
+ "Goal",
39
+ "Contract References",
40
+ "Scope Boundary",
41
+ "Completed/Pending/Blocked",
42
+ "Evidence Anchors",
43
+ "Decisions",
44
+ "Open Questions/Risks",
45
+ "Verification State",
46
+ "Next Action",
47
+ "Forbidden Actions",
48
+ "Suggested Next-Session Prompt",
49
+ )
50
+ SPECIALIST_SECTIONS = (
51
+ "Touched Files / Artifact References",
52
+ "A33 Attachments / Owning-Repository Artifact Destinations",
53
+ "Preserved Rollback Worktrees / Evidence References",
54
+ "Subagent Activity",
55
+ "Blocker / Stop Reason",
56
+ )
57
+ PLACEHOLDER = re.compile(r"\[(?:FILL|TODO|TBD)(?::[^\]]*)?\]|\b(?:TODO|TBD)\b", re.IGNORECASE)
58
+ FORBIDDEN_HEADING = re.compile(r"^##\s+(?:Raw Logs?|Full (?:File|Transcript)s?|Private Data)\s*$", re.IGNORECASE | re.MULTILINE)
59
+ SECRET_PATTERNS = (
60
+ re.compile(r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----"),
61
+ re.compile(r"\bAKIA[0-9A-Z]{16}\b"),
62
+ re.compile(r"\bgh[pousr]_[A-Za-z0-9]{20,}\b"),
63
+ re.compile(r"(?i)\b(?:api[_-]?key|access[_-]?token|password|passwd|secret|authorization|cookie)\s*[:=]\s*(?!(?:\[REDACTED\]|<REDACTED>|REDACTED)(?:[\s.,;:)]|$))[^\s`]{6,}"),
64
+ re.compile(r"(?i)https?://[^/\s:@]+:[^@\s/]+@"),
65
+ )
66
+ BACKTICK_ABSOLUTE_PATH = re.compile(r"`(/[^`\n]+)`")
67
+ FORBIDDEN_ACTIONS_TEXT = "Do not infer contract, permission, Git truth, verification, completion, publication, or destructive authority from this handoff."
68
+
69
+
70
+ class HandoffError(ValueError):
71
+ pass
72
+
73
+
74
+ class HandoffResolveError(HandoffError):
75
+ def __init__(self, message: str, candidates: list[str]) -> None:
76
+ super().__init__(message)
77
+ self.candidates = candidates
78
+
79
+
80
+ def normalize_slug(value: str) -> str:
81
+ normalized = unicodedata.normalize("NFKD", value).encode("ascii", "ignore").decode("ascii").lower()
82
+ slug = re.sub(r"[^a-z0-9]+", "-", normalized).strip("-")[:48].rstrip("-")
83
+ return slug or "handoff"
84
+
85
+
86
+ def new_snapshot(
87
+ repository_root: str | Path,
88
+ task_root: str | Path,
89
+ slug: str,
90
+ *,
91
+ language: str = "en",
92
+ continues_from: str | Path | None = None,
93
+ now: datetime | None = None,
94
+ ) -> dict[str, Any]:
95
+ repository, task = _validated_roots(repository_root, task_root)
96
+ if not LANGUAGE.fullmatch(language):
97
+ raise HandoffError(f"invalid language tag: {language!r}")
98
+ current = now or datetime.now(timezone.utc)
99
+ if current.tzinfo is None:
100
+ raise HandoffError("new snapshot time must be timezone-aware")
101
+ current = current.astimezone(timezone.utc)
102
+ timestamp = current.strftime("%Y%m%dT%H%M%SZ")
103
+ created_at = current.isoformat().replace("+00:00", "Z")
104
+ safe_slug = normalize_slug(slug)
105
+ history = _history_directory(task, create=continues_from is None)
106
+ predecessor_path: str | None = None
107
+ predecessor_digest: str | None = None
108
+ if continues_from is not None:
109
+ predecessor = _resolve_snapshot_path(repository, task, history, continues_from)
110
+ loaded = _load_finalized_snapshot(repository, task, history, predecessor)
111
+ predecessor_path = predecessor.relative_to(task).as_posix()
112
+ predecessor_digest = loaded["sha256"]
113
+
114
+ sequence = 1
115
+ while True:
116
+ suffix = "" if sequence == 1 else f"--{sequence:02d}"
117
+ name = f"{timestamp}--{safe_slug}{suffix}.md"
118
+ target = history / name
119
+ snapshot_id = target.stem
120
+ metadata = {
121
+ "schema_version": SCHEMA_VERSION,
122
+ "snapshot_id": snapshot_id,
123
+ "task_root": _relative_task_root(repository, task),
124
+ "status": "draft",
125
+ "created_at": created_at,
126
+ "finalized_at": None,
127
+ "language": language,
128
+ "continues_from": predecessor_path,
129
+ "continues_from_sha256": predecessor_digest,
130
+ "content_sha256": None,
131
+ }
132
+ body = _draft_body(snapshot_id, target.relative_to(repository).as_posix(), safe_slug, language)
133
+ document = _render_document(metadata, body)
134
+ try:
135
+ descriptor = os.open(target, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
136
+ except FileExistsError:
137
+ sequence += 1
138
+ continue
139
+ try:
140
+ with os.fdopen(descriptor, "w", encoding="utf-8", newline="\n") as handle:
141
+ handle.write(document)
142
+ handle.flush()
143
+ os.fsync(handle.fileno())
144
+ except Exception:
145
+ target.unlink(missing_ok=True)
146
+ raise
147
+ return {
148
+ "status": "draft",
149
+ "snapshot_id": snapshot_id,
150
+ "path": target.relative_to(repository).as_posix(),
151
+ "task_path": target.relative_to(task).as_posix(),
152
+ "continues_from": predecessor_path,
153
+ }
154
+
155
+
156
+ def finalize_snapshot(
157
+ repository_root: str | Path,
158
+ task_root: str | Path,
159
+ snapshot: str | Path,
160
+ *,
161
+ allowed_external_roots: tuple[str | Path, ...] = (),
162
+ replace: Callable[[str | bytes | os.PathLike[str] | os.PathLike[bytes], str | bytes | os.PathLike[str] | os.PathLike[bytes]], None] = os.replace,
163
+ now: datetime | None = None,
164
+ ) -> dict[str, Any]:
165
+ repository, task = _validated_roots(repository_root, task_root)
166
+ history = _history_directory(task, create=False)
167
+ target = _resolve_snapshot_path(repository, task, history, snapshot)
168
+ text = _read_regular_text(target, MAX_SNAPSHOT_BYTES)
169
+ metadata, body = _parse_document(text)
170
+ _validate_snapshot_identity(repository, task, target, metadata)
171
+ if metadata["status"] != "draft":
172
+ raise HandoffError("only a draft snapshot can be finalized")
173
+ _validate_body(
174
+ body,
175
+ repository,
176
+ target,
177
+ metadata,
178
+ _validated_external_roots(allowed_external_roots),
179
+ )
180
+ _validate_predecessor(repository, task, history, metadata)
181
+ _validate_existing_pointer(repository, task, history)
182
+
183
+ current = now or datetime.now(timezone.utc)
184
+ if current.tzinfo is None:
185
+ raise HandoffError("finalization time must be timezone-aware")
186
+ finalized_at = current.astimezone(timezone.utc).isoformat().replace("+00:00", "Z")
187
+ if _parse_utc(finalized_at, "finalized_at") < _parse_utc(metadata["created_at"], "created_at"):
188
+ raise HandoffError("snapshot finalized_at precedes created_at")
189
+ finalized = dict(metadata)
190
+ finalized["status"] = "finalized"
191
+ finalized["finalized_at"] = finalized_at
192
+ finalized["content_sha256"] = None
193
+ canonical = _render_document(finalized, body)
194
+ finalized["content_sha256"] = hashlib.sha256(canonical.encode("utf-8")).hexdigest()
195
+ final_text = _render_document(finalized, body)
196
+ if len(final_text.encode("utf-8")) > MAX_SNAPSHOT_BYTES:
197
+ raise HandoffError(f"finalized snapshot exceeds {MAX_SNAPSHOT_BYTES} bytes")
198
+ _atomic_write(target, final_text, replace=os.replace)
199
+ snapshot_digest = hashlib.sha256(final_text.encode("utf-8")).hexdigest()
200
+
201
+ pointer = history / "LATEST.md"
202
+ pointer_text = _render_pointer(target.relative_to(task).as_posix(), finalized, snapshot_digest)
203
+ try:
204
+ _atomic_write(pointer, pointer_text, replace=replace)
205
+ except Exception as exc:
206
+ raise HandoffError(f"snapshot finalized but LATEST.md replacement failed: {exc}") from exc
207
+ resolved = _resolve_pointer(repository, task, history)
208
+ if resolved["sha256"] != snapshot_digest or resolved["path"] != target.relative_to(repository).as_posix():
209
+ raise HandoffError("LATEST.md post-write verification failed")
210
+ return {
211
+ "status": "finalized",
212
+ "snapshot_id": finalized["snapshot_id"],
213
+ "path": target.relative_to(repository).as_posix(),
214
+ "task_path": target.relative_to(task).as_posix(),
215
+ "sha256": snapshot_digest,
216
+ "finalized_at": finalized_at,
217
+ "language": finalized["language"],
218
+ "latest_path": pointer.relative_to(repository).as_posix(),
219
+ "resume_prompt": build_resume_prompt(target.relative_to(repository).as_posix(), str(finalized["language"])),
220
+ }
221
+
222
+
223
+ def list_history(repository_root: str | Path, task_root: str | Path) -> dict[str, Any]:
224
+ repository, task = _validated_roots(repository_root, task_root)
225
+ try:
226
+ history = _history_directory(task, create=False)
227
+ except HandoffError as exc:
228
+ if "does not exist" in str(exc):
229
+ return {"history": (task / "handoffs").relative_to(repository).as_posix(), "snapshots": [], "invalid": []}
230
+ raise
231
+ snapshots: list[dict[str, Any]] = []
232
+ invalid: list[dict[str, str]] = []
233
+ seen_ids: dict[str, str] = {}
234
+ for entry in sorted(history.iterdir(), key=_history_entry_sort_key, reverse=True):
235
+ if entry.name == "LATEST.md":
236
+ continue
237
+ if not SNAPSHOT_NAME.fullmatch(entry.name):
238
+ invalid.append({"path": entry.relative_to(repository).as_posix(), "reason": "unrecognized history entry"})
239
+ continue
240
+ try:
241
+ text = _read_regular_prefix(entry, MAX_FRONTMATTER_BYTES)
242
+ metadata = _parse_frontmatter(text)
243
+ _validate_snapshot_identity(repository, task, entry, metadata)
244
+ row = {
245
+ "path": entry.relative_to(repository).as_posix(),
246
+ "task_path": entry.relative_to(task).as_posix(),
247
+ "snapshot_id": metadata["snapshot_id"],
248
+ "status": metadata["status"],
249
+ "created_at": metadata["created_at"],
250
+ "finalized_at": metadata["finalized_at"],
251
+ "language": metadata["language"],
252
+ "continues_from": metadata["continues_from"],
253
+ }
254
+ previous = seen_ids.get(str(metadata["snapshot_id"]))
255
+ if previous:
256
+ invalid.append({"path": row["path"], "reason": f"duplicate snapshot_id also used by {previous}"})
257
+ continue
258
+ seen_ids[str(metadata["snapshot_id"])] = row["path"]
259
+ snapshots.append(row)
260
+ except (HandoffError, OSError, UnicodeError, json.JSONDecodeError) as exc:
261
+ invalid.append({"path": entry.relative_to(repository).as_posix(), "reason": str(exc)})
262
+ return {"history": history.relative_to(repository).as_posix(), "snapshots": snapshots, "invalid": invalid}
263
+
264
+
265
+ def resolve_snapshot(
266
+ repository_root: str | Path,
267
+ task_root: str | Path,
268
+ *,
269
+ snapshot: str | Path | None = None,
270
+ legacy: bool = False,
271
+ ) -> dict[str, Any]:
272
+ repository, task = _validated_roots(repository_root, task_root)
273
+ if legacy:
274
+ if snapshot is not None:
275
+ raise HandoffError("legacy and exact snapshot selection are mutually exclusive")
276
+ target = task / "handoff.md"
277
+ text = _read_regular_text(target, MAX_SNAPSHOT_BYTES)
278
+ return {
279
+ "status": "legacy",
280
+ "legacy": True,
281
+ "path": target.relative_to(repository).as_posix(),
282
+ "sha256": hashlib.sha256(text.encode("utf-8")).hexdigest(),
283
+ "content": text,
284
+ "authority": "navigation-only; revalidate all current state",
285
+ }
286
+ history = _history_directory(task, create=False)
287
+ if snapshot is not None:
288
+ target = _resolve_snapshot_path(repository, task, history, snapshot)
289
+ loaded = _load_finalized_snapshot(repository, task, history, target)
290
+ return {**loaded, "resolution": "exact", "legacy": False}
291
+ try:
292
+ return {**_resolve_pointer(repository, task, history), "resolution": "latest", "legacy": False}
293
+ except HandoffError as exc:
294
+ inventory = list_history(repository, task)
295
+ candidates = [
296
+ str(row["path"])
297
+ for row in inventory["snapshots"]
298
+ if row.get("status") == "finalized"
299
+ ][:10]
300
+ raise HandoffResolveError(str(exc), candidates) from exc
301
+
302
+
303
+ def build_resume_prompt(snapshot_path: str, language: str) -> str:
304
+ if language.lower().startswith("zh"):
305
+ return (
306
+ f"从精确的不可变 handoff 快照 `{snapshot_path}` 恢复。它只用于导航,不是合同、权限、Git 真相、验证或完成证据。"
307
+ "先重新验证当前 repository root、worktree、branch/HEAD、dirty 状态、权限、合同、附件和引用证据,并简要重述当前 scope;"
308
+ "遇到冲突或 Unverified 项立即停止受影响工作,然后只从快照的 Next Action 继续。"
309
+ )
310
+ return (
311
+ f"Resume from the exact immutable handoff snapshot `{snapshot_path}`. Treat it as navigation only, not contract, permission, Git truth, verification, or completion evidence. "
312
+ "First revalidate the current repository root, worktree, branch/HEAD, dirty state, permissions, contracts, attachments, and referenced evidence, and restate the current scope concisely. "
313
+ "Stop affected work on any conflict or Unverified item, then continue only from the snapshot's Next Action."
314
+ )
315
+
316
+
317
+ def format_create_success(snapshot_path: str, language: str) -> str:
318
+ path = Path(snapshot_path)
319
+ if path.is_absolute() or any(part in {"", ".", ".."} for part in path.parts) or "`" in snapshot_path or "\n" in snapshot_path:
320
+ raise HandoffError("successful CREATE output requires one normalized repository-relative snapshot path")
321
+ prompt = build_resume_prompt(path.as_posix(), language)
322
+ if language.lower().startswith("zh"):
323
+ lead = f"[工具结果] 已最终化 handoff 快照:`{path.as_posix()}`。"
324
+ else:
325
+ lead = f"[COMPUTED] Finalized handoff snapshot: `{path.as_posix()}`."
326
+ return f"{lead}\n\n```text\n{prompt}\n```"
327
+
328
+
329
+ def _validated_roots(repository_root: str | Path, task_root: str | Path) -> tuple[Path, Path]:
330
+ repository = _canonical_directory(repository_root, "repository root")
331
+ task = _canonical_directory(task_root, "task root")
332
+ if task != repository and repository not in task.parents:
333
+ raise HandoffError(f"task root is outside repository root: {task}")
334
+ _assert_no_symlink_below(repository, task)
335
+ return repository, task
336
+
337
+
338
+ def _relative_task_root(repository: Path, task: Path) -> str:
339
+ relative = task.relative_to(repository).as_posix()
340
+ return relative or "."
341
+
342
+
343
+ def _validated_external_roots(values: tuple[str | Path, ...]) -> tuple[Path, ...]:
344
+ roots: list[Path] = []
345
+ for index, value in enumerate(values):
346
+ root = _canonical_directory(value, f"allowed external root {index + 1}")
347
+ if root not in roots:
348
+ roots.append(root)
349
+ return tuple(roots)
350
+
351
+
352
+ def _canonical_directory(value: str | Path, label: str) -> Path:
353
+ raw = Path(value)
354
+ if not raw.is_absolute():
355
+ raise HandoffError(f"{label} must be an absolute canonical path")
356
+ lexical = Path(os.path.abspath(os.fspath(raw)))
357
+ try:
358
+ canonical = raw.resolve(strict=True)
359
+ except OSError as exc:
360
+ raise HandoffError(f"{label} does not exist: {raw}") from exc
361
+ if lexical != canonical or not canonical.is_dir():
362
+ raise HandoffError(f"{label} is ambiguous, symlinked, or not a directory: {raw}")
363
+ return canonical
364
+
365
+
366
+ def _assert_no_symlink_below(parent: Path, target: Path) -> None:
367
+ relative = target.relative_to(parent)
368
+ current = parent
369
+ for part in relative.parts:
370
+ current = current / part
371
+ if stat.S_ISLNK(current.lstat().st_mode):
372
+ raise HandoffError(f"symlinked task-root component is forbidden: {current}")
373
+
374
+
375
+ def _history_directory(task: Path, *, create: bool) -> Path:
376
+ history = task / "handoffs"
377
+ if create:
378
+ try:
379
+ history.mkdir(mode=0o700)
380
+ except FileExistsError:
381
+ pass
382
+ try:
383
+ mode = history.lstat().st_mode
384
+ except FileNotFoundError as exc:
385
+ raise HandoffError(f"handoff history does not exist: {history}") from exc
386
+ if stat.S_ISLNK(mode) or not stat.S_ISDIR(mode):
387
+ raise HandoffError(f"handoff history is not a regular directory: {history}")
388
+ return history
389
+
390
+
391
+ def _history_entry_sort_key(path: Path) -> tuple[int, str, int, str]:
392
+ match = SNAPSHOT_NAME.fullmatch(path.name)
393
+ if match is None:
394
+ return (0, "", 0, path.name)
395
+ sequence = int(match.group(3)) if match.group(3) is not None else 1
396
+ return (1, match.group(1), sequence, path.name)
397
+
398
+
399
+ def _resolve_snapshot_path(repository: Path, task: Path, history: Path, value: str | Path) -> Path:
400
+ raw = Path(value)
401
+ if any(part in {".", ".."} for part in raw.parts):
402
+ raise HandoffError(f"snapshot path is not normalized: {value}")
403
+ if raw.is_absolute():
404
+ candidates = [Path(os.path.abspath(os.fspath(raw)))]
405
+ else:
406
+ candidates = [repository / raw, task / raw]
407
+ matches: list[Path] = []
408
+ for candidate in candidates:
409
+ if candidate.parent == history and SNAPSHOT_NAME.fullmatch(candidate.name) and candidate not in matches:
410
+ matches.append(candidate)
411
+ if len(matches) != 1:
412
+ raise HandoffError(f"snapshot must be one exact file directly under {history}: {value}")
413
+ candidate = matches[0]
414
+ try:
415
+ mode = candidate.lstat().st_mode
416
+ except FileNotFoundError as exc:
417
+ raise HandoffError(f"snapshot does not exist: {candidate}") from exc
418
+ if stat.S_ISLNK(mode) or not stat.S_ISREG(mode):
419
+ raise HandoffError(f"snapshot is not a regular non-symlink file: {candidate}")
420
+ return candidate
421
+
422
+
423
+ def _draft_body(snapshot_id: str, repository_path: str, slug: str, language: str) -> str:
424
+ prompt = build_resume_prompt(repository_path, language)
425
+ fill = {
426
+ "Goal": "[FILL: concise goal]",
427
+ "Contract References": "[FILL: paths and accepted decisions only]",
428
+ "Scope Boundary": "[FILL: in-scope and out-of-scope boundary]",
429
+ "Completed/Pending/Blocked": "[FILL: bounded status]",
430
+ "Evidence Anchors": "[FILL: compact reproducible anchors]",
431
+ "Decisions": "[FILL: accepted decisions and sources]",
432
+ "Open Questions/Risks": "[FILL: Open Question / Unverified items or None]",
433
+ "Verification State": "[FILL: fresh, stale, skipped, or unavailable checks]",
434
+ "Next Action": "[FILL: one exact next action]",
435
+ "Forbidden Actions": FORBIDDEN_ACTIONS_TEXT,
436
+ "Suggested Next-Session Prompt": prompt,
437
+ }
438
+ sections = "\n\n".join(f"## {name}\n\n{fill[name]}" for name in CORE_SECTIONS)
439
+ return f"# Session Handoff: {slug}\n\nSnapshot ID: `{snapshot_id}`\n\n{sections}\n"
440
+
441
+
442
+ def _render_document(metadata: dict[str, Any], body: str) -> str:
443
+ lines = ["---"]
444
+ for field in FRONTMATTER_FIELDS:
445
+ lines.append(f"{field}: {json.dumps(metadata.get(field), ensure_ascii=False, separators=(',', ':'))}")
446
+ lines.extend(["---", body.rstrip(), ""])
447
+ return "\n".join(lines)
448
+
449
+
450
+ def _parse_document(text: str) -> tuple[dict[str, Any], str]:
451
+ metadata = _parse_frontmatter(text)
452
+ closing = text.find("\n---\n", 4)
453
+ if closing < 0:
454
+ raise HandoffError("snapshot frontmatter closing delimiter is missing")
455
+ return metadata, text[closing + 5 :]
456
+
457
+
458
+ def _parse_frontmatter(text: str) -> dict[str, Any]:
459
+ if not text.startswith("---\n"):
460
+ raise HandoffError("snapshot frontmatter opening delimiter is missing")
461
+ closing = text.find("\n---\n", 4)
462
+ if closing < 0 or closing > MAX_FRONTMATTER_BYTES:
463
+ raise HandoffError("snapshot frontmatter is missing or exceeds the bounded limit")
464
+ values: dict[str, Any] = {}
465
+ for line in text[4:closing].splitlines():
466
+ if ": " not in line:
467
+ raise HandoffError(f"invalid frontmatter line: {line!r}")
468
+ key, encoded = line.split(": ", 1)
469
+ if key in values:
470
+ raise HandoffError(f"duplicate frontmatter field: {key}")
471
+ try:
472
+ values[key] = json.loads(encoded)
473
+ except json.JSONDecodeError as exc:
474
+ raise HandoffError(f"invalid frontmatter value for {key}") from exc
475
+ if tuple(values) != FRONTMATTER_FIELDS:
476
+ raise HandoffError("snapshot frontmatter fields/order differ from the exact envelope")
477
+ if values["schema_version"] != SCHEMA_VERSION:
478
+ raise HandoffError("unsupported snapshot schema_version")
479
+ if not isinstance(values["status"], str) or values["status"] not in {"draft", "finalized"}:
480
+ raise HandoffError("snapshot status must be draft or finalized")
481
+ if not isinstance(values["snapshot_id"], str):
482
+ raise HandoffError("snapshot_id is invalid")
483
+ task_root = values["task_root"]
484
+ if not isinstance(task_root, str) or not task_root or Path(task_root).is_absolute() or any(part in {"", ".."} for part in Path(task_root).parts):
485
+ raise HandoffError("snapshot task_root reference is invalid")
486
+ if not isinstance(values["language"], str) or not LANGUAGE.fullmatch(values["language"]):
487
+ raise HandoffError("snapshot language is invalid")
488
+ _parse_utc(values["created_at"], "created_at")
489
+ if values["status"] == "draft":
490
+ if values["finalized_at"] is not None or values["content_sha256"] is not None:
491
+ raise HandoffError("draft snapshot cannot carry finalization fields")
492
+ else:
493
+ _parse_utc(values["finalized_at"], "finalized_at")
494
+ if not isinstance(values["content_sha256"], str) or not re.fullmatch(r"[0-9a-f]{64}", values["content_sha256"]):
495
+ raise HandoffError("finalized snapshot content_sha256 is invalid")
496
+ predecessor = values["continues_from"]
497
+ predecessor_digest = values["continues_from_sha256"]
498
+ if (predecessor is None) != (predecessor_digest is None):
499
+ raise HandoffError("continues_from and its SHA-256 must be present together")
500
+ if predecessor is not None and (
501
+ not isinstance(predecessor, str)
502
+ or not isinstance(predecessor_digest, str)
503
+ or not re.fullmatch(r"[0-9a-f]{64}", predecessor_digest)
504
+ ):
505
+ raise HandoffError("continues_from metadata is invalid")
506
+ return values
507
+
508
+
509
+ def _parse_utc(value: Any, field: str) -> datetime:
510
+ if not isinstance(value, str) or not value.endswith("Z"):
511
+ raise HandoffError(f"snapshot {field} is invalid")
512
+ try:
513
+ parsed = datetime.fromisoformat(value[:-1] + "+00:00")
514
+ except ValueError as exc:
515
+ raise HandoffError(f"snapshot {field} is invalid") from exc
516
+ if parsed.utcoffset() != timezone.utc.utcoffset(parsed):
517
+ raise HandoffError(f"snapshot {field} is not UTC")
518
+ return parsed
519
+
520
+
521
+ def _validate_snapshot_identity(repository: Path, task: Path, path: Path, metadata: dict[str, Any]) -> None:
522
+ match = SNAPSHOT_NAME.fullmatch(path.name)
523
+ if not match:
524
+ raise HandoffError(f"invalid snapshot filename: {path.name}")
525
+ if metadata.get("snapshot_id") != path.stem:
526
+ raise HandoffError("snapshot_id does not match filename")
527
+ if metadata.get("task_root") != _relative_task_root(repository, task):
528
+ raise HandoffError("snapshot task_root does not match the current task root")
529
+ created_at = _parse_utc(metadata.get("created_at"), "created_at")
530
+ if created_at.strftime("%Y%m%dT%H%M%SZ") != match.group(1):
531
+ raise HandoffError("snapshot created_at does not match filename")
532
+ if metadata["status"] == "finalized":
533
+ finalized_at = _parse_utc(metadata.get("finalized_at"), "finalized_at")
534
+ if finalized_at < created_at:
535
+ raise HandoffError("snapshot finalized_at precedes created_at")
536
+
537
+
538
+ def _validate_body(
539
+ body: str,
540
+ repository: Path,
541
+ target: Path,
542
+ metadata: dict[str, Any],
543
+ allowed_external_roots: tuple[Path, ...],
544
+ ) -> None:
545
+ if PLACEHOLDER.search(body):
546
+ raise HandoffError("snapshot contains an unresolved placeholder")
547
+ if FORBIDDEN_HEADING.search(body):
548
+ raise HandoffError("snapshot contains a forbidden raw/full/private section")
549
+ for pattern in SECRET_PATTERNS:
550
+ if pattern.search(body):
551
+ raise HandoffError("snapshot contains secret-like material")
552
+ headings = re.findall(r"^##\s+(.+?)\s*$", body, re.MULTILINE)
553
+ allowed_headings = set(CORE_SECTIONS) | set(SPECIALIST_SECTIONS)
554
+ unknown = [heading for heading in headings if heading not in allowed_headings]
555
+ if unknown:
556
+ raise HandoffError(f"snapshot contains unknown recovery section: {unknown[0]}")
557
+ for section in CORE_SECTIONS:
558
+ if headings.count(section) != 1:
559
+ raise HandoffError(f"snapshot must contain exactly one core section: {section}")
560
+ if not _section_content(body, section):
561
+ raise HandoffError(f"snapshot core section is empty: {section}")
562
+ for section in SPECIALIST_SECTIONS:
563
+ if headings.count(section) > 1:
564
+ raise HandoffError(f"snapshot specialist section is duplicated: {section}")
565
+ if headings.count(section) == 1 and not _section_content(body, section):
566
+ raise HandoffError(f"snapshot specialist section is empty: {section}")
567
+ expected_prompt = build_resume_prompt(target.relative_to(repository).as_posix(), str(metadata["language"]))
568
+ if _section_content(body, "Forbidden Actions") != FORBIDDEN_ACTIONS_TEXT:
569
+ raise HandoffError("snapshot Forbidden Actions differs from the fixed non-authority boundary")
570
+ if _section_content(body, "Suggested Next-Session Prompt") != expected_prompt:
571
+ raise HandoffError("snapshot Suggested Next-Session Prompt differs from the fixed exact-path recovery prompt")
572
+ for fenced in re.findall(r"```[^\n]*\n(.*?)```", body, re.DOTALL):
573
+ if len(fenced.splitlines()) > 40:
574
+ raise HandoffError("snapshot contains an oversized code/log/file block")
575
+ roots = (repository, *allowed_external_roots)
576
+ for match in BACKTICK_ABSOLUTE_PATH.finditer(body):
577
+ candidate = Path(os.path.abspath(match.group(1)))
578
+ if not any(candidate == root or root in candidate.parents for root in roots):
579
+ raise HandoffError(f"snapshot references an unapproved external path: {candidate}")
580
+
581
+
582
+ def _section_content(body: str, section: str) -> str:
583
+ match = re.search(rf"^##\s+{re.escape(section)}\s*$\n(.*?)(?=^##\s+|\Z)", body, re.MULTILINE | re.DOTALL)
584
+ return match.group(1).strip() if match else ""
585
+
586
+
587
+ def _validate_predecessor(repository: Path, task: Path, history: Path, metadata: dict[str, Any]) -> None:
588
+ predecessor = metadata.get("continues_from")
589
+ predecessor_digest = metadata.get("continues_from_sha256")
590
+ if predecessor is None and predecessor_digest is None:
591
+ return
592
+ if not isinstance(predecessor, str) or not isinstance(predecessor_digest, str) or not re.fullmatch(r"[0-9a-f]{64}", predecessor_digest):
593
+ raise HandoffError("continues_from and its SHA-256 must be present together")
594
+ path = _resolve_snapshot_path(repository, task, history, predecessor)
595
+ loaded = _load_finalized_snapshot(repository, task, history, path)
596
+ if loaded["sha256"] != predecessor_digest:
597
+ raise HandoffError("continues_from snapshot digest changed")
598
+
599
+
600
+ def _load_finalized_snapshot(repository: Path, task: Path, history: Path, path: Path) -> dict[str, Any]:
601
+ text = _read_regular_text(path, MAX_SNAPSHOT_BYTES)
602
+ metadata, body = _parse_document(text)
603
+ _validate_snapshot_identity(repository, task, path, metadata)
604
+ if metadata["status"] != "finalized":
605
+ raise HandoffError(f"snapshot is not finalized: {path}")
606
+ _validate_content_digest(metadata, body)
607
+ digest = hashlib.sha256(text.encode("utf-8")).hexdigest()
608
+ return {
609
+ "status": "finalized",
610
+ "path": path.relative_to(repository).as_posix(),
611
+ "task_path": path.relative_to(task).as_posix(),
612
+ "snapshot_id": metadata["snapshot_id"],
613
+ "sha256": digest,
614
+ "finalized_at": metadata["finalized_at"],
615
+ "language": metadata["language"],
616
+ "continues_from": metadata["continues_from"],
617
+ "content": text,
618
+ "authority": "navigation-only; revalidate all current state",
619
+ }
620
+
621
+
622
+ def _validate_content_digest(metadata: dict[str, Any], body: str) -> None:
623
+ expected = metadata.get("content_sha256")
624
+ if not isinstance(expected, str) or not re.fullmatch(r"[0-9a-f]{64}", expected):
625
+ raise HandoffError("finalized snapshot content_sha256 is invalid")
626
+ canonical_metadata = dict(metadata)
627
+ canonical_metadata["content_sha256"] = None
628
+ actual = hashlib.sha256(_render_document(canonical_metadata, body).encode("utf-8")).hexdigest()
629
+ if actual != expected:
630
+ raise HandoffError("finalized snapshot SHA-256 mismatch")
631
+
632
+
633
+ def _validate_existing_pointer(repository: Path, task: Path, history: Path) -> None:
634
+ pointer = history / "LATEST.md"
635
+ if pointer.exists() or pointer.is_symlink():
636
+ _resolve_pointer(repository, task, history)
637
+
638
+
639
+ def _render_pointer(snapshot_path: str, metadata: dict[str, Any], digest: str) -> str:
640
+ return (
641
+ "# Latest Session Handoff\n\n"
642
+ f"snapshot_path: `{snapshot_path}`\n"
643
+ f"snapshot_id: `{metadata['snapshot_id']}`\n"
644
+ f"sha256: `{digest}`\n"
645
+ f"finalized_at: `{metadata['finalized_at']}`\n"
646
+ )
647
+
648
+
649
+ def _resolve_pointer(repository: Path, task: Path, history: Path) -> dict[str, Any]:
650
+ pointer = history / "LATEST.md"
651
+ text = _read_regular_text(pointer, MAX_FRONTMATTER_BYTES)
652
+ match = re.fullmatch(
653
+ r"# Latest Session Handoff\n\n"
654
+ r"snapshot_path: `([^`]+)`\n"
655
+ r"snapshot_id: `([^`]+)`\n"
656
+ r"sha256: `([0-9a-f]{64})`\n"
657
+ r"finalized_at: `([^`]+)`\n",
658
+ text,
659
+ )
660
+ if match is None:
661
+ raise HandoffError("LATEST.md is malformed")
662
+ snapshot_path, snapshot_id, expected_digest, finalized_at = match.groups()
663
+ target = _resolve_snapshot_path(repository, task, history, snapshot_path)
664
+ loaded = _load_finalized_snapshot(repository, task, history, target)
665
+ if loaded["snapshot_id"] != snapshot_id or loaded["sha256"] != expected_digest or loaded["finalized_at"] != finalized_at:
666
+ raise HandoffError("LATEST.md target metadata or digest mismatch")
667
+ return {**loaded, "latest_path": pointer.relative_to(repository).as_posix()}
668
+
669
+
670
+ def _read_regular_text(path: Path, limit: int) -> str:
671
+ mode = path.lstat().st_mode
672
+ if stat.S_ISLNK(mode) or not stat.S_ISREG(mode):
673
+ raise HandoffError(f"path is not a regular non-symlink file: {path}")
674
+ if path.stat().st_size > limit:
675
+ raise HandoffError(f"file exceeds {limit} bytes: {path}")
676
+ return path.read_text(encoding="utf-8")
677
+
678
+
679
+ def _read_regular_prefix(path: Path, limit: int) -> str:
680
+ mode = path.lstat().st_mode
681
+ if stat.S_ISLNK(mode) or not stat.S_ISREG(mode):
682
+ raise HandoffError(f"path is not a regular non-symlink file: {path}")
683
+ with path.open("rb") as handle:
684
+ prefix = handle.read(limit + 1)
685
+ closing = prefix.find(b"\n---\n", 4)
686
+ if closing < 0 or closing > limit:
687
+ raise HandoffError("snapshot frontmatter is missing or exceeds the bounded limit")
688
+ return prefix[: closing + 5].decode("utf-8")
689
+
690
+
691
+ def _atomic_write(path: Path, text: str, *, replace: Callable[..., None]) -> None:
692
+ if path.exists() or path.is_symlink():
693
+ mode = path.lstat().st_mode
694
+ if stat.S_ISLNK(mode) or not stat.S_ISREG(mode):
695
+ raise HandoffError(f"atomic target is not a regular non-symlink file: {path}")
696
+ temporary = path.parent / f".{path.name}.{os.getpid()}.{uuid.uuid4().hex}.tmp"
697
+ descriptor = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
698
+ try:
699
+ with os.fdopen(descriptor, "w", encoding="utf-8", newline="\n") as handle:
700
+ handle.write(text)
701
+ handle.flush()
702
+ os.fsync(handle.fileno())
703
+ replace(temporary, path)
704
+ try:
705
+ directory = os.open(path.parent, os.O_RDONLY)
706
+ try:
707
+ os.fsync(directory)
708
+ finally:
709
+ os.close(directory)
710
+ except OSError:
711
+ pass
712
+ finally:
713
+ temporary.unlink(missing_ok=True)
714
+
715
+
716
+ def _command_parser() -> argparse.ArgumentParser:
717
+ parser = argparse.ArgumentParser(description=__doc__)
718
+ subparsers = parser.add_subparsers(dest="operation", required=True)
719
+ for name in ("new", "finalize", "list", "resolve"):
720
+ command = subparsers.add_parser(name)
721
+ command.add_argument("--repository-root", required=True)
722
+ command.add_argument("--task-root", required=True)
723
+ create = subparsers.choices["new"]
724
+ create.add_argument("--slug", required=True)
725
+ create.add_argument("--language", default="en")
726
+ create.add_argument("--continues-from")
727
+ finalize = subparsers.choices["finalize"]
728
+ finalize.add_argument("--snapshot", required=True)
729
+ finalize.add_argument("--allowed-external-root", action="append", default=[])
730
+ finalize.add_argument("--user-output", action="store_true")
731
+ resolve = subparsers.choices["resolve"]
732
+ selection = resolve.add_mutually_exclusive_group()
733
+ selection.add_argument("--snapshot")
734
+ selection.add_argument("--legacy", action="store_true")
735
+ return parser
736
+
737
+
738
+ def main(argv: list[str] | None = None) -> int:
739
+ parser = _command_parser()
740
+ args = parser.parse_args(argv)
741
+ try:
742
+ if args.operation == "new":
743
+ result = new_snapshot(args.repository_root, args.task_root, args.slug, language=args.language, continues_from=args.continues_from)
744
+ elif args.operation == "finalize":
745
+ result = finalize_snapshot(args.repository_root, args.task_root, args.snapshot, allowed_external_roots=tuple(args.allowed_external_root))
746
+ elif args.operation == "list":
747
+ result = list_history(args.repository_root, args.task_root)
748
+ else:
749
+ result = resolve_snapshot(args.repository_root, args.task_root, snapshot=args.snapshot, legacy=args.legacy)
750
+ except (HandoffError, OSError, UnicodeError, json.JSONDecodeError) as exc:
751
+ error = {"status": "blocked", "error": str(exc)}
752
+ if isinstance(exc, HandoffResolveError):
753
+ error["recoverable_candidates"] = exc.candidates
754
+ print(json.dumps(error, ensure_ascii=False))
755
+ return 5
756
+ if args.operation == "finalize" and args.user_output:
757
+ print(format_create_success(str(result["path"]), str(result["language"])))
758
+ return 0
759
+ print(json.dumps(result, ensure_ascii=False, sort_keys=True))
760
+ return 0
761
+
762
+
763
+ if __name__ == "__main__":
764
+ sys.exit(main())