@khoavu882/doflow 2.0.0-beta.3 → 2.0.0-beta.5

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 (236) hide show
  1. package/bin/doflow.js +2 -2
  2. package/core/.antigravity-plugin/plugin.json +0 -1
  3. package/core/.claude-plugin/plugin.json +1 -1
  4. package/core/.codex-plugin/plugin.json +1 -1
  5. package/core/.plugin/plugin.json +1 -1
  6. package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
  7. package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
  8. package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
  9. package/core/harnesses/claude/hooks/session-start.sh +5 -3
  10. package/core/harnesses/claude/hooks/stop-check.sh +5 -3
  11. package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
  12. package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
  13. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
  14. package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
  15. package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
  16. package/core/harnesses/codex/hooks/session-start.sh +5 -3
  17. package/core/harnesses/codex/hooks/stop-check.sh +6 -5
  18. package/core/harnesses/codex/hooks/subagent-audit.sh +17 -6
  19. package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
  20. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +7 -18
  21. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +7 -18
  22. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +7 -19
  23. package/core/harnesses/gemini/hooks/session-start.sh +5 -3
  24. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +20 -5
  25. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +20 -5
  26. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +20 -5
  27. package/core/harnesses/kiro/hooks/session-start.sh +11 -5
  28. package/core/harnesses/kiro/hooks/stop-check.sh +21 -30
  29. package/core/harnesses/pi/extensions/doflow-guard.js +159 -0
  30. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -11
  31. package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
  32. package/core/harnesses/shared/hooks/bridge/bridge.sh +184 -0
  33. package/core/harnesses/shared/hooks/bridge/envelope.jq +60 -0
  34. package/core/harnesses/shared/hooks/bridge/respond.sh +62 -0
  35. package/core/harnesses/shared/hooks/policies/capture-failure.sh +4 -2
  36. package/core/harnesses/shared/hooks/policies/contract.sh +75 -0
  37. package/core/harnesses/shared/hooks/policies/deny-json.sh +39 -4
  38. package/core/harnesses/shared/hooks/policies/lib.sh +4 -2
  39. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +46 -34
  40. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +75 -25
  41. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +67 -73
  42. package/core/harnesses/shared/hooks/policies/session-context.sh +37 -7
  43. package/core/harnesses/shared/hooks/policies/stop-check.sh +42 -15
  44. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +35 -5
  45. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +40 -7
  46. package/core/harnesses/shared/hooks/stream-hook-runner.js +69 -181
  47. package/core/registry/assets.json +19 -0
  48. package/core/registry/contracts.json +2 -2
  49. package/core/registry/harnesses.json +196 -10
  50. package/core/registry/lifecycle.json +2626 -7
  51. package/core/registry/storage.json +110 -83
  52. package/core/registry/verification.json +1 -1
  53. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -2
  54. package/core/shared/scripts/doflow/bin/doflow-run +10 -6
  55. package/core/shared/skills/do-design/SKILL.md +6 -6
  56. package/core/shared/skills/do-execute-plan/SKILL.md +11 -10
  57. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +2 -0
  58. package/core/shared/skills/do-execute-plan/references/scaffold.md +2 -2
  59. package/core/shared/skills/do-implement/SKILL.md +3 -2
  60. package/core/shared/skills/do-plan/SKILL.md +7 -8
  61. package/package.json +4 -2
  62. package/src/adapters/antigravity/{index.ts → index.js} +141 -105
  63. package/src/adapters/claude/{index.ts → index.js} +63 -49
  64. package/src/adapters/claude/{mcp.ts → mcp.js} +16 -16
  65. package/src/adapters/codex/{agents.ts → agents.js} +24 -24
  66. package/src/adapters/codex/{config.ts → config.js} +49 -49
  67. package/src/adapters/codex/{hooks.ts → hooks.js} +62 -27
  68. package/src/adapters/codex/{index.ts → index.js} +148 -105
  69. package/src/adapters/codex/{mcp.ts → mcp.js} +38 -38
  70. package/src/adapters/codex/{native-projection.ts → native-projection.js} +5 -5
  71. package/src/adapters/copilot/{index.ts → index.js} +42 -42
  72. package/src/adapters/{copy-tree.ts → copy-tree.js} +74 -74
  73. package/src/adapters/gemini/{hooks.ts → hooks.js} +78 -37
  74. package/src/adapters/gemini/{index.ts → index.js} +96 -57
  75. package/src/adapters/{hook-commands.ts → hook-commands.js} +8 -8
  76. package/src/adapters/{index.ts → index.js} +13 -13
  77. package/src/adapters/kiro/{index.ts → index.js} +49 -35
  78. package/src/adapters/{mcp-entries.ts → mcp-entries.js} +55 -55
  79. package/src/adapters/opencode/{index.ts → index.js} +52 -52
  80. package/src/adapters/pi/hooks.js +119 -0
  81. package/src/adapters/pi/{index.ts → index.js} +64 -39
  82. package/src/adapters/pi/{json-members.ts → json-members.js} +23 -23
  83. package/src/adapters/pi/{mcp.ts → mcp.js} +70 -70
  84. package/src/adapters/{resolve-source.ts → resolve-source.js} +1 -1
  85. package/src/cli/commands/{install.ts → install.js} +25 -25
  86. package/src/cli/commands/{list-backups.ts → list-backups.js} +4 -4
  87. package/src/cli/commands/{reconcile.ts → reconcile.js} +19 -19
  88. package/src/cli/commands/{remove.ts → remove.js} +10 -10
  89. package/src/cli/commands/{rollback.ts → rollback.js} +17 -17
  90. package/src/cli/commands/{self-update.ts → self-update.js} +3 -3
  91. package/src/cli/commands/{status.ts → status.js} +24 -24
  92. package/src/cli/commands/{tools.ts → tools.js} +16 -16
  93. package/src/cli/commands/{update.ts → update.js} +20 -20
  94. package/src/cli/{index.ts → index.js} +43 -31
  95. package/src/cli/{runtime-commands.ts → runtime-commands.js} +97 -58
  96. package/src/cli/{shared.ts → shared.js} +40 -40
  97. package/src/helper/{bash-helper.ts → bash-helper.js} +7 -7
  98. package/src/helper/{git.ts → git.js} +2 -2
  99. package/src/helper/{guardrails.ts → guardrails.js} +10 -10
  100. package/src/helper/{harness-paths.ts → harness-paths.js} +24 -24
  101. package/src/helper/{marker-merge.ts → marker-merge.js} +4 -4
  102. package/src/helper/{prompt.ts → prompt.js} +5 -5
  103. package/src/helper/{repo-root.ts → repo-root.js} +2 -2
  104. package/src/helper/{settings-merge.ts → settings-merge.js} +27 -27
  105. package/src/helper/{settings-scope.ts → settings-scope.js} +3 -3
  106. package/src/helper/{toml.ts → toml.js} +48 -48
  107. package/src/install/{backup-set.ts → backup-set.js} +16 -16
  108. package/src/install/{backup.ts → backup.js} +65 -65
  109. package/src/install/{context.ts → context.js} +15 -15
  110. package/src/install/{manifest.ts → manifest.js} +27 -27
  111. package/src/install/{mcp.ts → mcp.js} +30 -30
  112. package/src/install/{paths.ts → paths.js} +15 -15
  113. package/src/install/{targets.ts → targets.js} +10 -10
  114. package/src/install/{tool-lifecycle.ts → tool-lifecycle.js} +20 -20
  115. package/src/lifecycle/{bash-availability.ts → bash-availability.js} +1 -1
  116. package/src/lifecycle/coverage.js +57 -0
  117. package/src/lifecycle/hook-step.js +61 -0
  118. package/src/lifecycle/{index.ts → index.js} +160 -146
  119. package/src/lifecycle/{mcp-index.ts → mcp-index.js} +1 -1
  120. package/src/lifecycle/{policies.ts → policies.js} +11 -11
  121. package/src/lifecycle/{view.ts → view.js} +28 -28
  122. package/src/registry/{index.ts → index.js} +195 -66
  123. package/src/release/build-package.js +91 -0
  124. package/src/release/changelog-section.js +70 -0
  125. package/src/release/{sync-plugin-versions.ts → sync-plugin-versions.js} +4 -4
  126. package/src/runtime/advice/{cli.ts → cli.js} +31 -31
  127. package/src/runtime/advice/{client.ts → client.js} +44 -44
  128. package/src/runtime/advice/{config.ts → config.js} +16 -16
  129. package/src/runtime/advice/{doctor.ts → doctor.js} +32 -32
  130. package/src/runtime/advice/{endpoint-guard.ts → endpoint-guard.js} +20 -20
  131. package/src/runtime/advice/{kinds.ts → kinds.js} +16 -16
  132. package/src/runtime/advice/providers/{ollama-systemone.ts → ollama-systemone.js} +21 -21
  133. package/src/runtime/advice/{record.ts → record.js} +19 -19
  134. package/src/runtime/advice/{registry.ts → registry.js} +16 -16
  135. package/src/runtime/advice/{strength.ts → strength.js} +7 -7
  136. package/src/runtime/{brief-tools.ts → brief-tools.js} +50 -50
  137. package/src/runtime/{c4-project.ts → c4-project.js} +27 -27
  138. package/src/runtime/candidate.js +505 -0
  139. package/src/runtime/{capability-router.ts → capability-router.js} +39 -39
  140. package/src/runtime/{checkouts.ts → checkouts.js} +21 -21
  141. package/src/runtime/{claims.ts → claims.js} +44 -35
  142. package/src/runtime/{cli-result.ts → cli-result.js} +15 -8
  143. package/src/runtime/{cli.ts → cli.js} +59 -292
  144. package/src/runtime/{command-detect.ts → command-detect.js} +47 -47
  145. package/src/runtime/context/budget.js +203 -0
  146. package/src/runtime/context/compose.js +1367 -0
  147. package/src/runtime/context/delivery-store.js +102 -0
  148. package/src/runtime/context/mandatory.js +594 -0
  149. package/src/runtime/context/pointer.js +142 -0
  150. package/src/runtime/context-pack.js +1277 -0
  151. package/src/runtime/{decision-register.ts → decision-register.js} +67 -67
  152. package/src/runtime/evidence-fields.js +39 -0
  153. package/src/runtime/{evidence-ledger.ts → evidence-ledger.js} +32 -25
  154. package/src/runtime/failure/{capture.ts → capture.js} +29 -29
  155. package/src/runtime/failure/{classifier.ts → classifier.js} +4 -4
  156. package/src/runtime/failure/{cli.ts → cli.js} +30 -30
  157. package/src/runtime/failure/{home.ts → home.js} +6 -6
  158. package/src/runtime/failure/{store.ts → store.js} +32 -32
  159. package/src/runtime/{feature-resolve.ts → feature-resolve.js} +6 -6
  160. package/src/runtime/{freshness.ts → freshness.js} +59 -22
  161. package/src/runtime/{health.ts → health.js} +72 -72
  162. package/src/runtime/{history-compactor.ts → history-compactor.js} +25 -25
  163. package/src/runtime/{implementation-gate.ts → implementation-gate.js} +30 -30
  164. package/src/runtime/inventory/{coverage.ts → coverage.js} +10 -10
  165. package/src/runtime/inventory/{identity.ts → identity.js} +2 -2
  166. package/src/runtime/inventory/{index.ts → index.js} +36 -36
  167. package/src/runtime/inventory/{precedence.ts → precedence.js} +2 -2
  168. package/src/runtime/inventory/{read-scopes.ts → read-scopes.js} +9 -9
  169. package/src/runtime/inventory/{siblings.ts → siblings.js} +15 -15
  170. package/src/runtime/knowledge/{chunker.ts → chunker.js} +12 -12
  171. package/src/runtime/knowledge/{hybrid.ts → hybrid.js} +17 -17
  172. package/src/runtime/knowledge/{index-store.ts → index-store.js} +23 -23
  173. package/src/runtime/knowledge/{retrieval.ts → retrieval.js} +27 -27
  174. package/src/runtime/{leak-scan.ts → leak-scan.js} +12 -12
  175. package/src/runtime/lifecycle/{cli.ts → cli.js} +32 -32
  176. package/src/runtime/lifecycle/{event-store.ts → event-store.js} +48 -48
  177. package/src/runtime/lifecycle/{fold.ts → fold.js} +41 -41
  178. package/src/runtime/lifecycle/{followup.ts → followup.js} +54 -54
  179. package/src/runtime/lifecycle/{goal.ts → goal.js} +50 -50
  180. package/src/runtime/lifecycle/{intent-writer.ts → intent-writer.js} +5 -5
  181. package/src/runtime/lifecycle/{overview.ts → overview.js} +47 -47
  182. package/src/runtime/lifecycle/{release.ts → release.js} +29 -29
  183. package/src/runtime/lifecycle/{report-store.ts → report-store.js} +25 -25
  184. package/src/runtime/lifecycle/{retention.ts → retention.js} +37 -37
  185. package/src/runtime/lifecycle/{root.ts → root.js} +9 -9
  186. package/src/runtime/lifecycle/{status.ts → status.js} +64 -64
  187. package/src/runtime/lifecycle/{store-upkeep.ts → store-upkeep.js} +46 -46
  188. package/src/runtime/{locator-resolve.ts → locator-resolve.js} +7 -7
  189. package/src/runtime/{mask.ts → mask.js} +22 -22
  190. package/src/runtime/{mcp-consultations.ts → mcp-consultations.js} +54 -54
  191. package/src/runtime/{mcp-reach.ts → mcp-reach.js} +24 -24
  192. package/src/runtime/{model-router.ts → model-router.js} +20 -20
  193. package/src/runtime/{outcome.ts → outcome.js} +27 -27
  194. package/src/runtime/{reach.ts → reach.js} +21 -21
  195. package/src/runtime/{readiness-record.ts → readiness-record.js} +12 -12
  196. package/src/runtime/{readiness.ts → readiness.js} +113 -43
  197. package/src/runtime/{recovery.ts → recovery.js} +15 -15
  198. package/src/runtime/{research-request.ts → research-request.js} +34 -34
  199. package/src/runtime/{retrieval-plan.ts → retrieval-plan.js} +453 -104
  200. package/src/runtime/retrieval-record.js +223 -0
  201. package/src/runtime/scaffold/{artifacts.ts → artifacts.js} +22 -19
  202. package/src/runtime/scaffold/{fingerprint.ts → fingerprint.js} +3 -3
  203. package/src/runtime/scaffold/{generate.ts → generate.js} +109 -109
  204. package/src/runtime/scaffold/{languages.ts → languages.js} +34 -34
  205. package/src/runtime/source-version.js +289 -0
  206. package/src/runtime/storage/{cli.ts → cli.js} +9 -9
  207. package/src/runtime/storage/{engine.ts → engine.js} +37 -37
  208. package/src/runtime/storage/{registry.ts → registry.js} +19 -19
  209. package/src/runtime/storage/{roots.ts → roots.js} +8 -8
  210. package/src/runtime/storage/{rules.ts → rules.js} +26 -26
  211. package/src/runtime/storage/{upkeep.ts → upkeep.js} +35 -35
  212. package/src/runtime/storage/{work-status.ts → work-status.js} +39 -39
  213. package/src/runtime/{task-attempts.ts → task-attempts.js} +62 -62
  214. package/src/runtime/{task-classifier.ts → task-classifier.js} +34 -34
  215. package/src/runtime/{task-scope.ts → task-scope.js} +40 -15
  216. package/src/runtime/{task-state.ts → task-state.js} +86 -21
  217. package/src/runtime/trace/{indicators.ts → indicators.js} +22 -22
  218. package/src/runtime/trace/{ledger.ts → ledger.js} +42 -42
  219. package/src/runtime/trace/{render.ts → render.js} +7 -7
  220. package/src/runtime/trace/{views.ts → views.js} +40 -40
  221. package/src/runtime/verification/{contract-runner.ts → contract-runner.js} +23 -23
  222. package/src/runtime/verification/{engine.ts → engine.js} +143 -143
  223. package/src/runtime/verification/{registry.ts → registry.js} +6 -6
  224. package/src/runtime/verification/{scope-bound.ts → scope-bound.js} +50 -50
  225. package/src/runtime/verification/{test-selection.ts → test-selection.js} +28 -28
  226. package/src/runtime/{workflow-engine.ts → workflow-engine.js} +50 -50
  227. package/src/runtime/{workflow-orchestrator.ts → workflow-orchestrator.js} +91 -91
  228. package/src/runtime/{worktree.ts → worktree.js} +31 -31
  229. package/src/state/{index.ts → index.js} +26 -26
  230. package/src/state/{lockfile.ts → lockfile.js} +20 -20
  231. package/src/state/{run-lock.ts → run-lock.js} +44 -44
  232. package/core/.antigravity-plugin/hooks.json +0 -39
  233. package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
  234. package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
  235. package/core/harnesses/shared/hooks/hooks.json +0 -47
  236. package/src/runtime/context-pack.ts +0 -492
@@ -2,27 +2,21 @@
2
2
  'use strict';
3
3
 
4
4
  /**
5
- * DoFlow Cross-Harness Hook Runner (design.md C2) — front door for every in-scope lifecycle
6
- * event (SessionStart, PreToolUse, PostToolUse, Stop, PreInvocation) for the two harnesses whose
7
- * native hook contract speaks JSON stdin/stdout rather than a bare exit code: Gemini CLI and
8
- * Antigravity. It owns event dispatch and per-harness payload/decision translation (delegated to
9
- * adapters/{gemini,antigravity}.js, D2) and delegates every policy decision to the Canonical
10
- * Policy Library (core/harnesses/shared/hooks/policies/*.sh, Phase A) — it implements no guard
11
- * policy logic itself.
5
+ * DoFlow Cross-Harness Hook Runner — front door for every in-scope lifecycle
6
+ * event (SessionStart, PreToolUse, PostToolUse, Stop, PreInvocation) for Antigravity. It owns
7
+ * event dispatch and native payload/decision
8
+ * translation (delegated to adapters/antigravity.js) and delegates every policy decision
9
+ * to the shared lifecycle bridge — it implements no guard policy logic itself.
12
10
  *
13
11
  * Invocation: `node stream-hook-runner.js [EventName]`, JSON payload on stdin, JSON decision on
14
- * stdout. EventName is the preferred way to select which canonical policy applies; when omitted
15
- * (older hooks.json wiring that doesn't yet pass it), the event is inferred from the payload's
16
- * own shape as a fallback, matching this runner's behavior before event names were threaded
17
- * through argv.
12
+ * stdout. EventName selects which canonical policy applies.
18
13
  */
19
14
 
20
- const { execFileSync } = require('node:child_process');
15
+ const { spawnSync } = require('node:child_process');
21
16
  const fs = require('node:fs');
22
17
  const os = require('node:os');
23
18
  const path = require('node:path');
24
19
 
25
- const geminiAdapter = require('./adapters/gemini');
26
20
  const antigravityAdapter = require('./adapters/antigravity');
27
21
 
28
22
  // Tool-name classification for PreToolUse, generalized to the union every Phase A canonical
@@ -249,15 +243,6 @@ function captureHookFailure({ command, kind, frame = null }, env = process.env)
249
243
  }
250
244
  }
251
245
 
252
- // Canonical script name -> pre-normalization per-harness file name, for the fallback search only.
253
- const LEGACY_SCRIPT_NAMES = {
254
- 'session-context.sh': 'session-start.sh',
255
- 'pre-implementation-gate.sh': 'pre-implement-gate.sh',
256
- 'mcp-tool-guard.sh': 'mcp-tool-guard.sh',
257
- 'stop-check.sh': 'stop-check.sh',
258
- 'pre-bash-guard.sh': 'pre-bash-guard.sh',
259
- };
260
-
261
246
  // A hook payload is one tool call's JSON — a few KB in the overwhelming majority of cases, a few
262
247
  // hundred KB for a large file edit at most. These bounds exist only to stop a misbehaving harness
263
248
  // process (stdin that never closes, or streams unboundedly) from hanging this front door forever;
@@ -334,75 +319,58 @@ function resolveProjectRoot(workspacePaths) {
334
319
  }
335
320
 
336
321
  /**
337
- * Resolve a Canonical Policy Library script's absolute path. Primary location is this runner's
338
- * own `policies/` sibling directory (core/harnesses/shared/hooks/policies/) — the canonical
339
- * source of truth (design.md C1) that ships alongside this file, so the lookup needs no
340
- * project-root guessing and works identically in-repo and once installed, as long as the
341
- * `shared/hooks/` tree is projected together. The per-harness candidate search below is kept only
342
- * as a fallback for an install that hasn't picked up the canonical policies/ directory yet.
322
+ * Delegate the native payload to the shared lifecycle bridge. The bridge owns encoding,
323
+ * classification, effects and diagnostics; this runner only applies Antigravity's native render.
343
324
  */
344
- function resolvePolicyScript(projectRoot, scriptName) {
345
- const primary = path.join(__dirname, 'policies', scriptName);
346
- if (fs.existsSync(primary)) return primary;
347
-
348
- const legacyName = LEGACY_SCRIPT_NAMES[scriptName] || scriptName;
349
- const candidates = [
350
- path.join(projectRoot, '.gemini/hooks', legacyName),
351
- path.join(projectRoot, '.claude/hooks', legacyName),
352
- path.join(projectRoot, '.codex/hooks', legacyName),
353
- path.join(projectRoot, '.doflow/scripts', legacyName),
354
- ];
355
- for (const candidate of candidates) {
356
- if (fs.existsSync(candidate)) return candidate;
357
- }
358
- return null;
359
- }
360
-
361
- /**
362
- * Delegate one canonical-decision evaluation to a Phase A policy script (design.md's Canonical
363
- * Policy Script Contract): the canonical payload goes in on stdin, DOFLOW_PROJECT_DIR/DOFLOW_AGENT
364
- * are set, and the exit code decides allow/deny — exit 0 = allow, non-zero = deny with the reason
365
- * read from stderr (falling back to stdout if a script has none). Returns the CANONICAL decision
366
- * shape ({decision:'allow'} | {decision:'deny', reason}); harness-native translation happens one
367
- * layer up, in the adapter's toNative, not here — this function never speaks a harness-specific
368
- * decision vocabulary.
369
- */
370
- function delegateToPolicy(projectRoot, agent, scriptName, canonicalPayload) {
371
- const scriptPath = resolvePolicyScript(projectRoot, scriptName);
372
- if (!scriptPath) return { decision: 'allow' };
373
-
374
- try {
375
- execFileSync('bash', [scriptPath], {
376
- cwd: projectRoot,
377
- input: JSON.stringify(canonicalPayload || {}),
378
- stdio: ['pipe', 'pipe', 'pipe'],
379
- env: { ...process.env, DOFLOW_PROJECT_DIR: projectRoot, DOFLOW_AGENT: agent },
380
- });
381
- return { decision: 'allow' };
382
- } catch (err) {
383
- // A real policy deny has a numeric exit status — the script ran and its own canonical exit
384
- // code (1 = deny) is the contract this delegates to. `err.status` is null/undefined when the
385
- // process never produced an exit code at all (bash missing from PATH, EACCES, etc.) — that is
386
- // an execution fault, not a policy decision, and every sourced .sh policy in this library
387
- // fails open on its own uncertainty (`command -v jq || exit 0`); this delegator matches that
388
- // posture instead of turning an environment fault into a silent deny.
389
- if (typeof err.status !== 'number') {
390
- // A policy killed by a signal is a fault in DoFlow's own install worth recording (IC-018); a
391
- // missing `bash` or a permission error is the user's environment and is not. Either way the
392
- // decision is the same as it always was.
393
- // SIGTERM, SIGINT and SIGPIPE are the user or the harness stopping the process (DEC-031), not
394
- // a fault. Any other signal is recorded under its own kind, so a SEGV and a KILL do not share a fingerprint.
395
- if (typeof err.signal === 'string' && err.signal && !['SIGTERM', 'SIGINT', 'SIGPIPE'].includes(err.signal)) {
396
- captureHookFailure({ command: scriptName.replace(/\.sh$/, ''), kind: `policy-exec-fault:${err.signal}` });
397
- }
398
- return { decision: 'allow' };
325
+ function delegateToPolicy(projectRoot, agent, event, policyId, nativeRaw) {
326
+ const bridgeSh = path.join(__dirname, 'bridge', 'bridge.sh');
327
+ if (!fs.existsSync(bridgeSh)) {
328
+ captureHookFailure({ command: policyId, kind: 'bridge-missing' });
329
+ if (policyId === 'stop-check') {
330
+ return { v: 1, harness: agent, event, policy: policyId, policyVersion: 0, outcome: 'unsupported', code: 'bridge-missing', effect: 'proceed' };
399
331
  }
400
- const reason = err.stderr ? err.stderr.toString().trim() : (err.stdout ? err.stdout.toString().trim() : '');
401
332
  return {
402
- decision: 'deny',
403
- reason: reason || `[${scriptName}] blocked by DoFlow guard policy`,
333
+ v: 1, harness: agent, event, policy: policyId, policyVersion: 0,
334
+ outcome: 'deny', code: 'bridge-missing', reason: '[doflow] shared hooks lack the bridge; run: doflow update -t antigravity', effect: 'block',
404
335
  };
405
336
  }
337
+ const profile = process.env.DOFLOW_FAULT_PROFILE || 'compat-1';
338
+ const effectiveProfile = profile === 'compat-1' || profile === 'strict-1' ? profile : 'compat-1';
339
+ if (profile !== effectiveProfile) captureHookFailure({ command: policyId, kind: 'fault-profile-unknown' });
340
+ const blockingPolicy = ['pre-implementation-gate', 'pre-bash-guard', 'mcp-tool-guard', 'stop-check'].includes(policyId);
341
+ const faultEffect = effectiveProfile === 'strict-1' && blockingPolicy ? 'block' : 'proceed';
342
+ const faultDecision = (code) => ({
343
+ v: 1, harness: agent, event, policy: policyId, policyVersion: 0,
344
+ outcome: 'error', code, reason: code, faultProfile: effectiveProfile, effect: faultEffect,
345
+ });
346
+ const result = spawnSync('bash', [bridgeSh, agent, event, policyId, '--emit', 'decision'], {
347
+ cwd: projectRoot,
348
+ input: nativeRaw,
349
+ encoding: 'utf8',
350
+ timeout: 5000,
351
+ maxBuffer: 1024 * 1024,
352
+ env: { ...process.env, DOFLOW_PROJECT_DIR: projectRoot, DOFLOW_AGENT: agent },
353
+ });
354
+
355
+ if (result.error && result.error.code === 'ETIMEDOUT') {
356
+ captureHookFailure({ command: policyId, kind: 'bridge-timeout' });
357
+ return faultDecision('bridge-timeout');
358
+ }
359
+ // A bridge that exits before spawnSync finishes writing its stdin reports EPIPE although it ran to
360
+ // completion; its exit status and answer are judged below, like any other run.
361
+ const completed = result.error && result.error.code === 'EPIPE' && result.status !== null;
362
+ if ((result.error && !completed) || result.status === null) {
363
+ if (result.signal && !['SIGTERM', 'SIGINT', 'SIGPIPE'].includes(result.signal)) {
364
+ captureHookFailure({ command: policyId, kind: `bridge-exec-fault:${result.signal}` });
365
+ }
366
+ return faultDecision('bridge-spawn');
367
+ }
368
+ try {
369
+ const decision = JSON.parse(result.stdout.trim());
370
+ if (decision && typeof decision === 'object' && typeof decision.effect === 'string') return decision;
371
+ } catch { /* malformed bridge output is a bridge fault */ }
372
+ captureHookFailure({ command: policyId, kind: 'bridge-answer-invalid' });
373
+ return faultDecision('bridge-answer-invalid');
406
374
  }
407
375
 
408
376
  function classifyPreToolUsePolicy(toolName) {
@@ -412,11 +380,11 @@ function classifyPreToolUsePolicy(toolName) {
412
380
  return null;
413
381
  }
414
382
 
415
- function evaluatePreToolUsePolicy(canonicalPayload, projectRoot, agent) {
416
- const toolName = (canonicalPayload && canonicalPayload.tool_name) || '';
383
+ function evaluatePreToolUsePolicy(payload, nativeRaw, projectRoot, agent) {
384
+ const toolName = payload && payload.toolCall && payload.toolCall.name || payload && payload.tool_name || '';
417
385
  const scriptName = classifyPreToolUsePolicy(toolName);
418
386
  if (!scriptName) return { decision: 'allow' };
419
- return delegateToPolicy(projectRoot, agent, scriptName, canonicalPayload);
387
+ return delegateToPolicy(projectRoot, agent, 'PreToolUse', scriptName.replace(/\.sh$/, ''), nativeRaw);
420
388
  }
421
389
 
422
390
  /**
@@ -431,17 +399,17 @@ function evaluatePreToolUsePolicy(canonicalPayload, projectRoot, agent) {
431
399
  * role is out of FR-001's four-policy scope) — ack only; the adapter's toNative
432
400
  * decides the harness-native shape regardless of this placeholder decision.
433
401
  */
434
- function evaluateEvent(event, canonicalPayload, projectRoot, agent) {
402
+ function evaluateEvent(event, payload, nativeRaw, projectRoot, agent) {
435
403
  switch (event) {
436
404
  case 'SessionStart':
437
- delegateToPolicy(projectRoot, agent, 'session-context.sh', canonicalPayload);
405
+ delegateToPolicy(projectRoot, agent, event, 'session-context', nativeRaw);
438
406
  return { decision: 'allow' };
439
407
 
440
408
  case 'PreToolUse':
441
- return evaluatePreToolUsePolicy(canonicalPayload, projectRoot, agent);
409
+ return evaluatePreToolUsePolicy(payload, nativeRaw, projectRoot, agent);
442
410
 
443
411
  case 'Stop':
444
- return delegateToPolicy(projectRoot, agent, 'stop-check.sh', canonicalPayload);
412
+ return delegateToPolicy(projectRoot, agent, event, 'stop-check', nativeRaw);
445
413
 
446
414
  case 'PostToolUse':
447
415
  case 'PreInvocation':
@@ -450,82 +418,7 @@ function evaluateEvent(event, canonicalPayload, projectRoot, agent) {
450
418
  }
451
419
  }
452
420
 
453
- // Falls back to 'antigravity' (this runner's legacy default) for any value other than
454
- // 'gemini', including unset/empty. An unrecognized non-empty value is likely a typo, so it
455
- // gets a stderr warning even though the fallback still applies.
456
- function resolveAgent() {
457
- const raw = process.env.DOFLOW_AGENT || '';
458
- const agent = raw.toLowerCase();
459
- if (agent === 'gemini') return 'gemini';
460
- if (raw && agent !== 'antigravity') {
461
- process.stderr.write(`stream-hook-runner: unrecognized DOFLOW_AGENT="${raw}", falling back to antigravity\n`);
462
- }
463
- return 'antigravity';
464
- }
465
-
466
- function resolveAdapter(agent) {
467
- return agent === 'gemini' ? geminiAdapter : antigravityAdapter;
468
- }
469
-
470
- function sniffEventFromPayload(payload) {
471
- if (!payload || typeof payload !== 'object') return null;
472
- // 'toolCall' in payload (key presence), not payload.toolCall (truthiness): a real Antigravity
473
- // PreToolUse payload can carry a null/empty toolCall alongside stepIdx (also present on
474
- // PostToolUse payloads) — truthiness alone let such a payload fall through to the stepIdx check
475
- // below and get misclassified as PostToolUse, silently skipping the gate entirely
476
- // (022-code-review finding). Checked before Stop/PostToolUse since a missed PreToolUse
477
- // classification fails the gate open, the worse failure mode of the two.
478
- if ('toolCall' in payload || payload.tool_name !== undefined) return 'PreToolUse';
479
- if (payload.invocationNum !== undefined) return 'PreInvocation';
480
- if (payload.terminationReason !== undefined || payload.executionNum !== undefined) return 'Stop';
481
- if (payload.stepIdx !== undefined || payload.terminationBehavior !== undefined) return 'PostToolUse';
482
- if (payload.session_id !== undefined && payload.cwd !== undefined) return 'SessionStart';
483
- return null;
484
- }
485
-
486
- // ── Back-compat helpers ──────────────────────────────────────────────────────
487
- //
488
- // evaluateToolCall/evaluatePayload predate the event-dispatcher/adapter split and are kept for
489
- // callers (including test/hooks/stream-hook-runner.test.js) that evaluate a single Antigravity-
490
- // shaped payload directly without going through main()'s argv/env-driven dispatch. Both return the
491
- // CANONICAL decision shape (never harness-native-translated) exactly as they always have.
492
-
493
- function evaluateToolCall(toolCall, projectRoot) {
494
- if (!toolCall || typeof toolCall !== 'object') {
495
- return { decision: 'allow' };
496
- }
497
- const canonicalPayload = antigravityAdapter.toCanonical('PreToolUse', { toolCall });
498
- return evaluatePreToolUsePolicy(canonicalPayload, projectRoot, 'antigravity');
499
- }
500
-
501
- function evaluatePayload(payload, projectRoot) {
502
- if (!payload || typeof payload !== 'object') {
503
- return { decision: 'allow' };
504
- }
505
-
506
- // PreToolUse: toolCall object present
507
- if (payload.toolCall) {
508
- return evaluateToolCall(payload.toolCall, projectRoot);
509
- }
510
-
511
- // PreInvocation: injection hook
512
- if (payload.invocationNum !== undefined) {
513
- return { injectSteps: [] };
514
- }
515
-
516
- // Stop hook: delegate to the canonical stop-check policy
517
- if (payload.terminationReason !== undefined) {
518
- const canonicalPayload = antigravityAdapter.toCanonical('Stop', payload);
519
- return delegateToPolicy(projectRoot, 'antigravity', 'stop-check.sh', canonicalPayload);
520
- }
521
-
522
- // PostToolUse / PostInvocation: empty object response
523
- if (payload.stepIdx !== undefined || payload.terminationBehavior !== undefined) {
524
- return {};
525
- }
526
-
527
- return { decision: 'allow' };
528
- }
421
+ const RUNNER_EVENTS = new Set(['SessionStart', 'PreToolUse', 'PostToolUse', 'Stop', 'PreInvocation']);
529
422
 
530
423
  async function main() {
531
424
  try {
@@ -542,17 +435,17 @@ async function main() {
542
435
  }
543
436
 
544
437
  const projectRoot = resolveProjectRoot(payload.workspacePaths);
545
- const agent = resolveAgent();
546
- const adapter = resolveAdapter(agent);
547
- const event = explicitEvent || sniffEventFromPayload(payload);
438
+ const agent = 'antigravity';
439
+ const adapter = antigravityAdapter;
440
+ const event = explicitEvent;
548
441
 
549
- if (!event) {
442
+ if (!event || !RUNNER_EVENTS.has(event)) {
443
+ captureHookFailure({ command: 'stream-hook-runner', kind: 'runner-event-missing' });
550
444
  process.stdout.write(JSON.stringify({ decision: 'allow' }) + '\n');
551
445
  return;
552
446
  }
553
447
 
554
- const canonicalPayload = adapter.toCanonical(event, payload);
555
- const decision = evaluateEvent(event, canonicalPayload, projectRoot, agent);
448
+ const decision = evaluateEvent(event, payload, rawInput, projectRoot, agent);
556
449
  const native = adapter.toNative(event, decision);
557
450
 
558
451
  // An empty object is a harness's documented "say nothing" answer (e.g. Antigravity's Stop
@@ -576,15 +469,10 @@ if (require.main === module) {
576
469
  }
577
470
 
578
471
  module.exports = {
579
- evaluateToolCall,
580
- evaluatePayload,
581
472
  resolveProjectRoot,
473
+ delegateToPolicy,
582
474
  evaluateEvent,
583
- resolveAgent,
584
- resolveAdapter,
585
- resolvePolicyScript,
586
475
  classifyPreToolUsePolicy,
587
- sniffEventFromPayload,
588
476
  isProgrammingError,
589
477
  captureHookFailure,
590
478
  };
@@ -754,6 +754,7 @@
754
754
  "claude",
755
755
  "codex",
756
756
  "gemini",
757
+ "pi",
757
758
  "kiro",
758
759
  "antigravity"
759
760
  ],
@@ -762,6 +763,7 @@
762
763
  "claude": "../.doflow/shared/hooks",
763
764
  "codex": "../.doflow/shared/hooks",
764
765
  "gemini": "../.doflow/shared/hooks",
766
+ "pi": "../.doflow/shared/hooks",
765
767
  "kiro": "../.doflow/shared/hooks",
766
768
  "antigravity": "../.doflow/shared/hooks"
767
769
  },
@@ -778,6 +780,10 @@
778
780
  "renderer": "copy-tree",
779
781
  "capability": "hooks"
780
782
  },
783
+ "pi": {
784
+ "renderer": "copy-tree",
785
+ "capability": "hooks"
786
+ },
781
787
  "kiro": {
782
788
  "renderer": "copy-tree",
783
789
  "capability": "hooks"
@@ -787,6 +793,19 @@
787
793
  "capability": "hooks"
788
794
  }
789
795
  }
796
+ },
797
+ {
798
+ "id": "pi.guard-extension",
799
+ "kind": "hooks",
800
+ "source": "core/harnesses/pi/extensions/doflow-guard.js",
801
+ "appliesTo": ["pi"],
802
+ "projection": {
803
+ "pi": {
804
+ "renderer": "pi-guard-extension",
805
+ "capability": "hooks"
806
+ }
807
+ },
808
+ "ownership": "ledger-owned-file"
790
809
  }
791
810
  ]
792
811
  }
@@ -134,8 +134,8 @@
134
134
  {
135
135
  "harness": "pi",
136
136
  "completeness": "lower-bound",
137
- "note": "hookEvents is empty by design, not by omission: Pi implements lifecycle handlers as TypeScript extension modules registering handlers through pi.on(), executing with full system permissions, while DoFlow ships shell scripts invoked by a command string. The events exist natively and are recorded under capabilities.hooks.events with status \"different\"; wiring them would mean shipping executable code into the user's agent, which is a distribution decision rather than a projection. Widen this list only alongside that decision and an added evidence URL. See https://pi.dev/docs/latest/extensions.",
138
- "hookEvents": [],
137
+ "note": "DoFlow installs a user- or project-scope extension that registers tool_call through pi.on() and invokes the shared lifecycle bridge. Project scope requires project trust. The handler can block before tools execute; tool_execution_start remains observation-only. See https://pi.dev/docs/latest/extensions.",
138
+ "hookEvents": ["tool_call"],
139
139
  "evidence": [
140
140
  "https://pi.dev/docs/latest/extensions",
141
141
  "https://pi.dev/docs/latest/settings",