@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
@@ -1,43 +1,34 @@
1
1
  #!/usr/bin/env bash
2
- # Kiro adapter: run the canonical stop-check policy and translate its verdict to the Kiro engine.
3
- #
4
- # The policy exits 2 to deny the stop. Kiro engines read a Stop hook differently:
5
- # - Kiro CLI V3 / IDE (payload carries session_id): exit 0 finishes the turn; exit 0 with stdout
6
- # JSON {"decision":"block","reason":"..."} starts another turn with that reason; exit 1 also
7
- # starts another turn; other codes finish without forwarding output. Exit 2 does NOT block.
8
- # So a policy deny becomes the JSON block on stdout with exit 0.
9
- # - Kiro CLI 2.x (payload has no session_id): exit 0 succeeded, any other code shows stderr to the
10
- # user as a warning. A policy deny is passed through as stderr with exit 2.
11
- # Every other policy outcome exits 0, never 1: V3 treats exit 1 as "continue another turn".
12
- # The policy blocks a given message once, so the continuation turn is not blocked again.
13
- # Docs: https://kiro.dev/docs/hooks/types/ , https://kiro.dev/docs/cli/v3/hooks-migration/
2
+ # Kiro Stop front door: translate the native payload, then render the bridge verdict for its engine.
14
3
  set -uo pipefail
15
4
  export DOFLOW_AGENT="${DOFLOW_AGENT:-kiro}"
16
5
  here="$(dirname "$0")"
17
6
  # shellcheck source=lib.sh
18
7
  source "$here/lib.sh"
19
- set +e # lib.sh enables errexit; the policy exit code is handled explicitly below
20
-
21
8
  raw=$(cat)
22
- # Translate Kiro's payload (tool names, KIRO_SESSION_ID, assistant_response) to the canonical one.
23
9
  payload=$(kiro_bridge_payload <<<"$raw")
24
-
25
- # V3 and the IDE send session_id in the payload; 2.x does not (the bridge injects it from the env).
26
10
  raw_sid=""
11
+ event="Stop"
27
12
  if command -v jq >/dev/null 2>&1; then
28
13
  raw_sid=$(printf '%s' "$raw" | jq -r '.session_id // empty' 2>/dev/null) || raw_sid=""
14
+ event=$(printf '%s' "$raw" | jq -r '.hook_event_name // "Stop"' 2>/dev/null) || event="Stop"
29
15
  fi
30
-
31
- # Capture the policy's stderr (stdout discarded) and its exit code without a temp file.
32
- errtext=$(bash "$here/../../.doflow/shared/hooks/policies/stop-check.sh" <<<"$payload" 2>&1 >/dev/null)
33
- rc=$?
34
-
35
- if [ "$rc" -eq 2 ]; then
36
- if [ -n "$raw_sid" ] && command -v jq >/dev/null 2>&1; then
37
- jq -n --arg reason "$errtext" '{decision: "block", reason: $reason}'
38
- exit 0
39
- fi
40
- printf '%s\n' "$errtext" >&2
41
- exit 2
16
+ if [ -n "$raw_sid" ]; then
17
+ export DOFLOW_KIRO_ENGINE=v3
18
+ else
19
+ export DOFLOW_KIRO_ENGINE=2x
42
20
  fi
43
- exit 0
21
+ bridge="$here/../../.doflow/shared/hooks/bridge/bridge.sh"
22
+ if [ ! -f "$bridge" ]; then exit 0; fi
23
+ . "$bridge" 2>/dev/null || exit 0
24
+ exec 4>&1
25
+ if bridge_stderr=$(doflow_bridge kiro "$event" stop-check <<<"$payload" 2>&1 1>&4); then
26
+ status=0
27
+ else
28
+ status=$?
29
+ fi
30
+ exec 4>&-
31
+ if [ "$status" -eq 2 ] && [ "$DOFLOW_KIRO_ENGINE" = 2x ]; then
32
+ printf '%s\n' "$bridge_stderr" >&2
33
+ fi
34
+ exit "$status"
@@ -0,0 +1,159 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ // C.3's renderer replaces this relative source-tree path for each install scope.
6
+ const BRIDGE = new URL('../../shared/hooks/bridge/bridge.sh', import.meta.url);
7
+ const ROUTES = [
8
+ { tools: ['edit', 'write'], policy: 'pre-implementation-gate' },
9
+ { tools: ['bash'], policy: 'pre-bash-guard' },
10
+ ];
11
+ const LIMIT = 1024 * 1024;
12
+ const TIMEOUT_MS = 5000;
13
+ const ERROR_REASON = (policy) => `[${policy}] DoFlow policy error; details: doflow-run failure list`;
14
+
15
+ function route(toolName) {
16
+ for (const row of ROUTES) {
17
+ if (row.tools.includes(toolName)) return row.policy;
18
+ }
19
+ return typeof toolName === 'string' && toolName.startsWith('mcp__') ? 'mcp-tool-guard' : undefined;
20
+ }
21
+
22
+ function captureFailure(policy, point) {
23
+ const script = new URL('../policies/capture-failure.sh', BRIDGE);
24
+ try {
25
+ const child = spawn('bash', ['-c', '. "$1"; doflow_capture_failure "$2" "$3"', 'doflow-capture', fileURLToPath(script), policy, point], {
26
+ detached: true,
27
+ stdio: 'ignore',
28
+ });
29
+ child.unref();
30
+ } catch {
31
+ // Failure capture must never change Pi's tool-call decision.
32
+ }
33
+ }
34
+
35
+ function bridgeCall(policy, payload, ctx) {
36
+ return new Promise((resolve, reject) => {
37
+ let child;
38
+ try {
39
+ const env = { ...process.env };
40
+ delete env.DOFLOW_PROJECT_DIR;
41
+ child = spawn('bash', [fileURLToPath(BRIDGE), 'pi', 'tool_call', policy], {
42
+ cwd: ctx.cwd,
43
+ env,
44
+ stdio: ['pipe', 'pipe', 'pipe'],
45
+ detached: true,
46
+ });
47
+ } catch (error) {
48
+ reject(Object.assign(error, { failurePoint: 'bridge-spawn' }));
49
+ return;
50
+ }
51
+
52
+ const stdout = [];
53
+ let stdoutBytes = 0;
54
+ let stderrBytes = 0;
55
+ let settled = false;
56
+ let timedOut = false;
57
+ let overflow = false;
58
+ let aborted = false;
59
+ const finish = (error, result) => {
60
+ if (settled) return;
61
+ settled = true;
62
+ clearTimeout(timer);
63
+ ctx.signal?.removeEventListener('abort', onAbort);
64
+ if (error) reject(error);
65
+ else resolve(result);
66
+ };
67
+ const kill = () => {
68
+ try {
69
+ if (child.pid && process.platform !== 'win32') process.kill(-child.pid, 'SIGKILL');
70
+ else child.kill('SIGKILL');
71
+ } catch {
72
+ try { child.kill('SIGKILL'); } catch {}
73
+ }
74
+ };
75
+ const fail = (point) => {
76
+ overflow = point === 'bridge-output-limit';
77
+ kill();
78
+ finish(Object.assign(new Error(point), { failurePoint: point }));
79
+ };
80
+ const onAbort = () => {
81
+ aborted = true;
82
+ kill();
83
+ finish(null, undefined);
84
+ };
85
+ const timer = setTimeout(() => {
86
+ timedOut = true;
87
+ kill();
88
+ }, TIMEOUT_MS);
89
+ if (ctx.signal?.aborted) onAbort();
90
+ else ctx.signal?.addEventListener('abort', onAbort, { once: true });
91
+
92
+ child.on('error', (error) => finish(Object.assign(error, { failurePoint: 'bridge-spawn' })));
93
+ child.stdout.on('data', (chunk) => {
94
+ stdoutBytes += chunk.length;
95
+ if (stdoutBytes > LIMIT) return fail('bridge-output-limit');
96
+ stdout.push(chunk);
97
+ });
98
+ child.stderr.on('data', (chunk) => {
99
+ stderrBytes += chunk.length;
100
+ if (stderrBytes > LIMIT) return fail('bridge-output-limit');
101
+ });
102
+ child.on('close', (code) => {
103
+ if (settled) return;
104
+ if (aborted) return finish(null, undefined);
105
+ if (timedOut) return finish(Object.assign(new Error('bridge timed out'), { failurePoint: 'bridge-timeout' }));
106
+ if (overflow) return finish(Object.assign(new Error('bridge output exceeded limit'), { failurePoint: 'bridge-output-limit' }));
107
+ if (code !== 0) return finish(Object.assign(new Error(`bridge exited ${code}`), { failurePoint: 'bridge-spawn' }));
108
+ const output = Buffer.concat(stdout).toString('utf8');
109
+ if (!output.trim()) return finish(null, undefined);
110
+ if (output.trim().split(/\r?\n/).length !== 1) {
111
+ return finish(Object.assign(new Error('bridge answer must be one JSON line'), { failurePoint: 'bridge-spawn' }));
112
+ }
113
+ try {
114
+ const answer = JSON.parse(output);
115
+ if (!answer || typeof answer !== 'object' || Array.isArray(answer) || answer.block !== true || typeof answer.reason !== 'string') {
116
+ throw new Error('invalid bridge answer');
117
+ }
118
+ finish(null, { block: true, reason: answer.reason });
119
+ } catch {
120
+ finish(Object.assign(new Error('invalid bridge answer'), { failurePoint: 'bridge-spawn' }));
121
+ }
122
+ });
123
+ child.stdin.on('error', () => {});
124
+ child.stdin.end(payload);
125
+ });
126
+ }
127
+
128
+ export default function (pi) {
129
+ pi.on('tool_call', async (event, ctx) => {
130
+ let policy = 'unknown';
131
+ let profile = process.env.DOFLOW_FAULT_PROFILE || 'compat-1';
132
+ try {
133
+ policy = route(event.toolName);
134
+ if (!policy) return undefined;
135
+ profile = process.env.DOFLOW_FAULT_PROFILE || 'compat-1';
136
+ if (profile !== 'compat-1' && profile !== 'strict-1') captureFailure(policy, 'fault-profile-unknown');
137
+ if (!existsSync(BRIDGE)) {
138
+ captureFailure(policy, 'bridge-missing');
139
+ return { block: true, reason: '[doflow] shared hooks lack the bridge; run: doflow update -t pi' };
140
+ }
141
+ const payload = JSON.stringify({
142
+ type: event.type,
143
+ toolName: event.toolName,
144
+ toolCallId: event.toolCallId,
145
+ parentToolCallId: event.parentToolCallId,
146
+ input: event.input,
147
+ cwd: ctx.cwd,
148
+ sessionId: ctx.sessionManager?.getSessionId?.() ?? null,
149
+ });
150
+ if (Buffer.byteLength(payload) > LIMIT) throw Object.assign(new Error('bridge input exceeded limit'), { failurePoint: 'bridge-input-limit' });
151
+ return await bridgeCall(policy, payload, ctx);
152
+ } catch (error) {
153
+ const point = error?.failurePoint || 'bridge-spawn';
154
+ captureFailure(policy, point === 'bridge-timeout' || point === 'bridge-missing' ? point : 'bridge-spawn');
155
+ if (profile === 'strict-1') return { block: true, reason: ERROR_REASON(policy) };
156
+ return undefined;
157
+ }
158
+ });
159
+ }
@@ -1,16 +1,17 @@
1
1
  # The normalized hook envelope, and each front door's share of the migration
2
2
 
3
+ Every shipped bridge places envelope version 1 under `doflow_event`, beside the native fields. Policies read v1 fields only when `version` is the JSON number `1`; any other version is unsupported and is evaluated as such (IC-034, DEC-030).
4
+
3
5
  `policies/pre-implementation-gate.sh` prefers one normalized event over every native payload
4
6
  shape (review R7):
5
7
 
6
8
  ```json
7
- {"doflow_event": {"operation": "edit", "paths": ["src/a.js"], "projectRoot": "/repo", "taskId": "001-auth"}}
9
+ {"doflow_event": {"version": 1, "harness": "claude", "event": "PreToolUse", "operation": "edit", "paths": ["src/a.js"], "projectRoot": "/repo", "taskId": "001-auth"}}
8
10
  ```
9
11
 
10
- A front door that sends this envelope needs none of the policy's legacy union parsing — the four
11
- tool-name spellings, five file-path field names, and Codex `apply_patch` diff extraction exist
12
- only for front doors still forwarding raw native payloads. Each adapter that adopts the envelope
13
- retires its share of that union; when the last one does, the union decoder is deleted.
12
+ The envelope is the only policy input contract. A caller outside a shipped bridge must either
13
+ send a v1 envelope itself or pipe its native payload through
14
+ `bash .doflow/shared/hooks/bridge/bridge.sh <harness> <event> <policy>`.
14
15
 
15
16
  ## Which native event each front door decodes
16
17
 
@@ -24,16 +25,31 @@ tool's official docs (cross-checked against tool source where docs were incomple
24
25
  | Gemini CLI | `BeforeTool` | also offers `BeforeToolSelection` (pre-selection restriction), which the gate does not use |
25
26
  | GitHub Copilot | `preToolUse` | camelCase event names; `permissionRequest` is CLI-only, not fired in the cloud agent |
26
27
  | OpenCode | `tool.execute.before` | a pluggable hook method, not a sub-event of the generic `event` hook |
27
- | Pi | `tool_execution_start` | Pi's 36-event surface also exposes `tool_call`; the execution-start event is the gate's anchor |
28
+ | Pi | `tool_call` | The live-observed `cli-extension` pre-implementation gate row blocks. Project-interactive and user-interactive rows remain unknown; interactive loading and project-trust behavior were not observed. `tool_execution_start` is observation-only |
28
29
  | Antigravity | `PreToolUse` | 5-event surface, `hooks.json` + stdin/stdout JSON contract |
29
30
 
31
+ Pi 1.1.0 declares 41 `on()` overloads in its shipped extension types. `tool_call` is the enforcement event; `tool_execution_start` is not a gate.
32
+
30
33
  `operation` is `"edit"` for anything that writes source; a decoder maps its native create/replace/
31
34
  patch tool vocabulary down to that one word. Non-edit operations may be sent (the policy allows
32
35
  them through), but sending nothing for a non-edit tool is equally correct and cheaper.
33
36
 
34
- ## Rules that hold on both paths
35
-
36
- - The envelope, when present, **outranks** any native fields beside it.
37
+ ## Version and mixed-install rules
38
+
39
+ - A missing envelope or version, empty or non-object input, or a missing `contract.sh` is out of
40
+ step. The gate, bash guard and MCP guard deny with `DoFlow hooks out of step; run: doflow update
41
+ -t <harness>`; Stop and context policies exit silently, except the prompt hook emits its `{}`
42
+ no-op. Other envelope versions are unsupported and policies evaluate nothing.
43
+ - If a door cannot load `bridge/bridge.sh`, it renders its fixed native out-of-step response. The
44
+ Pi extension blocks with the fixed native out-of-step reason; a present bridge that fails to spawn or answer follows
45
+ the configured bridge-fault behavior.
46
+
47
+ Other current bridge behavior: Codex Stop crash stderr is suppressed (DEC-037). Stop-check
48
+ enforcement is answered from Stop coverage rows (DEC-038). Strict-1 never fault-blocks Stop, though
49
+ a real Stop deny still blocks (DEC-039). Bridge capture uses no temporary file, so an unwritable
50
+ `TMPDIR` does not turn a gate deny into an allow; background jobs do not hold Stop or SessionStart
51
+ open; strict-1 renders native denies without `jq`. `doflow update -t codex -f` re-merges edited
52
+ Codex `hooks.json` while keeping user entries (DEC-040).
37
53
  - Root resolution: `DOFLOW_PROJECT_DIR` beats the envelope's `projectRoot` beats git discovery.
38
- - The policy remains a pure file-existence gate (FR-012): the envelope changes how it hears about
39
- an edit, never what it decides, and it must never consult readiness or evidence state.
54
+ - The pre-implementation gate checks required artifacts and reads run and readiness records; it
55
+ does not consult evidence state.
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * Antigravity payload/decision adapter for the Cross-Harness Hook Runner (design.md C2's
5
- * Payload Adapter / Decision Adapter seam, D2).
4
+ * Antigravity native decision renderer for the Cross-Harness Hook Runner's
5
+ * Decision Adapter seam, D2). The shared bridge owns native payload decoding.
6
6
  *
7
7
  * Antigravity's own native contracts — evidenced by core/harnesses/antigravity/hooks/*.sh (the
8
8
  * shims src/adapters/antigravity/index.js actually projects today) and
@@ -19,67 +19,24 @@
19
19
  * vocabulary than PreToolUse's allow/deny/ask/force_ask, not an oversight.
20
20
  *
21
21
  * SessionStart and PostToolUse have no dedicated Antigravity-native shim in
22
- * src/adapters/antigravity/index.js's live projection; core/.antigravity-plugin/hooks.json (a
23
- * separate, read-only-to-this-task artifact) documents both routed directly to the *Gemini*
24
- * session-start.sh / post-edit-lint.sh scripts, which read the Claude/Gemini-style
25
- * session_id/cwd/tool_name/tool_input field names, not the toolCall envelope above — this
26
- * adapter honors that evidence for those two events rather than guessing at a toolCall shape
27
- * with no corroborating source. No Phase A canonical policy owns PostToolUse today (only
22
+ * src/adapters/antigravity/index.js's live projection. No Phase A canonical policy owns PostToolUse today (only
28
23
  * session-context, pre-implementation-gate, mcp-tool-guard, stop-check, pre-bash-guard exist),
29
24
  * so its native shape is an ack passthrough either way.
30
25
  */
31
26
 
32
- const { FILE_PATH_ARG_FIELDS, COMMAND_ARG_FIELDS, firstArgField } = require('./arg-fields');
33
-
34
- function toCanonical(event, payload) {
35
- const safePayload = payload && typeof payload === 'object' ? payload : {};
36
-
37
- switch (event) {
38
- case 'SessionStart':
39
- return { session_id: safePayload.session_id || '', cwd: safePayload.cwd || '' };
40
-
41
- case 'PreToolUse': {
42
- const toolCall = safePayload.toolCall || {};
43
- const args = toolCall.args || {};
44
- return {
45
- tool_name: toolCall.name || '',
46
- tool_input: {
47
- file_path: firstArgField(args, FILE_PATH_ARG_FIELDS),
48
- command: firstArgField(args, COMMAND_ARG_FIELDS),
49
- },
50
- };
51
- }
52
-
53
- case 'PostToolUse':
54
- // No Phase A policy consumes this event — canonical shape kept for symmetry only.
55
- return {
56
- tool_name: safePayload.tool_name || '',
57
- session_id: safePayload.session_id || '',
58
- tool_input: { file_path: (safePayload.tool_input && safePayload.tool_input.file_path) || '' },
59
- };
60
-
61
- case 'Stop':
62
- return {
63
- session_id: safePayload.session_id || '',
64
- transcript_path: safePayload.transcriptPath || safePayload.transcript_path || '',
65
- };
66
-
67
- case 'PreInvocation':
68
- // No Phase A policy — ack only (injectSteps handled entirely in toNative).
69
- return {};
70
-
71
- default:
72
- return safePayload;
73
- }
74
- }
75
-
76
27
  function toNative(event, decision) {
77
28
  const safeDecision = decision || { decision: 'allow' };
29
+ const blocked = safeDecision.effect === 'block' || safeDecision.decision === 'deny';
30
+ const reason = safeDecision.outcome === 'deny'
31
+ ? safeDecision.reason
32
+ : safeDecision.effect === 'block'
33
+ ? `[${safeDecision.policy}] DoFlow policy error; details: doflow-run failure list`
34
+ : safeDecision.reason;
78
35
 
79
36
  switch (event) {
80
37
  case 'PreToolUse':
81
- if (safeDecision.decision === 'deny') {
82
- return { decision: 'deny', reason: safeDecision.reason };
38
+ if (blocked) {
39
+ return { decision: 'deny', reason };
83
40
  }
84
41
  return { decision: 'allow' };
85
42
 
@@ -88,8 +45,8 @@ function toNative(event, decision) {
88
45
  return {};
89
46
 
90
47
  case 'Stop':
91
- if (safeDecision.decision === 'deny') {
92
- return { decision: 'continue', reason: safeDecision.reason };
48
+ if (blocked) {
49
+ return { decision: 'continue', reason };
93
50
  }
94
51
  // Silence (no positive field) lets the session stop, per Antigravity's own documented
95
52
  // Stop contract — not the allow/deny vocabulary PreToolUse uses.
@@ -108,4 +65,4 @@ function toNative(event, decision) {
108
65
  }
109
66
  }
110
67
 
111
- module.exports = { toCanonical, toNative };
68
+ module.exports = { toNative };
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env bash
2
+ # Shared lifecycle bridge. Sourcing defines functions only; it sets no shell options or traps.
3
+ case ${BASH_SOURCE[0]} in */*) _DOFLOW_BRIDGE_DIR=${BASH_SOURCE[0]%/*} ;; *) _DOFLOW_BRIDGE_DIR=. ;; esac
4
+
5
+ doflow_bridge_failure() {
6
+ local policy="$1" point="$2"
7
+ ( . "$_DOFLOW_BRIDGE_DIR/../policies/capture-failure.sh"; doflow_capture_failure "$policy" "$point" ) >/dev/null 2>&1 || true
8
+ }
9
+
10
+ doflow_bridge_classify() {
11
+ local code="$1" record="$2" outcome="" decision_code="" version=0 reason="$3"
12
+ if [ -n "$record" ]; then
13
+ outcome="${record#*\"outcome\":\"}"; outcome="${outcome%%\"*}"
14
+ decision_code="${record#*\"code\":\"}"; decision_code="${decision_code%%\"*}"
15
+ version="${record#*\"policyVersion\":}"; version="${version%%,*}"
16
+ case "$version" in ''|*[^0-9]*) version=0;; esac
17
+ fi
18
+ DF_BRIDGE_OUTCOME=error; DF_BRIDGE_CODE=exit-"$code"; DF_BRIDGE_VERSION="$version"; DF_BRIDGE_REASON="$DF_BRIDGE_CODE"; DF_BRIDGE_POINT="eval-error:exit-$code"
19
+ if [ "$code" -eq 2 ] 2>/dev/null; then
20
+ if [ "$outcome" = deny ]; then DF_BRIDGE_OUTCOME=deny; DF_BRIDGE_CODE="$decision_code"; DF_BRIDGE_REASON="$reason"; DF_BRIDGE_POINT=""
21
+ else DF_BRIDGE_CODE=exit-2-unmarked; DF_BRIDGE_REASON=exit-2-unmarked; DF_BRIDGE_POINT=eval-error:exit-2-unmarked; fi
22
+ elif [ "$code" -eq 0 ] 2>/dev/null; then
23
+ case "$outcome" in
24
+ allow|advisory|error|unsupported) DF_BRIDGE_OUTCOME="$outcome"; DF_BRIDGE_CODE="$decision_code"; DF_BRIDGE_REASON="$decision_code"; DF_BRIDGE_POINT="" ;;
25
+ deny) DF_BRIDGE_OUTCOME=allow; DF_BRIDGE_CODE=decision-mismatch; DF_BRIDGE_REASON=decision-mismatch; DF_BRIDGE_POINT=decision-mismatch ;;
26
+ *) DF_BRIDGE_OUTCOME=allow; DF_BRIDGE_CODE=allow; DF_BRIDGE_REASON=allow; DF_BRIDGE_POINT=""; DF_BRIDGE_VERSION=0 ;;
27
+ esac
28
+ elif [ "$code" -ge 129 ] && [ "$code" -le 255 ] && [ "$code" -ne 255 ] 2>/dev/null; then
29
+ local sig=""
30
+ sig="$(kill -l "$((code - 128))" 2>/dev/null)" || sig=""
31
+ sig="${sig#SIG}"
32
+ case "$sig" in ''|*[!A-Za-z0-9_-]*) return;; esac
33
+ DF_BRIDGE_OUTCOME=error; DF_BRIDGE_CODE=signal-"$sig"; DF_BRIDGE_REASON="$DF_BRIDGE_CODE"; DF_BRIDGE_POINT="eval-error:signal-$sig"
34
+ fi
35
+ }
36
+
37
+ doflow_bridge_effect() {
38
+ local harness="$1" event="$2" policy="$3" outcome="$4" profile="${DOFLOW_FAULT_PROFILE:-compat-1}" block=0
39
+ doflow_bridge_native_event_var "$harness" "$event"; event=$DF_NE
40
+ if [ "$profile" != compat-1 ] && [ "$profile" != strict-1 ]; then
41
+ profile=compat-1; doflow_bridge_failure "$policy" fault-profile-unknown
42
+ fi
43
+ DF_BRIDGE_PROFILE="$profile"
44
+ case "$policy" in pre-implementation-gate|pre-bash-guard|mcp-tool-guard|stop-check) block=1;; esac
45
+ DF_BRIDGE_EFFECT=proceed
46
+ if [ "$outcome" = deny ]; then DF_BRIDGE_EFFECT=block
47
+ elif [ "$profile" = strict-1 ] && [ "$block" -eq 1 ] && [ "$policy" != stop-check ] && { [ "$outcome" = error ] || [ "$outcome" = unsupported ] || [ "$DF_BRIDGE_CODE" = bridge-spawn ] || [ "$DF_BRIDGE_CODE" = envelope-encode ]; }; then DF_BRIDGE_EFFECT=block
48
+ elif [ "$outcome" = error ] && [ "$DF_BRIDGE_CODE" != bridge-spawn ] && [ -n "$DF_BRIDGE_POINT" ] && [ "$profile" = compat-1 ]; then
49
+ case "$harness:$event" in
50
+ claude:PreToolUse|codex:PreToolUse|gemini:BeforeTool|pi:tool_call) DF_BRIDGE_EFFECT=block ;;
51
+ antigravity:PreToolUse|antigravity:Stop) case "$DF_BRIDGE_CODE" in signal-*) DF_BRIDGE_EFFECT=proceed;; *) DF_BRIDGE_EFFECT=block;; esac ;;
52
+ kiro:PreToolUse) [ "$DF_BRIDGE_CODE" = exit-2-unmarked ] && DF_BRIDGE_EFFECT=block ;;
53
+ claude:Stop) [ "$DF_BRIDGE_CODE" = exit-2-unmarked ] && DF_BRIDGE_EFFECT=block ;;
54
+ codex:Stop|codex:stop|kiro:Stop|kiro:stop) [ "$DF_BRIDGE_CODE" = exit-2-unmarked ] && DF_BRIDGE_EFFECT=block || DF_BRIDGE_EFFECT=proceed ;;
55
+ esac
56
+ fi
57
+ }
58
+
59
+ doflow_bridge_drops_tool_streams() {
60
+ case "$1" in
61
+ pre-implementation-gate|pre-bash-guard|mcp-tool-guard) ;;
62
+ *) return 1 ;;
63
+ esac
64
+ case "$2:$3" in
65
+ claude:PreToolUse|codex:PreToolUse|gemini:BeforeTool|kiro:PreToolUse|antigravity:PreToolUse|antigravity:Stop|pi:tool_call) return 0 ;;
66
+ *) return 1 ;;
67
+ esac
68
+ }
69
+
70
+ doflow_bridge_drops_tool_stderr() {
71
+ case "$1:$2:$3" in
72
+ pre-implementation-gate:kiro:PreToolUse|pre-bash-guard:kiro:PreToolUse|mcp-tool-guard:kiro:PreToolUse) return 1 ;;
73
+ esac
74
+ doflow_bridge_drops_tool_streams "$1" "$2" "$3"
75
+ }
76
+
77
+ doflow_bridge() {
78
+ local harness="${1:-}" event="${2:-}" policy="${3:-}" response_event="" emit="native" payload="" encoded="" policy_path="" captured="" err="" code=0 policy_code=0 record="" reason="" decision_reason="" stdout="" status=0 decision="" line="" spawn_failed=0 encode_failed=0
79
+ . "$_DOFLOW_BRIDGE_DIR/respond.sh"
80
+ doflow_bridge_native_event_var "$harness" "$event"; response_event=$DF_NE
81
+ if [ "${4:-}" = --emit ]; then emit="${5:-native}"; fi
82
+ payload="$(cat)"
83
+ if command -v jq >/dev/null 2>&1; then
84
+ encoded="$(printf '%s\n' "$payload" | DOFLOW_TASK_ID="${DOFLOW_TASK_ID:-}" DOFLOW_RUN_ID="${DOFLOW_RUN_ID:-}" DOFLOW_ATTEMPT="${DOFLOW_ATTEMPT:-}" DOFLOW_SESSION_ROLE="${DOFLOW_SESSION_ROLE:-}" jq -c --arg harness "$harness" --arg event "$event" --arg policy "$policy" --arg mode encode -f "$_DOFLOW_BRIDGE_DIR/envelope.jq" 2>/dev/null)" && {
85
+ case "$encoded" in \{*) payload="$encoded" ;; *) encode_failed=1 ;; esac
86
+ } || { encode_failed=1; }
87
+ if [ "$encode_failed" -eq 1 ]; then
88
+ encoded="$(jq -cn --arg harness "$harness" --arg event "$event" --arg policy "$policy" --arg mode minimal -f "$_DOFLOW_BRIDGE_DIR/envelope.jq" 2>/dev/null)" || encoded=""
89
+ case "$encoded" in \{*) payload="$encoded" ;; *) payload="{\"doflow_event\":{\"version\":1,\"harness\":\"$harness\",\"event\":\"$event\",\"operation\":\"tool\"}}" ;; esac
90
+ doflow_bridge_failure "$policy" envelope-encode
91
+ fi
92
+ else
93
+ doflow_bridge_failure "$policy" dependency-missing:jq
94
+ fi
95
+ case "$policy" in
96
+ pre-implementation-gate|pre-bash-guard|mcp-tool-guard|stop-check|session-context|user-prompt-submit|subagent-audit) ;;
97
+ *) spawn_failed=1 ;;
98
+ esac
99
+ policy_path="$_DOFLOW_BRIDGE_DIR/../policies/$policy.sh"
100
+ if [ "$spawn_failed" -eq 1 ] || [ ! -f "$policy_path" ]; then
101
+ code=127; err=""; spawn_failed=1
102
+ else
103
+ if [ "$emit" = decision ] || doflow_bridge_drops_tool_streams "$policy" "$harness" "$response_event" || { [ "$harness" = kiro ] && [ "$response_event" = Stop ]; }; then exec 5>/dev/null; else exec 5>&1; fi
104
+ captured="$( {
105
+ if DOFLOW_AGENT="${DOFLOW_AGENT:-$harness}" DOFLOW_DECISION_FD=3 bash "$policy_path" 2>&1 3>&1 1>&5 5>&- <<<"$payload"; then policy_code=0; else policy_code=$?; fi
106
+ printf '\037%s' "$policy_code"
107
+ } )"
108
+ exec 5>&-
109
+ code="${captured##*$'\037'}"
110
+ captured="${captured%$'\037'*}"
111
+ fi
112
+ while IFS= read -r line || [ -n "$line" ]; do
113
+ case "$line" in
114
+ *$'\036'*)
115
+ local prefix="${line%%$'\036'*}"
116
+ record="${line#*$'\036'}"
117
+ [ -z "$prefix" ] || err="$err$prefix"$'\n'
118
+ ;;
119
+ *) err="$err$line"$'\n' ;;
120
+ esac
121
+ done < <(printf '%s' "$captured")
122
+ reason="${err%$'\n'}"
123
+ doflow_bridge_classify "$code" "$record" "$reason"
124
+ if [ "$encode_failed" -eq 1 ] && [ "$DF_BRIDGE_OUTCOME" != deny ]; then DF_BRIDGE_CODE=envelope-encode; DF_BRIDGE_POINT=envelope-encode; fi
125
+ if [ "$spawn_failed" -eq 1 ]; then DF_BRIDGE_OUTCOME=error; DF_BRIDGE_CODE=bridge-spawn; DF_BRIDGE_REASON=bridge-spawn; DF_BRIDGE_VERSION=0; DF_BRIDGE_POINT=bridge-spawn; fi
126
+ if [ -n "$DF_BRIDGE_POINT" ] && [ "$DF_BRIDGE_POINT" != decision-mismatch ] && [ "$DF_BRIDGE_POINT" != eval-error:signal-TERM ] && [ "$DF_BRIDGE_POINT" != eval-error:signal-INT ] && [ "$DF_BRIDGE_POINT" != eval-error:signal-PIPE ]; then doflow_bridge_failure "$policy" "$DF_BRIDGE_POINT"; fi
127
+ doflow_bridge_effect "$harness" "$event" "$policy" "$DF_BRIDGE_OUTCOME"
128
+ if [ "$DF_BRIDGE_EFFECT" = block ] && [ "$DF_BRIDGE_OUTCOME" != deny ]; then reason="[$policy] DoFlow policy error; details: doflow-run failure list"; fi
129
+ if [ "$DF_BRIDGE_OUTCOME" != deny ] && [ "$DF_BRIDGE_EFFECT" = proceed ]; then
130
+ case "$harness:$response_event:$DF_BRIDGE_OUTCOME" in
131
+ kiro:PreToolUse:error|claude:Stop:error)
132
+ case "$DF_BRIDGE_CODE" in
133
+ exit-*|signal-*)
134
+ reason="[$policy] DoFlow policy error; details: doflow-run failure list"
135
+ if [ "$harness:$response_event" = claude:Stop ]; then reason="$reason"$'\n'; fi
136
+ ;;
137
+ esac
138
+ ;;
139
+ *) reason="$DF_BRIDGE_CODE" ;;
140
+ esac
141
+ fi
142
+ decision_reason="$DF_BRIDGE_CODE"
143
+ if [ "$DF_BRIDGE_OUTCOME" = deny ]; then decision_reason="$reason"; fi
144
+ if [ "$emit" = decision ] || [ -n "${DOFLOW_BRIDGE_TRACE:-}" ]; then
145
+ decision="$(jq -cn --arg harness "$harness" --arg event "$event" --arg policy "$policy" --arg outcome "$DF_BRIDGE_OUTCOME" --arg code "$DF_BRIDGE_CODE" --arg reason "$decision_reason" --arg profile "$DF_BRIDGE_PROFILE" --arg effect "$DF_BRIDGE_EFFECT" --argjson version "$DF_BRIDGE_VERSION" '{v:1,harness:$harness,event:$event,policy:$policy,policyVersion:$version,outcome:$outcome,code:$code,reason:$reason,faultProfile:$profile,effect:$effect}')"
146
+ fi
147
+ if [ -n "${DOFLOW_BRIDGE_TRACE:-}" ]; then printf '%s\n' "$decision" >>"$DOFLOW_BRIDGE_TRACE" 2>/dev/null || true; fi
148
+ if [ "$emit" = decision ]; then
149
+ printf '%s\n' "$decision"
150
+ return 0
151
+ fi
152
+ if [ "$DF_BRIDGE_EFFECT" = block ]; then
153
+ status=2
154
+ else
155
+ status=0
156
+ if [ "$DF_BRIDGE_OUTCOME" = error ]; then
157
+ case "$policy:$harness:$response_event" in
158
+ session-context:*:*|user-prompt-submit:*:*|subagent-audit:*:*|*:kiro:PreToolUse|*:claude:Stop) status="$code" ;;
159
+ esac
160
+ fi
161
+ fi
162
+ if [ "$spawn_failed" -eq 1 ] && [ "$DF_BRIDGE_EFFECT" = proceed ]; then status=0; fi
163
+ if [ "$DF_BRIDGE_EFFECT" = block ]; then
164
+ doflow_render_response "$harness" "$event" deny "$reason" "$status"
165
+ local render_status=$?
166
+ return "$render_status"
167
+ fi
168
+ case "$policy:$DF_BRIDGE_OUTCOME:$harness:$response_event:$DF_BRIDGE_CODE" in
169
+ session-context:error:*:*:*|user-prompt-submit:error:*:*:*|subagent-audit:error:*:*:*|session-context:unsupported:*:*:*|user-prompt-submit:unsupported:*:*:*|subagent-audit:unsupported:*:*:*) ;;
170
+ *:error:codex:Stop:exit-*|*:error:codex:Stop:signal-*|*:error:codex:stop:exit-*|*:error:codex:stop:signal-*|*:error:kiro:PreToolUse:exit-*|*:error:kiro:PreToolUse:signal-*|*:error:kiro:Stop:exit-*|*:error:kiro:Stop:signal-*|*:error:claude:Stop:exit-*|*:error:claude:Stop:signal-*) ;;
171
+ *)
172
+ if ! doflow_bridge_drops_tool_stderr "$policy" "$harness" "$response_event" && [ -n "$err" ]; then printf '%s' "$err" >&2; fi
173
+ ;;
174
+ esac
175
+ doflow_render_response "$harness" "$event" "$DF_BRIDGE_OUTCOME" "$reason" "$status"
176
+ local render_status=$?
177
+ return "$render_status"
178
+ }
179
+
180
+ if [ "${BASH_SOURCE[0]}" = "$0" ]; then
181
+ . "$_DOFLOW_BRIDGE_DIR/respond.sh"
182
+ doflow_bridge "${1:-}" "${2:-}" "${3:-}" "${4:-}" "${5:-}"
183
+ exit $?
184
+ fi
@@ -0,0 +1,60 @@
1
+ def nonempty($v): if ($v | type) == "string" and $v != "" then $v else null end;
2
+ def put($o; $k; $v): if $v == null then $o else $o + {($k): $v} end;
3
+ def event_name($fallback): if (.hook_event_name | type) == "string" then .hook_event_name else $fallback end;
4
+ def path_values($obj; $keys): first([$keys[] as $k | $obj[$k] | select(type == "string" and length > 0)][]) // null | if . == null then null else [.] end;
5
+ def string_value($obj; $keys): first([$keys[] as $k | $obj[$k] | select(type == "string") ][]) // null;
6
+ def session_identity($s): {session: nonempty($s)};
7
+ def correlation($id;$parent): {toolCallId:nonempty($id),parentToolCallId:nonempty($parent)};
8
+ def event_operation($e):
9
+ if $e == "SessionStart" or $e == "agentSpawn" then "session-start"
10
+ elif $e == "SessionEnd" then "session-end"
11
+ elif $e == "UserPromptSubmit" then "prompt"
12
+ elif $e == "Stop" or $e == "stop" then "stop"
13
+ elif $e == "SubagentStart" then "subagent-start"
14
+ elif $e == "SubagentStop" then "subagent-stop"
15
+ elif $e == "PreCompact" or $e == "PreCompress" then "compact"
16
+ elif $e == "PostToolUse" or $e == "AfterTool" then "post-tool"
17
+ else "tool" end;
18
+ def canonical_tool($name; $harness):
19
+ if ($name | type) != "string" then {operation:"tool"}
20
+ elif (($harness == "claude" or $harness == "codex" or $harness == "kiro") and ($name | test("^mcp__"))) or (($harness == "gemini" or $harness == "antigravity") and ($name | test("^mcp_"))) or ($harness == "pi" and ($name | test("^mcp__"))) then {operation:"mcp",tool:$name}
21
+ elif (($harness == "claude" or $harness == "codex" or $harness == "kiro") and (["Edit","Write","MultiEdit","apply_patch"] | index($name)) != null) or ($harness == "gemini" and (["write_file","replace"] | index($name)) != null) or ($harness == "antigravity" and (["write_to_file","replace_file_content","multi_replace_file_content","edit_file","write_file","create_file","replace_file","replace","Edit","Write","MultiEdit","apply_patch"] | index($name)) != null) or ($harness == "pi" and (["edit","write"] | index($name)) != null) then {operation:"edit",tool:$name}
22
+ elif (($harness == "claude" or $harness == "codex" or $harness == "kiro") and $name == "Bash") or ($harness == "gemini" and $name == "run_shell_command") or ($harness == "antigravity" and (["run_command","run_shell_command","Bash","bash"] | index($name)) != null) or ($harness == "pi" and $name == "bash") then {operation:"command",tool:$name}
23
+ else {operation:"tool",tool:$name} end;
24
+ def decode_claude:
25
+ (canonical_tool(.tool_name // ""; "claude")) as $t |
26
+ if .tool_name == null then {operation: event_operation($event), identity: session_identity(.session_id)}
27
+ else {operation:$t.operation, tool:$t.tool, paths:(if $t.operation == "edit" then path_values(.tool_input; ["file_path"]) else null end), command:(if $t.operation == "command" then string_value(.tool_input; ["command"]) else null end), identity:session_identity(.session_id)} end;
28
+ def decode_codex:
29
+ decode_claude as $d |
30
+ if .tool_name == "apply_patch" then $d + {patch:(string_value(.tool_input; ["command"]))} else $d end;
31
+ def decode_gemini:
32
+ (canonical_tool(.tool_name // ""; "gemini")) as $t |
33
+ if .tool_name == null then {operation:event_operation($event),identity:session_identity(.session_id)}
34
+ else {operation:$t.operation,tool:$t.tool,paths:(if $t.operation == "edit" then path_values(.tool_input; ["file_path","path"]) else null end),command:(if $t.operation == "command" then string_value(.tool_input; ["command"]) else null end),identity:session_identity(.session_id)} end;
35
+ def decode_kiro:
36
+ (if ((.tool_name // "") | type) == "string" and ((.tool_name // "") | startswith("mcp_")) then {operation:"mcp",tool:.tool_name}
37
+ elif ((.tool_name // "") | type) == "string" and ((.tool_name // "") | test("^@[^/]+/.+")) then
38
+ ((.tool_name | capture("^@(?<server>[^/]+)/(?<tool>.+)$")) as $m | {operation:"mcp",tool:("mcp__" + $m.server + "__" + $m.tool)})
39
+ else canonical_tool(.tool_name // ""; "kiro") end) as $t |
40
+ if .tool_name == null then {operation:event_operation($event),identity:session_identity(.session_id)}
41
+ else {operation:$t.operation,tool:$t.tool,paths:(if $t.operation == "edit" then path_values(.tool_input; ["file_path"]) else null end),command:(if $t.operation == "command" then string_value(.tool_input; ["command"]) else null end),identity:session_identity(.session_id)} end;
42
+ def decode_antigravity:
43
+ if .toolCall == null then {operation:event_operation($event),identity:session_identity(.session_id)}
44
+ else (canonical_tool(.toolCall.name // ""; "antigravity")) as $t |
45
+ {operation:$t.operation,tool:$t.tool,paths:(if $t.operation == "edit" then path_values(.toolCall.args; ["AbsolutePath","TargetFile","Path","file_path","path"]) else null end),command:(if $t.operation == "command" then string_value(.toolCall.args; ["CommandLine","command"]) else null end),identity:session_identity(.session_id)} end;
46
+ def decode_pi:
47
+ (canonical_tool(.toolName // ""; "pi")) as $t |
48
+ {operation:$t.operation,tool:$t.tool,paths:(if $t.operation == "edit" then path_values(.input; ["path"]) else null end),command:(if $t.operation == "command" then string_value(.input; ["command"]) else null end),identity:session_identity(.sessionId),correlation:(correlation(.toolCallId;.parentToolCallId))};
49
+ if ($ARGS.named.mode // "encode") == "minimal" then {doflow_event:{version:(1),harness:$harness,event:(event_name($event)),operation:event_operation($event)}} elif type != "object" then . else
50
+ . as $native |
51
+ ($ENV.DOFLOW_TASK_ID | nonempty(.)) as $task |
52
+ ($ENV.DOFLOW_RUN_ID | nonempty(.)) as $run |
53
+ ($ENV.DOFLOW_ATTEMPT | try tonumber catch null) as $attempt |
54
+ (if $ENV.DOFLOW_SESSION_ROLE == "main" or $ENV.DOFLOW_SESSION_ROLE == "worker" then $ENV.DOFLOW_SESSION_ROLE else null end) as $role |
55
+ (if $harness == "claude" then decode_claude elif $harness == "codex" then decode_codex elif $harness == "gemini" then decode_gemini elif $harness == "kiro" then decode_kiro elif $harness == "antigravity" then decode_antigravity elif $harness == "pi" then decode_pi else {operation:"tool"} end) as $d |
56
+ ({version:1,harness:$harness,event:(event_name($event)),operation:$d.operation,tool:$d.tool,paths:$d.paths,command:$d.command,patch:$d.patch,taskId:$task,
57
+ identity:($d.identity + {run:$run,attempt:$attempt,role:$role} | with_entries(select(.value != null)) | if length > 0 then . else null end),
58
+ correlation:(($d.correlation // {}) | with_entries(select(.value != null)) | if length > 0 then . else null end) } | with_entries(select(.value != null))) as $envelope |
59
+ $native + {doflow_event:$envelope}
60
+ end