Merge pull request #426 from PR0M3TH3AN/agents/daily/decompose-agent-20260221-3268553616377249278

chore: scheduler run for decompose-agent
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2026-02-21 17:44:09 -05:00
commit cd1c9406cd
11 files changed
+380 -312

No files matched your search

@@ -0,0 +1,12 @@
# Memory Update — decompose-agent — 2026-02-21
## Key findings
- Extracted `cmdCheck`, `cmdList`, `cmdComplete` from `src/lib.mjs` into separate files.
- `scripts/check-file-size.mjs` does not have a `BASELINE` object to update, effectively a read-only report tool.
## Patterns / reusable knowledge
- When decomposing `src/lib.mjs`, ensure dependencies (imports) are correctly moved or re-imported.
- `COMMAND_HANDLERS` in `src/lib.mjs` makes it easy to swap implementations.
## Warnings / gotchas
- `scripts/check-file-size.mjs` --update flag does not update the file itself.
+115
View File
@@ -0,0 +1,115 @@
import {
getRelays as _getRelays,
getNamespace as _getNamespace,
loadTorchConfig as _loadTorchConfig,
} from './torch-config.mjs';
import { todayDateStr } from './utils.mjs';
import { getRoster as _getRoster } from './roster.mjs';
import { queryLocks as _queryLocks } from './lock-ops.mjs';
import { getCompletedAgents } from './lock-utils.mjs';
import path from 'node:path';
import fs from 'node:fs/promises';
/**
* Checks the status of the repository locks for a given cadence.
*
* It aggregates data from three sources:
* 1. Configuration (paused agents)
* 2. Local logs (completed agents)
* 3. Nostr relays (current active locks)
*
* It outputs a JSON object describing the state (locked, available, excluded agents).
*
* @param {string} cadence - 'daily' or 'weekly'
* @param {Object} [deps] - Dependency injection
* @returns {Promise<Object>} - The check result object
*/
export async function cmdCheck(cadence, deps = {}) {
const {
getRelays = _getRelays,
getNamespace = _getNamespace,
loadTorchConfig = _loadTorchConfig,
queryLocks = _queryLocks,
getRoster = _getRoster,
getDateStr = todayDateStr,
log = console.log,
error = console.error,
logDir = 'task-logs',
ignoreLogs = false,
json = false,
jsonFile = null,
quiet = false,
} = deps;
const relays = await getRelays();
const namespace = await getNamespace();
const dateStr = getDateStr();
const config = await loadTorchConfig();
const pausedAgents = config.scheduler.paused[cadence] || [];
if (!quiet) {
error(`Checking locks: namespace=${namespace}, cadence=${cadence}, date=${dateStr}`);
error(`Relays: ${relays.join(', ')}`);
if (pausedAgents.length > 0) {
error(`Paused agents: ${pausedAgents.join(', ')}`);
}
}
let completedAgents = new Set();
if (!ignoreLogs) {
completedAgents = await getCompletedAgents(cadence, logDir, deps);
if (!quiet && completedAgents.size > 0) {
error(`Completed agents (logs): ${[...completedAgents].join(', ')}`);
}
}
const locks = await queryLocks(
relays,
cadence,
dateStr,
namespace,
quiet ? { errorLogger: () => {}, healthLogger: () => {} } : {},
);
const lockedAgents = [...new Set(locks.map((l) => l.agent).filter(Boolean))];
const roster = await getRoster(cadence);
const rosterSet = new Set(roster);
const excludedAgentsSet = new Set([...lockedAgents, ...pausedAgents, ...completedAgents]);
const excludedAgents = [...excludedAgentsSet];
const unknownLockedAgents = lockedAgents.filter((agent) => !rosterSet.has(agent));
const available = roster.filter((a) => !excludedAgentsSet.has(a));
const result = {
namespace,
cadence,
date: dateStr,
locked: lockedAgents.sort(),
paused: pausedAgents.sort(),
completed: [...completedAgents].sort(),
excluded: excludedAgents.sort(),
available: available.sort(),
lockCount: locks.length,
unknownLockedAgents: unknownLockedAgents.sort(),
locks: locks.map((l) => ({
agent: l.agent,
eventId: l.eventId,
createdAt: l.createdAtIso,
expiresAt: l.expiresAtIso,
platform: l.platform,
})),
};
const output = json ? JSON.stringify(result) : JSON.stringify(result, null, 2);
if (jsonFile) {
const resolvedPath = path.resolve(process.cwd(), jsonFile);
await fs.mkdir(path.dirname(resolvedPath), { recursive: true });
await fs.writeFile(resolvedPath, `${output}\n`, 'utf8');
}
if (json || !quiet) {
log(output);
}
return result;
}
+124
View File
@@ -0,0 +1,124 @@
import {
getRelays as _getRelays,
getNamespace as _getNamespace,
getHashtag as _getHashtag,
} from './torch-config.mjs';
import {
KIND_APP_DATA,
MS_PER_SECOND,
} from './constants.mjs';
import { todayDateStr, nowUnix, detectPlatform } from './utils.mjs';
import { queryLocks as _queryLocks, publishLock as _publishLock } from './lock-ops.mjs';
import { ExitError } from './errors.mjs';
import { generateSecretKey as _generateSecretKey, getPublicKey as _getPublicKey, finalizeEvent as _finalizeEvent } from 'nostr-tools/pure';
/**
* Marks a task as permanently completed by publishing a new lock event with
* `status: 'completed'` and no expiration.
*
* This function:
* 1. Verifies that the agent currently holds a valid lock.
* 2. Publishes a replacement event that preserves the original `startedAt` time.
*
* @param {string} agent - Agent name
* @param {string} cadence - 'daily' or 'weekly'
* @param {boolean} [dryRun=false] - If true, skips publishing
* @param {Object} [deps] - Dependency injection
* @returns {Promise<{status: string, eventId: string}>}
* @throws {ExitError} If no active lock exists for the agent
*/
export async function cmdComplete(agent, cadence, optionsOrDryRun = false, deps = {}) {
const options = typeof optionsOrDryRun === 'object' ? optionsOrDryRun : { dryRun: !!optionsOrDryRun };
const { dryRun = false, platform = null, model = null } = options;
const {
getRelays = _getRelays,
getNamespace = _getNamespace,
getHashtag = _getHashtag,
queryLocks = _queryLocks,
publishLock = _publishLock,
generateSecretKey = _generateSecretKey,
getPublicKey = _getPublicKey,
finalizeEvent = _finalizeEvent,
getDateStr = todayDateStr,
log = console.log,
error = console.error
} = deps;
const relays = await getRelays();
const namespace = await getNamespace();
const hashtag = await getHashtag();
const dateStr = getDateStr();
const now = nowUnix();
error(`Completing task: namespace=${namespace}, agent=${agent}, cadence=${cadence}, date=${dateStr}`);
error(`Relays: ${relays.join(', ')}`);
// 1. Find existing lock
const locks = await queryLocks(relays, cadence, dateStr, namespace);
const myLock = locks.find((l) => l.agent === agent);
if (!myLock) {
error(`ERROR: No active lock found for agent "${agent}" on ${dateStr}.`);
error(`Cannot complete a task that is not locked or has already expired.`);
throw new ExitError(1, 'No active lock found');
}
if (myLock.status === 'completed') {
error(`Task is already marked as completed (event ${myLock.eventId}).`);
log('LOCK_STATUS=completed');
return { status: 'completed', eventId: myLock.eventId };
}
// 2. Build completion event
const startedAtIso = myLock.createdAtIso;
error('Step 1: Generating completion event...');
const sk = generateSecretKey();
const pk = getPublicKey(sk);
const event = finalizeEvent(
{
kind: KIND_APP_DATA,
created_at: now,
tags: [
['d', `${namespace}-lock/${cadence}/${agent}/${dateStr}`],
['t', hashtag],
['t', `${namespace}-lock-${cadence}`],
['t', `${namespace}-lock-${cadence}-${dateStr}`],
// No expiration tag -> permanent
],
content: JSON.stringify({
agent,
cadence,
status: 'completed',
namespace,
date: dateStr,
platform: platform || process.env.AGENT_PLATFORM || detectPlatform() || 'unknown',
model: model || process.env.AGENT_MODEL || 'unknown',
startedAt: startedAtIso,
completedAt: new Date(now * MS_PER_SECOND).toISOString(),
}),
},
sk,
);
error(` Event ID: ${event.id}`);
if (dryRun) {
error('Step 2: [DRY RUN] Skipping publish — event built but not sent');
} else {
error('Step 2: Publishing completion event...');
await publishLock(relays, event);
error(' Published successfully.');
}
log('LOCK_STATUS=completed');
log(`LOCK_EVENT_ID=${event.id}`);
log(`LOCK_PUBKEY=${pk}`);
log(`LOCK_AGENT=${agent}`);
log(`LOCK_CADENCE=${cadence}`);
log(`LOCK_DATE=${dateStr}`);
return { status: 'completed', eventId: event.id };
}
+30 -14
View File
@@ -2,26 +2,36 @@ import {
getRelays as _getRelays,
getNamespace as _getNamespace,
} from './torch-config.mjs';
import {
VALID_CADENCES,
} from './constants.mjs';
import { todayDateStr, nowUnix } from './utils.mjs';
import { getRoster as _getRoster } from './roster.mjs';
import { queryLocks as _queryLocks } from './lock-ops.mjs';
import { todayDateStr as _todayDateStr, nowUnix as _nowUnix } from './utils.mjs';
/**
* Lists all active locks for the specified cadence (or all cadences if null).
* It prints a formatted table to stdout with lock age, TTL, and event ID.
*
* @param {string|null} cadence - Filter by cadence ('daily', 'weekly') or null for all
* @param {Object} [deps] - Dependency injection
* @returns {Promise<void>}
*/
export async function cmdList(cadence, deps = {}) {
const {
getRelays = _getRelays,
getNamespace = _getNamespace,
getRoster = _getRoster,
queryLocks = _queryLocks,
todayDateStr = _todayDateStr,
nowUnix = _nowUnix,
log = console.log,
error = console.error,
getRelays = _getRelays,
getNamespace = _getNamespace,
queryLocks = _queryLocks,
getRoster = _getRoster,
getDateStr = todayDateStr,
log = console.log,
error = console.error
} = deps;
const relays = await getRelays();
const namespace = await getNamespace();
const dateStr = todayDateStr();
const cadences = cadence ? [cadence] : ['daily', 'weekly'];
const dateStr = getDateStr();
const cadences = cadence ? [cadence] : [...VALID_CADENCES];
error(`Listing active locks: namespace=${namespace}, cadences=${cadences.join(', ')}`);
@@ -46,13 +56,19 @@ export async function cmdList(cadence, deps = {}) {
for (const lock of sorted) {
const age = nowUnix() - lock.createdAt;
const ageMin = Math.round(age / 60);
const remaining = lock.expiresAt ? lock.expiresAt - nowUnix() : null;
const remainMin = remaining ? Math.round(remaining / 60) : '?';
let remainMin = '?';
if (lock.status === 'completed') {
remainMin = 'done';
} else if (lock.expiresAt) {
const remaining = lock.expiresAt - nowUnix();
remainMin = Math.round(remaining / 60);
}
log(
` ${(lock.agent ?? 'unknown').padEnd(30)} ` +
`age: ${String(ageMin).padStart(4)}m ` +
`ttl: ${String(remainMin).padStart(4)}m ` +
`ttl: ${String(remainMin).padStart(4)} ` +
`platform: ${lock.platform ?? '?'} ` +
`event: ${lock.eventId?.slice(0, 12)}...`,
);
@@ -0,0 +1,31 @@
# Context: Decompose Agent Run 2026-02-21T22-21-27Z
- Agent: decompose-agent
- Cadence: daily
- Session: 2026-02-21T22-21-27Z
- Run-Start: 2026-02-21T22-21-27Z
- Prompt: src/prompts/daily/decompose-agent.md
## Goal
Decompose `src/lib.mjs` into smaller modules to reduce size by at least 200 lines.
## Scope
Decomposition of `src/lib.mjs` by extracting command implementations.
## Constraints
- Do not change behavior.
- Reduce size by at least 200 lines.
- Preserve existing functionality.
## Selection
- File: `src/lib.mjs`
- Original Size: 730 lines
- Reason: Largest grandfathered file not recently refactored (based on manual check).
- Commit SHA: a6c06ce173fbbff9170476e4b97c1bac4b30cb10
- Node/npm: v22.22.0 / 11.7.0
## Plan
Extract cohesive blocks:
1. `cmdCheck`
2. `cmdList`
3. `cmdComplete`
@@ -0,0 +1,22 @@
# Decisions: Decompose src/lib.mjs
- Agent: decompose-agent
- Cadence: daily
- Session: 2026-02-21T22-21-27Z
- Run-Start: 2026-02-21T22-21-27Z
- Prompt: src/prompts/daily/decompose-agent.md
## Decision
Extract `cmdCheck`, `cmdList`, and `cmdComplete` into separate modules.
## Rationale
These are distinct, cohesive command implementations that can be cleanly separated from the main entry point to reduce file size and improve maintainability. `src/lib.mjs` was over 700 lines.
## Naming
- `src/cmd-check.mjs`: Specific to check command.
- `src/cmd-list.mjs`: Specific to list command.
- `src/cmd-complete.mjs`: Specific to complete command.
## Baseline Update
- Reduced `src/lib.mjs` from 730 lines to 438 lines (292 lines reduced).
- `scripts/check-file-size.mjs` does not support programmatic baseline updates or contain a `BASELINE` object, so manual update of the script was not possible/needed.
+4 -298
View File
@@ -7,7 +7,6 @@ import { generateSecretKey as _generateSecretKey, getPublicKey as _getPublicKey,
import { useWebSocketImplementation } from 'nostr-tools/relay';
import WebSocket from 'ws';
import {
loadTorchConfig as _loadTorchConfig,
getRelays as _getRelays,
getNamespace as _getNamespace,
getTtl as _getTtl,
@@ -40,118 +39,16 @@ import fs from 'node:fs/promises';
import path from 'node:path';
import { createHash } from 'node:crypto';
import { execSync } from 'node:child_process';
import { getCompletedAgents } from './lock-utils.mjs';
import { cmdProposal } from './cmd-proposal.mjs';
import { cmdRollback } from './cmd-rollback.mjs';
import { cmdCheck } from './cmd-check.mjs';
import { cmdList } from './cmd-list.mjs';
import { cmdComplete } from './cmd-complete.mjs';
useWebSocketImplementation(WebSocket);
// Re-export for backward compatibility/library usage
export { parseLockEvent, cmdDashboard, _queryLocks as queryLocks, _publishLock as publishLock };
/**
* Checks the status of the repository locks for a given cadence.
*
* It aggregates data from three sources:
* 1. Configuration (paused agents)
* 2. Local logs (completed agents)
* 3. Nostr relays (current active locks)
*
* It outputs a JSON object describing the state (locked, available, excluded agents).
*
* @param {string} cadence - 'daily' or 'weekly'
* @param {Object} [deps] - Dependency injection
* @returns {Promise<Object>} - The check result object
*/
export async function cmdCheck(cadence, deps = {}) {
const {
getRelays = _getRelays,
getNamespace = _getNamespace,
loadTorchConfig = _loadTorchConfig,
queryLocks = _queryLocks,
getRoster = _getRoster,
getDateStr = todayDateStr,
log = console.log,
error = console.error,
logDir = 'task-logs',
ignoreLogs = false,
json = false,
jsonFile = null,
quiet = false,
} = deps;
const relays = await getRelays();
const namespace = await getNamespace();
const dateStr = getDateStr();
const config = await loadTorchConfig();
const pausedAgents = config.scheduler.paused[cadence] || [];
if (!quiet) {
error(`Checking locks: namespace=${namespace}, cadence=${cadence}, date=${dateStr}`);
error(`Relays: ${relays.join(', ')}`);
if (pausedAgents.length > 0) {
error(`Paused agents: ${pausedAgents.join(', ')}`);
}
}
let completedAgents = new Set();
if (!ignoreLogs) {
completedAgents = await getCompletedAgents(cadence, logDir, deps);
if (!quiet && completedAgents.size > 0) {
error(`Completed agents (logs): ${[...completedAgents].join(', ')}`);
}
}
const locks = await queryLocks(
relays,
cadence,
dateStr,
namespace,
quiet ? { errorLogger: () => {}, healthLogger: () => {} } : {},
);
const lockedAgents = [...new Set(locks.map((l) => l.agent).filter(Boolean))];
const roster = await getRoster(cadence);
const rosterSet = new Set(roster);
const excludedAgentsSet = new Set([...lockedAgents, ...pausedAgents, ...completedAgents]);
const excludedAgents = [...excludedAgentsSet];
const unknownLockedAgents = lockedAgents.filter((agent) => !rosterSet.has(agent));
const available = roster.filter((a) => !excludedAgentsSet.has(a));
const result = {
namespace,
cadence,
date: dateStr,
locked: lockedAgents.sort(),
paused: pausedAgents.sort(),
completed: [...completedAgents].sort(),
excluded: excludedAgents.sort(),
available: available.sort(),
lockCount: locks.length,
unknownLockedAgents: unknownLockedAgents.sort(),
locks: locks.map((l) => ({
agent: l.agent,
eventId: l.eventId,
createdAt: l.createdAtIso,
expiresAt: l.expiresAtIso,
platform: l.platform,
})),
};
const output = json ? JSON.stringify(result) : JSON.stringify(result, null, 2);
if (jsonFile) {
const resolvedPath = path.resolve(process.cwd(), jsonFile);
await fs.mkdir(path.dirname(resolvedPath), { recursive: true });
await fs.writeFile(resolvedPath, `${output}\n`, 'utf8');
}
if (json || !quiet) {
log(output);
}
return result;
}
export { parseLockEvent, cmdDashboard, _queryLocks as queryLocks, _publishLock as publishLock, cmdCheck, cmdList, cmdComplete };
/**
* Attempts to acquire an exclusive lock for an agent on the specified cadence.
@@ -339,197 +236,6 @@ export async function cmdLock(agent, cadence, optionsOrDryRun = false, deps = {}
return { status: 'ok', eventId: event.id };
}
/**
* Lists all active locks for the specified cadence (or all cadences if null).
* It prints a formatted table to stdout with lock age, TTL, and event ID.
*
* @param {string|null} cadence - Filter by cadence ('daily', 'weekly') or null for all
* @param {Object} [deps] - Dependency injection
* @returns {Promise<void>}
*/
export async function cmdList(cadence, deps = {}) {
const {
getRelays = _getRelays,
getNamespace = _getNamespace,
queryLocks = _queryLocks,
getRoster = _getRoster,
getDateStr = todayDateStr,
log = console.log,
error = console.error
} = deps;
const relays = await getRelays();
const namespace = await getNamespace();
const dateStr = getDateStr();
const cadences = cadence ? [cadence] : [...VALID_CADENCES];
error(`Listing active locks: namespace=${namespace}, cadences=${cadences.join(', ')}`);
const results = await Promise.all(
cadences.map(async (c) => {
const locks = await queryLocks(relays, c, dateStr, namespace);
return { c, locks };
}),
);
for (const { c, locks } of results) {
log(`\n${'='.repeat(72)}`);
log(`Active ${namespace} ${c} locks (${dateStr})`);
log('='.repeat(72));
if (locks.length === 0) {
log(' (no active locks)');
continue;
}
const sorted = locks.sort((a, b) => a.createdAt - b.createdAt);
for (const lock of sorted) {
const age = nowUnix() - lock.createdAt;
const ageMin = Math.round(age / 60);
let remainMin = '?';
if (lock.status === 'completed') {
remainMin = 'done';
} else if (lock.expiresAt) {
const remaining = lock.expiresAt - nowUnix();
remainMin = Math.round(remaining / 60);
}
log(
` ${(lock.agent ?? 'unknown').padEnd(30)} ` +
`age: ${String(ageMin).padStart(4)}m ` +
`ttl: ${String(remainMin).padStart(4)} ` +
`platform: ${lock.platform ?? '?'} ` +
`event: ${lock.eventId?.slice(0, 12)}...`,
);
}
const roster = await getRoster(c);
const rosterSet = new Set(roster);
const lockedAgents = new Set(locks.map((l) => l.agent).filter(Boolean));
const unknownLockedAgents = [...lockedAgents].filter((agent) => !rosterSet.has(agent));
const available = roster.filter((a) => !lockedAgents.has(a));
if (unknownLockedAgents.length > 0) {
log(` Warning: lock events found with non-roster agent names: ${unknownLockedAgents.join(', ')}`);
}
log(`\n Locked: ${lockedAgents.size}/${roster.length}`);
log(` Available: ${available.join(', ') || '(none)'}`);
}
}
/**
* Marks a task as permanently completed by publishing a new lock event with
* `status: 'completed'` and no expiration.
*
* This function:
* 1. Verifies that the agent currently holds a valid lock.
* 2. Publishes a replacement event that preserves the original `startedAt` time.
*
* @param {string} agent - Agent name
* @param {string} cadence - 'daily' or 'weekly'
* @param {boolean} [dryRun=false] - If true, skips publishing
* @param {Object} [deps] - Dependency injection
* @returns {Promise<{status: string, eventId: string}>}
* @throws {ExitError} If no active lock exists for the agent
*/
export async function cmdComplete(agent, cadence, optionsOrDryRun = false, deps = {}) {
const options = typeof optionsOrDryRun === 'object' ? optionsOrDryRun : { dryRun: !!optionsOrDryRun };
const { dryRun = false, platform = null, model = null } = options;
const {
getRelays = _getRelays,
getNamespace = _getNamespace,
getHashtag = _getHashtag,
queryLocks = _queryLocks,
publishLock = _publishLock,
generateSecretKey = _generateSecretKey,
getPublicKey = _getPublicKey,
finalizeEvent = _finalizeEvent,
getDateStr = todayDateStr,
log = console.log,
error = console.error
} = deps;
const relays = await getRelays();
const namespace = await getNamespace();
const hashtag = await getHashtag();
const dateStr = getDateStr();
const now = nowUnix();
error(`Completing task: namespace=${namespace}, agent=${agent}, cadence=${cadence}, date=${dateStr}`);
error(`Relays: ${relays.join(', ')}`);
// 1. Find existing lock
const locks = await queryLocks(relays, cadence, dateStr, namespace);
const myLock = locks.find((l) => l.agent === agent);
if (!myLock) {
error(`ERROR: No active lock found for agent "${agent}" on ${dateStr}.`);
error(`Cannot complete a task that is not locked or has already expired.`);
throw new ExitError(1, 'No active lock found');
}
if (myLock.status === 'completed') {
error(`Task is already marked as completed (event ${myLock.eventId}).`);
log('LOCK_STATUS=completed');
return { status: 'completed', eventId: myLock.eventId };
}
// 2. Build completion event
const startedAtIso = myLock.createdAtIso;
error('Step 1: Generating completion event...');
const sk = generateSecretKey();
const pk = getPublicKey(sk);
const event = finalizeEvent(
{
kind: KIND_APP_DATA,
created_at: now,
tags: [
['d', `${namespace}-lock/${cadence}/${agent}/${dateStr}`],
['t', hashtag],
['t', `${namespace}-lock-${cadence}`],
['t', `${namespace}-lock-${cadence}-${dateStr}`],
// No expiration tag -> permanent
],
content: JSON.stringify({
agent,
cadence,
status: 'completed',
namespace,
date: dateStr,
platform: platform || process.env.AGENT_PLATFORM || detectPlatform() || 'unknown',
model: model || process.env.AGENT_MODEL || 'unknown',
startedAt: startedAtIso,
completedAt: new Date(now * MS_PER_SECOND).toISOString(),
}),
},
sk,
);
error(` Event ID: ${event.id}`);
if (dryRun) {
error('Step 2: [DRY RUN] Skipping publish — event built but not sent');
} else {
error('Step 2: Publishing completion event...');
await publishLock(relays, event);
error(' Published successfully.');
}
log('LOCK_STATUS=completed');
log(`LOCK_EVENT_ID=${event.id}`);
log(`LOCK_PUBKEY=${pk}`);
log(`LOCK_AGENT=${agent}`);
log(`LOCK_CADENCE=${cadence}`);
log(`LOCK_DATE=${dateStr}`);
return { status: 'completed', eventId: event.id };
}
const COMMAND_HANDLERS = {
check: async (args) => {
if (!args.cadence || !VALID_CADENCES.has(args.cadence)) {
@@ -0,0 +1,15 @@
# Test Log: Decompose Agent Run 2026-02-21T22-21-27Z
- Agent: decompose-agent
- Cadence: daily
- Session: 2026-02-21T22-21-27Z
- Run-Start: 2026-02-21T22-21-27Z
- Prompt: src/prompts/daily/decompose-agent.md
## Lint
- Command: npm run lint
- Result: Passed
## Unit Tests
- Command: npm test
- Result: Passed (All tests passed)
+15
View File
@@ -0,0 +1,15 @@
# TODO: Decompose src/lib.mjs
- Agent: decompose-agent
- Cadence: daily
- Session: 2026-02-21T22-21-27Z
- Run-Start: 2026-02-21T22-21-27Z
- Prompt: src/prompts/daily/decompose-agent.md
## Pending Tasks
- [x] Extract `cmdCheck` logic to `src/cmd-check.mjs`
- [x] Extract `cmdList` logic to `src/cmd-list.mjs`
- [x] Extract `cmdComplete` logic to `src/cmd-complete.mjs`
- [x] Update `src/lib.mjs` imports
- [x] Verify `npm run lint`
- [x] Verify `npm test`
@@ -0,0 +1,11 @@
---
agent: decompose-agent
cadence: daily
session: 2026-02-21T22-21-27Z
run-start: 2026-02-21T22-21-27Z
prompt: src/prompts/daily/decompose-agent.md
platform: linux
---
Decomposed `src/lib.mjs` into `src/cmd-check.mjs`, `src/cmd-list.mjs`, and `src/cmd-complete.mjs`.
Reduced file size by ~292 lines.
+1
View File
@@ -1,2 +1,3 @@
{"ok":true,"cadence":"daily","namespace":"torch","timestamp":"2026-02-20T01:13:27.775Z","relays":["wss://relay.damus.io","wss://nos.lol","wss://relay.primal.net"],"relayResults":[{"relay":"wss://relay.damus.io","websocketReachable":true,"publishReadOk":true,"healthy":true,"checks":{"websocket":{"ok":true,"latencyMs":186},"publishRead":{"ok":true,"eventId":"7d217815c782e1dac6557a8b616f30713820872c3e2f13e49c82fc6f787269e2"}}},{"relay":"wss://nos.lol","websocketReachable":true,"publishReadOk":true,"healthy":true,"checks":{"websocket":{"ok":true,"latencyMs":358},"publishRead":{"ok":true,"eventId":"ce6e9fe40d4365b0a36ee90d15aad91fe28ddafc25b1b2d2887bbd54a5e6cf90"}}},{"relay":"wss://relay.primal.net","websocketReachable":true,"publishReadOk":true,"healthy":true,"checks":{"websocket":{"ok":true,"latencyMs":520},"publishRead":{"ok":true,"eventId":"0bc4ad0f2ed9d580d0147b9af847dffd3009ec9419e516c0105d29837c9bcd71"}}}],"summary":{"totalRelays":3,"healthyRelays":3,"unhealthyRelays":0,"allRelaysUnhealthy":false},"incidentSignal":null,"alerts":[],"alertStats":{"windowMinutes":60,"sampleCount":1,"successRate":1,"allDownDurationMinutes":0},"historyPath":"/app/task-logs/relay-health/daily.jsonl"}
{"ok":false,"cadence":"daily","namespace":"torch","timestamp":"2026-02-20T04:26:35.357Z","relays":["wss://relay.damus.io","wss://nos.lol","wss://relay.primal.net"],"relayResults":[{"relay":"wss://relay.damus.io","websocketReachable":false,"publishReadOk":false,"healthy":false,"checks":{"websocket":{"ok":false,"error":"getaddrinfo ENOTFOUND relay.damus.io"}}},{"relay":"wss://nos.lol","websocketReachable":false,"publishReadOk":false,"healthy":false,"checks":{"websocket":{"ok":false,"error":"getaddrinfo ENOTFOUND nos.lol"}}},{"relay":"wss://relay.primal.net","websocketReachable":false,"publishReadOk":false,"healthy":false,"checks":{"websocket":{"ok":false,"error":"getaddrinfo ENOTFOUND relay.primal.net"}}}],"summary":{"totalRelays":3,"healthyRelays":0,"unhealthyRelays":3,"allRelaysUnhealthy":true},"incidentSignal":{"id":"relay-health-daily-1771561595358","reason":"All relays unhealthy before lock acquisition","severity":"critical","suggestedAction":"Defer scheduler run and investigate relay availability before retrying lock acquisition."},"alerts":[{"type":"all_relays_down_duration","thresholdMinutes":10,"actualMinutes":193,"severity":"critical"},{"type":"relay_success_rate_below_threshold","threshold":0.7,"actual":0,"windowMinutes":60,"severity":"warning"}],"alertStats":{"windowMinutes":60,"sampleCount":1,"successRate":0,"allDownDurationMinutes":193},"historyPath":"/home/user/Documents/GitHub/TORCH/task-logs/relay-health/daily.jsonl"}
{"ok":true,"cadence":"daily","namespace":"torch","timestamp":"2026-02-21T22:20:34.054Z","relays":["wss://relay.damus.io","wss://nos.lol","wss://relay.primal.net"],"relayResults":[{"relay":"wss://relay.damus.io","websocketReachable":true,"publishReadOk":true,"healthy":true,"checks":{"websocket":{"ok":true,"latencyMs":183},"publishRead":{"ok":true,"eventId":"eed1c45fa6fdd7f55e36b06bedfa48709c574f3dcd6bce298c4b8e8cdc6de716"}}},{"relay":"wss://nos.lol","websocketReachable":true,"publishReadOk":true,"healthy":true,"checks":{"websocket":{"ok":true,"latencyMs":365},"publishRead":{"ok":true,"eventId":"6a9e536a302871f8ab6648ee7f90952c110582997d04e2cff6e0c39b53face3e"}}},{"relay":"wss://relay.primal.net","websocketReachable":true,"publishReadOk":true,"healthy":true,"checks":{"websocket":{"ok":true,"latencyMs":484},"publishRead":{"ok":true,"eventId":"f03863e5a2e3bbecc9102745bf9a96b069d3f9d03bf0f47a4c3c81cd57400478"}}}],"summary":{"totalRelays":3,"healthyRelays":3,"unhealthyRelays":0,"allRelaysUnhealthy":false},"incidentSignal":null,"alerts":[],"alertStats":{"windowMinutes":60,"sampleCount":1,"successRate":1,"allDownDurationMinutes":0},"historyPath":"/app/task-logs/relay-health/daily.jsonl"}