wip(freestyle/acr): grounding & edit-tool fixes + ACR-compiler experiment
This branch's uncommitted WIP, committed together (entangled at file level). Distinct pieces of work: Freestyle QA fixes (this session): - FileEditTool: pre-validate replace anchor in validateRequest — reject a missing/ambiguous target BEFORE the approval gate, mirroring read/write's file-not-found / read-before-write pre-checks. Shared not-found/ambiguous messages between validate and execute so they can't drift. - PlanGrounder: add `scanned` flag; when no RepoMapComputedEvent was recorded, repoMapPaths is "unknown" not "empty workspace" — skip scope grounding (which proves a path ABSENT) so real paths (apps/server/**) aren't falsely rejected. Build-manifest check still runs. - FreestyleDriver: wire scanned=(repoMap!=null); on plan rejection emit a session-terminal WorkflowFailedEvent so a rejected run reads FAILED, not the COMPLETED-lie (last verdict was the planning-phase WorkflowCompleted). - ServerModule: resolve project-memory workspace root from the session's bound workspace (sessionWorkspaceRoot) instead of boot-static pm.repoRoot(), fixing the workspace-binding divergence (correx vs empty scratch dir). Retire tracked in Vikunja #266. - LaunchRegistrationRaceTest: join registered jobs before asserting launchCount — computeIfAbsent returns the Job immediately but the fire-and-forget launch body lagged awaitAll (the 49-vs-50 flake). ACR concept-compiler experiment (pre-existing WIP on this branch): - ExecutionPlanCompiler/Model/PlanLinter, #264 needs-seam (sessionArtifacts), LSP diagnostics subsystem (LspDiagnosticEvents/Runner/Lsp4j), BootWorkspace, config surface, workflow prompts/schemas, orchestrator advance-don't-rerun. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@ Each TOML file in `workflows/` is a valid workflow loadable by the server. Keep
|
||||
- Add a new workflow example when a major new workflow type ships.
|
||||
- Prompts referenced by TOML files go in `workflows/prompts/`.
|
||||
- Freestyle architect prompts specify stage constraints, boundaries, and verification goals; they do not prescribe an exact resulting file list when the authoritative intent leaves implementation details open.
|
||||
- Freestyle discovery emits a structured comprehension brief; the analyst emits the addressable `dod` artifact used as the fixed implementation/review rubric.
|
||||
- Do not add configs/plugins/stages stubs speculatively — populate when there is real content.
|
||||
|
||||
## Verification
|
||||
|
||||
@@ -19,6 +19,11 @@ id = "analysis"
|
||||
schema_path = "schemas/analysis.json"
|
||||
llm_emitted = true
|
||||
|
||||
[[artifacts]]
|
||||
id = "dod"
|
||||
schema_path = "schemas/dod.json"
|
||||
llm_emitted = true
|
||||
|
||||
[[artifacts]]
|
||||
id = "design"
|
||||
schema_path = "schemas/design.json"
|
||||
|
||||
@@ -12,7 +12,7 @@ allowed_tools = ["file_read", "list_dir", "shell"]
|
||||
token_budget = 16384
|
||||
max_retries = 2
|
||||
|
||||
# analyst writes no files, but it owns task framing: task_search/task_context (read-only) find
|
||||
# analyst writes no files, but it owns task framing and the fixed definition of done:
|
||||
# existing work; task_create (T2, approval-gated — a task is an event-log entry, not a file write)
|
||||
# opens a single task; task_decompose (T2, one approval for the whole graph) splits a goal with
|
||||
# dependency seams into parent + DEPENDS_ON-linked children. Either way the analysis names the task
|
||||
@@ -20,7 +20,8 @@ max_retries = 2
|
||||
[[stages]]
|
||||
id = "analyst"
|
||||
prompt = "prompts/analyst_freestyle.md"
|
||||
produces = [{ name = "analysis", kind = "analysis" }]
|
||||
needs = ["discovery"]
|
||||
produces = [{ name = "dod", kind = "dod" }]
|
||||
allowed_tools = ["file_read", "list_dir", "shell", "task_search", "task_context", "task_create", "task_decompose"]
|
||||
token_budget = 16384
|
||||
max_retries = 2
|
||||
@@ -30,7 +31,7 @@ id = "architect"
|
||||
requires_approval = true
|
||||
inject_artifact_kinds = true
|
||||
prompt = "prompts/architect_freestyle.md"
|
||||
needs = ["analysis"]
|
||||
needs = ["dod"]
|
||||
produces = [{ name = "execution_plan", kind = "execution_plan" }]
|
||||
token_budget = 16384
|
||||
max_retries = 2
|
||||
@@ -47,7 +48,7 @@ id = "analyst-to-architect"
|
||||
from = "analyst"
|
||||
to = "architect"
|
||||
condition_type = "artifact_validated"
|
||||
condition_artifact_id = "analysis"
|
||||
condition_artifact_id = "dod"
|
||||
|
||||
[[transitions]]
|
||||
id = "architect-to-done"
|
||||
|
||||
@@ -1,33 +1,35 @@
|
||||
You are the **Analyst** in freestyle mode. Understand the user's goal (in the decision history
|
||||
above) and the code it touches. Read-only: `file_read` (also lists a directory's entries when
|
||||
given a directory path), `ls`, `grep`, `cat`, `find`.
|
||||
You are the **Analyst** in freestyle mode. Consume the structured discovery brief and operator
|
||||
answers in context, inspect the relevant code, and turn the settled request into one fixed,
|
||||
structured definition of done. Read-only tools: `file_read`, `list_dir`, `shell`, `task_search`,
|
||||
and `task_context`.
|
||||
|
||||
Before deriving requirements, check for existing work: `task_search` for related, duplicate, or
|
||||
blocking tasks and `task_context` to load any the goal names. Fold what you find into the
|
||||
analysis rather than re-deriving it; flag a duplicate instead of restating it.
|
||||
Before deriving criteria, check for existing work with `task_search` and load named work with
|
||||
`task_context`. Create or decompose a task only when needed by the existing task policy; include
|
||||
the single task this run owns in the DoD summary or criterion part so execution can thread it.
|
||||
|
||||
Then frame the work as a task (per the task policy):
|
||||
- If a task already covers this work, name its id (e.g. `auth-142`) in the analysis.
|
||||
- If the goal is a single coherent unit one run can carry to review, `task_create` one and name its
|
||||
id.
|
||||
- If the goal has **dependency seams** (a thing that must land before another) or **independent
|
||||
review/handoff points** (a piece worth shipping or reviewing on its own), `task_decompose` it into
|
||||
a parent epic + `DEPENDS_ON`-linked children — one approval for the whole graph. A session works
|
||||
one task at a time, so the children are claimed by *later* runs as they unblock; don't over-split.
|
||||
- After decomposing, **name in the analysis the single task this run will work** — the one already
|
||||
ready (no unmet dependency, e.g. the scaffold). Leave the blocked siblings for future runs.
|
||||
Emit the `dod` artifact once. Its criteria are the complete acceptance contract for this run:
|
||||
|
||||
Either way later stages thread the named task through the plan; the rest wait to be claimed.
|
||||
- Give every criterion a stable id (`c1`, `c2`, …), a checkable statement, and its feature area.
|
||||
- Tag mechanically checkable criteria `verified_by: "gate"` (compile, imports, typecheck/build,
|
||||
tests, required files). The reviewer must not adjudicate these.
|
||||
- Tag semantic or UX criteria `verified_by: "reviewer"`.
|
||||
- Copy discovery `brief.non_goals` into `out_of_scope`; this is a hard review boundary.
|
||||
- Cover the entire in-scope brief now. Later stages may not silently add criteria.
|
||||
|
||||
Produce the `analysis` artifact by calling the **`emit_artifact`** tool with these fields:
|
||||
- `summary`: the goal in your own words.
|
||||
- `requirements`: concrete, checkable requirements, one per line.
|
||||
- `affected_areas`: files/modules likely involved, one per line.
|
||||
Call `emit_artifact` with a JSON object matching this shape:
|
||||
`{"summary": string, "criteria": [{"id": string, "statement": string, "part": string,
|
||||
"verified_by": "gate" | "reviewer"}], "out_of_scope": [string]}`.
|
||||
|
||||
Call `emit_artifact` once you have read enough — do not write the JSON as a plain message.
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"summary": "Deliver the bounded validation gate for task gate-42.",
|
||||
"criteria": [
|
||||
{"id":"c1","statement":"The project typecheck passes before completion","part":"terminal gate","verified_by":"gate"},
|
||||
{"id":"c2","statement":"The operator sees the recorded diagnostic","part":"workflow UX","verified_by":"reviewer"}
|
||||
],
|
||||
"out_of_scope": ["Changing the workflow topology"]
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
Do not ask questions; discovery owns clarification. Do not design or implement.
|
||||
|
||||
@@ -7,8 +7,8 @@ message; call `emit_artifact` and nothing else.
|
||||
|
||||
## Inputs available to you
|
||||
|
||||
- `analysis` artifact — structured findings from the analyst stage (goal, constraints,
|
||||
risks, open questions).
|
||||
- `dod` artifact — the fixed acceptance contract from the analyst. Every implementation and
|
||||
review stage must consume it and must not widen it.
|
||||
- Decision history — the session's decision journal, including any user steering received
|
||||
at approval gates. User steering takes priority over your own judgment; honour it
|
||||
explicitly in the plan you emit.
|
||||
@@ -48,7 +48,7 @@ Emit a JSON object that validates against the `execution_plan` schema:
|
||||
|
||||
## Rules
|
||||
|
||||
**goal** — one sentence, derived from the analysis artifact and any user steering.
|
||||
**goal** — one sentence, derived from the DoD artifact and any user steering.
|
||||
|
||||
**stages** — ordered list; each stage must:
|
||||
- Have a unique `id` in `snake_case`.
|
||||
@@ -60,6 +60,8 @@ Emit a JSON object that validates against the `execution_plan` schema:
|
||||
llm-emitted kind.
|
||||
- Declare `needs`: every upstream artifact id the stage's prompt references. Every id in
|
||||
`needs` must be `produces`d by a strictly earlier stage.
|
||||
- Every implementation and review stage must include the session-scoped `dod` artifact in
|
||||
`needs`. The compiler also enforces this seam.
|
||||
- Include `tools` per stage as it needs them, using only names from this set:
|
||||
`file_read`, `file_write`, `file_edit`, `list_dir`, `shell`, `task_context`, `task_update`,
|
||||
`task_search`. Stages that write or edit files take the file set
|
||||
@@ -141,12 +143,15 @@ Emit a JSON object that validates against the `execution_plan` schema:
|
||||
`field`: `"verdict"`, `value`: `"approved"`, `operator`: `"eq"` → `to: "done"`
|
||||
- `"type": "artifact_field_equals"`, `artifact_id`: reviewer's produces id,
|
||||
`field`: `"verdict"`, `value`: `"approved"`, `operator`: `"neq"` → `to: "<implement_stage_id>"`
|
||||
- A reviewer prompt must use only DoD rows tagged `verified_by: reviewer`: approve iff every such
|
||||
row is met and no `out_of_scope` item was introduced. It must cite failed criterion ids and may
|
||||
not invent new requirements. Criteria tagged `gate` are already decided upstream.
|
||||
- Every stage except the first must have an inbound edge from an earlier stage; every
|
||||
stage must have an outbound edge. Unreachable stages fail to compile.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Do not add stages, roles, or tools not justified by the analysis artifact.
|
||||
- Do not add stages, roles, or tools not justified by the DoD artifact.
|
||||
- Do not reference artifact ids that no stage in this plan produces (except ids that
|
||||
pre-exist in the session, such as `analysis`).
|
||||
- The plan is locked once emitted; the implementer stages will execute it verbatim. Be
|
||||
|
||||
@@ -22,24 +22,47 @@ Two checks, both grounded in what you actually read:
|
||||
endpoint.
|
||||
|
||||
Emit the `discovery` artifact by calling **`emit_artifact`** with:
|
||||
- `brief`: the complete comprehension brief. Populate `what`, `why`, `who`, `scope`,
|
||||
`non_goals`, `constraints`, and `assumptions` even when questions remain. Use assumptions for
|
||||
reasonable, visible defaults instead of parking on micro-decisions.
|
||||
- `ready`: `true` when the request is clear and grounded enough to hand to the analyst; `false`
|
||||
when you are raising questions.
|
||||
- `questions`: the open questions (empty when `ready` is true). Batch **all** of them into this
|
||||
one list — do not ask one at a time. Each entry is an object:
|
||||
- `prompt` (required): the question, in full.
|
||||
- `options` (optional): suggested answers as strings, whenever the answer is a choice among
|
||||
known alternatives.
|
||||
- `options` (**required whenever the answer is a choice among known alternatives** — and it
|
||||
almost always is: stack, library, endpoint, layout, priority are all choices among things you
|
||||
can name). Provide 2–4 concrete prefilled answers as strings. An open-ended question with no
|
||||
`options` is only acceptable when no candidate set exists at all. Empty `options` on a
|
||||
choice-question is a defect — enumerate the real candidates you found in the repo.
|
||||
- `multiSelect` (optional, default false): true if more than one option may apply.
|
||||
- `header` (optional): a 1–2 word label (e.g. "Scope", "Stack", "Endpoint").
|
||||
|
||||
**Default to proceeding.** For a clear, well-grounded request this stage is pure overhead —
|
||||
emit `{"ready": true, "questions": []}` and let the analyst take over. Only ask when a genuine
|
||||
**Default to proceeding.** First inspect enough of the repository to enumerate the whole question
|
||||
surface, then ask every genuine operator-only question in one batch. For a clear, well-grounded
|
||||
request, proceed with visible assumptions. Do not raise one question, re-enter, and discover
|
||||
another question that the same initial inspection could have exposed.
|
||||
|
||||
**Converge once answered.** If the decision history above already contains the operator's answers
|
||||
to your questions, you are done vetting — emit the brief with `ready: true` and empty `questions`.
|
||||
Do NOT re-explore the repo hunting for new questions after the operator has answered; fold their
|
||||
answers into the brief and hand off. You get **one** clarification round: ask everything up front,
|
||||
then commit. Endless re-inspection is a failure, not diligence.
|
||||
fork or contradiction blocks planning. Do not nag, and do not re-ask what the operator has
|
||||
already answered in the decision history above.
|
||||
|
||||
Example (needs input):
|
||||
```json
|
||||
{
|
||||
"brief": {
|
||||
"what": "Build a browser client for the existing session API.",
|
||||
"why": "Operators need a visual session surface.",
|
||||
"who": ["operators"],
|
||||
"scope": ["browser session client"],
|
||||
"non_goals": ["server protocol redesign"],
|
||||
"constraints": ["reuse the existing endpoint"],
|
||||
"assumptions": []
|
||||
},
|
||||
"ready": false,
|
||||
"questions": [
|
||||
{"prompt": "Which frontend stack should the UI target?",
|
||||
@@ -52,5 +75,17 @@ Example (needs input):
|
||||
|
||||
Example (clear — the common case):
|
||||
```json
|
||||
{ "ready": true, "questions": [] }
|
||||
{
|
||||
"brief": {
|
||||
"what": "Add the requested deterministic validation gate.",
|
||||
"why": "Prevent invalid output from reaching review.",
|
||||
"who": ["workflow authors", "operators"],
|
||||
"scope": ["gate execution and recorded verdict"],
|
||||
"non_goals": ["workflow topology redesign"],
|
||||
"constraints": ["replay uses recorded observations"],
|
||||
"assumptions": ["existing event-store contracts remain authoritative"]
|
||||
},
|
||||
"ready": true,
|
||||
"questions": []
|
||||
}
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user