@kontourai/flow-agents 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/CODEOWNERS +8 -0
- package/.github/workflows/ci.yml +32 -0
- package/AGENTS.md +4 -4
- package/CHANGELOG.md +39 -0
- package/CONTEXT.md +14 -0
- package/README.md +9 -3
- package/agents/dev.json +1 -1
- package/agents/tool-code-reviewer.json +1 -1
- package/agents/tool-planner.json +3 -3
- package/agents/tool-verifier.json +3 -3
- package/build/src/cli/console-learning-projection.js +3 -2
- package/build/src/cli/init.js +104 -21
- package/build/src/cli/kit.js +37 -6
- package/build/src/cli/pull-work-provider.js +1 -1
- package/build/src/cli/usage-feedback.js +3 -3
- package/build/src/cli/validate-hook-influence.js +1 -0
- package/build/src/cli/validate-workflow-artifacts.js +22 -6
- package/build/src/cli/workflow-artifact-cleanup-audit.js +36 -2
- package/build/src/cli/workflow-sidecar.d.ts +14 -7
- package/build/src/cli/workflow-sidecar.js +792 -69
- package/build/src/flow-kit/validate.d.ts +23 -0
- package/build/src/flow-kit/validate.js +67 -1
- package/build/src/index.d.ts +1 -1
- package/build/src/index.js +1 -1
- package/build/src/lib/flow-resolver.d.ts +6 -4
- package/build/src/lib/flow-resolver.js +125 -58
- package/build/src/lib/local-artifact-root.d.ts +14 -2
- package/build/src/lib/local-artifact-root.js +22 -5
- package/build/src/lib/workflow-learning-projection.js +2 -2
- package/build/src/runtime-adapters.d.ts +12 -0
- package/build/src/runtime-adapters.js +56 -16
- package/build/src/tools/build-universal-bundles.js +49 -16
- package/build/src/tools/generate-context-map.js +10 -8
- package/build/src/tools/validate-source-tree.js +23 -1
- package/console.telemetry.json +22 -0
- package/context/contracts/artifact-contract.md +38 -7
- package/context/contracts/builder-kit-workflow-state-contract.md +3 -2
- package/context/contracts/decision-registry-contract.md +263 -0
- package/context/contracts/delivery-contract.md +1 -1
- package/context/contracts/planning-contract.md +2 -1
- package/context/contracts/review-contract.md +5 -3
- package/context/contracts/verification-contract.md +9 -1
- package/context/contracts/work-item-contract.md +1 -1
- package/context/deferred/parallelization.md +1 -1
- package/context/gate-awareness.md +4 -4
- package/context/scripts/git-status.sh +2 -2
- package/context/scripts/hooks/config-protection.js +502 -2
- package/context/scripts/hooks/lib/config-protection-remedies.js +69 -0
- package/context/scripts/hooks/run-hook.js +14 -1
- package/context/scripts/hooks/stop-goal-fit.js +1140 -99
- package/context/scripts/hooks/workflow-steering.js +212 -6
- package/docs/adr/0007-flow-skill-kit-tool-boundary.md +2 -0
- package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +35 -0
- package/docs/adr/0019-kit-dependency-ownership.md +62 -0
- package/docs/adr/0020-trust-reconcile-manifest-and-claim-classification.md +290 -0
- package/docs/adr/0021-assignment-leases-and-stale-claim-takeover.md +221 -0
- package/docs/adr/0022-fail-closed-delivery-reconciliation-with-governed-exemptions.md +283 -0
- package/docs/agent-system-guidebook.md +3 -3
- package/docs/agent-usage-feedback-loop.md +6 -6
- package/docs/context-map.md +9 -7
- package/docs/decisions/decision-records.md +57 -0
- package/docs/decisions/index.md +14 -0
- package/docs/decisions/promotion-gate.md +52 -0
- package/docs/developer-architecture.md +1 -1
- package/docs/fixture-ownership.md +3 -0
- package/docs/flow-kit-repository-contract.md +5 -5
- package/docs/getting-started.md +12 -2
- package/docs/integrations/harness-install.md +2 -1
- package/docs/kit-authoring-guide.md +64 -5
- package/docs/knowledge-kit.md +5 -1
- package/docs/learnings/2026-07-improvement-program.md +203 -0
- package/docs/migrations.md +6 -5
- package/docs/north-star.md +2 -2
- package/docs/operating-layers.md +4 -3
- package/docs/repository-structure.md +5 -5
- package/docs/skills-map.md +8 -8
- package/docs/spec/runtime-hook-surface.md +19 -15
- package/docs/spikes/graph-provider-2026-07.md +155 -0
- package/docs/standards-register.md +7 -7
- package/docs/trust-anchor-adoption.md +105 -4
- package/docs/work-item-adapters.md +1 -1
- package/docs/workflow-artifact-lifecycle.md +72 -9
- package/docs/workflow-eval-strategy.md +3 -3
- package/docs/workflow-shared-contracts.md +1 -1
- package/docs/workflow-usage-guide.md +254 -21
- package/evals/acceptance/DEMO-false-completion.md +20 -11
- package/evals/acceptance/prove-capture-teeth-declared.sh +15 -15
- package/evals/acceptance/prove-capture-teeth.sh +11 -11
- package/evals/acceptance/test_kiro_harness.sh +1 -1
- package/evals/ci/antigaming-suite.sh +5 -0
- package/evals/ci/run-baseline.sh +65 -1
- package/evals/fixtures/flow-kit-repository/invalid-bad-dependency/flows/review.flow.json +26 -0
- package/evals/fixtures/flow-kit-repository/invalid-bad-dependency/kit.json +13 -0
- package/evals/fixtures/flow-kit-repository/valid-with-dependency/flows/review.flow.json +26 -0
- package/evals/fixtures/flow-kit-repository/valid-with-dependency/kit.json +13 -0
- package/evals/fixtures/hook-influence/cases.json +32 -0
- package/evals/fixtures/pull-work-provider/github-issues.json +6 -6
- package/evals/fixtures/trust-reconcile-exploits/fabricated-attestation.json +59 -0
- package/evals/fixtures/trust-reconcile-exploits/no-label-bypass.json +36 -0
- package/evals/fixtures/trust-reconcile-exploits/skip-assumed-bypass.json +59 -0
- package/evals/fixtures/trust-reconcile-exploits/status-misassertion.json +48 -0
- package/evals/fixtures/trust-reconcile-exploits/waived-command-check.json +44 -0
- package/evals/fixtures/trust-reconcile-mixed-bundle/mixed-bundle.json +159 -0
- package/evals/fixtures/trust-reconcile-ws3/ws3-bundle.json +920 -0
- package/evals/integration/test_actor_identity.sh +254 -0
- package/evals/integration/test_builder_step_producers.sh +33 -23
- package/evals/integration/test_bundle_install.sh +22 -16
- package/evals/integration/test_bundle_lifecycle.sh +11 -11
- package/evals/integration/test_captured_fail_reconciliation.sh +50 -50
- package/evals/integration/test_checkpoint_signing.sh +43 -0
- package/evals/integration/test_claim_lookup.sh +4 -4
- package/evals/integration/test_codex_hook_resolution.sh +114 -0
- package/evals/integration/test_command_log_concurrency.sh +8 -8
- package/evals/integration/test_command_log_fork_classification.sh +3 -3
- package/evals/integration/test_command_log_integrity.sh +16 -16
- package/evals/integration/test_console_learning_projection.sh +1 -1
- package/evals/integration/test_dual_emit_flow_step.sh +145 -0
- package/evals/integration/test_enforcer_expects_driven.sh +12 -12
- package/evals/integration/test_evidence_capture_hook.sh +14 -13
- package/evals/integration/test_fixture_retirement_audit.sh +2 -2
- package/evals/integration/test_flow_agents_statusline.sh +2 -2
- package/evals/integration/test_flow_kit_install_git.sh +52 -0
- package/evals/integration/test_flow_kit_repository.sh +2 -0
- package/evals/integration/test_flowdef_session_activation.sh +6 -6
- package/evals/integration/test_flowdef_union_floor_regression.sh +429 -0
- package/evals/integration/test_gate_bypass_chain.sh +52 -40
- package/evals/integration/test_gate_lockdown.sh +97 -73
- package/evals/integration/test_gate_review_inquiry_records.sh +3 -3
- package/evals/integration/test_goal_fit_escape_hatch.sh +10 -10
- package/evals/integration/test_goal_fit_ghost_session.sh +104 -0
- package/evals/integration/test_goal_fit_hook.sh +32 -32
- package/evals/integration/test_goal_fit_rederive.sh +9 -9
- package/evals/integration/test_hook_category_behaviors.sh +103 -0
- package/evals/integration/test_hook_influence_cases.sh +1 -0
- package/evals/integration/test_install_merge.sh +262 -1
- package/evals/integration/test_kit_identity_trust.sh +6 -6
- package/evals/integration/test_liveness_conflict_injection.sh +587 -0
- package/evals/integration/test_liveness_heartbeat.sh +677 -0
- package/evals/integration/test_liveness_verdict.sh +394 -0
- package/evals/integration/test_local_flow_kit_install.sh +15 -0
- package/evals/integration/test_phase_map_and_gate_claim.sh +63 -11
- package/evals/integration/test_promote_gate.sh +178 -0
- package/evals/integration/test_publish_delivery.sh +2 -2
- package/evals/integration/test_pull_work_liveness_preflight.sh +386 -0
- package/evals/integration/test_pull_work_provider.sh +2 -2
- package/evals/integration/test_reconcile_soundness.sh +4 -4
- package/evals/integration/test_resolvefirststep_security.sh +22 -0
- package/evals/integration/test_runtime_adapter_activation.sh +87 -18
- package/evals/integration/test_session_resume_roundtrip.sh +147 -0
- package/evals/integration/test_sidecar_field_preservation.sh +249 -0
- package/evals/integration/test_trust_reconcile.sh +2 -2
- package/evals/integration/test_trust_reconcile_manifest.sh +79 -0
- package/evals/integration/test_trust_reconcile_mixed_bundle.sh +96 -0
- package/evals/integration/test_trust_reconcile_negatives.sh +151 -0
- package/evals/integration/test_usage_feedback_global.sh +5 -5
- package/evals/integration/test_validate_artifacts_portability.sh +60 -0
- package/evals/integration/test_verify_cli.sh +2 -2
- package/evals/integration/test_veritas_governance_kit.sh +117 -0
- package/evals/integration/test_workflow_artifact_cleanup_audit.sh +26 -1
- package/evals/integration/test_workflow_artifacts.sh +33 -0
- package/evals/integration/test_workflow_sidecar_writer.sh +723 -74
- package/evals/integration/test_workflow_steering_hook.sh +175 -5
- package/evals/lib/codex-provider.sh +1 -1
- package/evals/lib/node.sh +19 -1
- package/evals/run.sh +22 -0
- package/evals/static/test_decisions.sh +150 -0
- package/evals/static/test_flowdef_codeowners_coverage.sh +56 -0
- package/evals/static/test_package.sh +19 -10
- package/evals/static/test_universal_bundles.sh +48 -3
- package/evals/static/test_workflow_skills.sh +41 -8
- package/kits/builder/flows/build.flow.json +3 -74
- package/kits/builder/flows/publish-learn.flow.json +90 -0
- package/kits/builder/kit.json +11 -0
- package/kits/builder/skills/builder-shape/SKILL.md +1 -1
- package/kits/builder/skills/deliver/SKILL.md +13 -11
- package/kits/builder/skills/design-probe/SKILL.md +4 -4
- package/kits/builder/skills/evidence-gate/SKILL.md +26 -1
- package/kits/builder/skills/execute-plan/SKILL.md +2 -2
- package/kits/builder/skills/fix-bug/SKILL.md +2 -0
- package/kits/builder/skills/gate-review/SKILL.md +6 -6
- package/kits/builder/skills/idea-to-backlog/SKILL.md +1 -1
- package/kits/builder/skills/learning-review/SKILL.md +7 -7
- package/kits/builder/skills/pickup-probe/SKILL.md +5 -4
- package/kits/builder/skills/plan-work/SKILL.md +27 -5
- package/kits/builder/skills/pull-work/SKILL.md +80 -5
- package/kits/builder/skills/release-readiness/SKILL.md +4 -4
- package/kits/builder/skills/review-work/SKILL.md +24 -1
- package/kits/builder/skills/tdd-workflow/SKILL.md +2 -0
- package/kits/builder/skills/verify-work/SKILL.md +8 -1
- package/kits/catalog.json +6 -0
- package/kits/veritas-governance/adapter/readiness-to-trust-bundle.mjs +178 -0
- package/kits/veritas-governance/docs/README.md +75 -0
- package/kits/veritas-governance/fixtures/readiness/not-ready.readiness-report.json +4645 -0
- package/kits/veritas-governance/fixtures/readiness/ready.readiness-report.json +4403 -0
- package/kits/veritas-governance/flows/readiness-check.flow.json +35 -0
- package/kits/veritas-governance/kit.json +16 -0
- package/package.json +8 -4
- package/packaging/README.md +2 -2
- package/packaging/conformance/fixtures/config-protection--allow-read-state-json.json +20 -0
- package/packaging/conformance/fixtures/config-protection--block-write-state-json.json +20 -0
- package/packaging/conformance/fixtures/stop-goal-fit--block-bundle-disputed-claim.json +1 -1
- package/packaging/conformance/fixtures/stop-goal-fit--block-capture-contradicts-claimed-pass.json +1 -1
- package/packaging/manifest.json +9 -10
- package/schemas/decision-record.schema.json +109 -0
- package/schemas/workflow-evidence.schema.json +1 -1
- package/schemas/workflow-state.schema.json +5 -0
- package/scripts/README.md +8 -3
- package/scripts/check-content-boundary.cjs +1 -1
- package/scripts/check-decisions.cjs +356 -0
- package/scripts/ci/derive-claim-status.mjs +74 -0
- package/scripts/ci/trust-reconcile.js +436 -79
- package/scripts/git-status.sh +2 -2
- package/scripts/hooks/claude-telemetry-hook.js +37 -3
- package/scripts/hooks/codex-telemetry-hook.js +36 -2
- package/scripts/hooks/config-protection.js +102 -30
- package/scripts/hooks/evidence-capture.js +3 -3
- package/scripts/hooks/lib/actor-identity.js +368 -0
- package/scripts/hooks/lib/config-protection-remedies.js +69 -0
- package/scripts/hooks/lib/liveness-heartbeat.js +361 -0
- package/scripts/hooks/lib/liveness-policy.js +127 -0
- package/scripts/hooks/lib/liveness-read.js +105 -12
- package/scripts/hooks/lib/liveness-write.js +46 -0
- package/scripts/hooks/lib/local-artifact-paths.js +12 -6
- package/scripts/hooks/opencode-telemetry-hook.js +27 -1
- package/scripts/hooks/pi-telemetry-hook.js +27 -1
- package/scripts/hooks/stop-goal-fit.js +157 -26
- package/scripts/hooks/workflow-steering.js +77 -3
- package/scripts/install-codex-home.sh +138 -38
- package/scripts/statusline/flow-agents-statusline.js +2 -2
- package/src/cli/console-learning-projection.ts +3 -2
- package/src/cli/init.ts +101 -21
- package/src/cli/kit.ts +37 -6
- package/src/cli/public-api.test.mjs +58 -6
- package/src/cli/pull-work-provider.ts +1 -1
- package/src/cli/trust-bundle-policy-order.test.mjs +87 -0
- package/src/cli/usage-feedback.ts +3 -3
- package/src/cli/validate-hook-influence.ts +1 -0
- package/src/cli/validate-workflow-artifacts.ts +22 -6
- package/src/cli/veritas-readiness-adapter.test.mjs +267 -0
- package/src/cli/workflow-artifact-cleanup-audit.ts +35 -2
- package/src/cli/workflow-sidecar.ts +795 -71
- package/src/flow-kit/validate.ts +74 -1
- package/src/index.ts +6 -2
- package/src/lib/flow-resolver.ts +123 -55
- package/src/lib/local-artifact-root.ts +24 -5
- package/src/lib/workflow-learning-projection.ts +2 -2
- package/src/runtime-adapters.ts +61 -15
- package/src/tools/build-universal-bundles.ts +49 -16
- package/src/tools/generate-context-map.ts +10 -8
- package/src/tools/validate-source-tree.ts +23 -1
package/scripts/git-status.sh
CHANGED
|
@@ -32,11 +32,11 @@ if [ "$WORKTREE_COUNT" -gt 1 ]; then
|
|
|
32
32
|
echo "=== Active Worktrees ($WORKTREE_COUNT) ==="
|
|
33
33
|
git worktree list
|
|
34
34
|
echo ""
|
|
35
|
-
echo "⚠️ Multiple worktrees active — check .flow-agents/ for in-progress tasks before modifying shared files"
|
|
35
|
+
echo "⚠️ Multiple worktrees active — check .kontourai/flow-agents/ for in-progress tasks before modifying shared files"
|
|
36
36
|
fi
|
|
37
37
|
|
|
38
38
|
# List existing TODOs for awareness
|
|
39
|
-
TODO_DIR=".flow-agents"
|
|
39
|
+
TODO_DIR=".kontourai/flow-agents"
|
|
40
40
|
if [ -d "$TODO_DIR" ]; then
|
|
41
41
|
TODO_COUNT=$(find "$TODO_DIR" -name "*.md" 2>/dev/null | wc -l | tr -d ' ')
|
|
42
42
|
if [ "$TODO_COUNT" -gt 0 ]; then
|
|
@@ -54,7 +54,7 @@ function canonicalEvent(cliEvent, payload) {
|
|
|
54
54
|
return mapping[event] || event;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
function claudeSuccessOutput(event) {
|
|
57
|
+
function claudeSuccessOutput(event, conflict) {
|
|
58
58
|
if (event === 'SessionStart') {
|
|
59
59
|
return {
|
|
60
60
|
continue: true,
|
|
@@ -71,6 +71,26 @@ function claudeSuccessOutput(event) {
|
|
|
71
71
|
if (event === 'Stop' || event === 'SubagentStop' || event === 'SessionEnd') {
|
|
72
72
|
return { continue: true, suppressOutput: true };
|
|
73
73
|
}
|
|
74
|
+
// Mid-turn conflict injection (issue #320, AC4): fold a detected liveness conflict into the
|
|
75
|
+
// real hookSpecificOutput.additionalContext channel on PostToolUse/PostToolUseFailure,
|
|
76
|
+
// matching the precedent claude-hook-adapter.js:70-79 already establishes for policy hooks.
|
|
77
|
+
// Guarded on a well-formed `conflict` shape so a malformed value degrades to the unchanged
|
|
78
|
+
// fixed no-conflict output below, never a thrown error (AC8, fail-open).
|
|
79
|
+
if (
|
|
80
|
+
(event === 'PostToolUse' || event === 'PostToolUseFailure') &&
|
|
81
|
+
conflict &&
|
|
82
|
+
typeof conflict.actor === 'string' &&
|
|
83
|
+
typeof conflict.lastAt === 'string'
|
|
84
|
+
) {
|
|
85
|
+
return {
|
|
86
|
+
continue: true,
|
|
87
|
+
suppressOutput: false,
|
|
88
|
+
hookSpecificOutput: {
|
|
89
|
+
hookEventName: 'PostToolUse',
|
|
90
|
+
additionalContext: `[LIVENESS CONFLICT] actor "${conflict.actor}" claimed this subject at "${conflict.lastAt}" — run \`liveness verdict\` and coordinate.`,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
74
94
|
return { continue: true, suppressOutput: true };
|
|
75
95
|
}
|
|
76
96
|
|
|
@@ -79,9 +99,23 @@ async function main() {
|
|
|
79
99
|
const raw = await readStdinRaw();
|
|
80
100
|
const payload = parseJson(raw);
|
|
81
101
|
const hookEvent = payload.hook_event_name || eventArg;
|
|
102
|
+
const canonical = canonicalEvent(eventArg, payload);
|
|
82
103
|
const telemetryScript = path.resolve(__dirname, '..', 'telemetry', 'telemetry.sh');
|
|
83
104
|
|
|
84
|
-
|
|
105
|
+
let conflict;
|
|
106
|
+
if (canonical === 'postToolUse') {
|
|
107
|
+
try {
|
|
108
|
+
const heartbeatResult = require('./lib/liveness-heartbeat').maybeEmitHeartbeat({
|
|
109
|
+
cwd: process.cwd(),
|
|
110
|
+
env: process.env,
|
|
111
|
+
});
|
|
112
|
+
conflict = heartbeatResult && heartbeatResult.conflict;
|
|
113
|
+
} catch (err) {
|
|
114
|
+
process.stderr.write(`[ClaudeTelemetryHook] liveness heartbeat error: ${err.message}\n`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const result = spawnSync('bash', [telemetryScript, canonical, agentName], {
|
|
85
119
|
input: raw,
|
|
86
120
|
encoding: 'utf8',
|
|
87
121
|
cwd: process.cwd(),
|
|
@@ -106,7 +140,7 @@ async function main() {
|
|
|
106
140
|
process.stderr.write(`[ClaudeTelemetryHook] failed open: ${detail}\n`);
|
|
107
141
|
}
|
|
108
142
|
|
|
109
|
-
process.stdout.write(`${JSON.stringify(claudeSuccessOutput(hookEvent))}\n`);
|
|
143
|
+
process.stdout.write(`${JSON.stringify(claudeSuccessOutput(hookEvent, conflict))}\n`);
|
|
110
144
|
}
|
|
111
145
|
|
|
112
146
|
main().catch(err => {
|
|
@@ -38,7 +38,7 @@ function hookEventName(raw) {
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function codexSuccessOutput(event) {
|
|
41
|
+
function codexSuccessOutput(event, conflict) {
|
|
42
42
|
if (event === 'SessionStart') {
|
|
43
43
|
return {
|
|
44
44
|
continue: true,
|
|
@@ -48,6 +48,27 @@ function codexSuccessOutput(event) {
|
|
|
48
48
|
},
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
|
+
// Mid-turn conflict injection (issue #320, AC4): fold a detected liveness conflict into the
|
|
52
|
+
// real hookSpecificOutput.additionalContext channel on PostToolUse, matching the precedent
|
|
53
|
+
// codex-hook-adapter.js:69-77 already establishes for policy hooks. Guarded on a well-formed
|
|
54
|
+
// `conflict` shape so a malformed value degrades to the unchanged fixed no-conflict output
|
|
55
|
+
// below, never a thrown error (AC8, fail-open). Subject to the "Codex live hook influence"
|
|
56
|
+
// caveat (docs/spec/runtime-hook-surface.md §2.1): the wrapper still emits this; effectiveness
|
|
57
|
+
// is a separate, already-tracked residual.
|
|
58
|
+
if (
|
|
59
|
+
event === 'PostToolUse' &&
|
|
60
|
+
conflict &&
|
|
61
|
+
typeof conflict.actor === 'string' &&
|
|
62
|
+
typeof conflict.lastAt === 'string'
|
|
63
|
+
) {
|
|
64
|
+
return {
|
|
65
|
+
continue: true,
|
|
66
|
+
hookSpecificOutput: {
|
|
67
|
+
hookEventName: 'PostToolUse',
|
|
68
|
+
additionalContext: `[LIVENESS CONFLICT] actor "${conflict.actor}" claimed this subject at "${conflict.lastAt}" — run \`liveness verdict\` and coordinate.`,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
51
72
|
if (event === 'UserPromptSubmit' || event === 'Stop') {
|
|
52
73
|
return { continue: true };
|
|
53
74
|
}
|
|
@@ -60,6 +81,19 @@ async function main() {
|
|
|
60
81
|
const event = hookEventName(raw);
|
|
61
82
|
const telemetryScript = path.resolve(__dirname, '..', 'telemetry', 'telemetry.sh');
|
|
62
83
|
|
|
84
|
+
let conflict;
|
|
85
|
+
if (eventType === 'PostToolUse') {
|
|
86
|
+
try {
|
|
87
|
+
const heartbeatResult = require('./lib/liveness-heartbeat').maybeEmitHeartbeat({
|
|
88
|
+
cwd: process.cwd(),
|
|
89
|
+
env: process.env,
|
|
90
|
+
});
|
|
91
|
+
conflict = heartbeatResult && heartbeatResult.conflict;
|
|
92
|
+
} catch (err) {
|
|
93
|
+
process.stderr.write(`[CodexTelemetryHook] liveness heartbeat error: ${err.message}\n`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
63
97
|
const result = spawnSync('bash', [telemetryScript, eventType, agentName], {
|
|
64
98
|
input: raw,
|
|
65
99
|
encoding: 'utf8',
|
|
@@ -85,7 +119,7 @@ async function main() {
|
|
|
85
119
|
process.stderr.write(`[CodexTelemetryHook] failed open: ${detail}\n`);
|
|
86
120
|
}
|
|
87
121
|
|
|
88
|
-
const output = codexSuccessOutput(event);
|
|
122
|
+
const output = codexSuccessOutput(event, conflict);
|
|
89
123
|
if (output) process.stdout.write(`${JSON.stringify(output)}\n`);
|
|
90
124
|
}
|
|
91
125
|
|
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
* export FLOW_AGENTS_GOAL_FIT_MODE=off for the next bash -lc hook invocation.
|
|
16
16
|
* - .claude/settings.json / settings.local.json — where an agent could add an
|
|
17
17
|
* env block or delete the Stop hook entry entirely.
|
|
18
|
-
* - .flow-agents/current.json — where an agent could forge active_flow_id /
|
|
18
|
+
* - .kontourai/flow-agents/current.json — where an agent could forge active_flow_id /
|
|
19
19
|
* active_step_id to route the gate to a fake permissive FlowDefinition.
|
|
20
|
-
* - .flow-agents/<slug>/state.json — where an agent could forge phase=planning
|
|
20
|
+
* - .kontourai/flow-agents/<slug>/state.json — where an agent could forge phase=planning
|
|
21
21
|
* to downgrade from FULL_BLOCK to HARD_BLOCK (R5a: MEDIUM finding fix).
|
|
22
22
|
* SAFE: the CLI writes state.json via writeState → writeJson → fs.writeFileSync.
|
|
23
|
-
* - .flow-agents/<slug>/trust.bundle — where an agent could forge claims.
|
|
23
|
+
* - .kontourai/flow-agents/<slug>/trust.bundle — where an agent could forge claims.
|
|
24
24
|
* SAFE: the CLI writes trust.bundle via writeTrustBundle → writeJson → fs.writeFileSync.
|
|
25
25
|
* (Verified: ensureSession / advanceState / writeCurrent / writeState /
|
|
26
26
|
* writeTrustBundle all use writeJson which calls fs.writeFileSync directly —
|
|
@@ -78,7 +78,7 @@ const PROTECTED_FILES = new Set([
|
|
|
78
78
|
// These files need PATH context (not just basename) to avoid over-matching
|
|
79
79
|
// common file names in unrelated projects.
|
|
80
80
|
//
|
|
81
|
-
// R5a adds
|
|
81
|
+
// R5a adds flow-agents runtime state and trust.bundle paths.
|
|
82
82
|
//
|
|
83
83
|
// checkProtectedPathPattern(filePath) → { name, reason } | null
|
|
84
84
|
// ---------------------------------------------------------------------------
|
|
@@ -86,8 +86,9 @@ const PROTECTED_FILES = new Set([
|
|
|
86
86
|
/**
|
|
87
87
|
* Check whether a Write/Edit tool file path targets a gate kill-switch file
|
|
88
88
|
* that requires path-level matching (.claude/settings.json,
|
|
89
|
-
* .flow-agents/current.json, .flow-agents/<slug>/state.json,
|
|
90
|
-
* .flow-agents/<slug>/trust.bundle
|
|
89
|
+
* .kontourai/flow-agents/current.json, .kontourai/flow-agents/<slug>/state.json,
|
|
90
|
+
* .kontourai/flow-agents/<slug>/trust.bundle, and deprecated runtime-shaped
|
|
91
|
+
* .flow-agents equivalents).
|
|
91
92
|
*
|
|
92
93
|
* Returns { name, reason } when blocked, null when allowed.
|
|
93
94
|
*
|
|
@@ -119,35 +120,45 @@ function checkProtectedPathPattern(filePath) {
|
|
|
119
120
|
};
|
|
120
121
|
}
|
|
121
122
|
|
|
122
|
-
// .flow-agents/current.json — an agent could forge active_flow_id / active_step_id
|
|
123
|
+
// .kontourai/flow-agents/current.json — an agent could forge active_flow_id / active_step_id
|
|
123
124
|
// to route the gate to a permissive or empty-expects FlowDefinition.
|
|
124
125
|
// SAFE: the workflow CLI writes current.json via fs (writeJson → fs.writeFileSync),
|
|
125
126
|
// NOT via the Write/Edit tool — blocking the tool path does not break legit sidecar.
|
|
126
|
-
if (/(?:^|\/)
|
|
127
|
+
if (/(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/current\.json$/.test(norm)) {
|
|
127
128
|
return {
|
|
128
|
-
name: '.flow-agents/current.json',
|
|
129
|
+
name: '.kontourai/flow-agents/current.json',
|
|
129
130
|
reason: 'an agent could forge active_flow_id/active_step_id to route the gate to a permissive FlowDefinition',
|
|
130
131
|
};
|
|
131
132
|
}
|
|
132
133
|
|
|
133
|
-
// .flow-agents
|
|
134
|
+
// .kontourai/flow-agents/.goal-fit-block-streak.json controls soft-block
|
|
135
|
+
// release counting. An agent could force early advisory-gate release by
|
|
136
|
+
// writing a high count.
|
|
137
|
+
if (/(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/\.goal-fit-block-streak\.json$/.test(norm)) {
|
|
138
|
+
return {
|
|
139
|
+
name: '.kontourai/flow-agents/.goal-fit-block-streak.json',
|
|
140
|
+
reason: 'an agent could manipulate goal-fit block streak state to force early soft-block release',
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// .kontourai/flow-agents/<slug>/state.json — an agent could forge phase=planning to
|
|
134
145
|
// downgrade the block regime (FULL_BLOCK → HARD_BLOCK) and weaken gate checks.
|
|
135
146
|
// SAFE: the CLI writes state.json via writeState → writeJson → fs.writeFileSync,
|
|
136
147
|
// NOT via the Write/Edit tool — blocking the tool path does not break legit sidecar.
|
|
137
|
-
if (/(?:^|\/)
|
|
148
|
+
if (/(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/[^/]+\/state\.json$/.test(norm)) {
|
|
138
149
|
return {
|
|
139
|
-
name: '.flow-agents/<slug>/state.json',
|
|
150
|
+
name: '.kontourai/flow-agents/<slug>/state.json',
|
|
140
151
|
reason: 'an agent could forge phase=planning to downgrade the block regime and weaken gate enforcement',
|
|
141
152
|
};
|
|
142
153
|
}
|
|
143
154
|
|
|
144
|
-
// .flow-agents/<slug>/trust.bundle — an agent could forge claims (e.g. status=verified
|
|
155
|
+
// .kontourai/flow-agents/<slug>/trust.bundle — an agent could forge claims (e.g. status=verified
|
|
145
156
|
// or impactLevel=low) to suppress gate blocks or make disputed evidence appear accepted.
|
|
146
157
|
// SAFE: the CLI writes trust.bundle via writeTrustBundle → writeJson → fs.writeFileSync,
|
|
147
158
|
// NOT via the Write/Edit tool — blocking the tool path does not break legit sidecar.
|
|
148
|
-
if (/(?:^|\/)
|
|
159
|
+
if (/(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/[^/]+\/trust\.bundle$/.test(norm)) {
|
|
149
160
|
return {
|
|
150
|
-
name: '.flow-agents/<slug>/trust.bundle',
|
|
161
|
+
name: '.kontourai/flow-agents/<slug>/trust.bundle',
|
|
151
162
|
reason: 'an agent could forge trust claims (verified status, impact level) to bypass gate integrity checks',
|
|
152
163
|
};
|
|
153
164
|
}
|
|
@@ -371,14 +382,14 @@ function checkCommandForBypass(command) {
|
|
|
371
382
|
// When the agent uses a Bash tool (tool_input.command), detect redirects that
|
|
372
383
|
// write to protected paths via shell redirect operators (> >>) or `tee`.
|
|
373
384
|
//
|
|
374
|
-
// R5a extends coverage to
|
|
385
|
+
// R5a extends coverage to flow-agents runtime state and trust.bundle.
|
|
375
386
|
//
|
|
376
387
|
// HONEST — INCOMPLETE COVERAGE: this catches the obvious forms only:
|
|
377
388
|
// - `cmd > ~/.bashrc` ✓ caught (> redirect operator)
|
|
378
389
|
// - `cmd >> .claude/settings.json` ✓ caught (>> redirect operator)
|
|
379
|
-
// - `tee .flow-agents/current.json`
|
|
380
|
-
// - `cmd > .flow-agents/s/state.json`
|
|
381
|
-
// - `tee .flow-agents/s/trust.bundle`
|
|
390
|
+
// - `tee .kontourai/flow-agents/current.json` ✓ caught (tee command)
|
|
391
|
+
// - `cmd > .kontourai/flow-agents/s/state.json` ✓ caught (> redirect operator)
|
|
392
|
+
// - `tee .kontourai/flow-agents/s/trust.bundle` ✓ caught (tee command)
|
|
382
393
|
// - `sed -i ... .bashrc` ✗ NOT caught (in-place edit)
|
|
383
394
|
// - `interpreted-lang -c "open(...,'w')" ✗ NOT caught (see interpreter check)
|
|
384
395
|
// - `/dev/fd` tricks, process substitution ✗ NOT caught
|
|
@@ -391,10 +402,13 @@ function checkCommandForBypass(command) {
|
|
|
391
402
|
/**
|
|
392
403
|
* Regex that matches a normalized (forward-slash) path ending with a protected
|
|
393
404
|
* kill-switch file: shell profiles, .claude/settings*.json,
|
|
394
|
-
* .flow-agents/current.json,
|
|
395
|
-
* .flow-agents
|
|
405
|
+
* .kontourai/flow-agents/current.json,
|
|
406
|
+
* .kontourai/flow-agents/.goal-fit-block-streak.json,
|
|
407
|
+
* .kontourai/flow-agents/<slug>/state.json,
|
|
408
|
+
* .kontourai/flow-agents/<slug>/trust.bundle, and deprecated runtime-shaped
|
|
409
|
+
* .flow-agents equivalents.
|
|
396
410
|
*/
|
|
397
|
-
const REDIRECT_PROTECTED_RE = /(?:^|\/|~\/)(\.bash_profile|\.bashrc|\.profile|\.zprofile|\.zshrc)$|(?:^|\/)\.claude\/settings(?:\.local)?\.json$|(?:^|\/)
|
|
411
|
+
const REDIRECT_PROTECTED_RE = /(?:^|\/|~\/)(\.bash_profile|\.bashrc|\.profile|\.zprofile|\.zshrc)$|(?:^|\/)\.claude\/settings(?:\.local)?\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/current\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/\.goal-fit-block-streak\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/[^/]+\/state\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/[^/]+\/trust\.bundle$|(?:^|\/)delivery\/trust\.bundle$|(?:^|\/)delivery\/trust\.checkpoint\.json$/;
|
|
398
412
|
|
|
399
413
|
/**
|
|
400
414
|
* Return true when a token (an unquoted redirect target or tee argument) matches
|
|
@@ -592,6 +606,61 @@ function checkCopyMoveToProtected(command) {
|
|
|
592
606
|
return null;
|
|
593
607
|
}
|
|
594
608
|
|
|
609
|
+
// ---------------------------------------------------------------------------
|
|
610
|
+
// Sanctioned remedies for blocked writes.
|
|
611
|
+
//
|
|
612
|
+
// AC7: for sidecar/gate kill-switch paths the block message MUST name the
|
|
613
|
+
// sanctioned `npm run workflow:sidecar -- <command>` writer (or a human maintainer
|
|
614
|
+
// for host-owned files) and MUST NEVER advise disabling the config-protection hook.
|
|
615
|
+
// ---------------------------------------------------------------------------
|
|
616
|
+
const READ_ONLY_TOOL_NAMES = new Set(['read', 'glob', 'grep', 'ls', 'notebookread', 'websearch', 'webfetch']);
|
|
617
|
+
|
|
618
|
+
// WS8 (AC15): the remedy table (SHELL_PROFILE_REMEDY, SANCTIONED_REMEDIES, remedyFor,
|
|
619
|
+
// REMEDY_COMMAND_CANDIDATES) was extracted verbatim to ./lib/config-protection-remedies.js
|
|
620
|
+
// so it has an independently testable surface and this file is smaller. remedyForCommand
|
|
621
|
+
// stays here because it depends on this file's tokenizer (splitSegments/tokenize/
|
|
622
|
+
// checkProtectedPathPattern/PROTECTED_FILES). Behavior-preserving — no message text or
|
|
623
|
+
// matching data changed.
|
|
624
|
+
const { SHELL_PROFILE_REMEDY, remedyFor, REMEDY_COMMAND_CANDIDATES } = require('./lib/config-protection-remedies.js');
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Recover the sanctioned remedy for a blocked Bash command.
|
|
628
|
+
*
|
|
629
|
+
* Pass 1: exact path-pattern match on individual tokens -- handles shell
|
|
630
|
+
* redirects / tee / cp where the protected path is its own clean token
|
|
631
|
+
* (a bare redirect target, or a cp/mv destination argument).
|
|
632
|
+
*
|
|
633
|
+
* Pass 2: substring match against the raw command text -- handles
|
|
634
|
+
* interpreter-write commands where the protected path sits inside a quoted
|
|
635
|
+
* string followed by punctuation, so no token ends at the basename and
|
|
636
|
+
* Pass 1's dollar-anchored regex never matches. Any blocked command that
|
|
637
|
+
* references a path with a SANCTIONED_REMEDIES entry gets that entry's
|
|
638
|
+
* remedy instead of falling through to the generic (and often factually
|
|
639
|
+
* wrong) shell-profile advice.
|
|
640
|
+
*
|
|
641
|
+
* Never returns advice to disable the hook.
|
|
642
|
+
*/
|
|
643
|
+
function remedyForCommand(command) {
|
|
644
|
+
if (typeof command !== 'string') return SHELL_PROFILE_REMEDY;
|
|
645
|
+
|
|
646
|
+
const segments = splitSegments(command);
|
|
647
|
+
for (const seg of segments) {
|
|
648
|
+
for (const tok of tokenize(seg)) {
|
|
649
|
+
const match = checkProtectedPathPattern(tok);
|
|
650
|
+
if (match) return remedyFor(match.name);
|
|
651
|
+
if (PROTECTED_FILES.has(path.basename(tok))) return SHELL_PROFILE_REMEDY;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
for (const candidate of REMEDY_COMMAND_CANDIDATES) {
|
|
656
|
+
if (candidate.needles.some((needle) => command.includes(needle))) {
|
|
657
|
+
return remedyFor(candidate.name);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return SHELL_PROFILE_REMEDY;
|
|
662
|
+
}
|
|
663
|
+
|
|
595
664
|
function run(inputOrRaw, options = {}) {
|
|
596
665
|
if (options.truncated) {
|
|
597
666
|
return {
|
|
@@ -604,8 +673,11 @@ function run(inputOrRaw, options = {}) {
|
|
|
604
673
|
try {
|
|
605
674
|
input = typeof inputOrRaw === 'string' ? JSON.parse(inputOrRaw) : inputOrRaw;
|
|
606
675
|
} catch { return { exitCode: 0 }; }
|
|
676
|
+
const toolName = String(input?.tool_name || '').trim().toLowerCase();
|
|
607
677
|
const filePath = input?.tool_input?.path || input?.tool_input?.file_path || '';
|
|
608
|
-
|
|
678
|
+
// Read-only tools never mutate a file, so path-based protection must not block them.
|
|
679
|
+
// (Bash is NOT read-only and stays fully covered by the command-based checks below.)
|
|
680
|
+
if (filePath && !READ_ONLY_TOOL_NAMES.has(toolName)) {
|
|
609
681
|
const basename = path.basename(filePath);
|
|
610
682
|
if (PROTECTED_FILES.has(basename)) {
|
|
611
683
|
return {
|
|
@@ -623,7 +695,7 @@ function run(inputOrRaw, options = {}) {
|
|
|
623
695
|
exitCode: 2,
|
|
624
696
|
stderr: `BLOCKED: Writing to ${pathMatch.name} is not allowed. ` +
|
|
625
697
|
`This file is protected because ${pathMatch.reason}. ` +
|
|
626
|
-
|
|
698
|
+
remedyFor(pathMatch.name),
|
|
627
699
|
};
|
|
628
700
|
}
|
|
629
701
|
}
|
|
@@ -648,8 +720,8 @@ function run(inputOrRaw, options = {}) {
|
|
|
648
720
|
exitCode: 2,
|
|
649
721
|
stderr: `BLOCKED: Detected ${redirect} targeting a protected gate kill-switch file. ` +
|
|
650
722
|
'Writing to shell profiles or Claude/flow-agents config files via shell redirect could ' +
|
|
651
|
-
'disable or tamper with the gate.
|
|
652
|
-
|
|
723
|
+
'disable or tamper with the gate. Do not disable this hook. ' +
|
|
724
|
+
remedyForCommand(command) + ' ' +
|
|
653
725
|
'NOTE: This check has incomplete coverage (sed -i and similar forms are not caught).',
|
|
654
726
|
};
|
|
655
727
|
}
|
|
@@ -663,8 +735,8 @@ function run(inputOrRaw, options = {}) {
|
|
|
663
735
|
exitCode: 2,
|
|
664
736
|
stderr: `BLOCKED: Detected ${interpWrite} in a Bash command. ` +
|
|
665
737
|
'Interpreter invocations (node -e, py3 -c, sed -i, perl -e) that reference ' +
|
|
666
|
-
'protected gate files could tamper with the gate.
|
|
667
|
-
|
|
738
|
+
'protected gate files could tamper with the gate. Do not disable this hook. ' +
|
|
739
|
+
remedyForCommand(command) + ' ' +
|
|
668
740
|
'NOTE: This check has INCOMPLETE COVERAGE — runtime path construction evades it.',
|
|
669
741
|
};
|
|
670
742
|
}
|
|
@@ -677,8 +749,8 @@ function run(inputOrRaw, options = {}) {
|
|
|
677
749
|
exitCode: 2,
|
|
678
750
|
stderr: `BLOCKED: Detected ${copyMove} in a Bash command. ` +
|
|
679
751
|
'Writing to delivery/trust.bundle or delivery/trust.checkpoint.json via cp/mv/install ' +
|
|
680
|
-
'could forge the CI trust anchor.
|
|
681
|
-
|
|
752
|
+
'could forge the CI trust anchor. Do not disable this hook. ' +
|
|
753
|
+
remedyForCommand(command) + ' ' +
|
|
682
754
|
'NOTE: This check covers cp/mv/install only -- other copy tools may evade it.',
|
|
683
755
|
};
|
|
684
756
|
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* against this captured truth: a check claiming a command passed while the log
|
|
10
10
|
* shows it FAILED is a caught false-completion.
|
|
11
11
|
*
|
|
12
|
-
* Records to `.flow-agents/<slug>/command-log.jsonl`, one JSON object per line:
|
|
12
|
+
* Records to `.kontourai/flow-agents/<slug>/command-log.jsonl`, one JSON object per line:
|
|
13
13
|
* {
|
|
14
14
|
* "command": "<the command string the agent ran>",
|
|
15
15
|
* "observedResult": "pass" | "fail", // deterministically inferred
|
|
@@ -168,7 +168,7 @@ function readJsonFile(file) {
|
|
|
168
168
|
try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch { return null; }
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
// Newest-mtime state.json under .flow-agents/<slug>/, mirroring how
|
|
171
|
+
// Newest-mtime state.json under .kontourai/flow-agents/<slug>/, mirroring how
|
|
172
172
|
// workflow-steering.js and stop-goal-fit.js locate the active artifact dir.
|
|
173
173
|
function latestStateDir(flowAgentsDir) {
|
|
174
174
|
let best = null;
|
|
@@ -194,7 +194,7 @@ function latestStateDir(flowAgentsDir) {
|
|
|
194
194
|
|
|
195
195
|
/**
|
|
196
196
|
* Resolve the active artifact directory the same way the other hooks do:
|
|
197
|
-
* prefer .flow-agents/current.json (active_slug / artifact_dir), then fall back
|
|
197
|
+
* prefer .kontourai/flow-agents/current.json (active_slug / artifact_dir), then fall back
|
|
198
198
|
* to the newest-mtime state.json directory.
|
|
199
199
|
*/
|
|
200
200
|
function resolveArtifactDir(root) {
|