distinguish a dev-tree clide from a packaged install (T-256)

CliInstaller.inspect() gains a devTree state: when `clide` on PATH resolves
to a dev-tree build artifact (native/<plat>/clide, the Makefile's
CLIDE_CLI_BIN output) it's classified separately from a packaged install
rather than silently treated as "installed". needsInstall stays false for a
dev build (it's intentional on a checkout, not a reinstall prompt), and the
launch-time check surfaces it as an info note. install() flags fromDevTree +
notes it in the result message when the copied source is a dev build.

Closes the last open child of the T-208 "give Claude hands" initiative.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 21:53:01 +02:00
co-authored by Claude Opus 4.8
parent 9cb7da3f57
commit 51cfa15c78
7 changed files with 116 additions and 4 deletions
+55
View File
@@ -108,6 +108,34 @@ void main() {
);
expect(i.inspect().state, CliInstallState.staleGui);
});
test('devTree when clide resolves to a native/<plat>/clide build (T-256)', () {
final dev = touchExec('${tmp.path}/native/linux-x64/clide').path;
final binDir = Directory('${tmp.path}/bin')..createSync();
Link('${binDir.path}/clide').createSync(dev);
final i = installer(
resolvedExecutable: '${tmp.path}/gui/clide',
env: {'PATH': binDir.path},
);
final s = i.inspect();
expect(s.state, CliInstallState.devTree);
// A dev build is intentional — it doesn't prompt a reinstall.
expect(s.needsInstall, isFalse);
});
});
group('isDevTreeClient', () {
test('matches the native/<platform>/clide build artifacts', () {
expect(isDevTreeClient('/repo/native/linux-x64/clide'), isTrue);
expect(isDevTreeClient('/repo/native/macos-arm64/clide'), isTrue);
expect(isDevTreeClient('native/linux-arm64/clide'), isTrue);
});
test('does not match installed or bundled paths', () {
expect(isDevTreeClient('/home/x/.local/bin/clide'), isFalse);
expect(isDevTreeClient('/usr/local/bin/clide'), isFalse);
expect(isDevTreeClient('/opt/clide/bundle/clide-cli'), isFalse);
});
});
group('defaults', () {
@@ -191,6 +219,33 @@ void main() {
}
});
test('flags fromDevTree when the source is a dev-tree build (T-256)', () {
final src = touchExec('${tmp.path}/native/linux-x64/clide');
final binDir = '${tmp.path}/bin';
final i = installer(
resolvedExecutable: '${tmp.path}/gui/clide',
env: {'PATH': binDir},
candidates: [src.path],
installDir: binDir,
);
final r = i.install();
expect(r.ok, isTrue);
expect(r.fromDevTree, isTrue);
expect(r.message, contains('dev-tree build'));
});
test('a bundled (non-dev) source is not flagged fromDevTree', () {
final src = touchExec('${tmp.path}/bundle/clide-cli');
final binDir = '${tmp.path}/bin';
final i = installer(
resolvedExecutable: '${tmp.path}/gui/clide',
env: {'PATH': binDir},
candidates: [src.path],
installDir: binDir,
);
expect(i.install().fromDevTree, isFalse);
});
test('overwrites a stale symlink rather than following it', () {
final src = touchExec('${tmp.path}/bundle/clide-cli', contents: 'NEW');
final gui = touchExec('${tmp.path}/gui/clide', contents: 'GUI').path;