add clide status orientation snapshot; close Epic C (T-221)
clide status returns a one-shot snapshot for an orienting agent: the workspace root, a git summary (branch/ahead/behind/clean/counts), the active editor buffer + selection, the read-only readers' viewed docs (new ReaderNavRegistry.currentByReader, the T-220 fold), focusedFile, the live view-pane list (T-219), and the layout (slots + visibility + focus mode). Previously 'status' was an unknown command (exit 3). The verb handler (status_command.dart) is a thin Flutter-free wrapper; the snapshot is assembled in main.dart where the live kernel + subsystem state is in scope, with readerNav captured post-boot. Composite shape is verified live; the pieces are unit-tested. Closes T-221 and T-218 (Epic C) under T-208 'Give Claude hands' -- the observe half of D-6 parity is now in place. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -127,6 +127,15 @@ class ReaderNavRegistry {
|
||||
final MessageBus _messages;
|
||||
final Map<String, ReaderNav> _navs = {};
|
||||
|
||||
/// The currently-viewed doc for every reader that has one, keyed by
|
||||
/// publisher id (e.g. `builtin.markdown`, `builtin.decisions`). Used by
|
||||
/// `clide status` to surface what the user is reading (T-221, D-6 parity) —
|
||||
/// viewer files aren't editor buffers, so they don't live in EditorRegistry.
|
||||
Map<String, String> get currentByReader => {
|
||||
for (final e in _navs.entries)
|
||||
if (e.value.current != null) e.key: e.value.current!,
|
||||
};
|
||||
|
||||
/// The retained [ReaderNav] for [publisherId], created on first use.
|
||||
/// [dataKey] is the bus-payload key for this reader's entry.
|
||||
ReaderNav navFor(String publisherId, {required String dataKey}) {
|
||||
|
||||
@@ -36,6 +36,7 @@ import 'package:clide/src/daemon/editor_commands.dart';
|
||||
import 'package:clide/src/daemon/files_commands.dart';
|
||||
import 'package:clide/src/daemon/git_commands.dart';
|
||||
import 'package:clide/src/daemon/pane_commands.dart';
|
||||
import 'package:clide/src/daemon/status_command.dart';
|
||||
import 'package:clide/src/daemon/panel_commands.dart';
|
||||
import 'package:clide/src/daemon/panel_resizer_kernel.dart';
|
||||
import 'package:clide/src/daemon/pql_commands.dart';
|
||||
@@ -88,6 +89,9 @@ Future<void> main() async {
|
||||
DaemonBus? daemonBus;
|
||||
LayoutArrangement? kernelArrangement;
|
||||
PanelRegistry? kernelPanels;
|
||||
// Captured after boot so `clide status` can report the read-only reader's
|
||||
// viewed doc (D-81), which isn't an editor buffer (T-221).
|
||||
ReaderNavRegistry? kernelReaderNav;
|
||||
// IPC socket server (T-99 / T-124, per D-70/71/72). One server per
|
||||
// workspace; restarted when the active project switches because the
|
||||
// socket path is workspace-derived. The local DaemonClient connects
|
||||
@@ -203,6 +207,61 @@ Future<void> main() async {
|
||||
final pql = PqlClient(workDir: workRoot, toolchain: tc);
|
||||
registerPqlCommands(dispatcher, pql);
|
||||
registerPanelCommands(dispatcher, ArrangementPanelResizer(arrangement));
|
||||
// `clide status` — one-shot orientation snapshot (T-221): active pane,
|
||||
// focused file + selection, git summary, layout. Assembled here where the
|
||||
// live kernel + subsystem state is in scope; the reader's viewed doc is
|
||||
// read from the post-boot-captured ReaderNavRegistry (D-81).
|
||||
registerStatusCommand(dispatcher, () async {
|
||||
Map<String, Object?>? gitJson;
|
||||
try {
|
||||
final git = await gitClient.status();
|
||||
gitJson = {
|
||||
'branch': git.branch,
|
||||
if (git.upstream != null) 'upstream': git.upstream,
|
||||
'ahead': git.ahead,
|
||||
'behind': git.behind,
|
||||
'clean': git.isClean,
|
||||
'hasConflicts': git.hasConflicts,
|
||||
'counts': {
|
||||
'staged': git.staged.length,
|
||||
'unstaged': git.unstaged.length,
|
||||
'untracked': git.untracked.length,
|
||||
'conflicted': git.conflicted.length,
|
||||
},
|
||||
};
|
||||
} catch (_) {
|
||||
gitJson = null; // never sink the snapshot on a git hiccup
|
||||
}
|
||||
final editorActive = editorRegistry.active;
|
||||
return {
|
||||
'workspace': workRoot.path,
|
||||
'git': gitJson,
|
||||
'editor': editorActive == null
|
||||
? null
|
||||
: {
|
||||
'id': editorActive.id,
|
||||
'path': editorActive.path,
|
||||
'selection': editorActive.selection.toJson(),
|
||||
'dirty': editorActive.dirty,
|
||||
},
|
||||
'readers': kernelReaderNav?.currentByReader ?? const <String, String>{},
|
||||
'focusedFile': editorActive?.path,
|
||||
'panes': [for (final v in snapshotViewPanes(panels, arrangement)) v.toJson()],
|
||||
'layout': {
|
||||
'focusMode': arrangement.focusModeSlot?.value,
|
||||
'slots': [
|
||||
for (final id in arrangement.slotsInOrder)
|
||||
{
|
||||
'id': id.value,
|
||||
'position': arrangement.positionOf(id)?.name,
|
||||
'visible': arrangement.isVisible(id),
|
||||
'collapsed': arrangement.isCollapsed(id),
|
||||
if (arrangement.sizeOf(id) != null) 'size': arrangement.sizeOf(id),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
});
|
||||
registerArgvUnwrap(dispatcher);
|
||||
return dispatcher;
|
||||
}
|
||||
@@ -254,6 +313,10 @@ Future<void> main() async {
|
||||
await swapIpcServer(dispatcher, Directory(path));
|
||||
},
|
||||
);
|
||||
// Expose the reader nav to the `clide status` snapshot (T-221). Boot
|
||||
// creates it before the daemonClientFactory runs, but the status closure
|
||||
// only reads it at request time (post-boot), so capturing it here is safe.
|
||||
kernelReaderNav = services.readerNav;
|
||||
|
||||
// Register every built-in. Tier 0 activates only the four that do
|
||||
// real work; the rest compile in as stubs so the extensions-ui can
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/// Registers the `status` command — a one-shot orientation snapshot
|
||||
/// (T-221, Gap 6 of self-analysis.md). It is the natural first call an
|
||||
/// agent makes: active pane, focused file + selection, git summary, and
|
||||
/// layout, in one round-trip, with exit 0.
|
||||
///
|
||||
/// The handler is intentionally thin: the snapshot is assembled by the
|
||||
/// caller (main.dart), which holds the live kernel + subsystem state
|
||||
/// (PanelRegistry, LayoutArrangement, EditorRegistry, GitClient,
|
||||
/// ReaderNavRegistry). This file just exposes the verb and wraps the
|
||||
/// assembled map, so it stays Flutter-free and trivially testable.
|
||||
library;
|
||||
|
||||
import '../ipc/envelope.dart';
|
||||
import 'dispatcher.dart';
|
||||
|
||||
/// Builds the orientation snapshot at request time. Returns a JSON-able map.
|
||||
typedef StatusSnapshot = Future<Map<String, Object?>> Function();
|
||||
|
||||
void registerStatusCommand(DaemonDispatcher d, StatusSnapshot snapshot) {
|
||||
d.register('status', (req) async => IpcResponse.ok(id: req.id, data: await snapshot()));
|
||||
}
|
||||
Reference in New Issue
Block a user