@khoavu882/doflow 2.0.0 → 2.0.2

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 (176) hide show
  1. package/README.md +4 -5
  2. package/bin/doflow.js +1 -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 +4 -1
  7. package/core/harnesses/claude/hooks/pre-implement-gate.sh +4 -1
  8. package/core/harnesses/claude/hooks/session-end.sh +21 -1
  9. package/core/harnesses/claude/settings/settings.json +8 -9
  10. package/core/harnesses/codex/hooks/hooks.json +0 -10
  11. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +4 -1
  12. package/core/harnesses/codex/hooks/pre-implement-gate.sh +4 -1
  13. package/core/harnesses/codex/hooks/session-end.impl.sh +21 -1
  14. package/core/harnesses/copilot/hooks/doflow-lib.sh +4 -3
  15. package/core/harnesses/copilot/hooks/doflow.json +0 -6
  16. package/core/harnesses/kiro/agents/doflow.json +0 -10
  17. package/core/harnesses/kiro/hooks/doflow.json +0 -10
  18. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +7 -3
  19. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +7 -3
  20. package/core/harnesses/pi/extensions/doflow-guard.js +3 -2
  21. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +1 -1
  22. package/core/harnesses/shared/hooks/adapters/antigravity.js +1 -1
  23. package/core/harnesses/shared/hooks/bridge/bridge.sh +35 -25
  24. package/core/harnesses/shared/hooks/bridge/envelope.jq +1 -1
  25. package/core/harnesses/shared/hooks/bridge/respond.sh +6 -0
  26. package/core/harnesses/shared/hooks/policies/capture-failure.sh +1 -1
  27. package/core/harnesses/shared/hooks/policies/compact-text.sh +8 -0
  28. package/core/harnesses/shared/hooks/policies/deny-json.sh +23 -1
  29. package/core/harnesses/shared/hooks/policies/mcp-policy.conf +1 -1
  30. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +4 -6
  31. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +20 -21
  32. package/core/harnesses/shared/hooks/policies/stop-check.sh +7 -6
  33. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +26 -2
  34. package/core/harnesses/shared/hooks/stream-hook-runner.js +11 -12
  35. package/core/registry/contracts.json +1 -1
  36. package/core/registry/contribution-points.json +5 -2
  37. package/core/registry/gates.json +5 -5
  38. package/core/registry/harnesses.json +9 -9
  39. package/core/registry/intake.json +53 -0
  40. package/core/registry/lifecycle.json +176 -503
  41. package/core/registry/package-templates/intake-category/files/category.json +8 -0
  42. package/core/registry/package-templates/intake-category/template.json +4 -0
  43. package/core/registry/packages/doflow/doflow-package.json +2 -2
  44. package/core/registry/rules.json +8 -8
  45. package/core/registry/settings.json +12 -16
  46. package/core/registry/storage.json +9 -2
  47. package/core/registry/telemetry.json +1 -1
  48. package/core/registry/verification.json +0 -5
  49. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +7 -0
  50. package/core/shared/guidance/references/DOFLOW_CHAIN.md +4 -4
  51. package/core/shared/guidance/references/ORCHESTRATION.md +2 -0
  52. package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +19 -1
  53. package/core/shared/guidance/rules/RULES.md +2 -2
  54. package/core/shared/scripts/doflow/bash/do-git-state.sh +12 -4
  55. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +86 -33
  56. package/core/shared/scripts/doflow/bash/do-review-package.sh +36 -2
  57. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +1 -1
  58. package/core/shared/scripts/doflow/bin/doflow-run +23 -2
  59. package/core/shared/skills/do/SKILL.md +3 -3
  60. package/core/shared/skills/do-brainstorm/SKILL.md +26 -24
  61. package/core/shared/skills/do-brainstorm/references/handoff_branches.md +2 -1
  62. package/core/shared/skills/do-code-review/SKILL.md +7 -4
  63. package/core/shared/skills/do-design/SKILL.md +11 -5
  64. package/core/shared/skills/do-design/references/data_model_example.md +167 -0
  65. package/core/shared/skills/do-diagnose/SKILL.md +4 -2
  66. package/core/shared/skills/do-document/SKILL.md +1 -0
  67. package/core/shared/skills/do-document/references/embedded-research.md +1 -1
  68. package/core/shared/skills/do-execute-plan/SKILL.md +4 -2
  69. package/core/shared/skills/do-execute-plan/references/run_position.md +3 -1
  70. package/core/shared/skills/do-execute-plan/references/scaffold.md +1 -1
  71. package/core/shared/skills/do-flow/SKILL.md +4 -4
  72. package/core/shared/skills/do-flow/references/task_classes.md +5 -5
  73. package/core/shared/skills/do-implement/SKILL.md +9 -7
  74. package/core/shared/skills/do-plan/SKILL.md +9 -6
  75. package/core/shared/templates/doflow/data-model-template.md +136 -102
  76. package/core/shared/workflows/do-flow-chain.md +2 -0
  77. package/package.json +1 -1
  78. package/src/adapters/antigravity/index.js +54 -12
  79. package/src/adapters/claude/index.js +23 -2
  80. package/src/adapters/codex/agents.js +15 -1
  81. package/src/adapters/codex/config.js +5 -2
  82. package/src/adapters/codex/hooks.js +39 -3
  83. package/src/adapters/codex/index.js +13 -0
  84. package/src/adapters/copilot/index.js +11 -4
  85. package/src/adapters/copy-tree.js +8 -2
  86. package/src/adapters/created-file.js +41 -0
  87. package/src/adapters/mcp-entries.js +11 -1
  88. package/src/adapters/opencode/index.js +32 -5
  89. package/src/adapters/pi/index.js +15 -4
  90. package/src/adapters/pi/mcp.js +44 -18
  91. package/src/cli/commands/remove.js +3 -1
  92. package/src/cli/commands/status.js +1 -1
  93. package/src/cli/commands/update.js +7 -3
  94. package/src/cli/index.js +43 -19
  95. package/src/cli/runtime-commands.js +13 -5
  96. package/src/cli/shared.js +5 -3
  97. package/src/helper/marker-merge.js +11 -7
  98. package/src/install/backup-set.js +7 -3
  99. package/src/install/backup.js +13 -7
  100. package/src/install/context.js +2 -1
  101. package/src/install/manifest.js +35 -11
  102. package/src/install/paths.js +38 -0
  103. package/src/install/targets.js +11 -2
  104. package/src/lifecycle/coverage.js +1 -30
  105. package/src/lifecycle/hook-tier.js +2 -2
  106. package/src/lifecycle/index.js +80 -3
  107. package/src/lifecycle/retired-harnesses.js +16 -1
  108. package/src/lifecycle/view.js +3 -2
  109. package/src/registry/index.js +21 -4
  110. package/src/release/docs-blocks.js +37 -53
  111. package/src/runtime/advice/doctor.js +2 -2
  112. package/src/runtime/brief-tools.js +2 -1
  113. package/src/runtime/capability-router.js +26 -3
  114. package/src/runtime/context/pointer.js +84 -17
  115. package/src/runtime/context-pack.js +52 -9
  116. package/src/runtime/dispatch/integrate.js +27 -8
  117. package/src/runtime/dispatch/loop.js +7 -0
  118. package/src/runtime/dispatch/verify-run.js +9 -3
  119. package/src/runtime/evidence-ledger.js +18 -0
  120. package/src/runtime/feature-pointer.js +10 -1
  121. package/src/runtime/health.js +68 -5
  122. package/src/runtime/implementation-gate.js +14 -6
  123. package/src/runtime/intake/engine.js +679 -0
  124. package/src/runtime/intake/index.js +136 -0
  125. package/src/runtime/intake/manifest.js +140 -0
  126. package/src/runtime/intake/taxonomy.js +206 -0
  127. package/src/runtime/knowledge/code-retrieval.js +11 -5
  128. package/src/runtime/knowledge/semble-provider.js +12 -2
  129. package/src/runtime/lifecycle/cli.js +76 -6
  130. package/src/runtime/lifecycle/followup.js +32 -6
  131. package/src/runtime/lifecycle/overview.js +34 -3
  132. package/src/runtime/lifecycle/release.js +16 -3
  133. package/src/runtime/lifecycle/retention.js +12 -15
  134. package/src/runtime/lifecycle/store-upkeep.js +13 -3
  135. package/src/runtime/mask.js +3 -3
  136. package/src/runtime/mcp-consultations.js +11 -5
  137. package/src/runtime/packages/points.js +10 -0
  138. package/src/runtime/research-request.js +3 -1
  139. package/src/runtime/review/command.js +3 -7
  140. package/src/runtime/review/start.js +24 -10
  141. package/src/runtime/roadmap/attendance.js +137 -0
  142. package/src/runtime/roadmap/gates.js +6 -3
  143. package/src/runtime/roadmap/loop.js +127 -33
  144. package/src/runtime/roadmap/merge.js +17 -8
  145. package/src/runtime/roadmap/rows.js +15 -5
  146. package/src/runtime/roadmap/seed.js +153 -41
  147. package/src/runtime/roadmap/worktree-cleanup.js +147 -0
  148. package/src/runtime/settings/files.js +3 -1
  149. package/src/runtime/settings/prepared-run.js +12 -2
  150. package/src/runtime/settings/registry.js +1 -1
  151. package/src/runtime/settings/resolve.js +13 -1
  152. package/src/runtime/settings/write.js +73 -2
  153. package/src/runtime/slots.js +1 -1
  154. package/src/runtime/storage/registry.js +2 -2
  155. package/src/runtime/storage/roots.js +4 -18
  156. package/src/runtime/storage/rules.js +22 -46
  157. package/src/runtime/storage/work-status.js +14 -4
  158. package/src/runtime/task-classifier.js +39 -4
  159. package/src/runtime/task-scope.js +17 -2
  160. package/src/runtime/task-state.js +13 -7
  161. package/src/runtime/trace/analyze.js +4 -2
  162. package/src/runtime/trace/indicators.js +25 -20
  163. package/src/runtime/trace/spans.js +4 -5
  164. package/src/runtime/verb-view.js +268 -0
  165. package/src/runtime/verification/engine.js +0 -45
  166. package/src/runtime/worker/command.js +4 -2
  167. package/src/runtime/worker/launch.js +5 -37
  168. package/src/runtime/worker/readiness.js +10 -6
  169. package/src/runtime/worker/supervisor.js +18 -6
  170. package/src/runtime/workflow-orchestrator.js +39 -7
  171. package/core/harnesses/claude/hooks/pre-bash-guard.sh +0 -10
  172. package/core/harnesses/codex/hooks/pre-bash-guard.sh +0 -10
  173. package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +0 -9
  174. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +0 -25
  175. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +0 -105
  176. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +0 -579
package/README.md CHANGED
@@ -28,7 +28,7 @@ flowchart LR
28
28
  |---|---|
29
29
  | Repeatable delivery work | 13 skills, including `/do-brainstorm`, `/do-plan`, `/do-test`, and `/do-code-review` |
30
30
  | Specialist review | 7 agents — `spec-analyst`, `system-architect`, `delivery-lead`, `core-implementer`, `quality-guardian`, `reviewer`, `research-writer` |
31
- | Safer automation | Claude hooks block destructive commands and unfinished implementation stubs |
31
+ | Safer automation | Hooks advise when an edit starts before the spec exists and when work looks unfinished; the runtime enforces order and readiness at the stage handoff |
32
32
  | Shared standards | Rules for safety, workflow, quality, and clarification across supported tools |
33
33
  | Session continuity | Claude and Codex hooks capture lightweight Git context and compact-session summaries and deliver them on a session's first prompt |
34
34
 
@@ -125,10 +125,9 @@ Kiro's hooks are not in Kiro v3's
125
125
  untrusted-workspace exclusion list; an untrusted workspace asks before every shell command, and
126
126
  whether that covers hook commands is undocumented. Copilot CLI documents hooks, settings, and a
127
127
  plugin marketplace upstream; DoFlow still installs none of those there. OpenCode and Pi
128
- standard-adopt `AGENTS.md` for seamless zero-drift rule projection. See `docs/capability-map.md`
129
- and the [installation matrix](docs/setup.md#what-gets-installed) for the exact mapping.
128
+ standard-adopt `AGENTS.md` for seamless zero-drift rule projection. See the
129
+ [installation matrix](docs/setup.md#what-gets-installed) for the exact mapping.
130
130
 
131
131
  ## Contributing
132
132
 
133
- The repository layout and deployment model are documented in the [Architecture guide](docs/architecture.md).
134
- Run `npm test` and the shell suites described there before submitting changes.
133
+ Run `npm test` and the shell suites under `test/` before submitting changes.
package/bin/doflow.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
  // doflow — DoFlow config installer CLI. Argument parsing, the installer command table and every
4
- // command handler live in src/cli/ (see docs/architecture.md); this file is only the forwarder
4
+ // command handler live in src/cli/; this file is only the forwarder
5
5
  // so the `doflow` binary path and the doflow-run dispatcher seam
6
6
  // (core/shared/scripts/doflow/bin/doflow-run, which execs `node <repoRoot>/bin/doflow.js`)
7
7
  // keep resolving to this exact location.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doflow",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Reusable engineering workflows, quality gates, and specialist guidance for Claude Code.",
5
5
  "author": {
6
6
  "name": "DoFlow"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doflow",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Reusable engineering workflows, quality gates, and specialist guidance for Codex.",
5
5
  "author": {
6
6
  "name": "DoFlow",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doflow",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Reusable engineering workflows, quality gates, and specialist guidance for GitHub Copilot CLI.",
5
5
  "author": {
6
6
  "name": "DoFlow",
@@ -3,7 +3,10 @@
3
3
  set -uo pipefail
4
4
  export DOFLOW_AGENT="${DOFLOW_AGENT:-claude}"
5
5
  if ! . "$(dirname "$0")/../../.doflow/shared/hooks/bridge/bridge.sh" 2>/dev/null; then
6
- printf '%s\n' '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"[doflow] shared hooks lack the bridge; run: doflow update -t claude"}}'
6
+ # D1: a missing bridge allows. Only DOFLOW_FAULT_PROFILE=strict-1, a person's opt-in, denies.
7
+ if [ "${DOFLOW_FAULT_PROFILE:-}" = strict-1 ]; then
8
+ printf '%s\n' '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"[doflow] shared hooks lack the bridge; run: doflow update -t claude"}}'
9
+ fi
7
10
  exit 0
8
11
  fi
9
12
  doflow_bridge claude PreToolUse mcp-tool-guard
@@ -3,7 +3,10 @@
3
3
  set -uo pipefail
4
4
  export DOFLOW_AGENT="${DOFLOW_AGENT:-claude}"
5
5
  if ! . "$(dirname "$0")/../../.doflow/shared/hooks/bridge/bridge.sh" 2>/dev/null; then
6
- printf '%s\n' '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"[doflow] shared hooks lack the bridge; run: doflow update -t claude"}}'
6
+ # D1: a missing bridge allows. Only DOFLOW_FAULT_PROFILE=strict-1, a person's opt-in, denies.
7
+ if [ "${DOFLOW_FAULT_PROFILE:-}" = strict-1 ]; then
8
+ printf '%s\n' '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"[doflow] shared hooks lack the bridge; run: doflow update -t claude"}}'
9
+ fi
7
10
  exit 0
8
11
  fi
9
12
  doflow_bridge claude PreToolUse pre-implementation-gate
@@ -5,7 +5,7 @@ case "${OSTYPE:-}" in msys*|cygwin*|win32*) if command -v jq >/dev/null 2>&1; th
5
5
  # Runs when Claude Code closes a session. Responsibilities:
6
6
  # 1. Append END marker to sessions.log
7
7
  # 2. Write uncommitted-warning.txt if the working tree is dirty
8
- # 3. Delete the session-scoped state directory (cleanup)
8
+ # 3. Delete the session-scoped state directory (cleanup) and the main session's model observation
9
9
  # 4. Trim sessions.log to last 500 lines (lock-guarded for parallel safety)
10
10
  #
11
11
  # Multi-session safe: with_file_lock (lib.sh) prevents concurrent sessions from
@@ -81,6 +81,26 @@ if [[ -n "$SESSION_ID" ]]; then
81
81
  rm -rf "$SESSION_PATH" 2>/dev/null || true
82
82
  fi
83
83
 
84
+ # ── 3b. Remove this main session's model observation (120 IC-005) ─────────────
85
+
86
+ # observe-model.sh keys the file by the first 16 hex of sha256(session id), under primary/<cwd_hash>/<harness>/.
87
+ # Only the ending main session's own file goes: other sessions, worker observations and a missing file are left
88
+ # alone, and a failure here changes nothing (SessionEnd is fire-and-forget).
89
+ forget_model_observation() {
90
+ local key="" cwd="${CWD:-}" file=""
91
+ [[ -n "$SESSION_ID" ]] || return 0
92
+ [[ "$(doflow_session_role || echo main)" == "main" ]] || return 0
93
+ if command -v sha256sum >/dev/null 2>&1; then key=$(printf '%s' "$SESSION_ID" | sha256sum | cut -c1-16)
94
+ elif command -v shasum >/dev/null 2>&1; then key=$(printf '%s' "$SESSION_ID" | shasum -a 256 | cut -c1-16)
95
+ else return 0; fi
96
+ [[ $key =~ ^[0-9a-f]{16}$ ]] || return 0
97
+ case "$cwd" in /*) ;; *) cwd=$PWD ;; esac
98
+ file="$STATE_DIR/primary/$(cwd_hash "$cwd")/claude/$key.json"
99
+ if [[ -f "$file" && ! -L "$file" ]]; then rm -f "$file"; fi
100
+ return 0
101
+ }
102
+ forget_model_observation 2>/dev/null || true
103
+
84
104
  start_storage_upkeep
85
105
 
86
106
  # ── 4. Trim sessions.log (lock-guarded) ───────────────────────────────────────
@@ -21,6 +21,14 @@
21
21
  "Bash(git push --force*:*)",
22
22
  "Bash(git reset --hard*:*)",
23
23
  "Bash(git clean -fd*:*)"
24
+ ],
25
+ "ask": [
26
+ "Bash(git push:*)",
27
+ "Bash(git tag:*)",
28
+ "Bash(git * push:*)",
29
+ "Bash(git * tag:*)",
30
+ "Bash(git * push)",
31
+ "Bash(git * tag)"
24
32
  ]
25
33
  },
26
34
  "alwaysThinkingEnabled": true,
@@ -87,15 +95,6 @@
87
95
  }
88
96
  ],
89
97
  "PreToolUse": [
90
- {
91
- "matcher": "Bash",
92
- "hooks": [
93
- {
94
- "type": "command",
95
- "command": "~/.claude/hooks/pre-bash-guard.sh"
96
- }
97
- ]
98
- },
99
98
  {
100
99
  "matcher": "mcp__.*",
101
100
  "hooks": [
@@ -43,16 +43,6 @@
43
43
  }
44
44
  ],
45
45
  "PreToolUse": [
46
- {
47
- "matcher": "^Bash$",
48
- "hooks": [
49
- {
50
- "type": "command",
51
- "command": "bash \"$(root=$(git rev-parse --show-toplevel 2>/dev/null) && [ -f \"$root/.codex/hooks/pre-bash-guard.sh\" ] && printf '%s/.codex/hooks/pre-bash-guard.sh' \"$root\" || printf '%s/hooks/pre-bash-guard.sh' \"${CODEX_HOME:-$HOME/.codex}\")\"",
52
- "statusMessage": "Checking command safety"
53
- }
54
- ]
55
- },
56
46
  {
57
47
  "matcher": "^mcp__.*",
58
48
  "hooks": [
@@ -3,7 +3,10 @@
3
3
  set -uo pipefail
4
4
  export DOFLOW_AGENT="${DOFLOW_AGENT:-codex}"
5
5
  if ! . "$(dirname "$0")/../../.doflow/shared/hooks/bridge/bridge.sh" 2>/dev/null; then
6
- printf '%s\n' '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"[doflow] shared hooks lack the bridge; run: doflow update -t codex"}}'
6
+ # D1: a missing bridge allows. Only DOFLOW_FAULT_PROFILE=strict-1, a person's opt-in, denies.
7
+ if [ "${DOFLOW_FAULT_PROFILE:-}" = strict-1 ]; then
8
+ printf '%s\n' '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"[doflow] shared hooks lack the bridge; run: doflow update -t codex"}}'
9
+ fi
7
10
  exit 0
8
11
  fi
9
12
  doflow_bridge codex PreToolUse mcp-tool-guard
@@ -3,7 +3,10 @@
3
3
  set -uo pipefail
4
4
  export DOFLOW_AGENT="${DOFLOW_AGENT:-codex}"
5
5
  if ! . "$(dirname "$0")/../../.doflow/shared/hooks/bridge/bridge.sh" 2>/dev/null; then
6
- printf '%s\n' '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"[doflow] shared hooks lack the bridge; run: doflow update -t codex"}}'
6
+ # D1: a missing bridge allows. Only DOFLOW_FAULT_PROFILE=strict-1, a person's opt-in, denies.
7
+ if [ "${DOFLOW_FAULT_PROFILE:-}" = strict-1 ]; then
8
+ printf '%s\n' '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"[doflow] shared hooks lack the bridge; run: doflow update -t codex"}}'
9
+ fi
7
10
  exit 0
8
11
  fi
9
12
  doflow_bridge codex PreToolUse pre-implementation-gate
@@ -5,7 +5,7 @@ case "${OSTYPE:-}" in msys*|cygwin*|win32*) if command -v jq >/dev/null 2>&1; th
5
5
  # Runs when Claude Code closes a session. Responsibilities:
6
6
  # 1. Append END marker to sessions.log
7
7
  # 2. Write uncommitted-warning.txt if the working tree is dirty
8
- # 3. Delete the session-scoped state directory (cleanup)
8
+ # 3. Delete the session-scoped state directory (cleanup) and the main session's model observation
9
9
  # 4. Trim sessions.log to last 500 lines (lock-guarded for parallel safety,
10
10
  # via lib.sh's portable with_file_lock — no flock dependency)
11
11
  #
@@ -82,6 +82,26 @@ if [[ -n "$SESSION_ID" ]]; then
82
82
  rm -rf "$SESSION_PATH" 2>/dev/null || true
83
83
  fi
84
84
 
85
+ # ── 3b. Remove this main session's model observation (120 IC-005) ─────────────
86
+
87
+ # observe-model.sh keys the file by the first 16 hex of sha256(session id), under primary/<cwd_hash>/<harness>/.
88
+ # Only the ending main session's own file goes: other sessions, worker observations and a missing file are left
89
+ # alone, and a failure here changes nothing (SessionEnd is fire-and-forget).
90
+ forget_model_observation() {
91
+ local key="" cwd="${CWD:-}" file=""
92
+ [[ -n "$SESSION_ID" ]] || return 0
93
+ [[ "$(doflow_session_role || echo main)" == "main" ]] || return 0
94
+ if command -v sha256sum >/dev/null 2>&1; then key=$(printf '%s' "$SESSION_ID" | sha256sum | cut -c1-16)
95
+ elif command -v shasum >/dev/null 2>&1; then key=$(printf '%s' "$SESSION_ID" | shasum -a 256 | cut -c1-16)
96
+ else return 0; fi
97
+ [[ $key =~ ^[0-9a-f]{16}$ ]] || return 0
98
+ case "$cwd" in /*) ;; *) cwd=$PWD ;; esac
99
+ file="$STATE_DIR/primary/$(cwd_hash "$cwd")/codex/$key.json"
100
+ if [[ -f "$file" && ! -L "$file" ]]; then rm -f "$file"; fi
101
+ return 0
102
+ }
103
+ forget_model_observation 2>/dev/null || true
104
+
85
105
  start_storage_upkeep
86
106
 
87
107
  # ── 4. Trim sessions.log (lock-guarded) ───────────────────────────────────────
@@ -69,8 +69,8 @@ copilot_bridge_payload() {
69
69
  }
70
70
 
71
71
  # copilot_front_door <script-dir> <event> <policy> <fail>: run one shared policy through the bridge.
72
- # <fail> is `deny` for a pre-tool policy (a missing bridge denies the tool call, as Copilot's
73
- # preToolUse hooks are fail-closed on a non-zero exit anyway) or `open` for every other event.
72
+ # <fail> is `deny` for a pre-tool policy (a missing bridge denies the tool call only under strict-1)
73
+ # or `open` for every other event.
74
74
  copilot_front_door() {
75
75
  local dir="$1" event="$2" policy="$3" fail="${4:-open}" raw payload bridge status=0
76
76
  raw=$(cat)
@@ -83,7 +83,8 @@ copilot_front_door() {
83
83
  # User hooks under a custom $COPILOT_HOME sit beside no .doflow; the bridge is then in $HOME/.doflow.
84
84
  [ -f "$bridge" ] || bridge="${HOME:-}/.doflow/shared/hooks/bridge/bridge.sh"
85
85
  if [ ! -f "$bridge" ] || ! . "$bridge" 2>/dev/null; then
86
- if [ "$fail" = deny ]; then
86
+ # D1: a missing bridge allows. Only DOFLOW_FAULT_PROFILE=strict-1, a person's opt-in, denies a pre-tool call.
87
+ if [ "$fail" = deny ] && [ "${DOFLOW_FAULT_PROFILE:-}" = strict-1 ]; then
87
88
  printf '%s\n' '{"permissionDecision":"deny","permissionDecisionReason":"[doflow] shared hooks lack the bridge; run: doflow update -t copilot"}'
88
89
  fi
89
90
  return 0
@@ -16,12 +16,6 @@
16
16
  "cwd": ".",
17
17
  "timeoutSec": 20
18
18
  },
19
- {
20
- "type": "command",
21
- "bash": "bash \"$(root=$(git rev-parse --show-toplevel 2>/dev/null) && [ -f \"$root/.github/hooks/doflow-pre-bash-guard.sh\" ] && printf '%s/.github/hooks/doflow-pre-bash-guard.sh' \"$root\" || printf '%s/hooks/doflow-pre-bash-guard.sh' \"${COPILOT_HOME:-$HOME/.copilot}\")\"",
22
- "cwd": ".",
23
- "timeoutSec": 20
24
- },
25
19
  {
26
20
  "type": "command",
27
21
  "bash": "bash \"$(root=$(git rev-parse --show-toplevel 2>/dev/null) && [ -f \"$root/.github/hooks/doflow-mcp-tool-guard.sh\" ] && printf '%s/.github/hooks/doflow-mcp-tool-guard.sh' \"$root\" || printf '%s/hooks/doflow-mcp-tool-guard.sh' \"${COPILOT_HOME:-$HOME/.copilot}\")\"",
@@ -16,16 +16,6 @@
16
16
  },
17
17
  "timeout": 10
18
18
  },
19
- {
20
- "name": "doflow-pre-bash-guard",
21
- "trigger": "preToolUse",
22
- "matcher": "shell",
23
- "action": {
24
- "type": "command",
25
- "command": "bash \"$(root=$(git rev-parse --show-toplevel 2>/dev/null) && [ -f \"$root/.kiro/hooks/pre-bash-guard.sh\" ] && printf '%s/.kiro/hooks/pre-bash-guard.sh' \"$root\" || printf '%s/.kiro/hooks/pre-bash-guard.sh' \"$HOME\")\""
26
- },
27
- "timeout": 10
28
- },
29
19
  {
30
20
  "name": "doflow-mcp-tool-guard",
31
21
  "trigger": "preToolUse",
@@ -19,16 +19,6 @@
19
19
  },
20
20
  "enabled": true
21
21
  },
22
- {
23
- "name": "doflow-pre-bash-guard",
24
- "trigger": "PreToolUse",
25
- "matcher": "shell",
26
- "action": {
27
- "type": "command",
28
- "command": "bash \"$(root=$(git rev-parse --show-toplevel 2>/dev/null) && [ -f \"$root/.kiro/hooks/pre-bash-guard.sh\" ] && printf '%s/.kiro/hooks/pre-bash-guard.sh' \"$root\" || printf '%s/.kiro/hooks/pre-bash-guard.sh' \"$HOME\")\""
29
- },
30
- "enabled": true
31
- },
32
22
  {
33
23
  "name": "doflow-mcp-tool-guard",
34
24
  "trigger": "PreToolUse",
@@ -11,11 +11,15 @@ payload=$(kiro_bridge_payload <<<"$raw")
11
11
  event="PreToolUse"
12
12
  if command -v jq >/dev/null 2>&1; then event=$(printf '%s' "$raw" | jq -r '.hook_event_name // "PreToolUse"' 2>/dev/null) || event="PreToolUse"; fi
13
13
  bridge="$here/../../.doflow/shared/hooks/bridge/bridge.sh"
14
- if [ ! -f "$bridge" ]; then
14
+ # D1: a missing bridge allows with a warning on stderr. Only strict-1, a person's opt-in, blocks.
15
+ bridge_missing=0
16
+ [ -f "$bridge" ] || bridge_missing=1
17
+ if [ "$bridge_missing" -eq 0 ]; then . "$bridge" 2>/dev/null || bridge_missing=1; fi
18
+ if [ "$bridge_missing" -eq 1 ]; then
15
19
  printf '%s\n' '[doflow] shared hooks lack the bridge; run: doflow update -t kiro' >&2
16
- exit 2
20
+ if [ "${DOFLOW_FAULT_PROFILE:-}" = strict-1 ]; then exit 2; fi
21
+ exit 0
17
22
  fi
18
- . "$bridge" 2>/dev/null || { printf '%s\n' '[doflow] shared hooks lack the bridge; run: doflow update -t kiro' >&2; exit 2; }
19
23
  if doflow_bridge kiro "$event" mcp-tool-guard <<<"$payload"; then
20
24
  status=0
21
25
  else
@@ -11,11 +11,15 @@ payload=$(kiro_bridge_payload <<<"$raw")
11
11
  event="PreToolUse"
12
12
  if command -v jq >/dev/null 2>&1; then event=$(printf '%s' "$raw" | jq -r '.hook_event_name // "PreToolUse"' 2>/dev/null) || event="PreToolUse"; fi
13
13
  bridge="$here/../../.doflow/shared/hooks/bridge/bridge.sh"
14
- if [ ! -f "$bridge" ]; then
14
+ # D1: a missing bridge allows with a warning on stderr. Only strict-1, a person's opt-in, blocks.
15
+ bridge_missing=0
16
+ [ -f "$bridge" ] || bridge_missing=1
17
+ if [ "$bridge_missing" -eq 0 ]; then . "$bridge" 2>/dev/null || bridge_missing=1; fi
18
+ if [ "$bridge_missing" -eq 1 ]; then
15
19
  printf '%s\n' '[doflow] shared hooks lack the bridge; run: doflow update -t kiro' >&2
16
- exit 2
20
+ if [ "${DOFLOW_FAULT_PROFILE:-}" = strict-1 ]; then exit 2; fi
21
+ exit 0
17
22
  fi
18
- . "$bridge" 2>/dev/null || { printf '%s\n' '[doflow] shared hooks lack the bridge; run: doflow update -t kiro' >&2; exit 2; }
19
23
  if doflow_bridge kiro "$event" pre-implementation-gate <<<"$payload"; then
20
24
  status=0
21
25
  else
@@ -6,7 +6,6 @@ import { fileURLToPath } from 'node:url';
6
6
  const BRIDGE = new URL('../../shared/hooks/bridge/bridge.sh', import.meta.url);
7
7
  const ROUTES = [
8
8
  { tools: ['edit', 'write'], policy: 'pre-implementation-gate' },
9
- { tools: ['bash'], policy: 'pre-bash-guard' },
10
9
  ];
11
10
  const LIMIT = 1024 * 1024;
12
11
  const TIMEOUT_MS = 5000;
@@ -136,7 +135,9 @@ export default function (pi) {
136
135
  if (profile !== 'compat-1' && profile !== 'strict-1') captureFailure(policy, 'fault-profile-unknown');
137
136
  if (!existsSync(BRIDGE)) {
138
137
  captureFailure(policy, 'bridge-missing');
139
- return { block: true, reason: '[doflow] shared hooks lack the bridge; run: doflow update -t pi' };
138
+ // D1: a missing bridge allows (the failure is recorded for doctor); only strict-1, a person's opt-in, blocks.
139
+ if (profile === 'strict-1') return { block: true, reason: '[doflow] shared hooks lack the bridge; run: doflow update -t pi' };
140
+ return undefined;
140
141
  }
141
142
  let model;
142
143
  try {
@@ -36,7 +36,7 @@ them through), but sending nothing for a non-edit tool is equally correct and ch
36
36
  ## Version and mixed-install rules
37
37
 
38
38
  - A missing envelope or version, empty or non-object input, or a missing `contract.sh` is out of
39
- step. The gate, bash guard and MCP guard deny with `DoFlow hooks out of step; run: doflow update
39
+ step. The gate and MCP guard deny with `DoFlow hooks out of step; run: doflow update
40
40
  -t <harness>`; Stop and context policies exit silently, except the prompt hook emits its `{}`
41
41
  no-op. Other envelope versions are unsupported and policies evaluate nothing.
42
42
  - If a door cannot load `bridge/bridge.sh`, it renders its fixed native out-of-step response. The
@@ -20,7 +20,7 @@
20
20
  *
21
21
  * SessionStart and PostToolUse have no dedicated Antigravity-native shim in
22
22
  * src/adapters/antigravity/index.js's live projection. No Phase A canonical policy owns PostToolUse today (only
23
- * session-context, pre-implementation-gate, mcp-tool-guard, stop-check, pre-bash-guard exist),
23
+ * session-context, pre-implementation-gate, mcp-tool-guard, stop-check exist),
24
24
  * so its native shape is an ack passthrough either way.
25
25
  */
26
26
 
@@ -42,31 +42,19 @@ doflow_bridge_effect() {
42
42
  profile=compat-1; doflow_bridge_failure "$policy" fault-profile-unknown
43
43
  fi
44
44
  DF_BRIDGE_PROFILE="$profile"
45
- case "$policy" in pre-implementation-gate|pre-bash-guard|mcp-tool-guard|stop-check) block=1;; esac
45
+ case "$policy" in pre-implementation-gate|mcp-tool-guard|stop-check) block=1;; esac
46
46
  DF_BRIDGE_EFFECT=proceed
47
47
  if [ "$outcome" = deny ]; then DF_BRIDGE_EFFECT=block
48
48
  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
49
- elif [ "$outcome" = error ] && [ "$DF_BRIDGE_CODE" != bridge-spawn ] && [ -n "$DF_BRIDGE_POINT" ] && [ "$profile" = compat-1 ]; then
50
- case "$harness:$event" in
51
- claude:PreToolUse|codex:PreToolUse|copilot:PreToolUse|pi:tool_call) DF_BRIDGE_EFFECT=block ;;
52
- antigravity:PreToolUse|antigravity:Stop) case "$DF_BRIDGE_CODE" in signal-*) DF_BRIDGE_EFFECT=proceed;; *) DF_BRIDGE_EFFECT=block;; esac ;;
53
- kiro:PreToolUse) [ "$DF_BRIDGE_CODE" = exit-2-unmarked ] && DF_BRIDGE_EFFECT=block ;;
54
- claude:Stop) [ "$DF_BRIDGE_CODE" = exit-2-unmarked ] && DF_BRIDGE_EFFECT=block ;;
55
- codex:Stop|codex:stop|copilot:Stop|kiro:Stop|kiro:stop) [ "$DF_BRIDGE_CODE" = exit-2-unmarked ] && DF_BRIDGE_EFFECT=block || DF_BRIDGE_EFFECT=proceed ;;
56
- esac
57
- fi
58
- # Copilot CLI: a guarded preToolUse policy that errors or cannot decide (jq or the policy script
59
- # missing, an edit with no decodable target) denies whatever the fault profile; only a policy that
60
- # answered allow or advisory lets the call through. Copilot-only: every other harness keeps the
61
- # profile rules above byte for byte.
62
- if [ "$block" -eq 1 ] && [ "$DF_BRIDGE_EFFECT" = proceed ] && [ "$harness:$event" = copilot:PreToolUse ] && [ "$policy" != stop-check ]; then
63
- case "$outcome" in error|unsupported) DF_BRIDGE_EFFECT=block ;; esac
64
49
  fi
50
+ # D1 (advise-only hooks): under compat-1 a faulting policy never blocks, on any harness. Only a policy
51
+ # that answered deny (mcp-tool-guard blocked-pattern) blocks, and strict-1, a person's opt-in, blocks
52
+ # on a fault of a blocking policy (above).
65
53
  }
66
54
 
67
55
  doflow_bridge_drops_tool_streams() {
68
56
  case "$1" in
69
- pre-implementation-gate|pre-bash-guard|mcp-tool-guard) ;;
57
+ pre-implementation-gate|mcp-tool-guard) ;;
70
58
  *) return 1 ;;
71
59
  esac
72
60
  case "$2:$3" in
@@ -77,7 +65,7 @@ doflow_bridge_drops_tool_streams() {
77
65
 
78
66
  doflow_bridge_drops_tool_stderr() {
79
67
  case "$1:$2:$3" in
80
- pre-implementation-gate:kiro:PreToolUse|pre-bash-guard:kiro:PreToolUse|mcp-tool-guard:kiro:PreToolUse) return 1 ;;
68
+ pre-implementation-gate:kiro:PreToolUse|mcp-tool-guard:kiro:PreToolUse) return 1 ;;
81
69
  esac
82
70
  doflow_bridge_drops_tool_streams "$1" "$2" "$3"
83
71
  }
@@ -93,7 +81,7 @@ doflow_bridge_hook_line() {
93
81
  [ "${DOFLOW_TELEMETRY:-}" != off ] || return 0
94
82
  case "$DF_TUPLE" in ''|'{"dft":1}') return 0 ;; esac
95
83
  case "$1:$3:$2" in
96
- claude:pre-bash-guard:PreToolUse|codex:pre-bash-guard:PreToolUse|kiro:pre-bash-guard:PreToolUse|antigravity:pre-bash-guard:PreToolUse|pi:pre-bash-guard:tool_call|claude:user-prompt-submit:UserPromptSubmit|codex:user-prompt-submit:UserPromptSubmit) ;;
84
+ claude:user-prompt-submit:UserPromptSubmit|codex:user-prompt-submit:UserPromptSubmit) ;;
97
85
  *) return 0 ;;
98
86
  esac
99
87
  if [ -n "${DOFLOW_CONFIG_DIR:-}" ]; then
@@ -145,7 +133,7 @@ doflow_bridge_hook_line() {
145
133
  }
146
134
 
147
135
  doflow_bridge() {
148
- local harness="${1:-}" event="${2:-}" policy="${3:-}" response_event="" emit="native" payload="" encoded="" policy_path="" captured="" err="" code=0 policy_code=0 record="" emit_record="" reason="" decision_reason="" stdout="" status=0 decision="" line="" spawn_failed=0 encode_failed=0 _df_t0=""
136
+ local harness="${1:-}" event="${2:-}" policy="${3:-}" response_event="" emit="native" payload="" encoded="" policy_path="" captured="" err="" code=0 policy_code=0 record="" emit_record="" reason="" decision_reason="" stdout="" status=0 decision="" advice="" out_of_step=0 line="" spawn_failed=0 encode_failed=0 _df_t0=""
149
137
  DF_TUPLE=""
150
138
  if [ "${BASH_VERSINFO[0]:-0}" -ge 5 ] 2>/dev/null; then _df_t0=${EPOCHREALTIME:-}; fi
151
139
  . "$_DOFLOW_BRIDGE_DIR/respond.sh"
@@ -181,12 +169,24 @@ doflow_bridge() {
181
169
  doflow_bridge_failure "$policy" dependency-missing:jq
182
170
  fi
183
171
  case "$policy" in
184
- pre-implementation-gate|pre-bash-guard|mcp-tool-guard|stop-check|session-context|user-prompt-submit|subagent-audit) ;;
172
+ pre-implementation-gate|mcp-tool-guard|stop-check|session-context|user-prompt-submit|subagent-audit) ;;
185
173
  *) spawn_failed=1 ;;
186
174
  esac
187
175
  policy_path="$_DOFLOW_BRIDGE_DIR/../policies/$policy.sh"
188
- if [ "$spawn_failed" -eq 1 ] || [ ! -f "$policy_path" ]; then
176
+ if [ "$spawn_failed" -eq 1 ] || [ ! -f "$policy_path" ] || [ ! -f "$_DOFLOW_BRIDGE_DIR/../policies/contract.sh" ]; then
189
177
  code=127; err=""; spawn_failed=1
178
+ # Without contract.sh no policy can run. The gate and the tool guard still say so: the one
179
+ # out-of-step line goes out as advice below, and the fault is recorded as bridge-spawn.
180
+ case "$policy" in
181
+ pre-implementation-gate|mcp-tool-guard)
182
+ if [ -f "$policy_path" ] && [ ! -f "$_DOFLOW_BRIDGE_DIR/../policies/contract.sh" ]; then
183
+ out_of_step="${DOFLOW_AGENT:-$harness}"
184
+ if [[ "$out_of_step" =~ ^[a-z]+$ ]]; then err="[$policy] DoFlow hooks out of step; run: doflow update -t $out_of_step"$'\n'
185
+ else err="[$policy] DoFlow hooks out of step; run: doflow update"$'\n'; fi
186
+ out_of_step=1
187
+ fi
188
+ ;;
189
+ esac
190
190
  else
191
191
  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
192
192
  captured="$( {
@@ -248,7 +248,9 @@ doflow_bridge() {
248
248
  status=0
249
249
  if [ "$DF_BRIDGE_OUTCOME" = error ]; then
250
250
  case "$policy:$harness:$response_event" in
251
- session-context:*:*|user-prompt-submit:*:*|subagent-audit:*:*|*:kiro:PreToolUse|*:claude:Stop) status="$code" ;;
251
+ session-context:*:*|user-prompt-submit:*:*|subagent-audit:*:*) status="$code" ;;
252
+ # The blocking-set policies are advise-only (D1): a proceed effect must not let a raw exit 2 block.
253
+ *:kiro:PreToolUse|*:claude:Stop) case "$policy" in pre-implementation-gate|mcp-tool-guard|stop-check) status=0 ;; *) status="$code" ;; esac ;;
252
254
  esac
253
255
  fi
254
256
  fi
@@ -258,14 +260,22 @@ doflow_bridge() {
258
260
  local render_status=$?
259
261
  return "$render_status"
260
262
  fi
263
+ # D1: an advisory (or out-of-step) line from a blocking-set policy is shown where the harness shows it.
264
+ advice=""
265
+ if [ "$DF_BRIDGE_EFFECT" = proceed ] && { [ "$DF_BRIDGE_OUTCOME" = advisory ] || [ "$DF_BRIDGE_OUTCOME" = unsupported ] || [ "$out_of_step" = 1 ]; } && [ -n "$err" ]; then
266
+ case "$policy:$harness:$response_event" in
267
+ pre-implementation-gate:claude:PreToolUse|pre-implementation-gate:codex:PreToolUse|mcp-tool-guard:claude:PreToolUse|mcp-tool-guard:codex:PreToolUse|stop-check:claude:Stop|stop-check:codex:Stop|stop-check:codex:stop)
268
+ advice="${err%$'\n'}"; advice="${advice//$'\n'/ }" ;;
269
+ esac
270
+ fi
261
271
  case "$policy:$DF_BRIDGE_OUTCOME:$harness:$response_event:$DF_BRIDGE_CODE" in
262
272
  session-context:error:*:*:*|user-prompt-submit:error:*:*:*|subagent-audit:error:*:*:*|session-context:unsupported:*:*:*|user-prompt-submit:unsupported:*:*:*|subagent-audit:unsupported:*:*:*) ;;
263
273
  *:error:codex:Stop:exit-*|*:error:codex:Stop:signal-*|*:error:copilot:Stop:exit-*|*:error:copilot: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-*) ;;
264
274
  *)
265
- if ! doflow_bridge_drops_tool_stderr "$policy" "$harness" "$response_event" && [ -n "$err" ]; then printf '%s' "$err" >&2; fi
275
+ if [ -z "$advice" ] && ! doflow_bridge_drops_tool_stderr "$policy" "$harness" "$response_event" && [ -n "$err" ]; then printf '%s' "$err" >&2; fi
266
276
  ;;
267
277
  esac
268
- doflow_render_response "$harness" "$event" "$DF_BRIDGE_OUTCOME" "$reason" "$status"
278
+ doflow_render_response "$harness" "$event" "$DF_BRIDGE_OUTCOME" "$reason" "$status" "$advice"
269
279
  local render_status=$?
270
280
  return "$render_status"
271
281
  }
@@ -103,7 +103,7 @@ def dft_tuple($native; $d):
103
103
  if . == {dft:1} then . else
104
104
  (if $ARGS.named.mode == "observe" then . + {duration_ms:null, tool_duration_ms:($native.duration_ms |
105
105
  if type == "number" and . >= 0 and . <= 9007199254740991 and . == floor then . else null end)} else . + {tool_duration_ms:null} end) |
106
- if ($policy == "pre-bash-guard" or $ARGS.named.mode == "observe") and $d.operation == "command"
106
+ if $ARGS.named.mode == "observe" and $d.operation == "command"
107
107
  then . + {test_class:classify($d.command)} else . + {test_class:null} end
108
108
  end) catch {dft:1}] | if length == 1 then .[0] else {dft:1} end;
109
109
  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
@@ -61,6 +61,12 @@ doflow_render_response() {
61
61
  *) printf '%s\n' "$reason" >&2; return 2 ;;
62
62
  esac
63
63
  fi
64
+ if [ -n "${6:-}" ]; then
65
+ case "$harness:$event" in
66
+ claude:PreToolUse|codex:PreToolUse) emit_pretooluse_advice "$6" || return $?; return 0 ;;
67
+ claude:Stop|codex:Stop|codex:stop) emit_stop_advice "$6" || return $?; return 0 ;;
68
+ esac
69
+ fi
64
70
  case "$harness:$event" in
65
71
  codex:Stop|codex:stop) printf '%s\n' '{}' ;;
66
72
  antigravity:PreToolUse) printf '%s\n' '{"decision":"allow"}' ;;
@@ -6,7 +6,7 @@
6
6
  # example its pattern file is missing). It exists only to be sourced, in a subshell with all output
7
7
  # discarded, from inside a branch where the policy already fails open:
8
8
  #
9
- # ( . "<policy-dir>/capture-failure.sh"; doflow_capture_failure pre-bash-guard patterns-missing ) >/dev/null 2>&1 || true
9
+ # ( . "<policy-dir>/capture-failure.sh"; doflow_capture_failure mcp-tool-guard contract-missing ) >/dev/null 2>&1 || true
10
10
  #
11
11
  # The subshell keeps anything this file does away from the policy, which runs with `set -uo pipefail`,
12
12
  # does not source lib.sh and would change exit codes under `set -e`. So this file:
@@ -21,6 +21,14 @@ doflow_compact_text() {
21
21
  "$preserve" "${CWD:-unknown}")
22
22
  fi
23
23
 
24
+ # Malformed UTF-8 (a commit subject or path can carry any byte) becomes U+FFFD here, before the cap, so the
25
+ # Claude (raw stdout) and Codex (jq --arg) outputs decode to the same text and the cap counts the bytes sent.
26
+ # jq is a hard requirement of both hooks; without it the text is left as it was (FU-535bfv).
27
+ if command -v jq >/dev/null 2>&1; then
28
+ local clean
29
+ if clean=$(printf '%s' "$text" | jq -Rsj '.' 2>/dev/null); then text="$clean"; fi
30
+ fi
31
+
24
32
  # The cap is 490 BYTES in every locale: LC_ALL=C makes the substring and length below count bytes,
25
33
  # where a UTF-8 locale would count characters. A cut can then end inside a multi-byte character, so
26
34
  # a trailing partial UTF-8 sequence is dropped (FU-tsq77m).
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  case "${OSTYPE:-}" in msys*|cygwin*|win32*) if command -v jq >/dev/null 2>&1; then jq() { command jq -b "$@"; }; fi ;; esac # native jq.exe ends lines with CRLF unless -b
3
- # deny-json.sh — shared PreToolUse deny-JSON emitters for front-door dispatchers.
3
+ # deny-json.sh — shared PreToolUse deny-JSON and advice-JSON emitters for front-door dispatchers.
4
4
  #
5
5
  # Deliberately carries no `set` directive (unlike lib.sh, which sets -euo
6
6
  # pipefail on source): a front door needs `set -e` OFF while it captures a
@@ -53,3 +53,25 @@ emit_pretooluse_deny_nested() {
53
53
  printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":%s}}\n' "$(_doflow_json_reason "$reason")"
54
54
  fi
55
55
  }
56
+
57
+ # Advice emitters (D1, advise-only hooks). An advisory outcome never changes a decision; these put its
58
+ # one line where a harness shows it. Claude/Codex PreToolUse: additionalContext reaches the model, and
59
+ # carries no permissionDecision, so the user's own permission prompt still applies. Claude/Codex Stop:
60
+ # systemMessage is shown to the user and, unlike additionalContext, does not continue the turn.
61
+ emit_pretooluse_advice() {
62
+ local line="$1"
63
+ if command -v jq >/dev/null 2>&1; then
64
+ jq -n --arg line "$line" '{hookSpecificOutput: {hookEventName: "PreToolUse", additionalContext: $line}}'
65
+ else
66
+ printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":%s}}\n' "$(_doflow_json_reason "$line")"
67
+ fi
68
+ }
69
+
70
+ emit_stop_advice() {
71
+ local line="$1"
72
+ if command -v jq >/dev/null 2>&1; then
73
+ jq -n --arg line "$line" '{systemMessage: $line}'
74
+ else
75
+ printf '{"systemMessage":%s}\n' "$(_doflow_json_reason "$line")"
76
+ fi
77
+ }
@@ -1,6 +1,6 @@
1
1
  # mcp-policy.conf — MCP tool deny patterns for mcp-tool-guard.sh
2
2
  #
3
- # Format (TAB-separated), identical convention to blocked-patterns.conf:
3
+ # Format (TAB-separated):
4
4
  # regex<TAB>reason shown to the harness
5
5
  #
6
6
  # Rules: