feat(guardrails): steering channel + shell-in-file rule + capability-gap detector
Bundles three operator-reliability guardrails (Vikunja #28/#29/#30) plus the in-flight branch WIP they were built on top of (reasoning_content capture, operator/project profile editor, write-jail workspaceRoot fix) — the tree is interdependent (SessionOrchestrator references reasoningArtifactId from the WIP) and does not compile as separable subsets, so it lands as one commit. Guardrails: - #28 mid-stage steering: ClientMessage.SteerSession -> GlobalStreamHandler -> orchestrator.submitSteering, reusing SteeringNoteAddedEvent + existing context fold (advisory, non-authoritative; invariants #3/#7). Closes the gap where steering typed off an approval gate was silently dropped. - #29 shell-in-file guardrail: ShellInFileContentRule (core:toolintent) blocks a file_write whose content is a bare shell command (e.g. "mkdir -p ..."); FileWriteTool description now advertises auto-mkdir of parent dirs. Basename-allowlist so the extensionless case is caught; scripts/Makefiles/multiline exempt. - #30 pt1 capability-gap detector: deterministic CapabilityGapDetector maps stage intent -> implied ToolCapability, compares to granted tools, emits advisory CapabilityGapDetectedEvent in FreestyleDriver.lockAndRun. Recorded, never fails the gate and never auto-grants (invariants #3/#4/#5). Reflection rung is pt2. Verified: ./gradlew check green (whole tree).
This commit is contained in:
@@ -26,32 +26,8 @@ Produce the `analysis` artifact by calling the **`emit_artifact`** tool with the
|
||||
|
||||
Call `emit_artifact` once you have read enough — do not write the JSON as a plain message.
|
||||
|
||||
If — and only if — something genuinely blocks a plan (an ambiguous goal, a missing decision, a
|
||||
fork only the user can resolve), add a `questions` array. Each entry is an object:
|
||||
- `prompt` (required): the question, in full.
|
||||
- `options` (optional): an array of suggested answers as strings. Offer these whenever the
|
||||
answer is a choice among known alternatives.
|
||||
- `multiSelect` (optional, default false): true if more than one option may apply.
|
||||
- `header` (optional): a 1–2 word label for the question (e.g. "Scope", "Stack").
|
||||
|
||||
Greenfield or new-surface work (a new UI, app, or module) almost always hides such a fork even
|
||||
when the high-level goal is clear: the build tool, styling approach, state/routing libraries, or
|
||||
component library are choices only the user can pin, and a placeholder instruction does not
|
||||
resolve them. Ask about stack/tooling in that case rather than guessing.
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"summary": "...",
|
||||
"requirements": ["..."],
|
||||
"affected_areas": ["..."],
|
||||
"questions": [
|
||||
{"prompt": "Which frontend stack should the UI target?",
|
||||
"options": ["React", "Vue", "Svelte"], "header": "Stack"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Ask nothing you can answer yourself by reading the code. Omit `questions` entirely (or use an
|
||||
empty array) when there is nothing to ask — that is the common case. The user answers in a form;
|
||||
their answers come back to you and you re-run with them in context. Do not design or plan yet.
|
||||
Always produce the analysis — this is your single exit. Open questions and operator forks are the
|
||||
**Discovery** stage's job, and it has already run before you: any ambiguity or contradiction the
|
||||
user needed to resolve was raised and answered upstream, and those answers are in the decision
|
||||
history above. Treat the request as settled, ground your requirements in what you actually read,
|
||||
and do not ask the user anything. Do not design or plan yet.
|
||||
|
||||
Reference in New Issue
Block a user