clawdie-ai/setup/upstream.ts
Clawdie AI 8456fcc526 test: expand coverage + fix setup/ TypeScript errors
New test files (83 tests):
- src/agent-identity.test.ts — resolveAgentIdentity across locales/genders
- src/env.test.ts — readEnvFile parsing, quoting, edge cases
- src/jail-registry.test.ts — getJailIp with/without env override
- src/local-hosts.test.ts — block markers, entries, render, upsert
- src/mount-security.test.ts — validateMount allowlist enforcement
- src/transcription.test.ts — initTranscription + transcribeAudio with mocked OpenAI

setup/ TypeScript audit (tsconfig.setup.json):
- agent-jails: JAILS value serialised to JSON string for emitStatus
- environment.test: use import type for pg.Pool type cast
- onboarding: wrap showProfileMenu in normalizePiTuiProfile
- preflight.test: fix process.exit mock type + typed call array casts
- sanoid: execSync → spawnSync for multi-arg zfs invocation
- skills-memory: bracket access for legacy chunking_version field
- upstream: pass process.cwd() to isGitRepo()
- verify: import StripeKeyMode type, annotate stripeKeyMode variable

Full suite: 69 files, 1162 tests passing; tsc --noEmit clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---
Build: pass | Tests: pass — Tests  1162 passed (1162)

---
Build: pass | Tests: pass — Tests  1162 passed (1162)
2026-04-14 09:40:28 +00:00

109 lines
3.3 KiB
TypeScript

/**
* Step: upstream — Configure NanoClaw upstream remote for tracking improvements.
*
* Toggle: NANOCLAW_UPSTREAM_ENABLED in .env
* Remote: nanoclaw → github.com/qwibitai/nanoclaw
*
* This step is optional. When enabled, the agent can check for upstream
* improvements via the check_upstream_updates MCP tool. The operator
* decides what to apply — nothing is auto-merged.
*/
import { logger } from '../src/logger.js';
import {
addRemote,
fetchRemote,
getUpstreamConfig,
isGitRepo,
remoteExists,
writeEnvFlag,
} from '../src/upstream/git.js';
import { getUpstreamStatus } from '../src/upstream/status.js';
import { emitStatus } from './status.js';
function statusReport(fetch: boolean): void {
const status = getUpstreamStatus({ repoDir: process.cwd() });
const divergence = status.commits.length
? status.commits.map((commit) => `${commit.hash.slice(0, 7)} ${commit.subject}`).join(' | ')
: (fetch ? 'up-to-date' : 'not-fetched');
emitStatus('SETUP_UPSTREAM', {
NANOCLAW_REMOTE: status.remoteConfigured,
NANOCLAW_REMOTE_URL: status.remoteUrl,
DIVERGENCE: divergence,
ENABLED: status.enabled,
AHEAD_COUNT: status.aheadCount,
BEHIND_COUNT: status.behindCount,
STATUS: status.status,
LOG: 'logs/setup.log',
});
}
export async function run(args: string[]): Promise<void> {
logger.info('Upstream configuration step');
if (!isGitRepo(process.cwd())) {
emitStatus('SETUP_UPSTREAM', {
STATUS: 'failed',
ERROR: 'not_a_git_repo',
LOG: 'logs/setup.log',
});
process.exit(1);
}
const doEnable = args.includes('--enable');
const doDisable = args.includes('--disable');
const doFetch = args.includes('--fetch');
const doStatus = args.includes('--status') || (!doEnable && !doDisable && !doFetch);
if (doStatus) {
statusReport(false);
return;
}
if (doDisable) {
writeEnvFlag(process.cwd(), 'NANOCLAW_UPSTREAM_ENABLED', false);
logger.info('NanoClaw upstream tracking disabled');
emitStatus('SETUP_UPSTREAM', {
NANOCLAW_REMOTE: remoteExists(process.cwd(), 'nanoclaw'),
ENABLED: false,
STATUS: 'disabled',
LOG: 'logs/setup.log',
});
return;
}
if (doEnable) {
const config = getUpstreamConfig({ repoDir: process.cwd() });
if (!remoteExists(config.repoDir, config.remoteName)) {
logger.info({ url: config.remoteUrl }, 'Adding NanoClaw remote');
addRemote(config.repoDir, config.remoteName, config.remoteUrl);
logger.info('NanoClaw remote added');
} else {
logger.info('NanoClaw remote already configured');
}
writeEnvFlag(config.repoDir, 'NANOCLAW_UPSTREAM_ENABLED', true);
logger.info('Fetching NanoClaw upstream (no-tags, read-only)...');
fetchRemote(config.repoDir, config.remoteName);
statusReport(true);
return;
}
if (doFetch) {
const config = getUpstreamConfig({ repoDir: process.cwd() });
if (!remoteExists(config.repoDir, config.remoteName)) {
emitStatus('SETUP_UPSTREAM', {
STATUS: 'failed',
ERROR: 'remote_not_configured — run with --enable first',
LOG: 'logs/setup.log',
});
process.exit(1);
}
logger.info('Fetching NanoClaw upstream (no-tags, read-only)...');
fetchRemote(config.repoDir, config.remoteName);
statusReport(true);
return;
}
}