@guanlin4924/devflow-pi 0.1.94 → 0.1.95

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 (238) hide show
  1. package/README.md +4 -4
  2. package/node_modules/@playwright/test/LICENSE +202 -0
  3. package/node_modules/@playwright/test/NOTICE +5 -0
  4. package/node_modules/@playwright/test/README.md +318 -0
  5. package/node_modules/@playwright/test/cli.js +19 -0
  6. package/node_modules/@playwright/test/index.d.ts +18 -0
  7. package/node_modules/@playwright/test/index.js +17 -0
  8. package/node_modules/@playwright/test/index.mjs +18 -0
  9. package/node_modules/@playwright/test/package.json +35 -0
  10. package/node_modules/@playwright/test/reporter.d.ts +17 -0
  11. package/node_modules/@playwright/test/reporter.js +17 -0
  12. package/node_modules/@playwright/test/reporter.mjs +17 -0
  13. package/node_modules/playwright/LICENSE +202 -0
  14. package/node_modules/playwright/NOTICE +5 -0
  15. package/node_modules/playwright/README.md +318 -0
  16. package/node_modules/playwright/ThirdPartyNotices.txt +14 -0
  17. package/node_modules/playwright/cli.js +19 -0
  18. package/node_modules/playwright/index.d.ts +17 -0
  19. package/node_modules/playwright/index.js +17 -0
  20. package/node_modules/playwright/index.mjs +18 -0
  21. package/node_modules/playwright/jsx-runtime.js +42 -0
  22. package/node_modules/playwright/jsx-runtime.mjs +21 -0
  23. package/node_modules/playwright/lib/agents/agentParser.js +89 -0
  24. package/node_modules/playwright/lib/agents/copilot-setup-steps.yml +34 -0
  25. package/node_modules/playwright/lib/agents/generateAgents.js +395 -0
  26. package/node_modules/playwright/lib/agents/playwright-test-coverage.prompt.md +31 -0
  27. package/node_modules/playwright/lib/agents/playwright-test-generate.prompt.md +8 -0
  28. package/node_modules/playwright/lib/agents/playwright-test-generator.agent.md +88 -0
  29. package/node_modules/playwright/lib/agents/playwright-test-heal.prompt.md +6 -0
  30. package/node_modules/playwright/lib/agents/playwright-test-healer.agent.md +56 -0
  31. package/node_modules/playwright/lib/agents/playwright-test-plan.prompt.md +9 -0
  32. package/node_modules/playwright/lib/agents/playwright-test-planner.agent.md +74 -0
  33. package/node_modules/playwright/lib/cli/reportActions.js +78 -0
  34. package/node_modules/playwright/lib/cli/testActions.js +214 -0
  35. package/node_modules/playwright/lib/common/index.js +2923 -0
  36. package/node_modules/playwright/lib/common/index.js.txt +34 -0
  37. package/node_modules/playwright/lib/errorContext.js +129 -0
  38. package/node_modules/playwright/lib/globals.js +58 -0
  39. package/node_modules/playwright/lib/index.js +815 -0
  40. package/node_modules/playwright/lib/isomorphic.js +260 -0
  41. package/node_modules/playwright/lib/isomorphic.js.txt +9 -0
  42. package/node_modules/playwright/lib/loader/loaderProcessEntry.js +34 -0
  43. package/node_modules/playwright/lib/loader/loaderProcessEntry.js.txt +9 -0
  44. package/node_modules/playwright/lib/matchers/expect.js +13490 -0
  45. package/node_modules/playwright/lib/matchers/expect.js.LICENSE +640 -0
  46. package/node_modules/playwright/lib/matchers/expect.js.txt +67 -0
  47. package/node_modules/playwright/lib/mcp/test/browserBackend.js +135 -0
  48. package/node_modules/playwright/lib/mcp/test/generatorTools.js +122 -0
  49. package/node_modules/playwright/lib/mcp/test/plannerTools.js +150 -0
  50. package/node_modules/playwright/lib/mcp/test/seed.js +82 -0
  51. package/node_modules/playwright/lib/mcp/test/streams.js +44 -0
  52. package/node_modules/playwright/lib/mcp/test/testBackend.js +99 -0
  53. package/node_modules/playwright/lib/mcp/test/testContext.js +306 -0
  54. package/node_modules/playwright/lib/mcp/test/testTool.js +30 -0
  55. package/node_modules/playwright/lib/mcp/test/testTools.js +98 -0
  56. package/node_modules/playwright/lib/package.js +47 -0
  57. package/node_modules/playwright/lib/program.js +241 -0
  58. package/node_modules/playwright/lib/runner/index.js +8527 -0
  59. package/node_modules/playwright/lib/runner/index.js.txt +61 -0
  60. package/node_modules/playwright/lib/transform/babelBundle.js +72274 -0
  61. package/node_modules/playwright/lib/transform/babelBundle.js.LICENSE +2384 -0
  62. package/node_modules/playwright/lib/transform/babelBundle.js.txt +325 -0
  63. package/node_modules/playwright/lib/transform/esmLoader.js +8054 -0
  64. package/node_modules/playwright/lib/transform/esmLoader.js.LICENSE +355 -0
  65. package/node_modules/playwright/lib/transform/esmLoader.js.txt +60 -0
  66. package/node_modules/playwright/lib/util.js +373 -0
  67. package/node_modules/playwright/lib/worker/workerProcessEntry.js +1861 -0
  68. package/node_modules/playwright/lib/worker/workerProcessEntry.js.txt +19 -0
  69. package/node_modules/playwright/package.json +55 -0
  70. package/node_modules/playwright/test.d.ts +18 -0
  71. package/node_modules/playwright/test.js +24 -0
  72. package/node_modules/playwright/test.mjs +35 -0
  73. package/node_modules/playwright/types/test.d.ts +11074 -0
  74. package/node_modules/playwright/types/testReporter.d.ts +950 -0
  75. package/node_modules/playwright-core/LICENSE +202 -0
  76. package/node_modules/playwright-core/NOTICE +5 -0
  77. package/node_modules/playwright-core/README.md +3 -0
  78. package/node_modules/playwright-core/ThirdPartyNotices.txt +13 -0
  79. package/node_modules/playwright-core/bin/install_media_pack.ps1 +5 -0
  80. package/node_modules/playwright-core/bin/install_webkit_wsl.ps1 +33 -0
  81. package/node_modules/playwright-core/bin/reinstall_chrome_beta_linux.sh +42 -0
  82. package/node_modules/playwright-core/bin/reinstall_chrome_beta_mac.sh +13 -0
  83. package/node_modules/playwright-core/bin/reinstall_chrome_beta_win.ps1 +24 -0
  84. package/node_modules/playwright-core/bin/reinstall_chrome_stable_linux.sh +42 -0
  85. package/node_modules/playwright-core/bin/reinstall_chrome_stable_mac.sh +12 -0
  86. package/node_modules/playwright-core/bin/reinstall_chrome_stable_win.ps1 +24 -0
  87. package/node_modules/playwright-core/bin/reinstall_msedge_beta_linux.sh +48 -0
  88. package/node_modules/playwright-core/bin/reinstall_msedge_beta_mac.sh +11 -0
  89. package/node_modules/playwright-core/bin/reinstall_msedge_beta_win.ps1 +23 -0
  90. package/node_modules/playwright-core/bin/reinstall_msedge_dev_linux.sh +48 -0
  91. package/node_modules/playwright-core/bin/reinstall_msedge_dev_mac.sh +11 -0
  92. package/node_modules/playwright-core/bin/reinstall_msedge_dev_win.ps1 +23 -0
  93. package/node_modules/playwright-core/bin/reinstall_msedge_stable_linux.sh +48 -0
  94. package/node_modules/playwright-core/bin/reinstall_msedge_stable_mac.sh +11 -0
  95. package/node_modules/playwright-core/bin/reinstall_msedge_stable_win.ps1 +24 -0
  96. package/node_modules/playwright-core/browsers.json +52 -0
  97. package/node_modules/playwright-core/cli.js +21 -0
  98. package/node_modules/playwright-core/index.d.ts +17 -0
  99. package/node_modules/playwright-core/index.js +17 -0
  100. package/node_modules/playwright-core/index.mjs +28 -0
  101. package/node_modules/playwright-core/lib/bootstrap.js +88 -0
  102. package/node_modules/playwright-core/lib/coreBundle.js +75813 -0
  103. package/node_modules/playwright-core/lib/entry/cliDaemon.js +5 -0
  104. package/node_modules/playwright-core/lib/entry/dashboardApp.js +3 -0
  105. package/node_modules/playwright-core/lib/entry/mcp.js +10 -0
  106. package/node_modules/playwright-core/lib/entry/oopBrowserDownload.js +3 -0
  107. package/node_modules/playwright-core/lib/package.js +50 -0
  108. package/node_modules/playwright-core/lib/server/chromium/appIcon.png +0 -0
  109. package/node_modules/playwright-core/lib/server/electron/loader.js +116 -0
  110. package/node_modules/playwright-core/lib/serverRegistry.js +1939 -0
  111. package/node_modules/playwright-core/lib/serverRegistry.js.LICENSE +65 -0
  112. package/node_modules/playwright-core/lib/tools/cli-client/channelSessions.js +141 -0
  113. package/node_modules/playwright-core/lib/tools/cli-client/cli.js +6 -0
  114. package/node_modules/playwright-core/lib/tools/cli-client/help.json +721 -0
  115. package/node_modules/playwright-core/lib/tools/cli-client/minimist.js +128 -0
  116. package/node_modules/playwright-core/lib/tools/cli-client/output.js +351 -0
  117. package/node_modules/playwright-core/lib/tools/cli-client/program.js +418 -0
  118. package/node_modules/playwright-core/lib/tools/cli-client/registry.js +180 -0
  119. package/node_modules/playwright-core/lib/tools/cli-client/session.js +259 -0
  120. package/node_modules/playwright-core/lib/tools/dashboard/appIcon.png +0 -0
  121. package/node_modules/playwright-core/lib/tools/skills/playwright-cli/SKILL.md +425 -0
  122. package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/element-attributes.md +23 -0
  123. package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/playwright-tests.md +39 -0
  124. package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/request-mocking.md +87 -0
  125. package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/running-code.md +241 -0
  126. package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/session-management.md +225 -0
  127. package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/storage-state.md +275 -0
  128. package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/test-generation.md +433 -0
  129. package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/tracing.md +139 -0
  130. package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/video-recording.md +143 -0
  131. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/SKILL.md +136 -0
  132. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/gallery-spec.md +144 -0
  133. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/migration.md +89 -0
  134. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/react.md +69 -0
  135. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/typing.md +173 -0
  136. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/vue.md +77 -0
  137. package/node_modules/playwright-core/lib/tools/skills/playwright-trace/SKILL.md +174 -0
  138. package/node_modules/playwright-core/lib/tools/utils/extension.js +112 -0
  139. package/node_modules/playwright-core/lib/tools/utils/socketConnection.js +108 -0
  140. package/node_modules/playwright-core/lib/utilsBundle.js +85357 -0
  141. package/node_modules/playwright-core/lib/utilsBundle.js.LICENSE +1890 -0
  142. package/node_modules/playwright-core/lib/vite/dashboard/assets/codeMirrorModule--QdMvsKi.css +1 -0
  143. package/node_modules/playwright-core/lib/vite/dashboard/assets/codeMirrorModule-CQ8RZGBm.js +32 -0
  144. package/node_modules/playwright-core/lib/vite/dashboard/assets/codicon-DCmgc-ay.ttf +0 -0
  145. package/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-1bWoP6pv.svg +1 -0
  146. package/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-beta-k3eOH_eK.svg +1 -0
  147. package/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-nightly-Cp5nfeDT.svg +1 -0
  148. package/node_modules/playwright-core/lib/vite/dashboard/assets/index-AwgwcYie.css +1 -0
  149. package/node_modules/playwright-core/lib/vite/dashboard/assets/index-hRvO4yU2.js +12 -0
  150. package/node_modules/playwright-core/lib/vite/dashboard/assets/safari-na3_-uQk.svg +1 -0
  151. package/node_modules/playwright-core/lib/vite/dashboard/index.html +29 -0
  152. package/node_modules/playwright-core/lib/vite/dashboard/playwright-logo.svg +24 -0
  153. package/node_modules/playwright-core/lib/vite/htmlReport/index.html +16 -0
  154. package/node_modules/playwright-core/lib/vite/htmlReport/report.css +2 -0
  155. package/node_modules/playwright-core/lib/vite/htmlReport/report.js +32 -0
  156. package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule--QdMvsKi.css +1 -0
  157. package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-CVQWJZAA.js +32 -0
  158. package/node_modules/playwright-core/lib/vite/recorder/assets/codicon-DCmgc-ay.ttf +0 -0
  159. package/node_modules/playwright-core/lib/vite/recorder/assets/index-BZpYJZ-P.css +1 -0
  160. package/node_modules/playwright-core/lib/vite/recorder/assets/index-hrlLqLtq.js +129 -0
  161. package/node_modules/playwright-core/lib/vite/recorder/index.html +29 -0
  162. package/node_modules/playwright-core/lib/vite/recorder/playwright-logo.svg +9 -0
  163. package/node_modules/playwright-core/lib/vite/traceViewer/assets/codeMirrorModule-BbkfBe3n.js +32 -0
  164. package/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-Ds6CBOo0.js +182 -0
  165. package/node_modules/playwright-core/lib/vite/traceViewer/assets/urlMatch-L3liM589.js +1 -0
  166. package/node_modules/playwright-core/lib/vite/traceViewer/assets/xtermModule-DywYcAf8.js +7 -0
  167. package/node_modules/playwright-core/lib/vite/traceViewer/codeMirrorModule.-QdMvsKi.css +1 -0
  168. package/node_modules/playwright-core/lib/vite/traceViewer/codicon.DCmgc-ay.ttf +0 -0
  169. package/node_modules/playwright-core/lib/vite/traceViewer/defaultSettingsView.Bqk9acqE.css +1 -0
  170. package/node_modules/playwright-core/lib/vite/traceViewer/index.B4cLoZK3.js +1 -0
  171. package/node_modules/playwright-core/lib/vite/traceViewer/index.B_TqY17P.css +1 -0
  172. package/node_modules/playwright-core/lib/vite/traceViewer/index.html +44 -0
  173. package/node_modules/playwright-core/lib/vite/traceViewer/manifest.webmanifest +16 -0
  174. package/node_modules/playwright-core/lib/vite/traceViewer/playwright-logo.svg +9 -0
  175. package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.B_Jk1wbt.js +1 -0
  176. package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.html +10 -0
  177. package/node_modules/playwright-core/lib/vite/traceViewer/sw.bundle.js +4 -0
  178. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CU5KtEkS.js +5 -0
  179. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CyMfwkXJ.css +1 -0
  180. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +18 -0
  181. package/node_modules/playwright-core/lib/vite/traceViewer/xtermModule.kHJ-D0s7.css +1 -0
  182. package/node_modules/playwright-core/lib/webp_codec.LICENSE +173 -0
  183. package/node_modules/playwright-core/lib/webp_codec.wasm +0 -0
  184. package/node_modules/playwright-core/lib/xdg-open +1267 -0
  185. package/node_modules/playwright-core/package.json +34 -0
  186. package/node_modules/playwright-core/types/protocol.d.ts +24645 -0
  187. package/node_modules/playwright-core/types/structs.d.ts +52 -0
  188. package/node_modules/playwright-core/types/types.d.ts +26632 -0
  189. package/package.json +12 -2
  190. package/plugins/devflow-core/extensions/devflow-core.ts +431 -7
  191. package/plugins/devflow-core/runtime/hooks/hook_common.py +269 -0
  192. package/plugins/devflow-core/runtime/hooks/index_gate.py +29 -16
  193. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/acceptance_test_runner.py +407 -71
  194. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/advance_state.rb +51 -0
  195. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/approval_pins.py +433 -0
  196. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/approval_store.py +137 -0
  197. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/dsl_coverage.py +87 -0
  198. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/dsl_v1.py +318 -0
  199. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/environment_profile.py +134 -0
  200. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/execution_contract.rb +1 -0
  201. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/execution_surface.py +138 -0
  202. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/playwright_adapter.py +515 -0
  203. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/tc_hash.py +132 -0
  204. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/test_command_policy.py +102 -8
  205. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/validate_state.rb +13 -0
  206. package/plugins/devflow-core/skills/workflow-orchestrator/templates/feature-development.yaml +13 -34
  207. package/plugins/devflow-core/skills/workflow-pipeline/SKILL.md +3 -1
  208. package/plugins/devflow-core/skills/workflow-pipeline/references/pipeline-flow.md +68 -140
  209. package/plugins/devflow-core/skills/workflow-project-context/scripts/validate_initialization_database.py +1 -1
  210. package/plugins/devflow-implementation/skills/workflow-execute-phase/references/execute-phase.md +3 -1
  211. package/plugins/devflow-intel/skills/workflow-code-index/scripts/code_index.py +23 -4
  212. package/plugins/devflow-quality/runtime/playwright/devflow-test-dsl-v1.schema.json +872 -0
  213. package/plugins/devflow-quality/runtime/playwright/playwright.config.mjs +46 -0
  214. package/plugins/devflow-quality/runtime/playwright/reporter.mjs +311 -0
  215. package/plugins/devflow-quality/runtime/playwright/runner.spec.mjs +1308 -0
  216. package/plugins/devflow-quality/runtime/playwright/runtime-manifest.json +11 -0
  217. package/plugins/devflow-quality/skills/workflow-acceptance-test-design/SKILL.md +3 -0
  218. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/SKILL.md +71 -58
  219. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/api-mainflow-rules.md +20 -15
  220. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/codebase-discovery.md +3 -1
  221. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/intermediate-format.md +115 -192
  222. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/mainflow-selection.md +27 -23
  223. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/project-dsl-contract.md +78 -153
  224. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/ui-mainflow-rules.md +36 -83
  225. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/compile_dsl.py +497 -556
  226. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/export_legacy_testagent.py +647 -0
  227. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/normalize_case_payloads.py +10 -3
  228. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/validate_case_payloads.py +165 -275
  229. package/plugins/devflow-quality/skills/workflow-human-acceptance/SKILL.md +61 -0
  230. package/plugins/devflow-quality/skills/workflow-release-closeout/SKILL.md +4 -4
  231. package/plugins/devflow-quality/skills/workflow-run-acceptance-tests/SKILL.md +15 -9
  232. package/src/approval-store.ts +340 -0
  233. package/src/constants.ts +1 -1
  234. package/src/doctor.ts +259 -2
  235. package/src/initialization-store.ts +48 -1
  236. package/src/paths.ts +12 -1
  237. package/src/project-context.ts +24 -1
  238. package/templates/omp/hook-config.json +6 -0
@@ -442,6 +442,275 @@ def tool_requests_protected_source(
442
442
  return False, []
443
443
 
444
444
 
445
+ # Core control store protection (approval records live in control.db). This is
446
+ # misuse prevention, not a defence against a hostile same-user process. It is
447
+ # always on: neither DEVFLOW_SOFT_GATES, the policy mode nor hook-config can
448
+ # disable it; hook-config may only add extra protected path globs.
449
+ CONTROL_STORE_NAME_RE = re.compile(r"^control\.db(?:$|[-.])", re.IGNORECASE)
450
+ CANONICAL_ACCEPTANCE_RE = re.compile(
451
+ r"(?:^|/)\.devflow/planning/test-runs/[^/]+/acceptance-results(?:\.meta)?\.json$",
452
+ re.IGNORECASE,
453
+ )
454
+ CANONICAL_ACCEPTANCE_COMMAND_RE = re.compile(
455
+ r"\.devflow/planning/test-runs/(?:[^\s/'\"`]+|\$\{?[^\s}/]+\}?)/acceptance-results(?:\.meta)?\.json\b",
456
+ re.IGNORECASE,
457
+ )
458
+ _CONTROL_TABLE = r"[\"'`\[]?(?:main\.)?(?:approval_proposals|approvals|authority_state)\b[\"'`\]]?"
459
+ APPROVAL_SQL_RE = re.compile(
460
+ r"\b(?:insert\s+(?:or\s+\w+\s+)?into|replace\s+into|delete\s+from|drop\s+table(?:\s+if\s+exists)?|alter\s+table)\s+"
461
+ + _CONTROL_TABLE
462
+ + r"|\bupdate\s+(?:or\s+\w+\s+)?"
463
+ + _CONTROL_TABLE
464
+ + r"\s+set\b",
465
+ re.IGNORECASE,
466
+ )
467
+
468
+
469
+ def devflow_home() -> Path:
470
+ """DEVFLOW_HOME rule shared with src/paths.ts: unset/empty/whitespace-only -> ~/.devflow-pi,
471
+ otherwise expand a leading ``~`` and make absolute (no symlink resolution, existence not required)."""
472
+ configured = os.environ.get("DEVFLOW_HOME")
473
+ if configured is None or not configured.strip():
474
+ return Path(os.path.abspath(str(Path.home() / ".devflow-pi")))
475
+ if configured == "~" or configured.startswith("~/"):
476
+ configured = str(Path.home()) + configured[1:]
477
+ return Path(os.path.abspath(configured))
478
+
479
+
480
+ def _raw_tool_paths(event: dict[str, Any]) -> list[str]:
481
+ tool_input = event.get("tool_input", {})
482
+ values = list(_walk_path_values(tool_input))
483
+ if isinstance(tool_input, dict):
484
+ for key in ("patch", "input"):
485
+ patch = tool_input.get(key)
486
+ if isinstance(patch, str):
487
+ values.extend(re.findall(r"^\*\*\* (?:Add|Update|Delete|Move) (?:File|to):\s*(.+)$", patch, re.MULTILINE))
488
+ elif isinstance(tool_input, str):
489
+ values.extend(re.findall(r"^\*\*\* (?:Add|Update|Delete|Move) (?:File|to):\s*(.+)$", tool_input, re.MULTILINE))
490
+ return [str(value).strip().strip("'\"") for value in values if str(value).strip()]
491
+
492
+
493
+ def _extra_control_store_globs(root: Path) -> list[str]:
494
+ config = load_hook_config(root).get("gates", {})
495
+ section = config.get("control_store", {}) if isinstance(config, dict) else {}
496
+ extra = section.get("extra_protected_paths", []) if isinstance(section, dict) else []
497
+ return [str(item) for item in extra if isinstance(item, str) and item.strip()] if isinstance(extra, list) else []
498
+
499
+
500
+ def _under(item: Path, base: Path) -> bool:
501
+ try:
502
+ item.relative_to(base)
503
+ return True
504
+ except ValueError:
505
+ return False
506
+
507
+
508
+ def _data_home_project_roots() -> list[Path]:
509
+ """Every spelling of the Devflow ``projects`` tree (configured home, default home, symlink-resolved)."""
510
+ homes = [devflow_home(), Path(os.path.abspath(str(Path.home() / ".devflow-pi")))]
511
+ roots: list[Path] = []
512
+ for home in homes:
513
+ for base in (home, Path(os.path.realpath(str(home)))):
514
+ root = base / "projects"
515
+ if root not in roots:
516
+ roots.append(root)
517
+ return roots
518
+
519
+
520
+ def is_pure_read_tool(event: dict[str, Any]) -> bool:
521
+ """True for file-reader tools (Read/Grep/Glob/...) that cannot mutate anything."""
522
+ name = tool_name(event)
523
+ return (
524
+ any(marker in name for marker in READ_TOOL_MARKERS)
525
+ and not is_mutation_tool(event)
526
+ and not is_command_tool(event)
527
+ )
528
+
529
+
530
+ def path_is_control_store(
531
+ raw: str, cwd: Path, extra_globs: list[str] | None = None, allow_acceptance_read: bool = False
532
+ ) -> bool:
533
+ """``allow_acceptance_read`` opens canonical acceptance-results*.json for pure read tools only;
534
+ control.db, the data-home projects tree and extra globs stay denied for every access."""
535
+ candidate = Path(raw).expanduser()
536
+ if not candidate.is_absolute():
537
+ candidate = cwd / candidate
538
+ lexical = Path(os.path.normpath(str(candidate)))
539
+ resolved = Path(os.path.realpath(str(candidate)))
540
+ project_roots = _data_home_project_roots()
541
+ for item in (lexical, resolved):
542
+ if not allow_acceptance_read and CANONICAL_ACCEPTANCE_RE.search(item.as_posix()):
543
+ return True
544
+ if CONTROL_STORE_NAME_RE.match(item.name):
545
+ return True
546
+ if any(_under(item, root) for root in project_roots):
547
+ return True
548
+ if any(fnmatch.fnmatch(item.as_posix(), pattern) for pattern in extra_globs or []):
549
+ return True
550
+ return False
551
+
552
+
553
+ # Programs that can open a SQLite file (or run arbitrary code that can) from their arguments.
554
+ DB_CAPABLE_PROGRAM_RE = re.compile(
555
+ r"^(?:sqlite3?|python[\d.]*|pypy[\d.]*|bun|bunx|node|deno|ruby|perl|php|lua|litecli|duckdb|datasette|sqlite-utils|sqlitebrowser)$",
556
+ re.IGNORECASE,
557
+ )
558
+ # Programs that read/copy/delete their file arguments, so a bare ``control.db`` operand is a real file target.
559
+ FILE_OPERAND_PROGRAMS = {
560
+ "cat", "less", "more", "head", "tail", "strings", "xxd", "od", "hexdump", "file", "stat", "cp", "mv", "rm",
561
+ "tee", "ln", "dd", "install", "touch", "truncate", "rsync", "scp", "sha256sum", "shasum", "md5sum", "md5",
562
+ "cksum", "base64", "open", "tar", "zip", "gzip", "gunzip", "bzip2", "xz", "unlink", "shred", "chmod", "chown",
563
+ }
564
+ WRAPPER_PROGRAMS = {"sudo", "env", "command", "time", "nohup", "exec", "nice", "xargs", "builtin", "stdbuf", "timeout"}
565
+ SHELL_PROGRAMS = {"sh", "bash", "zsh", "dash", "ksh", "fish"}
566
+ CONTROL_OPERATORS = {";", "&&", "||", "|", "&", "|&", "(", ")", "\n"}
567
+ REDIRECT_OPERATORS = {">", ">>", ">|", "<", "<>", "&>", ">&", "<&", "&>>"}
568
+ ASSIGNMENT_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*=")
569
+
570
+
571
+ def _split_unquoted_newlines(command: str) -> str:
572
+ out: list[str] = []
573
+ quote = ""
574
+ escaped = False
575
+ for ch in command:
576
+ if escaped:
577
+ escaped = False
578
+ elif ch == "\\" and quote != "'":
579
+ escaped = True
580
+ elif quote:
581
+ if ch == quote:
582
+ quote = ""
583
+ elif ch in "'\"":
584
+ quote = ch
585
+ elif ch == "\n":
586
+ out.append(" ; ")
587
+ continue
588
+ out.append(ch)
589
+ return "".join(out)
590
+
591
+
592
+ def _shell_tokens(command: str) -> list[str]:
593
+ import shlex
594
+
595
+ prepared = _split_unquoted_newlines(command)
596
+ try:
597
+ lexer = shlex.shlex(prepared, posix=True, punctuation_chars=True)
598
+ lexer.whitespace_split = True
599
+ lexer.commenters = ""
600
+ return list(lexer)
601
+ except ValueError:
602
+ return [item.strip("'\"") for item in prepared.split()]
603
+
604
+
605
+ def _expand_shell_path(token: str) -> str:
606
+ home = devflow_home().as_posix()
607
+ user_home = str(Path.home())
608
+ for name, value in (("DEVFLOW_HOME", home), ("HOME", user_home)):
609
+ token = re.sub(r"\$\{" + name + r"(?::-[^}]*)?\}|\$" + name + r"\b", lambda _m, v=value: v, token)
610
+ if token == "~" or token.startswith("~/"):
611
+ token = user_home + token[1:]
612
+ return token
613
+
614
+
615
+ def _token_is_control_target(token: str, cwd: Path, *, bare_name_is_target: bool) -> bool:
616
+ if not token or any(ch.isspace() for ch in token):
617
+ return False
618
+ if token.startswith("-") and "=" in token:
619
+ token = token.split("=", 1)[1]
620
+ elif token.startswith("-"):
621
+ return False
622
+ expanded = _expand_shell_path(token).replace("\\", "/")
623
+ candidate = Path(expanded)
624
+ if not candidate.is_absolute():
625
+ candidate = cwd / candidate
626
+ lexical = Path(os.path.normpath(str(candidate)))
627
+ resolved = Path(os.path.realpath(str(candidate)))
628
+ project_roots = _data_home_project_roots()
629
+ for item in (lexical, resolved):
630
+ if any(_under(item, root) for root in project_roots):
631
+ return True
632
+ if ".devflow-pi/projects" in expanded:
633
+ return True
634
+ for item in (lexical, resolved):
635
+ if not CONTROL_STORE_NAME_RE.match(item.name):
636
+ continue
637
+ # ``rg control.db src/`` names a pattern, not a file: a bare name only counts when it is
638
+ # a real file, is redirected/handed to a file-operating program, or carries a directory part.
639
+ if "/" in expanded or bare_name_is_target or item.exists() or resolved.exists():
640
+ return True
641
+ return False
642
+
643
+
644
+ def _segments(tokens: list[str]) -> list[list[str]]:
645
+ segments: list[list[str]] = [[]]
646
+ for token in tokens:
647
+ if token in CONTROL_OPERATORS:
648
+ segments.append([])
649
+ else:
650
+ segments[-1].append(token)
651
+ return [segment for segment in segments if segment]
652
+
653
+
654
+ def _analyze_command(command: str, cwd: Path, depth: int = 0) -> bool:
655
+ tokens = _shell_tokens(command)
656
+ for segment in _segments(tokens):
657
+ index = 0
658
+ while index < len(segment) and (ASSIGNMENT_RE.match(segment[index]) or segment[index] in WRAPPER_PROGRAMS):
659
+ index += 1
660
+ if index >= len(segment):
661
+ continue
662
+ program = os.path.basename(segment[index]).lower()
663
+ operands = segment[index + 1 :]
664
+ db_program = bool(DB_CAPABLE_PROGRAM_RE.match(program))
665
+ if program in SHELL_PROGRAMS and depth < 3:
666
+ for pos, operand in enumerate(operands):
667
+ if operand.startswith("-") and "c" in operand[1:] and pos + 1 < len(operands):
668
+ if _analyze_command(operands[pos + 1], cwd, depth + 1):
669
+ return True
670
+ previous = ""
671
+ for operand in [*segment[:index], *operands]:
672
+ after_redirect = previous in REDIRECT_OPERATORS or bool(re.match(r"^\d*(?:>>?|<)$", previous))
673
+ bare_ok = db_program or after_redirect or program in FILE_OPERAND_PROGRAMS
674
+ if operand not in REDIRECT_OPERATORS:
675
+ if db_program and (
676
+ re.search(r"control\.db", operand, re.IGNORECASE) or ".devflow-pi/projects" in operand
677
+ ):
678
+ return True
679
+ if _token_is_control_target(operand, cwd, bare_name_is_target=bare_ok):
680
+ return True
681
+ glued = re.sub(r"^\d*[<>]+", "", operand)
682
+ if glued != operand and _token_is_control_target(glued, cwd, bare_name_is_target=True):
683
+ return True
684
+ previous = operand
685
+ return False
686
+
687
+
688
+ def command_touches_control_store(command: str, cwd: Path | None = None) -> bool:
689
+ """Deny shell commands that target the real control store or write approval tables.
690
+
691
+ Merely mentioning the text ``control.db`` (rg patterns, commit messages, echo) is allowed;
692
+ this is a mistake-prevention guard, not a sandbox."""
693
+ normalized = command.replace("\\", "/")
694
+ if APPROVAL_SQL_RE.search(normalized) or CANONICAL_ACCEPTANCE_COMMAND_RE.search(normalized):
695
+ return True
696
+ return _analyze_command(command, cwd or Path.cwd())
697
+
698
+
699
+ def tool_touches_control_store(event: dict[str, Any], root: Path) -> tuple[bool, list[str]]:
700
+ """Deny non-Core access to control.db and the Devflow project data dir; canonical
701
+ acceptance results are additionally readable by pure read tools (never writable)."""
702
+ cwd = Path(str(event.get("cwd") or root)).expanduser()
703
+ extra = _extra_control_store_globs(root)
704
+ allow_read = is_pure_read_tool(event)
705
+ blocked = [raw for raw in _raw_tool_paths(event) if path_is_control_store(raw, cwd, extra, allow_read)]
706
+ if blocked:
707
+ return True, sorted(dict.fromkeys(blocked))
708
+ command = command_text(event)
709
+ if command and command_touches_control_store(command, cwd):
710
+ return True, ["<shell command>"]
711
+ return False, []
712
+
713
+
445
714
  def evidence_observed(event: dict[str, Any], root: Path, artifacts: list[str]) -> list[str]:
446
715
  name = tool_name(event)
447
716
  if not (any(marker in name for marker in READ_TOOL_MARKERS) or is_command_tool(event)):
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """PreToolUse hook: block protected source access until indexes were observed."""
2
+ """PreToolUse hook: deny control-store access and block protected source access until indexes were observed."""
3
3
 
4
4
  from __future__ import annotations
5
5
 
@@ -16,12 +16,39 @@ from hook_common import (
16
16
  record_prechange_fingerprints,
17
17
  required_evidence,
18
18
  tool_requests_protected_source,
19
+ tool_touches_control_store,
19
20
  )
20
21
 
21
22
 
23
+ def deny(reason: str) -> int:
24
+ print(
25
+ json.dumps(
26
+ {
27
+ "hookSpecificOutput": {
28
+ "hookEventName": "PreToolUse",
29
+ "permissionDecision": "deny",
30
+ "permissionDecisionReason": reason,
31
+ }
32
+ },
33
+ ensure_ascii=False,
34
+ )
35
+ )
36
+ return 0
37
+
38
+
22
39
  def main() -> int:
23
40
  event = read_event()
24
41
  root = project_root(event)
42
+ # Hard protection, evaluated before any soft/strict policy handling.
43
+ control_blocked, control_paths = tool_touches_control_store(event, root)
44
+ if control_blocked:
45
+ return deny(
46
+ "CONTROL_STORE_PROTECTED: Devflow control.db, the Devflow data-home projects tree and writes to canonical "
47
+ "acceptance results are reserved for Core runtime tools; this misuse-prevention hook denies ordinary agent file and "
48
+ "shell access to them regardless of DEVFLOW_SOFT_GATES, but is not a same-user process sandbox. "
49
+ "Canonical acceptance-results*.json may be read with the Read/Grep file tools. "
50
+ f"Use devflow_approval(action=\"propose\"|\"list\") for approvals. Blocked target: {', '.join(control_paths)}."
51
+ )
25
52
  policy = load_policy(root)
26
53
  session_id = ensure_session(root, event)
27
54
  required = required_evidence(root, policy)
@@ -34,24 +61,11 @@ def main() -> int:
34
61
  missing = [path for path in required if path not in observed]
35
62
  strict = str(policy.get("mode", "strict")).lower() == "strict"
36
63
  if strict and required and missing and blocked:
37
- reason = (
64
+ return deny(
38
65
  "INDEX_GATE_BLOCKED: protected source access requires observed index evidence. "
39
66
  f"Read these files first, in order: {', '.join(missing)}. "
40
67
  f"Blocked target: {', '.join(paths)}. The gate records successful tool reads, not assistant promises."
41
68
  )
42
- print(
43
- json.dumps(
44
- {
45
- "hookSpecificOutput": {
46
- "hookEventName": "PreToolUse",
47
- "permissionDecision": "deny",
48
- "permissionDecisionReason": reason,
49
- }
50
- },
51
- ensure_ascii=False,
52
- )
53
- )
54
- return 0
55
69
 
56
70
  if is_mutation_tool(event):
57
71
  record_prechange_fingerprints(root, session_id, direct_tool_paths(event, root))
@@ -60,4 +74,3 @@ def main() -> int:
60
74
 
61
75
  if __name__ == "__main__":
62
76
  raise SystemExit(main())
63
-