Skip to content

gaia inquiry

Local semantic-inquiry loop for a package. Inquiry state is stored in .gaia state files; these commands do not edit Python source, compiled IR, priors, or beliefs.

gaia inquiry focus <target>                Set or clear the current focus claim
gaia inquiry context [path]                Render focus-centered agent context
gaia inquiry review [path]                 Run the inquiry review pipeline
gaia inquiry obligation add <qid> ...      Record an open obligation
gaia inquiry obligation list               List open obligations
gaia inquiry obligation close <qid>        Close an obligation
gaia inquiry hypothesis add "<text>"       Record a working hypothesis
gaia inquiry hypothesis list               List active hypotheses
gaia inquiry hypothesis remove <id>        Remove a hypothesis
gaia inquiry tactics log                   Print the tactic event log
gaia inquiry reject <label> ...            Reject a strategy path with a reason
Verb Purpose
focus Track or clear the current focus claim for the inquiry loop
context Render a read-only focus-centered context packet as Markdown, or a JSON envelope with an IR slice
review Run the inquiry review pipeline; emit text, JSON, or Markdown
obligation Add / list / close open obligations
hypothesis Add / list / remove working hypotheses
tactics log Print the tactic event log for the package
reject Mark a strategy path as rejected with a reason

Option flags on gaia inquiry review (--mode, --focus, --depth, --since, --json, --markdown, --strict, --no-infer) match the pre-alpha-0 surface exactly. See CLI Commands for examples.

The engine-side data model lives at gaia.engine.inquiry.

Context packets

gaia inquiry context renders the current focus claim and the selected reasoning trajectory behind it. Markdown is the default because the output is intended for agent context. Use --json for tools; the JSON output is a thin envelope whose ir field is a Gaia IR-shaped slice.

gaia inquiry focus acceleration_inquiry
gaia inquiry context .
gaia inquiry context . --trajectory shortest
gaia inquiry context . --focus acceleration_inquiry --json

The command is read-only: it does not save inquiry state, append tactic events, run inference, or display beliefs.

--trajectory most_uncertain and --trajectory shortest select from the currently enumerable backward support routes for the focus claim. Very large packages with many parallel routes may need route caps or beam-style selection in a future release.

Implementation

gaia.cli.commands.inquiry

gaia inquiry — public CLI sub-app.

Public commands per spec §G3: focus, review. Additional ProofState-extension subcommands (Round A1) exposed under inquiry obligation / hypothesis / reject / tactics to support Lean-like reasoning process tracking, all implemented via state.json only — no mutation of .py source / IR / priors / beliefs.

focus_command

focus_command(target: str | None = typer.Argument(None, help='Focus target.'), clear: bool = typer.Option(False, '--clear', help='Clear current focus.'), push: bool = typer.Option(False, '--push', help='Push current focus and set new.'), pop: bool = typer.Option(False, '--pop', help='Pop saved focus off the stack.'), show_stack: bool = typer.Option(False, '--stack', help='Print focus stack.'), path: str = typer.Option('.', '--path', help='Package path.')) -> None

Inspect or update the current inquiry focus.

The inquiry focus is a state.json-only pointer to the claim, strategy, or freeform topic the agent is currently working on. focus is inspect-or-set: no args reads the current focus; a bare TARGET sets it. The focus stack supports push/pop semantics so the agent can dive into a sub-claim and resume.

Example:

.. code-block:: bash

# Read current focus:
gaia inquiry focus

# Set focus to a claim label:
gaia inquiry focus my_claim_label

# Push current focus and switch to a new one:
gaia inquiry focus other_claim --push

# Pop back to the prior focus:
gaia inquiry focus --pop

# Inspect the focus stack:
gaia inquiry focus --stack

# Clear the focus entirely:
gaia inquiry focus --clear
Source code in gaia/cli/commands/inquiry.py
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
@inquiry_app.command("focus")
def focus_command(
    target: str | None = typer.Argument(None, help="Focus target."),
    clear: bool = typer.Option(False, "--clear", help="Clear current focus."),
    push: bool = typer.Option(False, "--push", help="Push current focus and set new."),
    pop: bool = typer.Option(False, "--pop", help="Pop saved focus off the stack."),
    show_stack: bool = typer.Option(False, "--stack", help="Print focus stack."),
    # NB: no ``--target`` alias here — this command already takes a positional
    # ``target`` (the focus subject), so ``--target`` would clash with that
    # established meaning. Package selection stays ``--path`` only.
    path: str = typer.Option(".", "--path", help="Package path."),
) -> None:
    """Inspect or update the current inquiry focus.

    The inquiry focus is a state.json-only pointer to the claim, strategy,
    or freeform topic the agent is currently working on. ``focus`` is
    inspect-or-set: no args reads the current focus; a bare ``TARGET``
    sets it. The focus stack supports push/pop semantics so the agent
    can dive into a sub-claim and resume.

    Example:

    .. code-block:: bash

        # Read current focus:
        gaia inquiry focus

        # Set focus to a claim label:
        gaia inquiry focus my_claim_label

        # Push current focus and switch to a new one:
        gaia inquiry focus other_claim --push

        # Pop back to the prior focus:
        gaia inquiry focus --pop

        # Inspect the focus stack:
        gaia inquiry focus --stack

        # Clear the focus entirely:
        gaia inquiry focus --clear
    """
    flags_set = sum([bool(clear), bool(push), bool(pop), bool(show_stack)])
    if flags_set > 1:
        typer.echo("Error: --clear/--push/--pop/--stack are mutually exclusive.", err=True)
        raise typer.Exit(2)

    state = load_state(path)
    graph = resolve_graph(path)

    if clear:
        push_focus_frame(state)  # preserve history — not required but cheap
        state.focus_stack.pop()  # we actually don't want the frame on clear
        state.focus = None
        state.focus_kind = None
        state.focus_resolved_id = None
        save_state(path, state)
        append_tactic_event(path, "focus_clear", {})
        typer.echo("focus cleared")
        return

    if show_stack:
        typer.echo(f"current: {state.focus or '(none)'}")
        if not state.focus_stack:
            typer.echo("stack: (empty)")
            return
        typer.echo("stack (top → bottom):")
        for frame in reversed(state.focus_stack):
            raw = frame.get("focus") or "(none)"
            kind = frame.get("focus_kind") or "none"
            typer.echo(f"  - {raw}  [{kind}]")
        return

    if pop:
        if not state.focus_stack:
            typer.echo("Error: focus stack is empty.", err=True)
            raise typer.Exit(1)
        old = pop_focus_frame(state)
        save_state(path, state)
        append_tactic_event(
            path,
            "focus_pop",
            {"old": (old or {}).get("focus"), "restored": state.focus},
        )
        typer.echo(f"popped: {(old or {}).get('focus')}{state.focus or '(none)'}")
        return

    if push:
        if not target:
            typer.echo("Error: --push requires a TARGET.", err=True)
            raise typer.Exit(2)
        push_focus_frame(state)
        binding = resolve_focus_target(target, graph)
        state.focus = binding.raw
        state.focus_kind = binding.kind
        state.focus_resolved_id = binding.resolved_id
        save_state(path, state)
        append_tactic_event(path, "focus_push", {"target": target})
        typer.echo(f"focus pushed: {target}")
        return

    if target is None:
        if state.focus is None:
            typer.echo("focus: (none)")
        else:
            kind = state.focus_kind or "freeform"
            rid = state.focus_resolved_id
            suffix = f"; id={rid}" if rid else ""
            typer.echo(f"focus: {state.focus}  [{kind}{suffix}]")
        return

    binding = resolve_focus_target(target, graph)
    state.focus = binding.raw
    state.focus_kind = binding.kind
    state.focus_resolved_id = binding.resolved_id
    save_state(path, state)
    append_tactic_event(path, "focus_set", {"target": target, "kind": binding.kind})
    typer.echo(f"focus set: {binding.raw}  [{binding.kind}]")

obligation_add

obligation_add(target_qid: str = typer.Argument(..., help='QID the obligation is about.'), content: str = typer.Option(..., '-c', '--content', help='What must be shown.'), kind: str = typer.Option('other', '--kind', help='Diagnostic kind.'), path: str = typer.Option('.', '--path', '--target', help='Package path (--target accepted as an alias).')) -> None

Add a synthetic obligation for an inquiry target.

Records a "what must be shown" note against a target QID — a ProofState-style obligation visible in the inquiry review report. Pure state.json mutation; no IR / priors / beliefs change.

--kind selects the diagnostic kind; allowed values are focus_weakness / other (default) / prior_hole / structural_hole / support_weak (see VALID_OBLIGATION_KINDS in :mod:gaia.engine.inquiry.state).

Example:

.. code-block:: bash

gaia inquiry obligation add my_claim \
    -c "Show the prior is justified by the cited evidence." \
    --kind prior_hole
Source code in gaia/cli/commands/inquiry.py
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
@obligation_app.command("add")
def obligation_add(
    target_qid: str = typer.Argument(..., help="QID the obligation is about."),
    content: str = typer.Option(..., "-c", "--content", help="What must be shown."),
    kind: str = typer.Option("other", "--kind", help="Diagnostic kind."),
    path: str = typer.Option(
        ".", "--path", "--target", help="Package path (--target accepted as an alias)."
    ),
) -> None:
    r"""Add a synthetic obligation for an inquiry target.

    Records a "what must be shown" note against a target QID — a
    ProofState-style obligation visible in the inquiry review report.
    Pure state.json mutation; no IR / priors / beliefs change.

    ``--kind`` selects the diagnostic kind; allowed values are
    ``focus_weakness`` / ``other`` (default) / ``prior_hole`` /
    ``structural_hole`` / ``support_weak`` (see ``VALID_OBLIGATION_KINDS``
    in :mod:`gaia.engine.inquiry.state`).

    Example:

    .. code-block:: bash

        gaia inquiry obligation add my_claim \
            -c "Show the prior is justified by the cited evidence." \
            --kind prior_hole
    """
    if kind not in VALID_OBLIGATION_KINDS:
        typer.echo(
            f"Error: invalid --kind {kind!r}; allowed: {sorted(VALID_OBLIGATION_KINDS)}",
            err=True,
        )
        raise typer.Exit(2)

    state = load_state(path)
    qid = mint_qid("oblig")
    state.synthetic_obligations.append(
        SyntheticObligation(qid=qid, target_qid=target_qid, content=content, diagnostic_kind=kind)
    )
    save_state(path, state)
    append_tactic_event(
        path,
        "obligation_add",
        {"qid": qid, "target_qid": target_qid, "kind": kind},
    )
    typer.echo(f"obligation added {qid}")

obligation_list

obligation_list(json_out: bool = typer.Option(False, '--json'), path: str = typer.Option('.', '--path', '--target')) -> None

List open synthetic obligations.

Prints every open obligation (kind, qid, target_qid, content) in state.json. --json emits the same rows as a JSON array for machine consumption.

Example:

.. code-block:: bash

gaia inquiry obligation list
gaia inquiry obligation list --json
Source code in gaia/cli/commands/inquiry.py
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
@obligation_app.command("list")
def obligation_list(
    json_out: bool = typer.Option(False, "--json"),
    path: str = typer.Option(".", "--path", "--target"),
) -> None:
    """List open synthetic obligations.

    Prints every open obligation (kind, qid, target_qid, content) in
    state.json. ``--json`` emits the same rows as a JSON array for
    machine consumption.

    Example:

    .. code-block:: bash

        gaia inquiry obligation list
        gaia inquiry obligation list --json
    """
    state = load_state(path)
    rows = [
        {
            "qid": o.qid,
            "target_qid": o.target_qid,
            "content": o.content,
            "diagnostic_kind": o.diagnostic_kind,
            "created_at": o.created_at,
        }
        for o in state.synthetic_obligations
    ]
    if json_out:
        typer.echo(json.dumps(rows, ensure_ascii=False, indent=2))
        return
    if not rows:
        typer.echo("(no open obligations)")
        return
    for r in rows:
        typer.echo(f"- [{r['diagnostic_kind']}] {r['qid']}{r['target_qid']}: {r['content']}")

obligation_close

obligation_close(qid: str = typer.Argument(...), path: str = typer.Option('.', '--path', '--target')) -> None

Close a synthetic obligation by QID.

Removes the obligation with the given QID from state.json; logs a obligation_close tactic event. Exits non-zero when no obligation matches the QID. QIDs are printed by gaia inquiry obligation list.

Example:

.. code-block:: bash

gaia inquiry obligation close oblig_a1b2c3d4
Source code in gaia/cli/commands/inquiry.py
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
@obligation_app.command("close")
def obligation_close(
    qid: str = typer.Argument(...),
    path: str = typer.Option(".", "--path", "--target"),
) -> None:
    """Close a synthetic obligation by QID.

    Removes the obligation with the given QID from state.json; logs a
    ``obligation_close`` tactic event. Exits non-zero when no obligation
    matches the QID. QIDs are printed by ``gaia inquiry obligation
    list``.

    Example:

    .. code-block:: bash

        gaia inquiry obligation close oblig_a1b2c3d4
    """
    state = load_state(path)
    before = len(state.synthetic_obligations)
    state.synthetic_obligations = [o for o in state.synthetic_obligations if o.qid != qid]
    if len(state.synthetic_obligations) == before:
        typer.echo(f"Error: no obligation with qid {qid!r}.", err=True)
        raise typer.Exit(1)
    save_state(path, state)
    append_tactic_event(path, "obligation_close", {"qid": qid})
    typer.echo(f"obligation closed {qid}")

hypothesis_add

hypothesis_add(content: str = typer.Argument(..., help='Hypothesis content.'), scope: str | None = typer.Option(None, '--scope', help='Scope QID.'), path: str = typer.Option('.', '--path', '--target')) -> None

Add a working hypothesis to inquiry state.

Records a tentative hypothesis the agent wants to track during inquiry without committing it to the DSL / IR. Pure state.json mutation. --scope optionally pins the hypothesis to a specific target QID so the inquiry review report can group it with related claims.

Example:

.. code-block:: bash

gaia inquiry hypothesis add "The prior is robust to ±10% perturbation."
gaia inquiry hypothesis add "Bayes factor > 10 under diffuse alt." \
    --scope my_claim_id
Source code in gaia/cli/commands/inquiry.py
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
@hypothesis_app.command("add")
def hypothesis_add(
    content: str = typer.Argument(..., help="Hypothesis content."),
    scope: str | None = typer.Option(None, "--scope", help="Scope QID."),
    path: str = typer.Option(".", "--path", "--target"),
) -> None:
    r"""Add a working hypothesis to inquiry state.

    Records a tentative hypothesis the agent wants to track during
    inquiry without committing it to the DSL / IR. Pure state.json
    mutation. ``--scope`` optionally pins the hypothesis to a specific
    target QID so the inquiry review report can group it with related
    claims.

    Example:

    .. code-block:: bash

        gaia inquiry hypothesis add "The prior is robust to ±10% perturbation."
        gaia inquiry hypothesis add "Bayes factor > 10 under diffuse alt." \
            --scope my_claim_id
    """
    state = load_state(path)
    qid = mint_qid("hyp")
    state.synthetic_hypotheses.append(
        SyntheticHypothesis(qid=qid, content=content, scope_qid=scope)
    )
    save_state(path, state)
    append_tactic_event(path, "hypothesis_add", {"qid": qid, "scope": scope})
    typer.echo(f"hypothesis added {qid}")

hypothesis_list

hypothesis_list(json_out: bool = typer.Option(False, '--json'), path: str = typer.Option('.', '--path', '--target')) -> None

List working hypotheses from inquiry state.

Prints every recorded hypothesis (qid, scope_qid, content) from state.json. --json emits a JSON array for machine consumption.

Example:

.. code-block:: bash

gaia inquiry hypothesis list
gaia inquiry hypothesis list --json
Source code in gaia/cli/commands/inquiry.py
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
@hypothesis_app.command("list")
def hypothesis_list(
    json_out: bool = typer.Option(False, "--json"),
    path: str = typer.Option(".", "--path", "--target"),
) -> None:
    """List working hypotheses from inquiry state.

    Prints every recorded hypothesis (qid, scope_qid, content) from
    state.json. ``--json`` emits a JSON array for machine consumption.

    Example:

    .. code-block:: bash

        gaia inquiry hypothesis list
        gaia inquiry hypothesis list --json
    """
    state = load_state(path)
    rows = [
        {
            "qid": h.qid,
            "content": h.content,
            "scope_qid": h.scope_qid,
            "created_at": h.created_at,
        }
        for h in state.synthetic_hypotheses
    ]
    if json_out:
        typer.echo(json.dumps(rows, ensure_ascii=False, indent=2))
        return
    if not rows:
        typer.echo("(no hypotheses)")
        return
    for r in rows:
        scope = f" @ {r['scope_qid']}" if r["scope_qid"] else ""
        typer.echo(f"- {r['qid']}{scope}: {r['content']}")

hypothesis_remove

hypothesis_remove(qid: str = typer.Argument(...), path: str = typer.Option('.', '--path', '--target')) -> None

Remove a working hypothesis by QID.

Drops the hypothesis with the given QID from state.json. QIDs are printed by gaia inquiry hypothesis list.

Example:

.. code-block:: bash

gaia inquiry hypothesis remove hyp_a1b2c3d4
Source code in gaia/cli/commands/inquiry.py
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
@hypothesis_app.command("remove")
def hypothesis_remove(
    qid: str = typer.Argument(...),
    path: str = typer.Option(".", "--path", "--target"),
) -> None:
    """Remove a working hypothesis by QID.

    Drops the hypothesis with the given QID from state.json. QIDs are
    printed by ``gaia inquiry hypothesis list``.

    Example:

    .. code-block:: bash

        gaia inquiry hypothesis remove hyp_a1b2c3d4
    """
    state = load_state(path)
    before = len(state.synthetic_hypotheses)
    state.synthetic_hypotheses = [h for h in state.synthetic_hypotheses if h.qid != qid]
    if len(state.synthetic_hypotheses) == before:
        typer.echo(f"Error: no hypothesis with qid {qid!r}.", err=True)
        raise typer.Exit(1)
    save_state(path, state)
    append_tactic_event(path, "hypothesis_remove", {"qid": qid})
    typer.echo(f"hypothesis removed {qid}")

reject_command

reject_command(strategy: str = typer.Argument(..., help='Target strategy label/id.'), content: str = typer.Option(..., '-c', '--content', help='Reason.'), path: str = typer.Option('.', '--path', '--target')) -> None

Record a synthetic rejection for a strategy.

Annotates a strategy with a free-text rejection note (e.g. "the cited evidence does not actually support this", "argument is circular"). Pure state.json mutation; the strategy stays in the IR. Useful as a self-review affordance: a rejected strategy can later be revisited or removed from the DSL source.

Example:

.. code-block:: bash

gaia inquiry reject my_strategy_label \
    -c "Premise A is not yet established."
Source code in gaia/cli/commands/inquiry.py
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
@inquiry_app.command("reject")
def reject_command(
    strategy: str = typer.Argument(..., help="Target strategy label/id."),
    content: str = typer.Option(..., "-c", "--content", help="Reason."),
    path: str = typer.Option(".", "--path", "--target"),
) -> None:
    r"""Record a synthetic rejection for a strategy.

    Annotates a strategy with a free-text rejection note (e.g. "the
    cited evidence does not actually support this", "argument is
    circular"). Pure state.json mutation; the strategy stays in the IR.
    Useful as a self-review affordance: a rejected strategy can later be
    revisited or removed from the DSL source.

    Example:

    .. code-block:: bash

        gaia inquiry reject my_strategy_label \
            -c "Premise A is not yet established."
    """
    state = load_state(path)
    qid = mint_qid("rej")
    state.synthetic_rejections.append(
        SyntheticRejection(qid=qid, target_strategy=strategy, content=content)
    )
    save_state(path, state)
    append_tactic_event(path, "reject", {"qid": qid, "strategy": strategy})
    typer.echo(f"strategy rejected {qid} ({strategy})")

tactics_log

tactics_log(json_out: bool = typer.Option(False, '--json'), path: str = typer.Option('.', '--path', '--target')) -> None

Print the inquiry tactic event log.

Renders the append-only audit log of every inquiry state mutation (focus / obligation / hypothesis / reject / review events) in chronological order. --json emits one JSON record per entry for machine consumption.

Example:

.. code-block:: bash

gaia inquiry tactics log
gaia inquiry tactics log --json
Source code in gaia/cli/commands/inquiry.py
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
@tactics_app.command("log")
def tactics_log(
    json_out: bool = typer.Option(False, "--json"),
    path: str = typer.Option(".", "--path", "--target"),
) -> None:
    """Print the inquiry tactic event log.

    Renders the append-only audit log of every inquiry state mutation
    (focus / obligation / hypothesis / reject / review events) in
    chronological order. ``--json`` emits one JSON record per entry for
    machine consumption.

    Example:

    .. code-block:: bash

        gaia inquiry tactics log
        gaia inquiry tactics log --json
    """
    from gaia.engine.inquiry.state import read_tactic_log

    rows = read_tactic_log(path)
    if json_out:
        typer.echo(json.dumps(rows, ensure_ascii=False, indent=2))
        return
    if not rows:
        typer.echo("(no tactic log entries)")
        return
    for rec in rows:
        ts = rec.get("timestamp", "")
        ev = rec.get("event", "")
        payload = rec.get("payload", {})
        typer.echo(f"{ts}  {ev}  {json.dumps(payload, ensure_ascii=False)}")

context_command

context_command(path: str = typer.Argument('.', help='Package path.'), focus_: str | None = typer.Option(None, '--focus'), trajectory: str = typer.Option('most_uncertain', '--trajectory'), order: str = typer.Option('backward', '--order'), json_out: bool = typer.Option(False, '--json')) -> None

Render a focus-centered context packet for the current inquiry claim.

Source code in gaia/cli/commands/inquiry.py
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
@inquiry_app.command("context")
def context_command(
    path: str = typer.Argument(".", help="Package path."),
    focus_: str | None = typer.Option(None, "--focus"),
    trajectory: str = typer.Option("most_uncertain", "--trajectory"),
    order: str = typer.Option("backward", "--order"),
    json_out: bool = typer.Option(False, "--json"),
) -> None:
    """Render a focus-centered context packet for the current inquiry claim."""
    if trajectory not in {"most_uncertain", "shortest"}:
        typer.echo(
            "Error: --trajectory must be one of: most_uncertain, shortest.",
            err=True,
        )
        raise typer.Exit(2)
    if order not in {"backward", "forward"}:
        typer.echo("Error: --order must be one of: backward, forward.", err=True)
        raise typer.Exit(2)

    try:
        packet = build_context_packet(
            path,
            focus_override=focus_,
            trajectory=cast(TrajectorySelector, trajectory),
            order=cast(RenderOrder, order),
        )
    except ValueError as exc:
        typer.echo(str(exc), err=True)
        raise typer.Exit(2) from exc
    except Exception as exc:
        typer.echo(f"Error: {exc}", err=True)
        raise typer.Exit(1) from exc

    if json_out:
        typer.echo(json.dumps(context_to_json_dict(packet), ensure_ascii=False, indent=2))
    else:
        typer.echo(render_context_markdown(packet), nl=False)

review_command

review_command(path: str = typer.Argument('.', help='Package path.'), focus_: str | None = typer.Option(None, '--focus'), mode: str = typer.Option('auto', '--mode'), no_infer: bool = typer.Option(False, '--no-infer'), depth: int = typer.Option(0, '--depth'), since: str | None = typer.Option(None, '--since'), json_out: bool = typer.Option(False, '--json'), markdown_out: bool = typer.Option(False, '--markdown'), strict: bool = typer.Option(False, '--strict')) -> None

Run the local semantic inquiry review.

Executes the semantic inquiry loop on the package: graph-health diagnostics, focus-relevance analysis, optional inference, and publish-readiness blockers. Writes a timestamped review artifact under .gaia/inquiry/reviews/.

--mode selects the review profile:

  • auto (default) — pick a profile based on the package state
  • formalize — emphasise scaffolded / unformalised claims
  • explore — bias towards weak / orphaned claims
  • verify — emphasise prior coherence + Bayes diagnostics
  • publish — full publish-readiness gates (combine with --strict)

--no-infer skips the BP inference step (faster but misses belief-derived diagnostics); --depth N controls dependency inference depth as in gaia run infer.

Example:

.. code-block:: bash

gaia inquiry review .
gaia inquiry review . --mode publish --strict
gaia inquiry review . --no-infer --json
gaia inquiry review . --focus my_claim_label --markdown > review.md
Source code in gaia/cli/commands/inquiry.py
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
@inquiry_app.command("review")
def review_command(
    path: str = typer.Argument(".", help="Package path."),
    focus_: str | None = typer.Option(None, "--focus"),
    mode: str = typer.Option("auto", "--mode"),
    no_infer: bool = typer.Option(False, "--no-infer"),
    depth: int = typer.Option(0, "--depth"),
    since: str | None = typer.Option(None, "--since"),
    json_out: bool = typer.Option(False, "--json"),
    markdown_out: bool = typer.Option(False, "--markdown"),
    strict: bool = typer.Option(False, "--strict"),
) -> None:
    """Run the local semantic inquiry review.

    Executes the semantic inquiry loop on the package: graph-health
    diagnostics, focus-relevance analysis, optional inference, and
    publish-readiness blockers. Writes a timestamped review artifact
    under ``.gaia/inquiry/reviews/``.

    ``--mode`` selects the review profile:

    * ``auto`` (default) — pick a profile based on the package state
    * ``formalize`` — emphasise scaffolded / unformalised claims
    * ``explore`` — bias towards weak / orphaned claims
    * ``verify`` — emphasise prior coherence + Bayes diagnostics
    * ``publish`` — full publish-readiness gates (combine with ``--strict``)

    ``--no-infer`` skips the BP inference step (faster but misses
    belief-derived diagnostics); ``--depth N`` controls dependency
    inference depth as in ``gaia run infer``.

    Example:

    .. code-block:: bash

        gaia inquiry review .
        gaia inquiry review . --mode publish --strict
        gaia inquiry review . --no-infer --json
        gaia inquiry review . --focus my_claim_label --markdown > review.md
    """
    if mode not in {"auto", "formalize", "explore", "verify", "publish"}:
        typer.echo(f"Error: invalid --mode {mode!r}.", err=True)
        raise typer.Exit(2)
    if json_out and markdown_out:
        typer.echo("Error: --json and --markdown are mutually exclusive.", err=True)
        raise typer.Exit(2)

    report = run_review(
        path,
        focus_override=focus_,
        mode=mode,
        no_infer=no_infer,
        depth=depth,
        since=since,
        strict=strict,
    )
    append_tactic_event(
        Path(path),
        "review",
        {"review_id": report.review_id, "mode": mode, "no_infer": no_infer},
    )

    if json_out:
        typer.echo(json.dumps(report.to_json_dict(), ensure_ascii=False, indent=2))
    elif markdown_out:
        typer.echo(render_markdown(report))
    else:
        typer.echo(render_text(report))

    if report.graph_health.get("errors"):
        raise typer.Exit(1)
    if strict and mode == "publish":
        blockers = publish_blockers(report)
        if blockers:
            for b in blockers:
                typer.echo(f"[publish-strict] {b}", err=True)
            raise typer.Exit(1)
    elif strict and report.graph_health.get("warnings"):
        raise typer.Exit(1)