@kontourai/flow-agents 0.1.1 → 0.1.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.
@@ -0,0 +1,271 @@
1
+ #!/usr/bin/env bash
2
+ # test_utterance_check.sh — Survey utterance check hook and CLI adapter coverage
3
+ set -uo pipefail
4
+
5
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
6
+ source "$ROOT/evals/lib/node.sh"
7
+
8
+ TMPDIR_EVAL="$(mktemp -d)"
9
+ errors=0
10
+
11
+ cleanup() {
12
+ rm -rf "$TMPDIR_EVAL"
13
+ }
14
+ trap cleanup EXIT
15
+
16
+ _pass() { echo " ✓ $1"; }
17
+ _fail() { echo " ✗ $1"; errors=$((errors + 1)); }
18
+
19
+ echo "=== Utterance Check Hook and CLI Adapter ==="
20
+
21
+ HOOK="$ROOT/scripts/hooks/utterance-check.js"
22
+ RUN_HOOK="$ROOT/scripts/hooks/run-hook.js"
23
+
24
+ # ---------------------------------------------------------------------------
25
+ # Hook: pass-through when disabled (default)
26
+ # ---------------------------------------------------------------------------
27
+
28
+ echo ""
29
+ echo "--- hook: disabled by default ---"
30
+
31
+ INPUT_JSON='{"hook_event_name":"PostToolUse","tool_response":"The coverage is 92% and all tests pass."}'
32
+
33
+ if node "$HOOK" >"$TMPDIR_EVAL/disabled.out" 2>"$TMPDIR_EVAL/disabled.err" <<< "$INPUT_JSON"; then
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"
36
+ else
37
+ _fail "utterance check hook pass-through output was not the raw input"
38
+ fi
39
+ else
40
+ _fail "utterance check hook should exit 0 when disabled"
41
+ fi
42
+
43
+ # ---------------------------------------------------------------------------
44
+ # Hook: pass-through with empty input
45
+ # ---------------------------------------------------------------------------
46
+
47
+ echo ""
48
+ echo "--- hook: empty input ---"
49
+
50
+ if FLOW_AGENTS_UTTERANCE_CHECK_ENABLED=true node "$HOOK" >"$TMPDIR_EVAL/empty.out" 2>"$TMPDIR_EVAL/empty.err" <<< '{}'; then
51
+ _pass "utterance check hook passes through when no utterance text is present"
52
+ else
53
+ _fail "utterance check hook should exit 0 on empty input"
54
+ fi
55
+
56
+ # ---------------------------------------------------------------------------
57
+ # Hook: pass-through when CLI is not built yet
58
+ # ---------------------------------------------------------------------------
59
+
60
+ echo ""
61
+ echo "--- hook: missing CLI gracefully fails open ---"
62
+
63
+ if FLOW_AGENTS_UTTERANCE_CHECK_ENABLED=true \
64
+ node "$HOOK" >"$TMPDIR_EVAL/nocli.out" 2>"$TMPDIR_EVAL/nocli.err" <<JSON
65
+ {"hook_event_name":"PostToolUse","tool_response":"Some agent text."}
66
+ JSON
67
+ then
68
+ # Either built CLI path worked, or hook failed open (exit 0)
69
+ _pass "utterance check hook fails open when CLI or survey is not available"
70
+ else
71
+ _fail "utterance check hook should not block when CLI is unavailable"
72
+ fi
73
+
74
+ # ---------------------------------------------------------------------------
75
+ # Hook: respects SA_DISABLED_HOOKS through run-hook.js
76
+ # ---------------------------------------------------------------------------
77
+
78
+ echo ""
79
+ echo "--- hook: run-hook.js respects SA_DISABLED_HOOKS ---"
80
+
81
+ HOOK_INPUT='{"hook_event_name":"PostToolUse","tool_response":"text"}'
82
+
83
+ if SA_DISABLED_HOOKS=post:utterance-check \
84
+ node "$RUN_HOOK" post:utterance-check utterance-check.js standard,strict \
85
+ >"$TMPDIR_EVAL/disabled-runner.out" 2>"$TMPDIR_EVAL/disabled-runner.err" <<< "$HOOK_INPUT"
86
+ then
87
+ if cmp -s "$TMPDIR_EVAL/disabled-runner.out" <(printf '%s
88
+ ' "$HOOK_INPUT"); then
89
+ _pass "run-hook.js passes input through when hook id is in SA_DISABLED_HOOKS"
90
+ else
91
+ _fail "run-hook.js disabled hook output did not match raw input"
92
+ fi
93
+ else
94
+ _fail "run-hook.js with disabled hook should exit 0"
95
+ fi
96
+
97
+ # ---------------------------------------------------------------------------
98
+ # CLI: build and test --not-configured
99
+ # ---------------------------------------------------------------------------
100
+
101
+ echo ""
102
+ echo "--- cli: not-configured output ---"
103
+
104
+ # Build the TypeScript source if needed
105
+ if [[ ! -f "$ROOT/build/src/cli.js" ]]; then
106
+ echo " (building TypeScript source...)"
107
+ if ! (cd "$ROOT" && npm run build --silent 2>"$TMPDIR_EVAL/build.err"); then
108
+ _fail "TypeScript build failed: $(cat "$TMPDIR_EVAL/build.err" | head -5)"
109
+ errors=$((errors + 1))
110
+ echo ""
111
+ echo "Utterance check integration tests failed: $errors issue(s)."
112
+ exit 1
113
+ fi
114
+ fi
115
+
116
+ if node "$ROOT/build/src/cli.js" utterance-check check --not-configured \
117
+ >"$TMPDIR_EVAL/not-configured.out" 2>"$TMPDIR_EVAL/not-configured.err"
118
+ then
119
+ if node -e '
120
+ const r = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
121
+ if (r.status !== "not_configured") process.exit(1);
122
+ if (!Array.isArray(r.statements)) process.exit(2);
123
+ if (typeof r.summary !== "string") process.exit(3);
124
+ ' "$TMPDIR_EVAL/not-configured.out"; then
125
+ _pass "CLI outputs not_configured JSON when --not-configured is set"
126
+ else
127
+ _fail "CLI not-configured output did not match expected shape"
128
+ fi
129
+ else
130
+ _fail "CLI should exit 0 with --not-configured"
131
+ fi
132
+
133
+ # ---------------------------------------------------------------------------
134
+ # CLI: --help exits 0 and prints usage
135
+ # ---------------------------------------------------------------------------
136
+
137
+ echo ""
138
+ echo "--- cli: help output ---"
139
+
140
+ if node "$ROOT/build/src/cli.js" utterance-check --help \
141
+ >"$TMPDIR_EVAL/help.out" 2>"$TMPDIR_EVAL/help.err"; then
142
+ if grep -q 'utterance-check check' "$TMPDIR_EVAL/help.err"; then
143
+ _pass "CLI --help prints usage"
144
+ else
145
+ _fail "CLI --help did not print expected usage text"
146
+ fi
147
+ else
148
+ _fail "CLI --help should exit 0"
149
+ fi
150
+
151
+ # ---------------------------------------------------------------------------
152
+ # CLI: missing --utterance exits non-zero
153
+ # ---------------------------------------------------------------------------
154
+
155
+ echo ""
156
+ echo "--- cli: missing utterance flag ---"
157
+
158
+ if node "$ROOT/build/src/cli.js" utterance-check check \
159
+ >"$TMPDIR_EVAL/no-utterance.out" 2>"$TMPDIR_EVAL/no-utterance.err"
160
+ then
161
+ _fail "CLI check without --utterance should exit non-zero"
162
+ else
163
+ _pass "CLI check without --utterance exits non-zero (usage error)"
164
+ fi
165
+
166
+ # ---------------------------------------------------------------------------
167
+ # CLI: survey not installed → not_configured output, exits 1
168
+ # ---------------------------------------------------------------------------
169
+
170
+ echo ""
171
+ echo "--- cli: @kontourai/survey not installed ---"
172
+
173
+ # Run with a NODE_PATH that does not include any survey package, so the
174
+ # dynamic import fails. node's module resolution will not find @kontourai/survey
175
+ # from this test since it is not installed in flow-agents/node_modules.
176
+ if node "$ROOT/build/src/cli.js" utterance-check check \
177
+ --utterance "The test coverage is 92%." \
178
+ >"$TMPDIR_EVAL/no-survey.out" 2>"$TMPDIR_EVAL/no-survey.err"
179
+ then
180
+ # survey might be installed; check for not_configured or ok status
181
+ status_val=$(node -e 'console.log(JSON.parse(require("fs").readFileSync(process.argv[1],"utf8")).status)' \
182
+ "$TMPDIR_EVAL/no-survey.out" 2>/dev/null || echo "parse-error")
183
+ if [[ "$status_val" == "ok" || "$status_val" == "not_configured" ]]; then
184
+ _pass "CLI utterance check produces valid report (status: $status_val)"
185
+ else
186
+ _fail "CLI utterance check output had unexpected status: $status_val"
187
+ fi
188
+ else
189
+ exit_code=$?
190
+ # Exit 1 means not_configured (survey not installed) — expected in CI
191
+ if [[ "$exit_code" -eq 1 ]]; then
192
+ if node -e '
193
+ const r = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
194
+ if (r.status !== "not_configured") process.exit(1);
195
+ ' "$TMPDIR_EVAL/no-survey.out" 2>/dev/null; then
196
+ _pass "CLI outputs not_configured when @kontourai/survey is not installed"
197
+ else
198
+ _fail "CLI exit 1 but output was not not_configured JSON"
199
+ fi
200
+ else
201
+ _fail "CLI should exit 0 or 1, got exit code: $exit_code"
202
+ fi
203
+ fi
204
+
205
+ # ---------------------------------------------------------------------------
206
+ # CLI: utterance check registers as a valid flow-agents command
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
220
+ # ---------------------------------------------------------------------------
221
+
222
+ echo ""
223
+ echo "--- hook: module.exports contract ---"
224
+
225
+ if node -e '
226
+ const h = require(process.argv[1]);
227
+ if (typeof h.run !== "function") { console.error("run missing"); process.exit(1); }
228
+ if (typeof h.extractUtteranceText !== "function") { console.error("extractUtteranceText missing"); process.exit(2); }
229
+ if (typeof h.findPackageRoot !== "function") { console.error("findPackageRoot missing"); process.exit(3); }
230
+ ' "$HOOK"; then
231
+ _pass "utterance-check hook exports run, extractUtteranceText, findPackageRoot"
232
+ else
233
+ _fail "utterance-check hook module.exports is missing expected functions"
234
+ fi
235
+
236
+ # ---------------------------------------------------------------------------
237
+ # Hook: extractUtteranceText extracts from PostToolUse and Stop events
238
+ # ---------------------------------------------------------------------------
239
+
240
+ echo ""
241
+ echo "--- hook: extractUtteranceText ---"
242
+
243
+ if node -e '
244
+ const { extractUtteranceText } = require(process.argv[1]);
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"
256
+ else
257
+ _fail "extractUtteranceText behavior was unexpected"
258
+ fi
259
+
260
+ # ---------------------------------------------------------------------------
261
+ # Summary
262
+ # ---------------------------------------------------------------------------
263
+
264
+ echo ""
265
+ if [[ "$errors" -eq 0 ]]; then
266
+ echo "Utterance check integration tests passed."
267
+ exit 0
268
+ fi
269
+
270
+ echo "Utterance check integration tests failed: $errors issue(s)."
271
+ exit 1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kontourai/flow-agents",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Flow Agents — a Kontour product that applies Flow and Veritas discipline inside the agent tools you already use: Claude Code, Codex, Kiro, and GitHub Actions.",
5
5
  "keywords": [
6
6
  "agents",
package/scripts/README.md CHANGED
@@ -59,6 +59,7 @@ renamed, or changes category, update the table and the validator together.
59
59
  | `report-only-guard.js` | policy hook | `evals/integration/test_hook_category_behaviors.sh` | Protects report-only specialist roles from production edits. |
60
60
  | `stop-format-typecheck.js` | policy hook | `evals/integration/test_hook_category_behaviors.sh` | Runs stop-time format/typecheck feedback. |
61
61
  | `stop-goal-fit.js` | policy hook | `evals/integration/test_goal_fit_hook.sh` | Warns when a workflow is about to stop short of Goal Fit. |
62
+ | `utterance-check.js` | policy hook | `evals/integration/test_utterance_check.sh` | Optionally checks agent utterances for evidence coverage using @kontourai/survey (disabled by default; opt-in via FLOW_AGENTS_UTTERANCE_CHECK_ENABLED). |
62
63
  | `workflow-steering.js` | policy hook | `evals/integration/test_workflow_steering_hook.sh` | Provides workflow guidance from current artifact state. |
63
64
  | `pre-commit-quality.js` | repo guardrail hook | `evals/integration/test_hook_category_behaviors.sh` | Supports repository Git hook checks, not installed runtime hooks. |
64
65
  | `desktop-notify.sh` | local notification helper | `evals/integration/test_hook_category_behaviors.sh` | Optional local desktop notification helper. |
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Utterance Check Hook — ADR 0003 §9 / survey integration.
4
+ *
5
+ * Optionally inspects agent output text for evidence coverage using
6
+ * @kontourai/survey's surveyAgentUtterance. Injects badge guidance into the
7
+ * agent context when concerning statements (unsupported/disputed/rejected) are
8
+ * found.
9
+ *
10
+ * Disabled by default. Enable with:
11
+ * FLOW_AGENTS_UTTERANCE_CHECK_ENABLED=true
12
+ *
13
+ * Hook category: PostToolUse / Stop (non-blocking, always exits 0).
14
+ *
15
+ * Strict mode (blocks Stop when concerning badges present):
16
+ * FLOW_AGENTS_UTTERANCE_CHECK_STRICT=true
17
+ *
18
+ * Bundle path (optional trust bundle for claim resolution):
19
+ * FLOW_AGENTS_UTTERANCE_CHECK_BUNDLE_PATH=/path/to/bundle.json
20
+ *
21
+ * Agent ID (for provenance):
22
+ * FLOW_AGENTS_UTTERANCE_CHECK_AGENT_ID=my-agent
23
+ */
24
+
25
+ 'use strict';
26
+
27
+ const fs = require('fs');
28
+ const path = require('path');
29
+ const { spawnSync } = require('child_process');
30
+
31
+ const MAX_STDIN = 1024 * 1024;
32
+ const CLI_TIMEOUT_MS = 30000;
33
+ // Maximum utterance text to pass to the CLI to keep stdin under MAX_STDIN.
34
+ const MAX_UTTERANCE_CHARS = 8192;
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Helpers
38
+ // ---------------------------------------------------------------------------
39
+
40
+ function parseJson(raw) {
41
+ try { return JSON.parse(raw || '{}'); } catch { return {}; }
42
+ }
43
+
44
+ /**
45
+ * Walk up from startDir to find the flow-agents package root.
46
+ * Identified by having both package.json and build/src/cli.js present.
47
+ */
48
+ function findPackageRoot(startDir) {
49
+ let dir = path.resolve(startDir || __dirname);
50
+ for (let depth = 0; depth < 10; depth++) {
51
+ if (
52
+ fs.existsSync(path.join(dir, 'package.json')) &&
53
+ fs.existsSync(path.join(dir, 'build', 'src', 'cli.js'))
54
+ ) {
55
+ return dir;
56
+ }
57
+ const parent = path.dirname(dir);
58
+ if (parent === dir) break;
59
+ dir = parent;
60
+ }
61
+ // Fallback: assume hooks dir is scripts/hooks/, so root is two levels up.
62
+ return path.resolve(__dirname, '..', '..');
63
+ }
64
+
65
+ /**
66
+ * Extract agent utterance text from the hook event input.
67
+ * - PostToolUse: tool_response or tool_output field
68
+ * - Stop: stop_reason or agent_message field (varies by harness)
69
+ * - Fallback: returns null (hook passes through)
70
+ */
71
+ function extractUtteranceText(input) {
72
+ if (!input || typeof input !== 'object') return null;
73
+
74
+ // PostToolUse: agent output is in tool_response or tool_output
75
+ const resp = input.tool_response || input.tool_output;
76
+ if (typeof resp === 'string' && resp.trim()) return resp.trim();
77
+
78
+ // Stop: some harnesses expose agent message content
79
+ const agentMsg = input.agent_message || input.message;
80
+ if (typeof agentMsg === 'string' && agentMsg.trim()) return agentMsg.trim();
81
+
82
+ // Stop with content array (Claude Code format)
83
+ if (Array.isArray(input.content)) {
84
+ const texts = input.content
85
+ .filter(b => b && b.type === 'text' && typeof b.text === 'string')
86
+ .map(b => String(b.text).trim())
87
+ .filter(Boolean);
88
+ if (texts.length > 0) return texts.join('\n\n');
89
+ }
90
+
91
+ return null;
92
+ }
93
+
94
+ function safeOneLineExcerpt(text, max = 120) {
95
+ const s = String(text || '').replace(/\s+/g, ' ').trim();
96
+ return s.length > max ? `${s.slice(0, max - 3)}...` : s;
97
+ }
98
+
99
+ // ---------------------------------------------------------------------------
100
+ // Hook runner
101
+ // ---------------------------------------------------------------------------
102
+
103
+ function run(rawInput) {
104
+ const enabled = String(process.env.FLOW_AGENTS_UTTERANCE_CHECK_ENABLED || '').toLowerCase() === 'true';
105
+ if (!enabled) return rawInput;
106
+
107
+ let input;
108
+ try { input = JSON.parse(rawInput || '{}'); } catch { return rawInput; }
109
+
110
+ const utteranceText = extractUtteranceText(input);
111
+ if (!utteranceText) return rawInput;
112
+
113
+ // Truncate very long utterances before passing to CLI.
114
+ const utterance = utteranceText.length > MAX_UTTERANCE_CHARS
115
+ ? utteranceText.slice(0, MAX_UTTERANCE_CHARS)
116
+ : utteranceText;
117
+
118
+ const packageRoot = findPackageRoot(__dirname);
119
+ const cliPath = path.join(packageRoot, 'build', 'src', 'cli.js');
120
+
121
+ if (!fs.existsSync(cliPath)) {
122
+ process.stderr.write(
123
+ `[UtteranceCheck] CLI not found at ${cliPath}. Run npm run build in the flow-agents checkout.\n`
124
+ );
125
+ return rawInput;
126
+ }
127
+
128
+ const cliArgs = ['utterance-check', 'check', '--utterance', utterance];
129
+
130
+ const bundlePath = process.env.FLOW_AGENTS_UTTERANCE_CHECK_BUNDLE_PATH;
131
+ if (bundlePath) cliArgs.push('--bundle-path', bundlePath);
132
+
133
+ const agentId = process.env.FLOW_AGENTS_UTTERANCE_CHECK_AGENT_ID || 'flow-agents-hook';
134
+ cliArgs.push('--agent-id', agentId);
135
+
136
+ const strict = String(process.env.FLOW_AGENTS_UTTERANCE_CHECK_STRICT || '').toLowerCase() === 'true';
137
+ if (strict) cliArgs.push('--strict');
138
+
139
+ const result = spawnSync(process.execPath, [cliPath, ...cliArgs], {
140
+ encoding: 'utf8',
141
+ timeout: CLI_TIMEOUT_MS,
142
+ cwd: packageRoot,
143
+ env: { ...process.env },
144
+ });
145
+
146
+ if (result.error || result.signal || result.status === null) {
147
+ const detail = result.error
148
+ ? result.error.message
149
+ : result.signal
150
+ ? `signal ${result.signal}`
151
+ : 'missing exit status';
152
+ process.stderr.write(`[UtteranceCheck] CLI execution failed (failing open): ${detail}\n`);
153
+ return rawInput;
154
+ }
155
+
156
+ // Parse the JSON report from stdout.
157
+ let report = null;
158
+ try { report = JSON.parse(String(result.stdout || '').trim()); } catch { /* pass through */ }
159
+
160
+ if (result.stderr) {
161
+ process.stderr.write(String(result.stderr));
162
+ }
163
+
164
+ if (!report) return rawInput;
165
+
166
+ // If survey is not configured, pass through silently.
167
+ if (report.status === 'not_configured') return rawInput;
168
+
169
+ // Build guidance text from the badge report.
170
+ const statements = Array.isArray(report.statements) ? report.statements : [];
171
+ const concerning = statements.filter(s =>
172
+ s && (s.badge === 'unsupported' || s.badge === 'disputed' || s.badge === 'rejected')
173
+ );
174
+
175
+ if (concerning.length === 0) return rawInput;
176
+
177
+ const lines = [
178
+ `UTTERANCE CHECK: ${concerning.length} statement(s) in this response lack evidence coverage.`,
179
+ `Summary: ${report.summary || 'unknown'}`,
180
+ ...concerning.slice(0, 4).map(s =>
181
+ ` - [${s.badge}] "${safeOneLineExcerpt(s.excerpt)}"`
182
+ ),
183
+ 'Evidence note: unsupported = no matching claim in the trust bundle; disputed = conflicting evidence; rejected = claim was rejected.',
184
+ 'Cite sources, note gaps, or run survey-utterance-check to record coverage.',
185
+ ];
186
+
187
+ // For PostToolUse: append guidance to the raw input as additional context.
188
+ // For Stop: report to stderr (non-blocking warning) unless strict mode.
189
+ const event = input.hook_event_name || '';
190
+ const guidance = '\n\n---\n' + lines.join('\n') + '\n---';
191
+
192
+ if (strict && result.status === 2) {
193
+ return {
194
+ stdout: rawInput,
195
+ stderr: lines.join('\n'),
196
+ exitCode: 2,
197
+ };
198
+ }
199
+
200
+ if (event === 'PostToolUse') {
201
+ return rawInput + guidance;
202
+ }
203
+
204
+ process.stderr.write(`[UtteranceCheck] ${lines.join('\n')}\n`);
205
+ return rawInput;
206
+ }
207
+
208
+ if (require.main === module) {
209
+ let data = '';
210
+ process.stdin.setEncoding('utf8');
211
+ process.stdin.on('data', chunk => {
212
+ if (data.length < MAX_STDIN) data += chunk.substring(0, MAX_STDIN - data.length);
213
+ });
214
+ process.stdin.on('end', () => {
215
+ const output = run(data);
216
+ if (output && typeof output === 'object') {
217
+ if (output.stderr) process.stderr.write(output.stderr.endsWith('\n') ? output.stderr : `${output.stderr}\n`);
218
+ process.stdout.write(String(output.stdout ?? data));
219
+ process.exit(Number.isInteger(output.exitCode) ? output.exitCode : 0);
220
+ }
221
+ process.stdout.write(String(output));
222
+ });
223
+ }
224
+
225
+ module.exports = { run, extractUtteranceText, findPackageRoot };
@@ -102,7 +102,7 @@ Gate: the opportunity is worth shaping, or it is parked/rejected.
102
102
 
103
103
  ### 4. Explore Options
104
104
 
105
- Use `knowledge-search`, `search-first`, `explore`, or `crowdsource` when context is missing.
105
+ Use `search-first` or `explore` when context is missing.
106
106
 
107
107
  Decide the path:
108
108