@guanlin4924/devflow-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 (254) hide show
  1. package/README.md +164 -0
  2. package/docs/OMP-NATIVE-ARCHITECTURE.md +104 -0
  3. package/package.json +40 -0
  4. package/plugins/devflow-core/extensions/devflow-core.ts +1642 -0
  5. package/plugins/devflow-core/package.json +14 -0
  6. package/plugins/devflow-core/runtime/hooks/context_sync_gate.py +174 -0
  7. package/plugins/devflow-core/runtime/hooks/hook_common.py +696 -0
  8. package/plugins/devflow-core/runtime/hooks/index_gate.py +63 -0
  9. package/plugins/devflow-core/runtime/hooks/injection_code_specification.py +7 -0
  10. package/plugins/devflow-core/runtime/hooks/post_tool.py +44 -0
  11. package/plugins/devflow-core/runtime/hooks/start.py +288 -0
  12. package/plugins/devflow-core/skills/workflow-orchestrator/SKILL.md +265 -0
  13. package/plugins/devflow-core/skills/workflow-orchestrator/agents/openai.yaml +4 -0
  14. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/advance_state.rb +520 -0
  15. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/control_io.rb +42 -0
  16. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/init_state.rb +138 -0
  17. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/init_workflow.rb +121 -0
  18. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/inspect_state.rb +553 -0
  19. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/manage_lessons.rb +219 -0
  20. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/sync_artifacts.rb +168 -0
  21. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/update_triage_route.rb +110 -0
  22. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/validate_state.rb +565 -0
  23. package/plugins/devflow-core/skills/workflow-orchestrator/templates/feature-development.yaml +783 -0
  24. package/plugins/devflow-core/skills/workflow-orchestrator/templates/uat-remediation.yaml +143 -0
  25. package/plugins/devflow-core/skills/workflow-orchestrator/templates/workflows-README.md +10 -0
  26. package/plugins/devflow-core/skills/workflow-pipeline/SKILL.md +117 -0
  27. package/plugins/devflow-core/skills/workflow-pipeline/references/pipeline-flow.md +167 -0
  28. package/plugins/devflow-core/skills/workflow-pipeline/references/slice-safety-guide.md +125 -0
  29. package/plugins/devflow-core/skills/workflow-project-context/SKILL.md +819 -0
  30. package/plugins/devflow-core/skills/workflow-project-context/assets/agents-md-template.md +25 -0
  31. package/plugins/devflow-core/skills/workflow-project-context/assets/codebase-backend-spec-template.md +68 -0
  32. package/plugins/devflow-core/skills/workflow-project-context/assets/codebase-frontend-spec-template.md +64 -0
  33. package/plugins/devflow-core/skills/workflow-project-context/assets/codebase-navigation-template.md +168 -0
  34. package/plugins/devflow-core/skills/workflow-project-context/assets/context-domain-index-template.md +43 -0
  35. package/plugins/devflow-core/skills/workflow-project-context/assets/context-spec-index-template.md +45 -0
  36. package/plugins/devflow-core/skills/workflow-project-context/assets/execution-contract-template.md +57 -0
  37. package/plugins/devflow-core/skills/workflow-project-context/assets/navigation-index-template.md +40 -0
  38. package/plugins/devflow-core/skills/workflow-project-context/assets/project-initialization-template.md +109 -0
  39. package/plugins/devflow-core/skills/workflow-project-context/assets/rules-index-template.md +80 -0
  40. package/plugins/devflow-core/skills/workflow-project-context/assets/run-manifest-template.json +40 -0
  41. package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/backend/directory-structure.md +46 -0
  42. package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/backend/engine.md +46 -0
  43. package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/backend/errors.md +46 -0
  44. package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/backend/locator.md +46 -0
  45. package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/backend/model.md +46 -0
  46. package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/backend/repository.md +46 -0
  47. package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/backend/router.md +46 -0
  48. package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/backend/scheduler.md +46 -0
  49. package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/backend/schema.md +46 -0
  50. package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/backend/service.md +46 -0
  51. package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/backend/testing.md +46 -0
  52. package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/frontend/api-client.md +46 -0
  53. package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/frontend/case-editor.md +46 -0
  54. package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/frontend/components.md +46 -0
  55. package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/frontend/directory-structure.md +46 -0
  56. package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/frontend/forms.md +46 -0
  57. package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/frontend/routing.md +46 -0
  58. package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/frontend/state.md +46 -0
  59. package/plugins/devflow-core/skills/workflow-project-context/assets/spec-templates/frontend/testing.md +46 -0
  60. package/plugins/devflow-core/skills/workflow-project-context/assets/stack-spec-template.md +106 -0
  61. package/plugins/devflow-core/skills/workflow-project-context/assets/state-template.md +77 -0
  62. package/plugins/devflow-core/skills/workflow-project-context/assets/toolchain-template.md +37 -0
  63. package/plugins/devflow-core/skills/workflow-project-context/references/navigation-guide.md +262 -0
  64. package/plugins/devflow-core/skills/workflow-project-context/scripts/validate_initialization.py +545 -0
  65. package/plugins/devflow-design/agents/contract-auditor.md +18 -0
  66. package/plugins/devflow-design/extensions/devflow-design.ts +47 -0
  67. package/plugins/devflow-design/package.json +14 -0
  68. package/plugins/devflow-design/skills/workflow-api-call-guide/SKILL.md +142 -0
  69. package/plugins/devflow-design/skills/workflow-api-contract/SKILL.md +150 -0
  70. package/plugins/devflow-design/skills/workflow-api-supplement/SKILL.md +121 -0
  71. package/plugins/devflow-design/skills/workflow-backend-tech-spec/SKILL.md +257 -0
  72. package/plugins/devflow-design/skills/workflow-backend-tech-spec/assets/adr-template.md +57 -0
  73. package/plugins/devflow-design/skills/workflow-backend-tech-spec/assets/backend-spec-template.md +205 -0
  74. package/plugins/devflow-design/skills/workflow-backend-tech-spec/references/affected-module-exploration.md +59 -0
  75. package/plugins/devflow-design/skills/workflow-backend-tech-spec/references/backend-conventions.md +30 -0
  76. package/plugins/devflow-design/skills/workflow-backend-tech-spec/references/code-explore-guide.md +40 -0
  77. package/plugins/devflow-design/skills/workflow-backend-tech-spec/references/project-rule-contract.md +66 -0
  78. package/plugins/devflow-design/skills/workflow-design-rule-pack/SKILL.md +91 -0
  79. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/index.md +71 -0
  80. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/accessibility.md +31 -0
  81. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/component-library.md +31 -0
  82. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/design-systems.md +47 -0
  83. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/responsive-grid.md +29 -0
  84. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/stack-guidelines.md +31 -0
  85. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/ux-patterns.md +40 -0
  86. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tokens/project-tokens.md +19 -0
  87. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/SKILL.md +377 -0
  88. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/charts.csv +26 -0
  89. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/colors.csv +97 -0
  90. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/icons.csv +101 -0
  91. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/landing.csv +31 -0
  92. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/products.csv +97 -0
  93. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/react-performance.csv +45 -0
  94. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  95. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  96. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  97. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  98. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  99. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  100. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  101. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  102. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/react.csv +54 -0
  103. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  104. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  105. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  106. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  107. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/styles.csv +68 -0
  108. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/typography.csv +58 -0
  109. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  110. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  111. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/data/web-interface.csv +31 -0
  112. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/scripts/core.py +253 -0
  113. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/scripts/design_system.py +1067 -0
  114. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/scripts/search.py +114 -0
  115. package/plugins/devflow-design/skills/workflow-design-rule-pack/scripts/install.py +67 -0
  116. package/plugins/devflow-design/skills/workflow-feature-breakdown/SKILL.md +370 -0
  117. package/plugins/devflow-design/skills/workflow-feature-breakdown/assets/admin-system-template.md +57 -0
  118. package/plugins/devflow-design/skills/workflow-feature-breakdown/assets/api-service-template.md +42 -0
  119. package/plugins/devflow-design/skills/workflow-feature-breakdown/assets/c-end-system-template.md +25 -0
  120. package/plugins/devflow-design/skills/workflow-feature-breakdown/assets/output-template.md +139 -0
  121. package/plugins/devflow-design/skills/workflow-feature-breakdown/references/bug-rules.md +12 -0
  122. package/plugins/devflow-design/skills/workflow-feature-breakdown/references/exception-checklist.md +31 -0
  123. package/plugins/devflow-design/skills/workflow-feature-breakdown/references/trace-integrity-guide.md +153 -0
  124. package/plugins/devflow-design/skills/workflow-feature-breakdown/references/writing-guide.md +92 -0
  125. package/plugins/devflow-design/skills/workflow-frontend-tech-spec/SKILL.md +223 -0
  126. package/plugins/devflow-design/skills/workflow-frontend-tech-spec/assets/frontend-spec-template.md +204 -0
  127. package/plugins/devflow-design/skills/workflow-frontend-tech-spec/references/affected-module-exploration.md +58 -0
  128. package/plugins/devflow-design/skills/workflow-frontend-tech-spec/references/component-patterns.md +42 -0
  129. package/plugins/devflow-design/skills/workflow-frontend-tech-spec/references/frontend-conventions.md +28 -0
  130. package/plugins/devflow-design/skills/workflow-frontend-tech-spec/references/frontend-stack-slots/common-web.md +53 -0
  131. package/plugins/devflow-design/skills/workflow-frontend-tech-spec/references/frontend-stack-slots/next.md +40 -0
  132. package/plugins/devflow-design/skills/workflow-frontend-tech-spec/references/frontend-stack-slots/react.md +41 -0
  133. package/plugins/devflow-design/skills/workflow-frontend-tech-spec/references/frontend-stack-slots/vue.md +41 -0
  134. package/plugins/devflow-design/skills/workflow-frontend-tech-spec/references/project-rule-contract.md +95 -0
  135. package/plugins/devflow-design/skills/workflow-integration-verify/SKILL.md +172 -0
  136. package/plugins/devflow-design/skills/workflow-ui-spec/SKILL.md +302 -0
  137. package/plugins/devflow-design/skills/workflow-ui-spec/assets/ui-spec-template.md +244 -0
  138. package/plugins/devflow-design/skills/workflow-ui-spec/references/affected-module-exploration.md +55 -0
  139. package/plugins/devflow-implementation/agents/implementation-worker.md +20 -0
  140. package/plugins/devflow-implementation/package.json +6 -0
  141. package/plugins/devflow-implementation/skills/workflow-bug-fix/SKILL.md +124 -0
  142. package/plugins/devflow-implementation/skills/workflow-bug-fix/references/INDEX.md +13 -0
  143. package/plugins/devflow-implementation/skills/workflow-bug-fix/references/adversarial-review.md +35 -0
  144. package/plugins/devflow-implementation/skills/workflow-bug-fix/references/first-principles.md +26 -0
  145. package/plugins/devflow-implementation/skills/workflow-bug-fix/references/lesson-format.md +28 -0
  146. package/plugins/devflow-implementation/skills/workflow-bug-fix/references/root-cause-ladder.md +51 -0
  147. package/plugins/devflow-implementation/skills/workflow-bug-fix/references/seam-analysis.md +30 -0
  148. package/plugins/devflow-implementation/skills/workflow-change-plan/SKILL.md +160 -0
  149. package/plugins/devflow-implementation/skills/workflow-change-plan/references/code-conventions.md +33 -0
  150. package/plugins/devflow-implementation/skills/workflow-change-plan/references/ddl-guidelines.md +60 -0
  151. package/plugins/devflow-implementation/skills/workflow-change-plan/references/output-template.md +217 -0
  152. package/plugins/devflow-implementation/skills/workflow-change-plan/references/project-rule-contract.md +55 -0
  153. package/plugins/devflow-implementation/skills/workflow-execute-phase/SKILL.md +271 -0
  154. package/plugins/devflow-implementation/skills/workflow-execute-phase/references/execute-phase.md +1251 -0
  155. package/plugins/devflow-implementation/skills/workflow-execute-phase/references/ui-brand.md +160 -0
  156. package/plugins/devflow-intel/agents/project-scout.md +27 -0
  157. package/plugins/devflow-intel/extensions/devflow-intel.ts +104 -0
  158. package/plugins/devflow-intel/package.json +14 -0
  159. package/plugins/devflow-intel/skills/code-index-query/SKILL.md +37 -0
  160. package/plugins/devflow-intel/skills/code-index-query/references/query-patterns.md +27 -0
  161. package/plugins/devflow-intel/skills/workflow-code-index/SKILL.md +68 -0
  162. package/plugins/devflow-intel/skills/workflow-code-index/references/adapter-contract.md +38 -0
  163. package/plugins/devflow-intel/skills/workflow-code-index/references/audit-checklist.md +10 -0
  164. package/plugins/devflow-intel/skills/workflow-code-index/references/confidence-rules.md +43 -0
  165. package/plugins/devflow-intel/skills/workflow-code-index/references/data-model.md +74 -0
  166. package/plugins/devflow-intel/skills/workflow-code-index/references/discovery.md +24 -0
  167. package/plugins/devflow-intel/skills/workflow-code-index/references/query-guide.md +20 -0
  168. package/plugins/devflow-intel/skills/workflow-code-index/scripts/adapters/__init__.py +1 -0
  169. package/plugins/devflow-intel/skills/workflow-code-index/scripts/adapters/base.py +30 -0
  170. package/plugins/devflow-intel/skills/workflow-code-index/scripts/adapters/generic_text.py +287 -0
  171. package/plugins/devflow-intel/skills/workflow-code-index/scripts/adapters/registry.py +17 -0
  172. package/plugins/devflow-intel/skills/workflow-code-index/scripts/code_index.py +437 -0
  173. package/plugins/devflow-intel/skills/workflow-code-index/scripts/core/__init__.py +1 -0
  174. package/plugins/devflow-intel/skills/workflow-code-index/scripts/core/discovery.py +139 -0
  175. package/plugins/devflow-intel/skills/workflow-code-index/scripts/core/hashing.py +11 -0
  176. package/plugins/devflow-intel/skills/workflow-code-index/scripts/core/scanner.py +115 -0
  177. package/plugins/devflow-intel/skills/workflow-code-index/scripts/core/schema.py +123 -0
  178. package/plugins/devflow-intel/skills/workflow-code-index/scripts/validators/__init__.py +1 -0
  179. package/plugins/devflow-intel/skills/workflow-code-index/scripts/validators/project_binding_check.py +66 -0
  180. package/plugins/devflow-intel/skills/workflow-code-index/scripts/validators/relation_check.py +36 -0
  181. package/plugins/devflow-intel/skills/workflow-code-index/scripts/validators/schema_validate.py +11 -0
  182. package/plugins/devflow-intel/skills/workflow-code-index/scripts/validators/stale_check.py +42 -0
  183. package/plugins/devflow-intel/skills/workflow-code-index/scripts/writers/__init__.py +1 -0
  184. package/plugins/devflow-intel/skills/workflow-code-index/scripts/writers/jsonl_writer.py +25 -0
  185. package/plugins/devflow-intel/skills/workflow-code-index/scripts/writers/markdown_writer.py +106 -0
  186. package/plugins/devflow-intel/skills/workflow-code-index/scripts/writers/sqlite_writer.py +143 -0
  187. package/plugins/devflow-intel/skills/workflow-context-sync/SKILL.md +381 -0
  188. package/plugins/devflow-intel/skills/workflow-context-sync/agents/openai.yaml +4 -0
  189. package/plugins/devflow-intel/skills/workflow-context-sync/scripts/context_sync_cursor.py +241 -0
  190. package/plugins/devflow-intel/skills/workflow-update-navigation/SKILL.md +186 -0
  191. package/plugins/devflow-intel/skills/workflow-update-navigation/references/incremental-update-guide.md +47 -0
  192. package/plugins/devflow-quality/agents/code-reviewer.md +18 -0
  193. package/plugins/devflow-quality/agents/verification-runner.md +21 -0
  194. package/plugins/devflow-quality/extensions/devflow-quality.ts +59 -0
  195. package/plugins/devflow-quality/package.json +14 -0
  196. package/plugins/devflow-quality/skills/code-review/SKILL.md +288 -0
  197. package/plugins/devflow-quality/skills/code-review/references/cross-layer-checklist.md +70 -0
  198. package/plugins/devflow-quality/skills/code-review/references/dao-checklist.md +70 -0
  199. package/plugins/devflow-quality/skills/code-review/references/handler-checklist.md +48 -0
  200. package/plugins/devflow-quality/skills/code-review/references/repository-checklist.md +44 -0
  201. package/plugins/devflow-quality/skills/code-review/references/service-checklist.md +57 -0
  202. package/plugins/devflow-quality/skills/code-review/references/spec-checklist.md +34 -0
  203. package/plugins/devflow-quality/skills/code-review/references/vo-checklist.md +26 -0
  204. package/plugins/devflow-quality/skills/workflow-audit-milestone/SKILL.md +31 -0
  205. package/plugins/devflow-quality/skills/workflow-audit-milestone/references/audit-milestone.md +328 -0
  206. package/plugins/devflow-quality/skills/workflow-complete-milestone/SKILL.md +132 -0
  207. package/plugins/devflow-quality/skills/workflow-complete-milestone/assets/milestone-archive.md +123 -0
  208. package/plugins/devflow-quality/skills/workflow-complete-milestone/references/complete-milestone.md +775 -0
  209. package/plugins/devflow-quality/skills/workflow-complete-milestone/references/milestone-archive.md +123 -0
  210. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/SKILL.md +126 -0
  211. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/api-mainflow-rules.md +27 -0
  212. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/codebase-discovery.md +54 -0
  213. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/intermediate-format.md +244 -0
  214. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/mainflow-selection.md +36 -0
  215. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/project-dsl-contract.md +190 -0
  216. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/ui-mainflow-rules.md +110 -0
  217. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/compile_dsl.py +583 -0
  218. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/discover_codebases.py +199 -0
  219. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/normalize_case_payloads.py +177 -0
  220. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/validate_case_payloads.py +370 -0
  221. package/plugins/devflow-quality/skills/workflow-verify-work/SKILL.md +106 -0
  222. package/plugins/devflow-quality/skills/workflow-verify-work/assets/UAT.md +265 -0
  223. package/plugins/devflow-quality/skills/workflow-verify-work/references/verify-work.md +759 -0
  224. package/plugins/devflow-requirements/agents/requirements-auditor.md +20 -0
  225. package/plugins/devflow-requirements/package.json +6 -0
  226. package/plugins/devflow-requirements/skills/requirement-feasibility-review/SKILL.md +219 -0
  227. package/plugins/devflow-requirements/skills/requirement-feasibility-review/agents/openai.yaml +4 -0
  228. package/plugins/devflow-requirements/skills/requirement-feasibility-review/references/review-method.md +257 -0
  229. package/plugins/devflow-requirements/skills/requirement-feasibility-review/references/review-template.md +80 -0
  230. package/plugins/devflow-requirements/skills/workflow-demand-clarify/SKILL.md +325 -0
  231. package/plugins/devflow-requirements/skills/workflow-domain-modeling/SKILL.md +163 -0
  232. package/plugins/devflow-requirements/skills/workflow-requirement-baseline/SKILL.md +748 -0
  233. package/plugins/devflow-requirements/skills/workflow-requirement-baseline/assets/baseline-template.md +281 -0
  234. package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/antipatterns.md +69 -0
  235. package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/challenge-review-guide.md +158 -0
  236. package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/decomposition-guide.md +115 -0
  237. package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/project-context-guide.md +119 -0
  238. package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/questioning.md +76 -0
  239. package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/requirement-review-checklist.md +115 -0
  240. package/plugins/devflow-requirements/skills/workflow-requirement-triage/SKILL.md +204 -0
  241. package/plugins/devflow-requirements/skills/workflow-wayfinder/SKILL.md +183 -0
  242. package/src/cli.ts +76 -0
  243. package/src/constants.ts +15 -0
  244. package/src/doctor.ts +66 -0
  245. package/src/paths.ts +37 -0
  246. package/src/platform-extension.ts +46 -0
  247. package/src/project-context.ts +165 -0
  248. package/src/runtime.ts +167 -0
  249. package/src/usage-stats.ts +101 -0
  250. package/templates/commands/start.md +58 -0
  251. package/templates/omp/AGENTS.md +36 -0
  252. package/templates/omp/config.yml +112 -0
  253. package/templates/omp/hook-config.json +18 -0
  254. package/templates/omp/index-policy.json +57 -0
@@ -0,0 +1,696 @@
1
+ #!/usr/bin/env python3
2
+ """Shared, dependency-free helpers for skill-kit lifecycle hooks."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import fnmatch
7
+ import hashlib
8
+ import json
9
+ import os
10
+ import re
11
+ import subprocess
12
+ import tempfile
13
+ import time
14
+ from contextlib import contextmanager
15
+ from pathlib import Path
16
+ from typing import Any, Iterator
17
+
18
+ try:
19
+ import fcntl
20
+ except ImportError: # pragma: no cover - Windows fallback
21
+ fcntl = None
22
+
23
+
24
+ DEFAULT_POLICY: dict[str, Any] = {
25
+ "version": 1,
26
+ "mode": "strict",
27
+ "protected_roots": [],
28
+ "required_evidence": [
29
+ ".planning/rules/INDEX.md",
30
+ ".context/spec/index.md",
31
+ ".planning/navigation/INDEX.md",
32
+ ],
33
+ "optional_evidence": [".planning/code-index/INDEX.md"],
34
+ "allowed_before_gate": [
35
+ "AGENTS.md",
36
+ "README.md",
37
+ ".planning/**",
38
+ ".context/**",
39
+ ".agent-gates/**",
40
+ ".omp/**",
41
+ ".skills/**",
42
+ ],
43
+ "context_sync": {
44
+ "enabled": True,
45
+ "auto_light": True,
46
+ "light_timeout_seconds": 120,
47
+ "full_file_threshold": 30,
48
+ "ignore": [
49
+ ".planning/**",
50
+ ".context/**",
51
+ ".agent-gates/**",
52
+ ".omp/**",
53
+ ".skills/**",
54
+ ],
55
+ "medium_patterns": [
56
+ "**/routes.*",
57
+ "**/router.*",
58
+ "**/routes/**",
59
+ "**/api/**",
60
+ "**/*handler.*",
61
+ "**/controllers/**",
62
+ "**/migrations/**",
63
+ "package.json",
64
+ "**/package.json",
65
+ "go.mod",
66
+ "**/go.mod",
67
+ "Cargo.toml",
68
+ "**/Cargo.toml",
69
+ "pyproject.toml",
70
+ "**/pyproject.toml",
71
+ "Makefile",
72
+ "**/Makefile",
73
+ "**/vite.config.*",
74
+ "**/next.config.*",
75
+ ],
76
+ "full_patterns": [],
77
+ },
78
+ }
79
+
80
+ COMMON_SOURCE_ROOTS = (
81
+ "src",
82
+ "app",
83
+ "apps",
84
+ "backend",
85
+ "frontend",
86
+ "server",
87
+ "client",
88
+ "packages",
89
+ "cmd",
90
+ "internal",
91
+ "pkg",
92
+ "lib",
93
+ )
94
+
95
+ DIRECT_PATH_KEYS = {
96
+ "file_path",
97
+ "filepath",
98
+ "path",
99
+ "paths",
100
+ "target",
101
+ "targets",
102
+ "directory",
103
+ "folder",
104
+ "workdir",
105
+ "cwd",
106
+ }
107
+
108
+ MUTATION_TOOL_MARKERS = ("edit", "write", "notebookedit", "apply_patch", "applypatch")
109
+ COMMAND_TOOL_MARKERS = ("bash", "shell", "exec_command", "execcommand", "terminal")
110
+ READ_TOOL_MARKERS = ("read", "grep", "glob", "search", "find")
111
+
112
+
113
+ def read_event() -> dict[str, Any]:
114
+ try:
115
+ payload = json.load(__import__("sys").stdin)
116
+ except (json.JSONDecodeError, OSError, TypeError):
117
+ return {}
118
+ return payload if isinstance(payload, dict) else {}
119
+
120
+
121
+ def cli_dir_name() -> str:
122
+ return ".omp"
123
+
124
+
125
+ def project_root(event: dict[str, Any] | None = None) -> Path:
126
+ event = event or {}
127
+ explicit = os.environ.get("OMP_PROJECT_DIR")
128
+ candidate = Path(explicit or event.get("cwd") or os.getcwd()).expanduser().resolve()
129
+ if candidate.is_file():
130
+ candidate = candidate.parent
131
+ for current in (candidate, *candidate.parents):
132
+ if any(
133
+ (current / marker).exists()
134
+ for marker in (".omp", ".agent-gates", ".planning", ".skills")
135
+ ):
136
+ return current
137
+ return candidate
138
+
139
+
140
+ def skill_kit_root(root: Path) -> Path:
141
+ """Resolve the version-pinned skill source for submodule and marketplace installs."""
142
+ configured = os.environ.get("DEVFLOW_KIT_ROOT")
143
+ intel = os.environ.get("DEVFLOW_INTEL_SKILL_ROOT")
144
+ candidates = [
145
+ Path(intel).expanduser() if intel else None,
146
+ Path(configured).expanduser() if configured else None,
147
+ root / ".skills",
148
+ Path(__file__).resolve().parents[2],
149
+ ]
150
+ for candidate in candidates:
151
+ if candidate and (
152
+ (candidate / "workflow-orchestrator/scripts/inspect_state.rb").is_file()
153
+ or (candidate / "workflow-code-index/scripts/code_index.py").is_file()
154
+ ):
155
+ return candidate.resolve()
156
+ return (root / ".skills").resolve()
157
+
158
+
159
+ def _deep_merge(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
160
+ result = dict(base)
161
+ for key, value in override.items():
162
+ if isinstance(value, dict) and isinstance(result.get(key), dict):
163
+ result[key] = _deep_merge(result[key], value)
164
+ else:
165
+ result[key] = value
166
+ return result
167
+
168
+
169
+ def load_hook_config(root: Path, cli_dir: str | None = None) -> dict[str, Any]:
170
+ cli_dir = cli_dir or cli_dir_name()
171
+ path = root / cli_dir / "hook-config.json"
172
+ try:
173
+ data = json.loads(path.read_text(encoding="utf-8"))
174
+ except (FileNotFoundError, PermissionError, json.JSONDecodeError):
175
+ return {}
176
+ return data if isinstance(data, dict) else {}
177
+
178
+
179
+ def load_policy(root: Path, cli_dir: str | None = None) -> dict[str, Any]:
180
+ config = load_hook_config(root, cli_dir)
181
+ policy_rel = config.get("gates", {}).get("index_first", {}).get(
182
+ "policy", ".agent-gates/index-first.json"
183
+ )
184
+ policy_path = root / str(policy_rel)
185
+ try:
186
+ data = json.loads(policy_path.read_text(encoding="utf-8"))
187
+ except (FileNotFoundError, PermissionError, json.JSONDecodeError):
188
+ data = {}
189
+ return _deep_merge(DEFAULT_POLICY, data if isinstance(data, dict) else {})
190
+
191
+
192
+ def normalize_rel(raw: str | os.PathLike[str], root: Path) -> str | None:
193
+ value = str(raw).strip().strip("'\"")
194
+ if not value or value.startswith("-"):
195
+ return None
196
+ value = value.replace("\\", "/")
197
+ path = Path(value).expanduser()
198
+ if path.is_absolute():
199
+ try:
200
+ return path.resolve(strict=False).relative_to(root.resolve()).as_posix()
201
+ except ValueError:
202
+ return None
203
+ normalized = Path(os.path.normpath(value)).as_posix()
204
+ if normalized == ".":
205
+ return "."
206
+ if normalized == ".." or normalized.startswith("../"):
207
+ return None
208
+ return normalized.removeprefix("./")
209
+
210
+
211
+ def _walk_path_values(value: Any, key: str = "") -> Iterator[str]:
212
+ if isinstance(value, dict):
213
+ for child_key, child_value in value.items():
214
+ yield from _walk_path_values(child_value, str(child_key).lower())
215
+ elif isinstance(value, list):
216
+ for item in value:
217
+ yield from _walk_path_values(item, key)
218
+ elif isinstance(value, str) and key in DIRECT_PATH_KEYS:
219
+ yield value
220
+
221
+
222
+ def command_text(event: dict[str, Any]) -> str:
223
+ tool_input = event.get("tool_input", {})
224
+ if not isinstance(tool_input, dict):
225
+ return ""
226
+ for key in ("command", "cmd", "script"):
227
+ value = tool_input.get(key)
228
+ if isinstance(value, str):
229
+ return value
230
+ return ""
231
+
232
+
233
+ def tool_name(event: dict[str, Any]) -> str:
234
+ return str(event.get("tool_name", "")).lower().replace("-", "_")
235
+
236
+
237
+ def is_command_tool(event: dict[str, Any]) -> bool:
238
+ name = tool_name(event)
239
+ return any(marker in name for marker in COMMAND_TOOL_MARKERS) or bool(command_text(event))
240
+
241
+
242
+ def is_mutation_tool(event: dict[str, Any]) -> bool:
243
+ name = tool_name(event)
244
+ return any(marker in name for marker in MUTATION_TOOL_MARKERS)
245
+
246
+
247
+ def direct_tool_paths(event: dict[str, Any], root: Path) -> list[str]:
248
+ tool_input = event.get("tool_input", {})
249
+ values = list(_walk_path_values(tool_input))
250
+ name = tool_name(event)
251
+ if "apply_patch" in name or "applypatch" in name:
252
+ patch = tool_input if isinstance(tool_input, str) else ""
253
+ if isinstance(tool_input, dict):
254
+ patch = str(tool_input.get("patch") or tool_input.get("input") or "")
255
+ values.extend(re.findall(r"^\*\*\* (?:Add|Update|Delete) File:\s*(.+)$", patch, re.MULTILINE))
256
+ result = []
257
+ for value in values:
258
+ normalized = normalize_rel(value, root)
259
+ if normalized is not None:
260
+ result.append(normalized)
261
+ return sorted(dict.fromkeys(result))
262
+
263
+
264
+ def path_matches(path: str, patterns: list[str]) -> bool:
265
+ for pattern in patterns:
266
+ pattern = str(pattern).removeprefix("./")
267
+ if fnmatch.fnmatch(path, pattern):
268
+ return True
269
+ if pattern.startswith("**/") and fnmatch.fnmatch(path, pattern[3:]):
270
+ return True
271
+ if pattern.endswith("/**") and (path == pattern[:-3] or path.startswith(pattern[:-2])):
272
+ return True
273
+ return False
274
+
275
+
276
+ def _navigation_codebase_roots(root: Path) -> list[str]:
277
+ path = root / ".planning/navigation/INDEX.md"
278
+ try:
279
+ text = path.read_text(encoding="utf-8")
280
+ except (FileNotFoundError, PermissionError):
281
+ return []
282
+ match = re.search(r"^## Codebase Registry\s*$([\s\S]*?)(?=^## |\Z)", text, re.MULTILINE)
283
+ if not match:
284
+ return []
285
+ section = match.group(1)
286
+ roots = []
287
+ for line in section.splitlines():
288
+ if not line.startswith("|") or "`" not in line:
289
+ continue
290
+ cells = [cell.strip() for cell in line.strip().strip("|").split("|")]
291
+ if len(cells) < 2 or cells[0].lower() in {"name", "---"}:
292
+ continue
293
+ found = re.search(r"`([^`]+)`", cells[1])
294
+ if found:
295
+ normalized = normalize_rel(found.group(1), root)
296
+ if normalized not in {None, "."}:
297
+ roots.append(normalized)
298
+ return roots
299
+
300
+
301
+ def protected_roots(root: Path, policy: dict[str, Any]) -> list[str]:
302
+ configured = []
303
+ for item in policy.get("protected_roots", []):
304
+ value = item.get("path") if isinstance(item, dict) else item
305
+ normalized = normalize_rel(str(value), root) if value else None
306
+ if normalized not in {None, "."}:
307
+ configured.append(normalized.rstrip("/"))
308
+ if configured:
309
+ return sorted(dict.fromkeys(configured))
310
+ discovered = _navigation_codebase_roots(root)
311
+ if discovered:
312
+ return sorted(dict.fromkeys(discovered))
313
+ return [name for name in COMMON_SOURCE_ROOTS if (root / name).is_dir()]
314
+
315
+
316
+ def required_evidence(root: Path, policy: dict[str, Any]) -> list[str]:
317
+ result = []
318
+ for item in policy.get("required_evidence", []):
319
+ value = item.get("path") if isinstance(item, dict) else item
320
+ optional = bool(item.get("optional")) if isinstance(item, dict) else False
321
+ normalized = normalize_rel(str(value), root) if value else None
322
+ if normalized and ((root / normalized).is_file() or not optional):
323
+ if (root / normalized).is_file():
324
+ result.append(normalized)
325
+ return list(dict.fromkeys(result))
326
+
327
+
328
+ def path_is_protected(path: str, roots: list[str], allowed: list[str]) -> bool:
329
+ if path_matches(path, allowed):
330
+ return False
331
+ return any(path == source_root or path.startswith(f"{source_root}/") for source_root in roots)
332
+
333
+
334
+ def _command_mentions_allowed_scope(command: str, allowed: list[str]) -> bool:
335
+ simple_prefixes = [pattern[:-3] for pattern in allowed if pattern.endswith("/**")]
336
+ exact_paths = [pattern for pattern in allowed if not any(char in pattern for char in "*?[")]
337
+ candidates = simple_prefixes + exact_paths
338
+ return any(
339
+ re.search(rf"(?:^|[\s'\"]){re.escape(prefix)}(?:/|[\s'\";|&]|$)", command)
340
+ for prefix in candidates
341
+ )
342
+
343
+
344
+ def command_touches_protected_source(
345
+ event: dict[str, Any], root: Path, roots: list[str], allowed: list[str]
346
+ ) -> bool:
347
+ command = command_text(event)
348
+ if not command:
349
+ return False
350
+ normalized_command = command.replace("\\", "/")
351
+ for source_root in roots:
352
+ if re.search(rf"(?:^|[\s'\"=:/]){re.escape(source_root)}(?:/|[\s'\";|&]|$)", normalized_command):
353
+ return True
354
+
355
+ event_cwd = normalize_rel(str(event.get("cwd") or root), root)
356
+ if event_cwd and any(event_cwd == item or event_cwd.startswith(f"{item}/") for item in roots):
357
+ return True
358
+
359
+ reader = re.search(
360
+ r"(?:^|[;&|]\s*|\s)(?:cat|sed|rg|grep|find|fd|awk|less|head|tail|nl|bat)\b",
361
+ normalized_command,
362
+ )
363
+ git_reader = re.search(r"\bgit\s+(?:diff|show|grep|blame)\b", normalized_command)
364
+ if not (reader or git_reader):
365
+ return False
366
+ if _command_mentions_allowed_scope(normalized_command, allowed) and not re.search(r"(?:^|\s)\.(?:\s|$)", normalized_command):
367
+ return False
368
+ return bool(roots)
369
+
370
+
371
+ def tool_requests_protected_source(
372
+ event: dict[str, Any], root: Path, policy: dict[str, Any]
373
+ ) -> tuple[bool, list[str]]:
374
+ roots = protected_roots(root, policy)
375
+ allowed = [str(item) for item in policy.get("allowed_before_gate", [])]
376
+ blocked_paths = [
377
+ path for path in direct_tool_paths(event, root) if path_is_protected(path, roots, allowed)
378
+ ]
379
+ if blocked_paths:
380
+ return True, blocked_paths
381
+ if is_command_tool(event) and command_touches_protected_source(event, root, roots, allowed):
382
+ return True, ["<shell command>"]
383
+ name = tool_name(event)
384
+ if "agent" in name or "task" in name:
385
+ prompt = str(event.get("tool_input", {})).lower()
386
+ if re.search(r"\b(source|code|implementation|debug|review|grep|search|route|api|architecture)\b", prompt):
387
+ return True, ["<subagent source request>"]
388
+ return False, []
389
+
390
+
391
+ def evidence_observed(event: dict[str, Any], root: Path, artifacts: list[str]) -> list[str]:
392
+ name = tool_name(event)
393
+ if not (any(marker in name for marker in READ_TOOL_MARKERS) or is_command_tool(event)):
394
+ return []
395
+ paths = set(direct_tool_paths(event, root))
396
+ command = command_text(event).replace("\\", "/")
397
+ shell_reader = bool(
398
+ re.search(
399
+ r"(?:^|[;&|]\s*|\s)(?:cat|sed|rg|grep|awk|less|head|tail|nl|bat)\b",
400
+ command,
401
+ )
402
+ or re.search(r"\bgit\s+(?:show|grep|blame)\b", command)
403
+ )
404
+ observed = []
405
+ for artifact in artifacts:
406
+ absolute = (root / artifact).as_posix()
407
+ if artifact in paths or absolute in paths:
408
+ observed.append(artifact)
409
+ elif shell_reader and command and (artifact in command or absolute in command):
410
+ observed.append(artifact)
411
+ return sorted(dict.fromkeys(observed))
412
+
413
+
414
+ def file_fingerprint(path: Path) -> str:
415
+ if not path.exists() and not path.is_symlink():
416
+ return "<missing>"
417
+ if path.is_dir():
418
+ return "<directory>"
419
+ try:
420
+ digest = hashlib.sha256()
421
+ with path.open("rb") as handle:
422
+ for chunk in iter(lambda: handle.read(1024 * 1024), b""):
423
+ digest.update(chunk)
424
+ return digest.hexdigest()
425
+ except (OSError, PermissionError):
426
+ return "<unreadable>"
427
+
428
+
429
+ def _run_git(root: Path, args: list[str]) -> subprocess.CompletedProcess[bytes]:
430
+ return subprocess.run(
431
+ ["git", "-C", str(root), *args],
432
+ stdout=subprocess.PIPE,
433
+ stderr=subprocess.PIPE,
434
+ check=False,
435
+ )
436
+
437
+
438
+ def git_head(root: Path) -> str:
439
+ result = _run_git(root, ["rev-parse", "HEAD"])
440
+ return result.stdout.decode("utf-8", errors="replace").strip() if result.returncode == 0 else ""
441
+
442
+
443
+ def git_status_paths(root: Path) -> dict[str, str]:
444
+ result = _run_git(root, ["status", "--porcelain=v1", "-z", "--untracked-files=all"])
445
+ if result.returncode != 0:
446
+ return {}
447
+ records = result.stdout.split(b"\0")
448
+ status_by_path: dict[str, str] = {}
449
+ index = 0
450
+ while index < len(records):
451
+ record = records[index]
452
+ index += 1
453
+ if not record or len(record) < 4:
454
+ continue
455
+ status = record[:2].decode("ascii", errors="replace")
456
+ raw_path = record[3:].decode("utf-8", errors="surrogateescape")
457
+ normalized = normalize_rel(raw_path, root)
458
+ if normalized:
459
+ status_by_path[normalized] = status
460
+ if "R" in status or "C" in status:
461
+ index += 1
462
+ return status_by_path
463
+
464
+
465
+ def worktree_snapshot(root: Path) -> dict[str, str]:
466
+ return {path: file_fingerprint(root / path) for path in git_status_paths(root)}
467
+
468
+
469
+ def _committed_changes(root: Path, baseline_head: str) -> set[str]:
470
+ current_head = git_head(root)
471
+ if not baseline_head or not current_head or baseline_head == current_head:
472
+ return set()
473
+ result = _run_git(root, ["diff", "--name-only", "-z", f"{baseline_head}..{current_head}"])
474
+ if result.returncode != 0:
475
+ return set()
476
+ paths = set()
477
+ for raw in result.stdout.split(b"\0"):
478
+ if not raw:
479
+ continue
480
+ normalized = normalize_rel(raw.decode("utf-8", errors="surrogateescape"), root)
481
+ if normalized:
482
+ paths.add(normalized)
483
+ return paths
484
+
485
+
486
+ def discover_git_repos(root: Path, policy: dict[str, Any] | None = None) -> list[Path]:
487
+ policy = policy or DEFAULT_POLICY
488
+ candidates = [root]
489
+ candidates.extend(root / path for path in _navigation_codebase_roots(root))
490
+ candidates.extend(root / path for path in COMMON_SOURCE_ROOTS if (root / path).is_dir())
491
+ for item in policy.get("context_sync", {}).get("repositories", []):
492
+ normalized = normalize_rel(str(item), root)
493
+ if normalized:
494
+ candidates.append(root / normalized)
495
+
496
+ repositories: dict[str, Path] = {}
497
+ for candidate in candidates:
498
+ if not candidate.exists():
499
+ continue
500
+ result = _run_git(candidate, ["rev-parse", "--show-toplevel"])
501
+ if result.returncode != 0:
502
+ continue
503
+ repo = Path(result.stdout.decode("utf-8", errors="replace").strip()).resolve()
504
+ try:
505
+ repo.relative_to(root.resolve())
506
+ except ValueError:
507
+ continue
508
+ repositories[str(repo)] = repo
509
+ return sorted(repositories.values(), key=lambda path: path.as_posix())
510
+
511
+
512
+ def _project_rel_from_repo(root: Path, repo: Path, path: str) -> str | None:
513
+ return normalize_rel(repo / path, root)
514
+
515
+
516
+ def repository_snapshot(root: Path, policy: dict[str, Any]) -> dict[str, dict[str, Any]]:
517
+ snapshot: dict[str, dict[str, Any]] = {}
518
+ for repo in discover_git_repos(root, policy):
519
+ repo_key = normalize_rel(repo, root) or "."
520
+ worktree = {}
521
+ for path in git_status_paths(repo):
522
+ project_path = _project_rel_from_repo(root, repo, path)
523
+ if project_path:
524
+ worktree[project_path] = file_fingerprint(root / project_path)
525
+ snapshot[repo_key] = {"head": git_head(repo), "worktree": worktree}
526
+ return snapshot
527
+
528
+
529
+ def _repository_changes(
530
+ root: Path, baseline_repos: dict[str, Any], policy: dict[str, Any]
531
+ ) -> set[str]:
532
+ changed: set[str] = set()
533
+ current_repos = {normalize_rel(repo, root) or ".": repo for repo in discover_git_repos(root, policy)}
534
+ for repo_key in set(baseline_repos) | set(current_repos):
535
+ baseline = baseline_repos.get(repo_key, {})
536
+ baseline = baseline if isinstance(baseline, dict) else {}
537
+ current_repo = current_repos.get(repo_key)
538
+ if current_repo is None:
539
+ if repo_key != ".":
540
+ changed.add(repo_key)
541
+ continue
542
+ current_worktree = {}
543
+ for path in git_status_paths(current_repo):
544
+ project_path = _project_rel_from_repo(root, current_repo, path)
545
+ if project_path:
546
+ current_worktree[project_path] = file_fingerprint(root / project_path)
547
+ baseline_worktree = baseline.get("worktree", {})
548
+ baseline_worktree = baseline_worktree if isinstance(baseline_worktree, dict) else {}
549
+ changed.update(
550
+ path
551
+ for path in set(baseline_worktree) | set(current_worktree)
552
+ if baseline_worktree.get(path, "<clean>") != current_worktree.get(path, "<clean>")
553
+ )
554
+ for repo_path in _committed_changes(current_repo, str(baseline.get("head") or "")):
555
+ project_path = _project_rel_from_repo(root, current_repo, repo_path)
556
+ if project_path:
557
+ changed.add(project_path)
558
+ return changed
559
+
560
+
561
+ def _state_root(root: Path) -> Path:
562
+ project_id = hashlib.sha256(str(root.resolve()).encode("utf-8")).hexdigest()[:16]
563
+ return Path(tempfile.gettempdir()) / "skill-kit-hooks" / project_id
564
+
565
+
566
+ def _safe_session_id(session_id: str) -> str:
567
+ clean = re.sub(r"[^A-Za-z0-9_.-]+", "_", session_id or "default")
568
+ return clean[:160] or "default"
569
+
570
+
571
+ def state_path(root: Path, session_id: str) -> Path:
572
+ return _state_root(root) / f"{_safe_session_id(session_id)}.json"
573
+
574
+
575
+ @contextmanager
576
+ def locked_state(root: Path, session_id: str) -> Iterator[dict[str, Any]]:
577
+ directory = _state_root(root)
578
+ directory.mkdir(parents=True, exist_ok=True)
579
+ path = state_path(root, session_id)
580
+ lock_path = path.with_suffix(path.suffix + ".lock")
581
+ with lock_path.open("a+", encoding="utf-8") as lock:
582
+ if fcntl is not None:
583
+ fcntl.flock(lock.fileno(), fcntl.LOCK_EX)
584
+ try:
585
+ try:
586
+ state = json.loads(path.read_text(encoding="utf-8"))
587
+ except (FileNotFoundError, json.JSONDecodeError, PermissionError):
588
+ state = {}
589
+ if not isinstance(state, dict):
590
+ state = {}
591
+ yield state
592
+ temp_path = path.with_name(f"{path.name}.{os.getpid()}.tmp")
593
+ temp_path.write_text(
594
+ json.dumps(state, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
595
+ encoding="utf-8",
596
+ )
597
+ os.replace(temp_path, path)
598
+ finally:
599
+ if fcntl is not None:
600
+ fcntl.flock(lock.fileno(), fcntl.LOCK_UN)
601
+
602
+
603
+ def initialize_session(root: Path, event: dict[str, Any], force: bool = False) -> dict[str, Any]:
604
+ session_id = str(event.get("session_id") or "default")
605
+ source = str(event.get("source") or "startup")
606
+ policy = load_policy(root)
607
+ with locked_state(root, session_id) as state:
608
+ should_reset = force or not state or source in {"startup", "clear"}
609
+ if should_reset:
610
+ state.clear()
611
+ state.update(
612
+ {
613
+ "version": 1,
614
+ "project_root": str(root),
615
+ "session_id": session_id,
616
+ "started_at": int(time.time()),
617
+ "baseline_head": git_head(root),
618
+ "baseline_worktree": worktree_snapshot(root),
619
+ "baseline_repositories": repository_snapshot(root, policy),
620
+ "evidence": [],
621
+ "touched_files": [],
622
+ "prechange_fingerprints": {},
623
+ "rescan_required": False,
624
+ "stop_block_count": 0,
625
+ }
626
+ )
627
+ return dict(state)
628
+
629
+
630
+ def ensure_session(root: Path, event: dict[str, Any]) -> str:
631
+ session_id = str(event.get("session_id") or "default")
632
+ path = state_path(root, session_id)
633
+ if not path.exists():
634
+ initialize_session(root, event)
635
+ return session_id
636
+
637
+
638
+ def changed_files_for_state(root: Path, state: dict[str, Any], policy: dict[str, Any]) -> list[str]:
639
+ baseline_repos = state.get("baseline_repositories")
640
+ if isinstance(baseline_repos, dict):
641
+ changed = _repository_changes(root, baseline_repos, policy)
642
+ else:
643
+ baseline = state.get("baseline_worktree", {})
644
+ baseline = baseline if isinstance(baseline, dict) else {}
645
+ current = worktree_snapshot(root)
646
+ changed = {
647
+ path
648
+ for path in set(baseline) | set(current)
649
+ if baseline.get(path, "<clean>") != current.get(path, "<clean>")
650
+ }
651
+ changed.update(_committed_changes(root, str(state.get("baseline_head") or "")))
652
+
653
+ prechange = state.get("prechange_fingerprints", {})
654
+ if isinstance(prechange, dict):
655
+ for path, before in prechange.items():
656
+ if file_fingerprint(root / path) != before:
657
+ changed.add(path)
658
+
659
+ ignore = [str(item) for item in policy.get("context_sync", {}).get("ignore", [])]
660
+ return sorted(path for path in changed if not path_matches(path, ignore))
661
+
662
+
663
+ def changes_signature(root: Path, paths: list[str]) -> str:
664
+ digest = hashlib.sha256()
665
+ for path in sorted(dict.fromkeys(paths)):
666
+ digest.update(path.encode("utf-8", errors="surrogateescape"))
667
+ digest.update(b"\0")
668
+ digest.update(file_fingerprint(root / path).encode("ascii", errors="replace"))
669
+ digest.update(b"\0")
670
+ digest.update(git_head(root).encode("ascii", errors="replace"))
671
+ return digest.hexdigest()
672
+
673
+
674
+ def classify_changes(paths: list[str], policy: dict[str, Any]) -> tuple[str, list[str]]:
675
+ sync = policy.get("context_sync", {})
676
+ full_patterns = [str(item) for item in sync.get("full_patterns", [])]
677
+ medium_patterns = [str(item) for item in sync.get("medium_patterns", [])]
678
+ full_matches = [path for path in paths if path_matches(path, full_patterns)]
679
+ medium_matches = [path for path in paths if path_matches(path, medium_patterns)]
680
+ threshold = int(sync.get("full_file_threshold", 30))
681
+ manifest_names = {"package.json", "go.mod", "Cargo.toml", "pyproject.toml", "Makefile"}
682
+ structural = any(Path(path).name in manifest_names for path in paths)
683
+ if full_matches or (len(paths) > threshold and structural):
684
+ return "Full", sorted(dict.fromkeys(full_matches or medium_matches))
685
+ if medium_matches:
686
+ return "Medium", sorted(dict.fromkeys(medium_matches))
687
+ return "Light", []
688
+
689
+
690
+ def record_prechange_fingerprints(root: Path, session_id: str, paths: list[str]) -> None:
691
+ if not paths:
692
+ return
693
+ with locked_state(root, session_id) as state:
694
+ fingerprints = state.setdefault("prechange_fingerprints", {})
695
+ for path in paths:
696
+ fingerprints.setdefault(path, file_fingerprint(root / path))