@kontourai/flow-agents 0.1.2 → 0.3.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/dependabot.yml +23 -0
- package/.github/workflows/release-please.yml +31 -0
- package/.github/workflows/runtime-compat.yml +118 -0
- package/CHANGELOG.md +46 -0
- package/CONTRIBUTING.md +4 -0
- package/README.md +80 -18
- package/build/src/cli/flow-kit.js +9 -4
- package/build/src/cli/init.js +215 -5
- package/build/src/cli/runtime-adapter.js +9 -5
- package/build/src/cli/telemetry-doctor.js +4 -1
- package/build/src/cli/utterance-check.js +65 -1
- package/build/src/runtime-adapters.js +34 -0
- package/build/src/tools/build-universal-bundles.js +285 -0
- package/build/src/tools/filter-installed-packs.js +3 -0
- package/build/src/tools/validate-source-tree.js +5 -1
- package/console.telemetry.json +115 -20
- package/context/scripts/telemetry/lib/config.sh +5 -1
- package/context/settings/flow-agents-settings.json +7 -0
- package/docs/_layouts/default.html +2 -0
- package/docs/context-map.md +1 -0
- package/docs/index.md +53 -4
- package/docs/integrations/conformance.md +246 -0
- package/docs/integrations/framework-adapter.md +275 -0
- package/docs/integrations/harness-install.md +213 -0
- package/docs/integrations/index.md +58 -0
- package/docs/integrations/knowledge-kit-live.md +211 -0
- package/docs/kit-authoring-guide.md +169 -0
- package/docs/north-star.md +2 -2
- package/docs/spec/runtime-hook-surface.md +525 -0
- package/docs/survey-utterance-check.md +211 -94
- package/docs/vision.md +45 -0
- package/evals/acceptance/run.sh +13 -2
- package/evals/acceptance/test_knowledge_kit_live.sh +221 -0
- package/evals/acceptance/test_opencode_harness.sh +121 -0
- package/evals/acceptance/test_pi_harness.sh +113 -0
- package/evals/integration/test_bundle_install.sh +226 -1
- package/evals/integration/test_bundle_lifecycle.sh +641 -0
- package/evals/integration/test_runtime_adapter_activation.sh +113 -1
- package/evals/integration/test_utterance_check.sh +291 -44
- package/evals/run.sh +2 -0
- package/evals/static/test_universal_bundles.sh +137 -2
- package/integrations/strands/README.md +256 -0
- package/integrations/strands/example.py +74 -0
- package/integrations/strands/examples/knowledge_kit_live.py +461 -0
- package/integrations/strands/flow_agents_strands/__init__.py +27 -0
- package/integrations/strands/flow_agents_strands/hooks.py +194 -0
- package/integrations/strands/flow_agents_strands/policy.py +348 -0
- package/integrations/strands/flow_agents_strands/steering.py +225 -0
- package/integrations/strands/flow_agents_strands/telemetry.py +238 -0
- package/integrations/strands/pyproject.toml +38 -0
- package/integrations/strands/tests/__init__.py +0 -0
- package/integrations/strands/tests/test_hooks.py +392 -0
- package/integrations/strands/tests/test_policy.py +315 -0
- package/integrations/strands/tests/test_telemetry.py +184 -0
- package/integrations/strands-ts/README.md +224 -0
- package/integrations/strands-ts/bin/conformance-shim.mjs +257 -0
- package/integrations/strands-ts/package.json +53 -0
- package/integrations/strands-ts/src/hooks.ts +312 -0
- package/integrations/strands-ts/src/index.ts +22 -0
- package/integrations/strands-ts/src/policy.ts +345 -0
- package/integrations/strands-ts/src/telemetry.ts +251 -0
- package/integrations/strands-ts/test/test-policy.ts +322 -0
- package/integrations/strands-ts/test/test-steering.ts +159 -0
- package/integrations/strands-ts/test/test-telemetry.ts +226 -0
- package/integrations/strands-ts/tsconfig.json +20 -0
- package/kits/catalog.json +6 -0
- package/kits/knowledge/adapters/default-store/index.js +821 -0
- package/kits/knowledge/adapters/flow-runner/index.js +1179 -0
- package/kits/knowledge/adapters/flow-runner/telemetry.js +174 -0
- package/kits/knowledge/docs/README.md +135 -0
- package/kits/knowledge/docs/store-contract.md +526 -0
- package/kits/knowledge/evals/consolidation/suite.test.js +1234 -0
- package/kits/knowledge/evals/contract-suite/suite.test.js +670 -0
- package/kits/knowledge/evals/ingest-compile/suite.test.js +574 -0
- package/kits/knowledge/evals/synthesis/suite.test.js +909 -0
- package/kits/knowledge/flows/compile.flow.json +60 -0
- package/kits/knowledge/flows/consolidate.flow.json +77 -0
- package/kits/knowledge/flows/ingest.flow.json +60 -0
- package/kits/knowledge/flows/store-contract.flow.json +48 -0
- package/kits/knowledge/flows/synthesize.flow.json +77 -0
- package/kits/knowledge/kit.json +78 -0
- package/package.json +7 -2
- package/packaging/conformance/README.md +142 -0
- package/packaging/conformance/fixtures/config-protection--allow-no-path.json +18 -0
- package/packaging/conformance/fixtures/config-protection--allow-safe-file.json +20 -0
- package/packaging/conformance/fixtures/config-protection--block-biome.json +20 -0
- package/packaging/conformance/fixtures/config-protection--block-eslintrc.json +20 -0
- package/packaging/conformance/fixtures/quality-gate--allow-no-path.json +17 -0
- package/packaging/conformance/fixtures/quality-gate--allow-nonexistent-file.json +19 -0
- package/packaging/conformance/fixtures/stop-goal-fit--allow-clean-cwd.json +17 -0
- package/packaging/conformance/fixtures/stop-goal-fit--block-strict-mode.json +23 -0
- package/packaging/conformance/fixtures/stop-goal-fit--warn-active-delivery.json +21 -0
- package/packaging/conformance/fixtures/workflow-steering--allow-no-state.json +16 -0
- package/packaging/conformance/fixtures/workflow-steering--inject-active-state.json +29 -0
- package/packaging/conformance/fixtures/workflow-steering--inject-subagent-steering.json +25 -0
- package/packaging/conformance/package.json +4 -0
- package/packaging/conformance/run-conformance.js +322 -0
- package/packaging/manifest.json +59 -0
- package/schemas/flow-agents-settings.schema.json +48 -0
- package/scripts/README.md +4 -0
- package/scripts/dogfood.js +16 -0
- package/scripts/hooks/opencode-hook-adapter.js +123 -0
- package/scripts/hooks/opencode-telemetry-hook.js +101 -0
- package/scripts/hooks/pi-hook-adapter.js +123 -0
- package/scripts/hooks/pi-telemetry-hook.js +105 -0
- package/scripts/hooks/run-hook.js +8 -0
- package/scripts/hooks/utterance-check.js +124 -22
- package/scripts/telemetry/lib/config.sh +5 -1
- package/src/cli/flow-kit.ts +10 -4
- package/src/cli/init.ts +219 -6
- package/src/cli/runtime-adapter.ts +10 -5
- package/src/cli/telemetry-doctor.ts +4 -1
- package/src/cli/utterance-check.ts +71 -1
- package/src/runtime-adapters.ts +35 -0
- package/src/tools/build-universal-bundles.ts +283 -0
- package/src/tools/filter-installed-packs.ts +3 -0
- package/src/tools/validate-source-tree.ts +5 -1
|
@@ -86,16 +86,128 @@ elif node - "$UNKNOWN_OUT" <<'NODE'
|
|
|
86
86
|
const fs = require("node:fs");
|
|
87
87
|
const data = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
88
88
|
if (!data.available_adapters?.includes("codex-local")) throw new Error("available adapters missing codex-local");
|
|
89
|
+
if (!data.available_adapters?.includes("strands-local")) throw new Error("available adapters missing strands-local");
|
|
89
90
|
if (!data.errors?.length) throw new Error("unknown adapter did not report errors");
|
|
90
91
|
console.log("ok");
|
|
91
92
|
NODE
|
|
92
93
|
then
|
|
93
|
-
pass "unknown adapter reports available adapters"
|
|
94
|
+
pass "unknown adapter reports available adapters (codex-local and strands-local)"
|
|
94
95
|
else
|
|
95
96
|
fail "unknown adapter diagnostics missing"
|
|
96
97
|
sed -n '1,120p' "$UNKNOWN_OUT"
|
|
97
98
|
fi
|
|
98
99
|
|
|
100
|
+
# -------------------------------------------------------------------------
|
|
101
|
+
# strands-local adapter activation (Issue #32 AC1)
|
|
102
|
+
# -------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
echo ""
|
|
105
|
+
echo "=== strands-local Adapter Activation Checks (Issue #32 AC1) ==="
|
|
106
|
+
|
|
107
|
+
STRANDS_DEST="$TMP_DIR/strands-dest"
|
|
108
|
+
STRANDS_OUT="$TMP_DIR/strands-activation.json"
|
|
109
|
+
mkdir -p "$STRANDS_DEST"
|
|
110
|
+
|
|
111
|
+
# Use the builder kit (stable fixture) — activate for strands-local from the repo source root
|
|
112
|
+
if flow_agents_node "$CLI" activate --dest "$STRANDS_DEST" --source-root "$ROOT" --adapter strands-local --format json >"$STRANDS_OUT" 2>&1; then
|
|
113
|
+
pass "strands-local activation succeeds"
|
|
114
|
+
else
|
|
115
|
+
fail "strands-local activation failed"
|
|
116
|
+
sed -n '1,220p' "$STRANDS_OUT"
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
if node - "$STRANDS_OUT" "$STRANDS_DEST" "$ROOT/kits/catalog.json" <<'NODE'
|
|
120
|
+
const fs = require("node:fs");
|
|
121
|
+
const path = require("node:path");
|
|
122
|
+
const data = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
123
|
+
const dest = process.argv[3];
|
|
124
|
+
const catalog = process.argv[4];
|
|
125
|
+
|
|
126
|
+
// Verify selected_adapter
|
|
127
|
+
if (data.selected_adapter !== "strands-local") throw new Error(`expected strands-local, got: ${data.selected_adapter}`);
|
|
128
|
+
if (JSON.stringify(data.supported_asset_classes) !== JSON.stringify(["flows"])) throw new Error(`unexpected supported_asset_classes: ${JSON.stringify(data.supported_asset_classes)}`);
|
|
129
|
+
|
|
130
|
+
// Verify builder kit flows are generated (builder kit is in catalog.json)
|
|
131
|
+
const ids = new Set(data.generated_runtime_files.map((item) => item.asset_id));
|
|
132
|
+
for (const expected of ["builder.shape", "builder.build", "strands-local.activation"]) {
|
|
133
|
+
if (!ids.has(expected)) throw new Error(`missing generated asset: ${expected}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Verify generated runtime files actually exist on disk
|
|
137
|
+
for (const item of data.generated_runtime_files) {
|
|
138
|
+
if (item.asset_class === "activation-manifest") continue;
|
|
139
|
+
const generatedPath = path.join(dest, item.path);
|
|
140
|
+
if (!fs.existsSync(generatedPath)) throw new Error(`generated file missing: ${generatedPath}`);
|
|
141
|
+
// Verify runtime files are under .flow-agents/runtime/strands/flows/
|
|
142
|
+
if (!item.path.includes(".flow-agents/runtime/strands/flows/")) {
|
|
143
|
+
throw new Error(`generated path not under strands runtime dir: ${item.path}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Verify activation.json written at strands runtime dir
|
|
148
|
+
const manifestPath = path.join(dest, ".flow-agents/runtime/strands/activation.json");
|
|
149
|
+
if (!fs.existsSync(manifestPath)) throw new Error("strands runtime activation.json missing");
|
|
150
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
151
|
+
if (manifest.adapter !== "strands-local") throw new Error(`activation.json adapter mismatch: ${manifest.adapter}`);
|
|
152
|
+
if (!Array.isArray(manifest.skipped_assets)) throw new Error("activation.json missing skipped_assets array");
|
|
153
|
+
|
|
154
|
+
// Verify skipped_assets have expected fields (parity with codex-local)
|
|
155
|
+
for (const item of manifest.skipped_assets) {
|
|
156
|
+
for (const key of ["asset_class", "path", "kit_id", "asset_id", "reason"]) {
|
|
157
|
+
if (!(key in item)) throw new Error(`skipped asset missing ${key}: ${JSON.stringify(item)}`);
|
|
158
|
+
}
|
|
159
|
+
if (!item.reason.includes("diagnostic-only")) throw new Error(`unexpected skip reason: ${item.reason}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Non-flow asset classes should appear in skipped_assets
|
|
163
|
+
const skippedClasses = new Set(manifest.skipped_assets.map((item) => item.asset_class));
|
|
164
|
+
// builder kit has flows only; skipped_assets check requires a kit with non-flow assets,
|
|
165
|
+
// which the codex-local path already validates via mixed-runtime-kit above.
|
|
166
|
+
// Here we just confirm the field structure is present.
|
|
167
|
+
if (!Array.isArray(data.skipped_assets)) throw new Error("result skipped_assets is not an array");
|
|
168
|
+
|
|
169
|
+
// Catalog not mutated
|
|
170
|
+
if (path.resolve(catalog) === path.resolve(path.join(dest, ".flow-agents/runtime/strands/activation.json"))) {
|
|
171
|
+
throw new Error("activation generated over kits/catalog.json");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
console.log("ok");
|
|
175
|
+
NODE
|
|
176
|
+
then
|
|
177
|
+
pass "strands-local: runtime flow files, activation.json, and skipped_assets present with correct structure"
|
|
178
|
+
else
|
|
179
|
+
fail "strands-local: activation diagnostics incomplete or incorrect"
|
|
180
|
+
sed -n '1,220p' "$STRANDS_OUT"
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
# Verify codex-local activation is still intact (AC3 — existing tests still pass)
|
|
184
|
+
if flow_agents_node "$CLI" activate --dest "$STRANDS_DEST" --source-root "$ROOT" --format json >"$TMP_DIR/codex-after-strands.json" 2>&1; then
|
|
185
|
+
pass "codex-local still activates after strands-local has run"
|
|
186
|
+
else
|
|
187
|
+
fail "codex-local activation failed after strands-local activation"
|
|
188
|
+
sed -n '1,220p' "$TMP_DIR/codex-after-strands.json"
|
|
189
|
+
fi
|
|
190
|
+
|
|
191
|
+
if node - "$TMP_DIR/codex-after-strands.json" "$STRANDS_DEST" <<'NODE'
|
|
192
|
+
const fs = require("node:fs");
|
|
193
|
+
const path = require("node:path");
|
|
194
|
+
const data = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
195
|
+
const dest = process.argv[3];
|
|
196
|
+
if (data.selected_adapter !== "codex-local") throw new Error(`expected codex-local, got: ${data.selected_adapter}`);
|
|
197
|
+
const manifestPath = path.join(dest, ".flow-agents/runtime/codex/activation.json");
|
|
198
|
+
if (!fs.existsSync(manifestPath)) throw new Error("codex activation.json still not present");
|
|
199
|
+
// Strands runtime dir must also still exist
|
|
200
|
+
const strandsManifestPath = path.join(dest, ".flow-agents/runtime/strands/activation.json");
|
|
201
|
+
if (!fs.existsSync(strandsManifestPath)) throw new Error("strands activation.json was removed by codex-local run");
|
|
202
|
+
console.log("ok");
|
|
203
|
+
NODE
|
|
204
|
+
then
|
|
205
|
+
pass "codex-local and strands-local runtime dirs co-exist independently (AC3)"
|
|
206
|
+
else
|
|
207
|
+
fail "co-existence check failed"
|
|
208
|
+
sed -n '1,220p' "$TMP_DIR/codex-after-strands.json"
|
|
209
|
+
fi
|
|
210
|
+
|
|
99
211
|
echo ""
|
|
100
212
|
if [[ "$errors" -eq 0 ]]; then
|
|
101
213
|
echo "Runtime adapter activation checks passed."
|
|
@@ -22,17 +22,17 @@ HOOK="$ROOT/scripts/hooks/utterance-check.js"
|
|
|
22
22
|
RUN_HOOK="$ROOT/scripts/hooks/run-hook.js"
|
|
23
23
|
|
|
24
24
|
# ---------------------------------------------------------------------------
|
|
25
|
-
# Hook: pass-through when disabled (
|
|
25
|
+
# Hook: pass-through when disabled by default (no config, no env var)
|
|
26
26
|
# ---------------------------------------------------------------------------
|
|
27
27
|
|
|
28
28
|
echo ""
|
|
29
|
-
echo "--- hook: disabled by default ---"
|
|
29
|
+
echo "--- hook: disabled by default (no config, no env var) ---"
|
|
30
30
|
|
|
31
31
|
INPUT_JSON='{"hook_event_name":"PostToolUse","tool_response":"The coverage is 92% and all tests pass."}'
|
|
32
32
|
|
|
33
33
|
if node "$HOOK" >"$TMPDIR_EVAL/disabled.out" 2>"$TMPDIR_EVAL/disabled.err" <<< "$INPUT_JSON"; then
|
|
34
34
|
if grep -qF '"hook_event_name"' "$TMPDIR_EVAL/disabled.out"; then
|
|
35
|
-
_pass "utterance check hook passes through when FLOW_AGENTS_UTTERANCE_CHECK_ENABLED is unset"
|
|
35
|
+
_pass "utterance check hook passes through when no config and FLOW_AGENTS_UTTERANCE_CHECK_ENABLED is unset"
|
|
36
36
|
else
|
|
37
37
|
_fail "utterance check hook pass-through output was not the raw input"
|
|
38
38
|
fi
|
|
@@ -40,6 +40,63 @@ else
|
|
|
40
40
|
_fail "utterance check hook should exit 0 when disabled"
|
|
41
41
|
fi
|
|
42
42
|
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
# Hook: env var force-off overrides a config that would enable
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
echo ""
|
|
48
|
+
echo "--- hook: env var force-off overrides config ---"
|
|
49
|
+
|
|
50
|
+
# Create a temp repo dir with a config that has enabled:true
|
|
51
|
+
FAKE_REPO="$TMPDIR_EVAL/fake-repo"
|
|
52
|
+
mkdir -p "$FAKE_REPO/context/settings"
|
|
53
|
+
cat > "$FAKE_REPO/AGENTS.md" <<'AGENTS_EOF'
|
|
54
|
+
# Fake repo for testing
|
|
55
|
+
AGENTS_EOF
|
|
56
|
+
cat > "$FAKE_REPO/context/settings/flow-agents-settings.json" <<'CONFIG_EOF'
|
|
57
|
+
{"schema_version":"1.0","utteranceCheck":{"enabled":true,"mode":"report","extractor":"reference"}}
|
|
58
|
+
CONFIG_EOF
|
|
59
|
+
|
|
60
|
+
INPUT_WITH_CWD="{\"hook_event_name\":\"PostToolUse\",\"tool_response\":\"text\",\"cwd\":\"$FAKE_REPO\"}"
|
|
61
|
+
|
|
62
|
+
if FLOW_AGENTS_UTTERANCE_CHECK_ENABLED=false \
|
|
63
|
+
node "$HOOK" >"$TMPDIR_EVAL/forceoff.out" 2>"$TMPDIR_EVAL/forceoff.err" <<< "$INPUT_WITH_CWD"; then
|
|
64
|
+
if grep -qF '"hook_event_name"' "$TMPDIR_EVAL/forceoff.out"; then
|
|
65
|
+
_pass "env var FLOW_AGENTS_UTTERANCE_CHECK_ENABLED=false forces hook off even when config has enabled:true"
|
|
66
|
+
else
|
|
67
|
+
_fail "force-off pass-through output did not match raw input"
|
|
68
|
+
fi
|
|
69
|
+
else
|
|
70
|
+
_fail "hook should exit 0 when force-off via env var"
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
# Hook: config-based enable (no env var override) passes through to CLI
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
echo ""
|
|
78
|
+
echo "--- hook: config-based enable reaches CLI (fail-open on missing CLI is acceptable) ---"
|
|
79
|
+
|
|
80
|
+
if node "$HOOK" >"$TMPDIR_EVAL/config-enable.out" 2>"$TMPDIR_EVAL/config-enable.err" <<< "$INPUT_WITH_CWD"; then
|
|
81
|
+
_pass "hook with config enabled exits 0 (fails open when CLI or survey is unavailable)"
|
|
82
|
+
else
|
|
83
|
+
_fail "hook with config enabled should exit 0 (fail-open)"
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# ---------------------------------------------------------------------------
|
|
87
|
+
# Hook: env var force-on (legacy behavior still works)
|
|
88
|
+
# ---------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
echo ""
|
|
91
|
+
echo "--- hook: env var force-on still works ---"
|
|
92
|
+
|
|
93
|
+
if FLOW_AGENTS_UTTERANCE_CHECK_ENABLED=true \
|
|
94
|
+
node "$HOOK" >"$TMPDIR_EVAL/forceon.out" 2>"$TMPDIR_EVAL/forceon.err" <<< "$INPUT_JSON"; then
|
|
95
|
+
_pass "FLOW_AGENTS_UTTERANCE_CHECK_ENABLED=true still enables the hook (legacy env var override)"
|
|
96
|
+
else
|
|
97
|
+
_fail "hook with force-on env var should exit 0"
|
|
98
|
+
fi
|
|
99
|
+
|
|
43
100
|
# ---------------------------------------------------------------------------
|
|
44
101
|
# Hook: pass-through with empty input
|
|
45
102
|
# ---------------------------------------------------------------------------
|
|
@@ -84,8 +141,7 @@ if SA_DISABLED_HOOKS=post:utterance-check \
|
|
|
84
141
|
node "$RUN_HOOK" post:utterance-check utterance-check.js standard,strict \
|
|
85
142
|
>"$TMPDIR_EVAL/disabled-runner.out" 2>"$TMPDIR_EVAL/disabled-runner.err" <<< "$HOOK_INPUT"
|
|
86
143
|
then
|
|
87
|
-
if cmp -s "$TMPDIR_EVAL/disabled-runner.out" <(printf '%s
|
|
88
|
-
' "$HOOK_INPUT"); then
|
|
144
|
+
if cmp -s "$TMPDIR_EVAL/disabled-runner.out" <(printf '%s\n' "$HOOK_INPUT"); then
|
|
89
145
|
_pass "run-hook.js passes input through when hook id is in SA_DISABLED_HOOKS"
|
|
90
146
|
else
|
|
91
147
|
_fail "run-hook.js disabled hook output did not match raw input"
|
|
@@ -94,6 +150,184 @@ else
|
|
|
94
150
|
_fail "run-hook.js with disabled hook should exit 0"
|
|
95
151
|
fi
|
|
96
152
|
|
|
153
|
+
# ---------------------------------------------------------------------------
|
|
154
|
+
# Hook: module.exports shape
|
|
155
|
+
# ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
echo ""
|
|
158
|
+
echo "--- hook: module.exports contract ---"
|
|
159
|
+
|
|
160
|
+
if node -e '
|
|
161
|
+
const h = require(process.argv[1]);
|
|
162
|
+
if (typeof h.run !== "function") { console.error("run missing"); process.exit(1); }
|
|
163
|
+
if (typeof h.extractUtteranceText !== "function") { console.error("extractUtteranceText missing"); process.exit(2); }
|
|
164
|
+
if (typeof h.findPackageRoot !== "function") { console.error("findPackageRoot missing"); process.exit(3); }
|
|
165
|
+
if (typeof h.findRepoRoot !== "function") { console.error("findRepoRoot missing"); process.exit(4); }
|
|
166
|
+
if (typeof h.loadRepoConfig !== "function") { console.error("loadRepoConfig missing"); process.exit(5); }
|
|
167
|
+
if (typeof h.resolvePolicy !== "function") { console.error("resolvePolicy missing"); process.exit(6); }
|
|
168
|
+
' "$HOOK"; then
|
|
169
|
+
_pass "utterance-check hook exports run, extractUtteranceText, findPackageRoot, findRepoRoot, loadRepoConfig, resolvePolicy"
|
|
170
|
+
else
|
|
171
|
+
_fail "utterance-check hook module.exports is missing expected functions"
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
# ---------------------------------------------------------------------------
|
|
175
|
+
# Hook: loadRepoConfig reads utteranceCheck from settings file
|
|
176
|
+
# ---------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
echo ""
|
|
179
|
+
echo "--- hook: loadRepoConfig reads from context/settings/flow-agents-settings.json ---"
|
|
180
|
+
|
|
181
|
+
if node -e '
|
|
182
|
+
const { loadRepoConfig } = require(process.argv[1]);
|
|
183
|
+
const fakeRepo = process.argv[2];
|
|
184
|
+
const cfg = loadRepoConfig(fakeRepo);
|
|
185
|
+
if (!cfg) { console.error("loadRepoConfig returned null for a repo with settings"); process.exit(1); }
|
|
186
|
+
if (cfg.enabled !== true) { console.error("expected enabled:true, got:", cfg.enabled); process.exit(2); }
|
|
187
|
+
if (cfg.mode !== "report") { console.error("expected mode:report, got:", cfg.mode); process.exit(3); }
|
|
188
|
+
if (cfg.extractor !== "reference") { console.error("expected extractor:reference, got:", cfg.extractor); process.exit(4); }
|
|
189
|
+
' "$HOOK" "$FAKE_REPO"; then
|
|
190
|
+
_pass "loadRepoConfig correctly reads utteranceCheck fields from settings file"
|
|
191
|
+
else
|
|
192
|
+
_fail "loadRepoConfig did not return expected config from settings file"
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
# ---------------------------------------------------------------------------
|
|
196
|
+
# Hook: loadRepoConfig returns null when settings file is absent
|
|
197
|
+
# ---------------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
echo ""
|
|
200
|
+
echo "--- hook: loadRepoConfig returns null when file is absent ---"
|
|
201
|
+
|
|
202
|
+
MISSING_REPO="$TMPDIR_EVAL/no-settings-repo"
|
|
203
|
+
mkdir -p "$MISSING_REPO"
|
|
204
|
+
touch "$MISSING_REPO/AGENTS.md"
|
|
205
|
+
|
|
206
|
+
if node -e '
|
|
207
|
+
const { loadRepoConfig } = require(process.argv[1]);
|
|
208
|
+
const cfg = loadRepoConfig(process.argv[2]);
|
|
209
|
+
if (cfg !== null) { console.error("expected null, got:", JSON.stringify(cfg)); process.exit(1); }
|
|
210
|
+
' "$HOOK" "$MISSING_REPO"; then
|
|
211
|
+
_pass "loadRepoConfig returns null when context/settings/flow-agents-settings.json is absent"
|
|
212
|
+
else
|
|
213
|
+
_fail "loadRepoConfig should return null for a repo without the settings file"
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
# ---------------------------------------------------------------------------
|
|
217
|
+
# Hook: resolvePolicy respects config enabled:false as default-off
|
|
218
|
+
# ---------------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
echo ""
|
|
221
|
+
echo "--- hook: resolvePolicy returns disabled when config has enabled:false ---"
|
|
222
|
+
|
|
223
|
+
mkdir -p "$TMPDIR_EVAL/disabled-repo/context/settings"
|
|
224
|
+
touch "$TMPDIR_EVAL/disabled-repo/AGENTS.md"
|
|
225
|
+
cat > "$TMPDIR_EVAL/disabled-repo/context/settings/flow-agents-settings.json" <<'DCFG_EOF'
|
|
226
|
+
{"schema_version":"1.0","utteranceCheck":{"enabled":false}}
|
|
227
|
+
DCFG_EOF
|
|
228
|
+
|
|
229
|
+
if node -e '
|
|
230
|
+
const { resolvePolicy } = require(process.argv[1]);
|
|
231
|
+
const policy = resolvePolicy(process.argv[2]);
|
|
232
|
+
if (policy.enabled !== false) { console.error("expected enabled:false, got:", policy.enabled); process.exit(1); }
|
|
233
|
+
' "$HOOK" "$TMPDIR_EVAL/disabled-repo"; then
|
|
234
|
+
_pass "resolvePolicy returns {enabled:false} when config has enabled:false"
|
|
235
|
+
else
|
|
236
|
+
_fail "resolvePolicy should return disabled policy when config has enabled:false"
|
|
237
|
+
fi
|
|
238
|
+
|
|
239
|
+
# ---------------------------------------------------------------------------
|
|
240
|
+
# Hook: resolvePolicy applies strict mode from config
|
|
241
|
+
# ---------------------------------------------------------------------------
|
|
242
|
+
|
|
243
|
+
echo ""
|
|
244
|
+
echo "--- hook: resolvePolicy applies mode:strict from config ---"
|
|
245
|
+
|
|
246
|
+
mkdir -p "$TMPDIR_EVAL/strict-repo/context/settings"
|
|
247
|
+
touch "$TMPDIR_EVAL/strict-repo/AGENTS.md"
|
|
248
|
+
cat > "$TMPDIR_EVAL/strict-repo/context/settings/flow-agents-settings.json" <<'SCFG_EOF'
|
|
249
|
+
{"schema_version":"1.0","utteranceCheck":{"enabled":true,"mode":"strict","extractor":"reference"}}
|
|
250
|
+
SCFG_EOF
|
|
251
|
+
|
|
252
|
+
if node -e '
|
|
253
|
+
const { resolvePolicy } = require(process.argv[1]);
|
|
254
|
+
const policy = resolvePolicy(process.argv[2]);
|
|
255
|
+
if (policy.enabled !== true) { console.error("expected enabled:true, got:", policy.enabled); process.exit(1); }
|
|
256
|
+
if (policy.mode !== "strict") { console.error("expected mode:strict, got:", policy.mode); process.exit(2); }
|
|
257
|
+
' "$HOOK" "$TMPDIR_EVAL/strict-repo"; then
|
|
258
|
+
_pass "resolvePolicy applies mode:strict from config"
|
|
259
|
+
else
|
|
260
|
+
_fail "resolvePolicy did not apply strict mode from config"
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
# ---------------------------------------------------------------------------
|
|
264
|
+
# Hook: resolvePolicy applies anthropic extractor from config
|
|
265
|
+
# ---------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
echo ""
|
|
268
|
+
echo "--- hook: resolvePolicy applies extractor:anthropic from config ---"
|
|
269
|
+
|
|
270
|
+
mkdir -p "$TMPDIR_EVAL/anthropic-repo/context/settings"
|
|
271
|
+
touch "$TMPDIR_EVAL/anthropic-repo/AGENTS.md"
|
|
272
|
+
cat > "$TMPDIR_EVAL/anthropic-repo/context/settings/flow-agents-settings.json" <<'ACFG_EOF'
|
|
273
|
+
{"schema_version":"1.0","utteranceCheck":{"enabled":true,"mode":"report","extractor":"anthropic","model":"claude-haiku-4-5"}}
|
|
274
|
+
ACFG_EOF
|
|
275
|
+
|
|
276
|
+
if node -e '
|
|
277
|
+
const { resolvePolicy } = require(process.argv[1]);
|
|
278
|
+
const policy = resolvePolicy(process.argv[2]);
|
|
279
|
+
if (policy.extractor !== "anthropic") { console.error("expected extractor:anthropic, got:", policy.extractor); process.exit(1); }
|
|
280
|
+
if (policy.model !== "claude-haiku-4-5") { console.error("expected model:claude-haiku-4-5, got:", policy.model); process.exit(2); }
|
|
281
|
+
' "$HOOK" "$TMPDIR_EVAL/anthropic-repo"; then
|
|
282
|
+
_pass "resolvePolicy applies extractor:anthropic and model from config"
|
|
283
|
+
else
|
|
284
|
+
_fail "resolvePolicy did not apply anthropic extractor from config"
|
|
285
|
+
fi
|
|
286
|
+
|
|
287
|
+
# ---------------------------------------------------------------------------
|
|
288
|
+
# Hook: resolvePolicy env var STRICT overrides report mode from config
|
|
289
|
+
# ---------------------------------------------------------------------------
|
|
290
|
+
|
|
291
|
+
echo ""
|
|
292
|
+
echo "--- hook: env var STRICT overrides report mode in config ---"
|
|
293
|
+
|
|
294
|
+
if node -e '
|
|
295
|
+
const { resolvePolicy } = require(process.argv[1]);
|
|
296
|
+
// Set env var before requiring resolvePolicy
|
|
297
|
+
process.env.FLOW_AGENTS_UTTERANCE_CHECK_STRICT = "true";
|
|
298
|
+
const policy = resolvePolicy(process.argv[2]);
|
|
299
|
+
delete process.env.FLOW_AGENTS_UTTERANCE_CHECK_STRICT;
|
|
300
|
+
if (policy.mode !== "strict") { console.error("expected mode:strict from env var, got:", policy.mode); process.exit(1); }
|
|
301
|
+
' "$HOOK" "$FAKE_REPO"; then
|
|
302
|
+
_pass "FLOW_AGENTS_UTTERANCE_CHECK_STRICT=true env var overrides report mode in config"
|
|
303
|
+
else
|
|
304
|
+
_fail "env var STRICT did not override report mode from config"
|
|
305
|
+
fi
|
|
306
|
+
|
|
307
|
+
# ---------------------------------------------------------------------------
|
|
308
|
+
# Hook: extractUtteranceText extracts from PostToolUse and Stop events
|
|
309
|
+
# ---------------------------------------------------------------------------
|
|
310
|
+
|
|
311
|
+
echo ""
|
|
312
|
+
echo "--- hook: extractUtteranceText ---"
|
|
313
|
+
|
|
314
|
+
if node -e '
|
|
315
|
+
const { extractUtteranceText } = require(process.argv[1]);
|
|
316
|
+
const postToolUse = { hook_event_name: "PostToolUse", tool_response: "The answer is 42." };
|
|
317
|
+
const text = extractUtteranceText(postToolUse);
|
|
318
|
+
if (text !== "The answer is 42.") { console.error("PostToolUse extract failed:", text); process.exit(1); }
|
|
319
|
+
const stopWithContent = { hook_event_name: "Stop", content: [{ type: "text", text: "Done!" }] };
|
|
320
|
+
const text2 = extractUtteranceText(stopWithContent);
|
|
321
|
+
if (text2 !== "Done!") { console.error("Stop content extract failed:", text2); process.exit(2); }
|
|
322
|
+
const emptyEvent = { hook_event_name: "PostToolUse" };
|
|
323
|
+
const text3 = extractUtteranceText(emptyEvent);
|
|
324
|
+
if (text3 !== null) { console.error("Empty event should return null, got:", text3); process.exit(3); }
|
|
325
|
+
' "$HOOK"; then
|
|
326
|
+
_pass "extractUtteranceText handles PostToolUse, Stop content, and empty events"
|
|
327
|
+
else
|
|
328
|
+
_fail "extractUtteranceText behavior was unexpected"
|
|
329
|
+
fi
|
|
330
|
+
|
|
97
331
|
# ---------------------------------------------------------------------------
|
|
98
332
|
# CLI: build and test --not-configured
|
|
99
333
|
# ---------------------------------------------------------------------------
|
|
@@ -148,6 +382,24 @@ else
|
|
|
148
382
|
_fail "CLI --help should exit 0"
|
|
149
383
|
fi
|
|
150
384
|
|
|
385
|
+
# ---------------------------------------------------------------------------
|
|
386
|
+
# CLI: --extractor flag appears in help
|
|
387
|
+
# ---------------------------------------------------------------------------
|
|
388
|
+
|
|
389
|
+
echo ""
|
|
390
|
+
echo "--- cli: --extractor flag in help ---"
|
|
391
|
+
|
|
392
|
+
if node "$ROOT/build/src/cli.js" utterance-check --help \
|
|
393
|
+
>"$TMPDIR_EVAL/help2.out" 2>"$TMPDIR_EVAL/help2.err"; then
|
|
394
|
+
if grep -q '\-\-extractor' "$TMPDIR_EVAL/help2.err"; then
|
|
395
|
+
_pass "CLI --help mentions --extractor flag"
|
|
396
|
+
else
|
|
397
|
+
_fail "CLI --help does not mention --extractor flag"
|
|
398
|
+
fi
|
|
399
|
+
else
|
|
400
|
+
_fail "CLI --help should exit 0"
|
|
401
|
+
fi
|
|
402
|
+
|
|
151
403
|
# ---------------------------------------------------------------------------
|
|
152
404
|
# CLI: missing --utterance exits non-zero
|
|
153
405
|
# ---------------------------------------------------------------------------
|
|
@@ -203,58 +455,53 @@ else
|
|
|
203
455
|
fi
|
|
204
456
|
|
|
205
457
|
# ---------------------------------------------------------------------------
|
|
206
|
-
# CLI:
|
|
207
|
-
# ---------------------------------------------------------------------------
|
|
208
|
-
|
|
209
|
-
echo ""
|
|
210
|
-
echo "--- cli: command registration ---"
|
|
211
|
-
|
|
212
|
-
if node "$ROOT/build/src/cli.js" commands 2>/dev/null | grep -q 'utterance-check'; then
|
|
213
|
-
_pass "utterance-check is registered as a flow-agents CLI command"
|
|
214
|
-
else
|
|
215
|
-
_fail "utterance-check is not registered in flow-agents CLI commands"
|
|
216
|
-
fi
|
|
217
|
-
|
|
218
|
-
# ---------------------------------------------------------------------------
|
|
219
|
-
# Hook: module.exports shape
|
|
458
|
+
# CLI: --extractor anthropic without ANTHROPIC_API_KEY fails open (exit 0)
|
|
220
459
|
# ---------------------------------------------------------------------------
|
|
221
460
|
|
|
222
461
|
echo ""
|
|
223
|
-
echo "---
|
|
462
|
+
echo "--- cli: anthropic extractor without API key fails open ---"
|
|
224
463
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
464
|
+
# Run without ANTHROPIC_API_KEY set.
|
|
465
|
+
# The CLI should emit not_configured JSON and exit 0 (fail open).
|
|
466
|
+
if env -u ANTHROPIC_API_KEY \
|
|
467
|
+
node "$ROOT/build/src/cli.js" utterance-check check \
|
|
468
|
+
--utterance "The test coverage is 92%." \
|
|
469
|
+
--extractor anthropic \
|
|
470
|
+
>"$TMPDIR_EVAL/no-apikey.out" 2>"$TMPDIR_EVAL/no-apikey.err"
|
|
471
|
+
then
|
|
472
|
+
status_val=$(node -e '
|
|
473
|
+
const r = JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"));
|
|
474
|
+
console.log(r.status);
|
|
475
|
+
' "$TMPDIR_EVAL/no-apikey.out" 2>/dev/null || echo "parse-error")
|
|
476
|
+
if [[ "$status_val" == "not_configured" ]]; then
|
|
477
|
+
_pass "CLI --extractor anthropic without ANTHROPIC_API_KEY emits not_configured and exits 0 (fail open)"
|
|
478
|
+
elif [[ "$status_val" == "ok" || "$status_val" == "error" ]]; then
|
|
479
|
+
# If survey is installed and somehow proceeded (shouldn't happen without key), still accept
|
|
480
|
+
_pass "CLI --extractor anthropic produced a valid report (status: $status_val)"
|
|
481
|
+
else
|
|
482
|
+
_fail "CLI --extractor anthropic without API key produced unexpected output (status: $status_val)"
|
|
483
|
+
fi
|
|
232
484
|
else
|
|
233
|
-
|
|
485
|
+
exit_code=$?
|
|
486
|
+
# Exit 1 means survey not installed — that's a different fail-open path, acceptable
|
|
487
|
+
if [[ "$exit_code" -eq 1 ]]; then
|
|
488
|
+
_pass "CLI --extractor anthropic: survey not installed, exits 1 (not_configured)"
|
|
489
|
+
else
|
|
490
|
+
_fail "CLI --extractor anthropic without API key should exit 0 or 1 (fail open), got: $exit_code"
|
|
491
|
+
fi
|
|
234
492
|
fi
|
|
235
493
|
|
|
236
494
|
# ---------------------------------------------------------------------------
|
|
237
|
-
#
|
|
495
|
+
# CLI: utterance check registers as a valid flow-agents command
|
|
238
496
|
# ---------------------------------------------------------------------------
|
|
239
497
|
|
|
240
498
|
echo ""
|
|
241
|
-
echo "---
|
|
499
|
+
echo "--- cli: command registration ---"
|
|
242
500
|
|
|
243
|
-
if node -
|
|
244
|
-
|
|
245
|
-
const postToolUse = { hook_event_name: "PostToolUse", tool_response: "The answer is 42." };
|
|
246
|
-
const text = extractUtteranceText(postToolUse);
|
|
247
|
-
if (text !== "The answer is 42.") { console.error("PostToolUse extract failed:", text); process.exit(1); }
|
|
248
|
-
const stopWithContent = { hook_event_name: "Stop", content: [{ type: "text", text: "Done!" }] };
|
|
249
|
-
const text2 = extractUtteranceText(stopWithContent);
|
|
250
|
-
if (text2 !== "Done!") { console.error("Stop content extract failed:", text2); process.exit(2); }
|
|
251
|
-
const emptyEvent = { hook_event_name: "PostToolUse" };
|
|
252
|
-
const text3 = extractUtteranceText(emptyEvent);
|
|
253
|
-
if (text3 !== null) { console.error("Empty event should return null, got:", text3); process.exit(3); }
|
|
254
|
-
' "$HOOK"; then
|
|
255
|
-
_pass "extractUtteranceText handles PostToolUse, Stop content, and empty events"
|
|
501
|
+
if node "$ROOT/build/src/cli.js" commands 2>/dev/null | grep -q 'utterance-check'; then
|
|
502
|
+
_pass "utterance-check is registered as a flow-agents CLI command"
|
|
256
503
|
else
|
|
257
|
-
_fail "
|
|
504
|
+
_fail "utterance-check is not registered in flow-agents CLI commands"
|
|
258
505
|
fi
|
|
259
506
|
|
|
260
507
|
# ---------------------------------------------------------------------------
|
package/evals/run.sh
CHANGED
|
@@ -190,6 +190,8 @@ run_integration() {
|
|
|
190
190
|
bash "$EVAL_DIR/integration/test_runtime_adapter_activation.sh" || result=1
|
|
191
191
|
echo ""
|
|
192
192
|
bash "$EVAL_DIR/integration/test_bundle_install.sh" || result=1
|
|
193
|
+
echo ""
|
|
194
|
+
bash "$EVAL_DIR/integration/test_bundle_lifecycle.sh" || result=1
|
|
193
195
|
return $result
|
|
194
196
|
}
|
|
195
197
|
|