diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..64a4102 --- /dev/null +++ b/.env.example @@ -0,0 +1,47 @@ +# TORCH Environment Variables +# Copy this file to .env and fill in the values for your deployment. + +# ─── Dashboard ──────────────────────────────────────────────────────── +# Basic Auth credentials for the dashboard (user:password format) +# TORCH_DASHBOARD_AUTH=admin:changeme + +# ─── Configuration ──────────────────────────────────────────────────── +# Path to torch-config.json (optional — overrides default lookup) +# TORCH_CONFIG_PATH=./torch-config.json + +# ─── Agent Platform Detection ───────────────────────────────────────── +# Override automatic platform detection (jules | claude | codex | goose) +# AGENT_PLATFORM= +# AGENT_MODEL= + +# Platform-specific API keys (used for automatic platform detection) +# CLAUDE_API_KEY= +# JULES_API_KEY= +# CODEX_API_KEY= +# GOOSE_API_KEY= + +# ─── Nostr Lock Configuration ──────────────────────────────────────── +# Comma-separated relay URLs (overrides config file) +# NOSTR_LOCK_RELAYS=wss://relay.damus.io,wss://nos.lol,wss://relay.primal.net + +# Lock TTL in seconds (default: 7200) +# NOSTR_LOCK_TTL=7200 + +# Relay connection timeout in milliseconds (default: 8000) +# NOSTR_LOCK_TIMEOUT=8000 + +# Minimum relay quorum for successful publish (default: 1) +# NOSTR_LOCK_QUORUM=1 + +# ─── Memory Subsystem ──────────────────────────────────────────────── +# Master toggle for the memory subsystem +# TORCH_MEMORY_ENABLED=false + +# Individual memory feature flags +# TORCH_MEMORY_INGEST_ENABLED=false +# TORCH_MEMORY_RETRIEVAL_ENABLED=false +# TORCH_MEMORY_PRUNE_ENABLED=false +# TORCH_MEMORY_PRUNE_MODE=off + +# ─── Telemetry (opt-in) ────────────────────────────────────────────── +# TELEMETRY=0 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..e772f3c --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,17 @@ +## Summary + + + +- + +## Test plan + + + +- [ ] `npm run lint` passes +- [ ] `npm test` passes +- [ ] Manual verification (if applicable) + +## Related issues + + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..ba55fdd --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 5 + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 3 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1234f7f..818d5fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,14 +9,21 @@ on: jobs: build-and-test: runs-on: ubuntu-latest + strategy: + matrix: + node-version: [22, 23] steps: - uses: actions/checkout@v4 - - name: Use Node.js 22 + - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: - node-version: 22 + node-version: ${{ matrix.node-version }} cache: 'npm' - run: npm ci - run: npm run build - run: npm run lint + - name: Check innerHTML usage + run: node scripts/check-innerhtml.mjs --report + - name: Security audit + run: npm audit --omit=dev --audit-level=high || true - run: npm test diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b1bfffd..424f2ad 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,11 +16,17 @@ Thank you for your interest in contributing to TORCH! npm install ``` +3. Copy the environment template (optional): + + ```bash + cp .env.example .env + ``` + ## Development Workflow ### Building -This project requires a build step for certain artifacts (e.g., distribution files, dashboard assets). +This project requires a build step for distribution artifacts (dashboard, landing page, tarball). ```bash npm run build @@ -28,32 +34,79 @@ npm run build ### Testing -Run the test suite to ensure your changes don't break existing functionality. +Run the full test suite: ```bash npm test ``` -For faster feedback loop on unit tests (excluding integration): +For faster feedback on lock-backend unit tests: ```bash npm run test:unit:lock-backend ``` +For integration/resilience tests: + +```bash +npm run test:extended-main +``` + ### Linting -Ensure your code follows the project's style guidelines. +Ensure your code follows the project's style guidelines: ```bash npm run lint ``` +ESLint is configured with `no-unused-vars` as an error. Prefix intentionally unused parameters with `_` (e.g., `_err`, `_unused`). + +## Commit Conventions + +This project uses [Conventional Commits](https://www.conventionalcommits.org/): + +``` +feat(scope): add new feature +fix(scope): fix a bug +chore(scope): maintenance task +docs(scope): documentation update +test(scope): add or fix tests +``` + +Common scopes: `dashboard`, `scheduler`, `lock-ops`, `memory`, `ci`, `tests`, `security`. + +## Adding a New Agent Prompt + +Agent prompts live in `src/prompts/daily/` and `src/prompts/weekly/`. To add a new agent: + +1. Create a markdown file following the naming convention: `.md` +2. Add the agent to the roster in `src/prompts/roster.json` +3. Run `npm run validate:scheduler` to verify roster/prompt consistency + +See existing prompts for the expected format. + +## Architecture Overview + +- **Core library:** `src/lib.mjs` — CLI command dispatch +- **Lock operations:** `src/lock-ops.mjs` — Nostr relay publish/query with health tracking +- **Dashboard:** `dashboard/` — Static web UI served by `src/dashboard.mjs` +- **Memory subsystem:** `src/services/memory/` — Event ingestion, retrieval, pruning +- **Scheduler:** `scripts/agent/run-scheduler-cycle.mjs` — Orchestrates agent runs + +For detailed architecture docs, see `docs/lib-overview.md` and `docs/lock-ops-overview.md`. + +## Test Integrity + +When modifying tests, follow the test integrity protocol in `CLAUDE.md`. Tests are behavioral specifications — never weaken assertions to make them pass. + ## Pull Requests -Please follow the repository conventions for Pull Requests. - -> **Note:** Detailed agent policies and conventions are normally documented in `AGENTS.md`. If this file is missing, please refer to existing patterns in the codebase or open an issue for clarification. +- Keep PR titles short (under 70 characters) +- Include a summary of changes and test plan in the description +- Ensure `npm run lint` and `npm test` pass before submitting +- Reference related issues where applicable ## Reporting Issues -If you encounter any bugs or have feature requests, please open an issue on GitHub. +If you encounter bugs or have feature requests, please open an issue on GitHub. diff --git a/dashboard/app.js b/dashboard/app.js index 996e259..f8982d4 100644 --- a/dashboard/app.js +++ b/dashboard/app.js @@ -1,5 +1,6 @@ +/* global marked */ import { KIND_APP_DATA } from '../src/constants.mjs'; -import { createElement, escapeHtml } from './domUtils.js'; +import { createElement } from './domUtils.js'; // ----------------------------------------------------------------------- // Modal Logic @@ -168,7 +169,7 @@ function parseDashboardConfig(torchConfig = {}) { let localPrefs = {}; try { localPrefs = JSON.parse(localStorage.getItem('torch_dashboard_prefs')) || {}; - } catch {} + } catch { /* ignore malformed localStorage */ } const dashboardConfig = torchConfig.dashboard || {}; const lockConfig = torchConfig.nostrLock || {}; @@ -218,8 +219,6 @@ function parseDashboardConfig(torchConfig = {}) { let DASHBOARD_CONFIG = parseDashboardConfig({}); let RELAYS = DASHBOARD_CONFIG.relays; -let D_TAG_PREFIX = `${DASHBOARD_CONFIG.namespace}-lock/`; - const LOCK_EVENT_KIND = KIND_APP_DATA; let HASHTAG = DASHBOARD_CONFIG.hashtag; @@ -787,7 +786,6 @@ async function bootstrap() { const torchConfig = await loadTorchConfigFile(); DASHBOARD_CONFIG = parseDashboardConfig(torchConfig); RELAYS = DASHBOARD_CONFIG.relays; - D_TAG_PREFIX = `${DASHBOARD_CONFIG.namespace}-lock/`; HASHTAG = DASHBOARD_CONFIG.hashtag; cadenceFilter.value = DASHBOARD_CONFIG.defaultCadenceView; diff --git a/docs/architecture/system-overview.md b/docs/architecture/system-overview.md new file mode 100644 index 0000000..b3d7c40 --- /dev/null +++ b/docs/architecture/system-overview.md @@ -0,0 +1,157 @@ +# TORCH System Architecture + +## High-Level Overview + +```mermaid +graph TB + subgraph Agents["AI Agent Platforms"] + Jules["Jules"] + Claude["Claude Code"] + Codex["Codex"] + Goose["Goose"] + end + + subgraph TORCH["TORCH CLI / Scheduler"] + CLI["bin/torch-lock.mjs"] + Lib["src/lib.mjs
Command Dispatch"] + LockOps["src/lock-ops.mjs
Lock Publisher / Query"] + Health["src/relay-health.mjs
Relay Health Monitor"] + Scheduler["scripts/agent/
run-scheduler-cycle.mjs"] + end + + subgraph Nostr["Nostr Network"] + R1["Relay 1
relay.damus.io"] + R2["Relay 2
nos.lol"] + R3["Relay 3
relay.primal.net"] + end + + subgraph Dashboard["Dashboard"] + Server["src/dashboard.mjs
HTTP Server"] + UI["dashboard/index.html
+ app.js"] + end + + subgraph Memory["Memory Subsystem"] + Ingestor["Ingestor"] + Retriever["Retriever"] + Pruner["Pruner"] + Store["memory-store.json"] + end + + Agents -->|"torch-lock lock/check/complete"| CLI + CLI --> Lib + Lib --> LockOps + LockOps -->|"NIP-01 Events
(kind 30078)"| R1 & R2 & R3 + Health -->|"Probe & Track"| R1 & R2 & R3 + Scheduler -->|"Orchestrate"| CLI + + Server -->|"Serve Static"| UI + UI -->|"WebSocket SUB"| R1 & R2 & R3 + + Lib --> Ingestor + Lib --> Retriever + Ingestor --> Store + Retriever --> Store + Pruner --> Store +``` + +## Lock Lifecycle + +```mermaid +sequenceDiagram + participant Agent + participant TORCH as TORCH CLI + participant Relays as Nostr Relays + + Agent->>TORCH: torch-lock check --cadence daily + TORCH->>Relays: Query active locks (REQ filter) + Relays-->>TORCH: Existing lock events + TORCH-->>Agent: Status: available / locked + + Agent->>TORCH: torch-lock lock --agent my-agent + TORCH->>TORCH: Generate NIP-01 event (kind 30078) + TORCH->>Relays: Publish lock event (fanout) + Relays-->>TORCH: ACK from N relays + TORCH->>Relays: Race-check query (verify no conflicts) + Relays-->>TORCH: Confirm lock acquired + TORCH-->>Agent: Lock acquired (exit 0) + + Note over Agent: Agent performs work... + + Agent->>TORCH: torch-lock complete --agent my-agent + TORCH->>Relays: Publish completion event + TORCH-->>Agent: Completed (exit 0) +``` + +## Scheduler Flow + +```mermaid +flowchart TD + Start["Scheduler Cycle Start"] --> LoadRoster["Load Roster
(roster.json)"] + LoadRoster --> Filter["Filter by cadence
(daily/weekly)"] + Filter --> CheckPaused{"Agent
paused?"} + CheckPaused -->|Yes| Skip["Skip agent"] + CheckPaused -->|No| CheckCompleted{"Already
completed
today?"} + CheckCompleted -->|Yes| Skip + CheckCompleted -->|No| CheckLock{"Lock
available?"} + CheckLock -->|No| Skip + CheckLock -->|Yes| Acquire["Acquire Lock"] + Acquire --> RunAgent["Execute Agent Prompt"] + RunAgent --> Complete["Publish Completion"] + Complete --> LogResult["Write Task Log"] + Skip --> LogResult + LogResult --> Next{"More
agents?"} + Next -->|Yes| CheckPaused + Next -->|No| End["Cycle Complete"] +``` + +## Memory Subsystem + +```mermaid +flowchart LR + Events["Runtime Events"] --> Ingestor + Ingestor --> |"Chunk + Summarize"| Store["Memory Store
(JSON file)"] + Store --> Retriever + Retriever --> |"Rank: semantic +
importance + recency"| Results["Ranked Memories"] + + Scheduler["Maintenance
Scheduler"] --> Pruner + Pruner --> |"TTL / merge /
archive / delete"| Store + + subgraph Jobs["Scheduled Jobs"] + J1["Ingest (1-5 min)"] + J2["Consolidate (1 hr)"] + J3["Prune (daily)"] + J4["Deep Merge (weekly)"] + end + Scheduler --> Jobs +``` + +## Key Data Flows + +| Flow | Protocol | Event Kind | Key Fields | +|------|----------|-----------|------------| +| Lock acquire | NIP-01 publish | 30078 | d-tag, expiration, agent, cadence, status | +| Lock query | NIP-01 REQ | 30078 | #t filter on hashtag | +| Lock complete | NIP-01 publish | 30078 | status: "completed" | +| Dashboard feed | NIP-01 WebSocket | 30078 | Real-time SUB with since filter | + +## Directory Structure + +``` +TORCH/ +├── bin/ # CLI entry point +├── src/ +│ ├── lib.mjs # Command dispatch +│ ├── lock-ops.mjs # Nostr relay operations +│ ├── relay-health.mjs # Relay health monitoring +│ ├── dashboard.mjs # Dashboard HTTP server +│ ├── ops.mjs # Install/update operations +│ ├── services/ +│ │ ├── memory/ # Memory subsystem +│ │ └── governance/ # Governance service +│ └── prompts/ # Agent prompt definitions +├── dashboard/ # Dashboard frontend +├── scripts/agent/ # Scheduler and agent scripts +├── test/ # Test suite +├── docs/ # Architecture documentation +└── migrations/ # Database schemas (future) +``` diff --git a/eslint.config.mjs b/eslint.config.mjs index 95d780e..b995318 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -16,7 +16,7 @@ export default [ sourceType: "module", }, rules: { - "no-unused-vars": "warn", + "no-unused-vars": ["error", { "argsIgnorePattern": "^_", "caughtErrorsIgnorePattern": "^_" }], "no-console": "off", }, }, diff --git a/package-lock.json b/package-lock.json index 2bf1f30..501d1e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,6 @@ }, "devDependencies": { "@eslint/js": "^10.0.1", - "@playwright/test": "^1.58.2", "eslint": "^10.0.0", "globals": "^17.3.0" }, @@ -260,22 +259,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@playwright/test": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", - "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright": "1.58.2" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@scure/base": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", @@ -691,21 +674,6 @@ "dev": true, "license": "ISC" }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -988,38 +956,6 @@ "node": ">=8" } }, - "node_modules/playwright": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", - "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright-core": "1.58.2" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", - "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", diff --git a/perf/benchmark-pruner.mjs b/perf/benchmark-pruner.mjs index 044532f..f5f5fc8 100644 --- a/perf/benchmark-pruner.mjs +++ b/perf/benchmark-pruner.mjs @@ -1,5 +1,5 @@ import { createLifecyclePlan } from '../src/services/memory/pruner.js'; -import crypto from 'node:crypto'; + const COUNT = 30000; const DUPLICATE_WINDOW_MS = 1000 * 60 * 60; // 1 hour diff --git a/scripts/agent/smoke-test.mjs b/scripts/agent/smoke-test.mjs index c2b3e49..f7c4499 100644 --- a/scripts/agent/smoke-test.mjs +++ b/scripts/agent/smoke-test.mjs @@ -16,11 +16,8 @@ const getArg = (key) => { if (flagIdx !== -1 && ARGS[flagIdx + 1] && !ARGS[flagIdx + 1].startsWith('--')) return ARGS[flagIdx + 1]; return null; }; -const hasArg = (key) => ARGS.includes(`--${key}`); - const RELAYS = (getArg('relays') || process.env.RELAY_URLS || '').split(',').filter(Boolean); const SERVE_CMD = getArg('serve') || 'npx'; -const CONFIRM_PUBLIC = hasArg('confirm-public'); const OUT_DIR = getArg('out') || 'artifacts'; // --- Logging --- @@ -45,7 +42,7 @@ async function waitForServer(url, timeoutMs = 10000) { try { const res = await fetch(url); if (res.ok) return true; - } catch (e) { + } catch (_e) { // ignore } await sleep(500); diff --git a/scripts/benchmark-dashboard.mjs b/scripts/benchmark-dashboard.mjs index e32fcfc..b50a085 100644 --- a/scripts/benchmark-dashboard.mjs +++ b/scripts/benchmark-dashboard.mjs @@ -22,7 +22,7 @@ async function waitForServer() { req.end(); }); return; - } catch (e) { + } catch (_e) { await sleep(100); } } @@ -59,8 +59,8 @@ async function benchmark() { if (current >= REQUESTS) break; try { await makeRequest(); - } catch (e) { - // console.error(e); + } catch (_e) { + // console.error(_e); } } }; diff --git a/scripts/benchmark-pruner.mjs b/scripts/benchmark-pruner.mjs index dae4f89..166cbd9 100644 --- a/scripts/benchmark-pruner.mjs +++ b/scripts/benchmark-pruner.mjs @@ -96,7 +96,7 @@ async function runBenchmark() { duplicateWindowMs: 1000 * 60 * 60, // 1 hour similarityThreshold: 0.99, getEmbedding: getEmbeddingWrapper, - generateSummary: async (prompt) => { + generateSummary: async (_prompt) => { await delay(LATENCY_MS); return JSON.stringify({ summary: 'Condensed summary based on prompt.', diff --git a/scripts/test-audit/run-flaky-check.mjs b/scripts/test-audit/run-flaky-check.mjs index cb05548..575e518 100644 --- a/scripts/test-audit/run-flaky-check.mjs +++ b/scripts/test-audit/run-flaky-check.mjs @@ -31,7 +31,7 @@ async function runTests(i) { child.stdout.on('data', (d) => stdout += d.toString()); - child.on('close', (code) => { + child.on('close', (_code) => { const lines = stdout.split('\n'); for (const line of lines) { if (line.startsWith('not ok ') && !line.includes('# skip') && !line.includes('# todo')) { diff --git a/scripts/test-audit/run-static-analysis.mjs b/scripts/test-audit/run-static-analysis.mjs index 7646a1a..243f0e4 100644 --- a/scripts/test-audit/run-static-analysis.mjs +++ b/scripts/test-audit/run-static-analysis.mjs @@ -12,7 +12,7 @@ function getFiles(dir, files = []) { files.push(name); } } - } catch (e) { + } catch (_e) { // ignore if dir doesn't exist } return files; diff --git a/src/cmd-proposal.mjs b/src/cmd-proposal.mjs index b098c9f..8598345 100644 --- a/src/cmd-proposal.mjs +++ b/src/cmd-proposal.mjs @@ -1,7 +1,6 @@ import { createProposal, listProposals, applyProposal, rejectProposal, getProposal } from './services/governance/index.js'; import { ExitError } from './errors.mjs'; import fs from 'node:fs/promises'; -import path from 'node:path'; export async function cmdProposal(subcommand, args = {}) { switch (subcommand) { @@ -30,7 +29,7 @@ async function handleCreate({ agent, target, contentFile, reason }) { let newContent; try { newContent = await fs.readFile(contentFile, 'utf8'); - } catch (e) { + } catch (_e) { console.error(`Failed to read content file: ${contentFile}`); throw new ExitError(1, 'File read error'); } diff --git a/src/lock-ops.mjs b/src/lock-ops.mjs index a17220c..8b1830a 100644 --- a/src/lock-ops.mjs +++ b/src/lock-ops.mjs @@ -9,19 +9,13 @@ import { } from './torch-config.mjs'; import { KIND_APP_DATA, - DEFAULT_RETRY_ATTEMPTS, - DEFAULT_RETRY_BASE_DELAY_MS, - DEFAULT_RETRY_CAP_DELAY_MS, DEFAULT_ROLLING_WINDOW_SIZE, DEFAULT_FAILURE_THRESHOLD, DEFAULT_QUARANTINE_COOLDOWN_MS, DEFAULT_MAX_QUARANTINE_COOLDOWN_MS, DEFAULT_SNAPSHOT_INTERVAL_MS, } from './constants.mjs'; - -function nowUnix() { - return Math.floor(Date.now() / 1000); -} +import { nowUnix } from './utils.mjs'; /** * Parses a raw Nostr event into a structured lock object. @@ -630,10 +624,6 @@ export class LockPublisher { async publish() { const { poolFactory = () => new SimplePool(), - getPublishTimeoutMsFn = getPublishTimeoutMs, - getMinSuccessfulRelayPublishesFn = getMinSuccessfulRelayPublishes, - getRelayFallbacksFn = getRelayFallbacks, - getMinActiveRelayPoolFn = getMinActiveRelayPool, retryAttempts = 4, retryBaseDelayMs = 500, retryCapDelayMs = 8_000, diff --git a/src/lock-utils.mjs b/src/lock-utils.mjs index b6a0b6d..29863e4 100644 --- a/src/lock-utils.mjs +++ b/src/lock-utils.mjs @@ -26,7 +26,7 @@ export async function getCompletedAgents(cadence, logDir, deps) { const match = file.match(/^(\d{4}-\d{2}-\d{2})T.*__([a-zA-Z0-9-_]+)__(.*)\.md$/); if (!match) continue; - const [_, datePart, agent, status] = match; + const [, datePart, agent, status] = match; if (status === 'completed') { if (cadence === 'daily' && datePart === today) { diff --git a/src/ops.mjs b/src/ops.mjs index b529018..2ea79a8 100644 --- a/src/ops.mjs +++ b/src/ops.mjs @@ -381,7 +381,7 @@ export function cmdUpdate(force = false, cwd = process.cwd()) { if (pkg.name === 'torch-lock') { installDirName = '.'; } - } catch (e) { + } catch (_e) { // Ignore error if package.json is missing or invalid } } diff --git a/src/perf/benchmark-config-load.mjs b/src/perf/benchmark-config-load.mjs index 4e00f1d..98d096d 100644 --- a/src/perf/benchmark-config-load.mjs +++ b/src/perf/benchmark-config-load.mjs @@ -25,7 +25,7 @@ async function run() { const res = await loadTorchConfig(); // Use res to prevent dead code elimination if JIT is smart (though side effects exist) if (!res) throw new Error("Failed"); - } catch (e) { + } catch (_e) { // ignore } } diff --git a/src/services/governance/index.js b/src/services/governance/index.js index a5d8532..f9179ea 100644 --- a/src/services/governance/index.js +++ b/src/services/governance/index.js @@ -30,7 +30,7 @@ export async function listProposals() { id: entry.name, ...meta }); - } catch (e) { + } catch (_e) { // Ignore invalid proposals } } @@ -107,7 +107,7 @@ export async function createProposal({ agent, target, newContent, reason }) { console.error('Diff generation failed:', e.message); } } - } catch (err) { + } catch (_err) { diff = '(New File)'; } @@ -187,7 +187,7 @@ export async function applyProposal(id) { // User requested /.md. const archivePath = path.join(archiveDir, `${path.basename(meta.target, '.md')}_${hash}.md`); await fs.writeFile(archivePath, oldContent); - } catch (e) { + } catch (_e) { // If file didn't exist, nothing to archive } @@ -265,7 +265,7 @@ export async function rollbackPrompt(target, hashOrStrategy = 'latest') { sourceContent = await fs.readFile(path.join(archiveDir, match), 'utf8'); } } - } catch (e) { + } catch (_e) { // Archive lookup failed } diff --git a/src/services/memory/embedder.js b/src/services/memory/embedder.js index 0029d0a..ba26c63 100644 --- a/src/services/memory/embedder.js +++ b/src/services/memory/embedder.js @@ -14,7 +14,7 @@ const VECTOR_BACKEND_ENV = 'MEMORY_VECTOR_BACKEND'; * @param {string} text * @returns {number[]} */ -function fallbackEmbedding(text) { +function fallbackEmbedding(_text) { // Disabled as per configuration: Jules uses text-based memory, not vector embeddings. return []; } diff --git a/src/services/memory/index.js b/src/services/memory/index.js index 7adfa49..24c2474 100644 --- a/src/services/memory/index.js +++ b/src/services/memory/index.js @@ -7,7 +7,7 @@ import { getMemoryPruneMode, isMemoryIngestEnabled } from './feature-flags.js'; import fs from 'node:fs'; import path from 'node:path'; import util from 'node:util'; -import { ensureDir } from '../../utils.mjs'; + const MEMORY_FILE_PATH = path.join(process.cwd(), '.scheduler-memory', 'memory-store.json'); const debug = util.debuglog('torch-memory'); diff --git a/test/cmd-list.test.mjs b/test/cmd-list.test.mjs index 226cfa7..e81c0bc 100644 --- a/test/cmd-list.test.mjs +++ b/test/cmd-list.test.mjs @@ -1,4 +1,4 @@ -import { test, describe, it } from 'node:test'; +import { describe, it } from 'node:test'; import assert from 'node:assert'; import { cmdList } from '../src/cmd-list.mjs'; diff --git a/test/dashboard-config-exposure.test.mjs b/test/dashboard-config-exposure.test.mjs index cc570a3..6cb4763 100644 --- a/test/dashboard-config-exposure.test.mjs +++ b/test/dashboard-config-exposure.test.mjs @@ -40,8 +40,6 @@ test('Dashboard Configuration Exposure Security', async (t) => { // Override env to use this config const originalEnvPath = process.env.TORCH_CONFIG_PATH; - const originalAuth = process.env.TORCH_DASHBOARD_AUTH; - process.env.TORCH_CONFIG_PATH = tempConfigPath; // Disable auth via env so we can access the endpoint without credentials // The vulnerability is about exposure of *other* fields. @@ -93,7 +91,7 @@ test('Dashboard Configuration Exposure Security', async (t) => { let exposedConfig; try { exposedConfig = JSON.parse(responseBody); - } catch (e) { + } catch (_e) { assert.fail(`Failed to parse JSON response: ${responseBody}`); } diff --git a/test/lib.test.mjs b/test/lib.test.mjs index 32ca64c..050308e 100644 --- a/test/lib.test.mjs +++ b/test/lib.test.mjs @@ -99,7 +99,6 @@ describe('src/lib.mjs', () => { }); describe('cmdLock', () => { - const mockPublishLock = async () => {}; const mockGenerateSecretKey = () => new Uint8Array(32); const mockGetPublicKey = () => 'mock-pubkey'; const mockFinalizeEvent = (t) => ({ ...t, id: 'mock-id', sig: 'mock-sig' }); @@ -353,7 +352,6 @@ describe('src/lib.mjs', () => { }); describe('cmdComplete', () => { - const mockPublishLock = async () => {}; const mockGenerateSecretKey = () => new Uint8Array(32); const mockGetPublicKey = () => 'mock-pubkey'; const mockFinalizeEvent = (t) => ({ ...t, id: 'mock-id-complete' }); diff --git a/test/memory-admin.test.mjs b/test/memory-admin.test.mjs index d27942c..e664c6c 100644 --- a/test/memory-admin.test.mjs +++ b/test/memory-admin.test.mjs @@ -138,7 +138,7 @@ test('runPruneCycle respects prune feature-flag modes', async () => { test('markMemoryMerged delegates to repository and clears cache', async () => { let callCount = 0; const repository = { - async markMerged(id, mergedInto) { + async markMerged(id, _mergedInto) { callCount++; return id === 'exists'; } diff --git a/test/memory-formatter.test.mjs b/test/memory-formatter.test.mjs index 9b7717e..4c0d0da 100644 --- a/test/memory-formatter.test.mjs +++ b/test/memory-formatter.test.mjs @@ -6,7 +6,6 @@ import { formatMemoryBlock, formatMemoriesForPrompt, renderPromptWithMemoryContext, - isMemoryRetrievalEnabled } from '../src/services/memory/formatter.js'; function createMemory(overrides = {}) { diff --git a/test/memory-index.test.mjs b/test/memory-index.test.mjs index 1e9709f..64a1d05 100644 --- a/test/memory-index.test.mjs +++ b/test/memory-index.test.mjs @@ -110,7 +110,7 @@ test('getRelevantMemories passes correct parameters to ranker', async (t) => { await getRelevantMemories(params); assert.equal(ranker.mock.callCount(), 1); - const [_, queryParams] = ranker.mock.calls[0].arguments; + const [, queryParams] = ranker.mock.calls[0].arguments; assert.equal(queryParams.agent_id, 'agent-1'); assert.equal(queryParams.query, 'my query'); diff --git a/test/memory-ingest.test.mjs b/test/memory-ingest.test.mjs index eff7b6d..af032a6 100644 --- a/test/memory-ingest.test.mjs +++ b/test/memory-ingest.test.mjs @@ -1,4 +1,4 @@ -import { test, describe, beforeEach, afterEach } from 'node:test'; +import { test, describe, beforeEach } from 'node:test'; import assert from 'node:assert/strict'; import { ingestEvents, getRelevantMemories } from '../src/services/memory/index.js'; @@ -11,7 +11,7 @@ describe('ingestEvents', () => { let linkedEmbeddings = new Map(); const mockOptions = { - embedText: async (text) => Array(128).fill(0.1), // Deterministic embedding + embedText: async (_text) => Array(128).fill(0.1), // Deterministic embedding emitTelemetry: (event, payload) => telemetryEvents.push({ event, payload }), metrics: { emit: (metric, payload) => metricsEvents.push({ metric, payload }), diff --git a/test/memory-pinning.test.mjs b/test/memory-pinning.test.mjs index 88869d3..85fd072 100644 --- a/test/memory-pinning.test.mjs +++ b/test/memory-pinning.test.mjs @@ -52,7 +52,7 @@ test('pinMemory returns null for non-existent ID but still clears cache', async listMemoriesCallCount++; return records; }, - async setPinned(id, pinned) { + async setPinned(_id, _pinned) { return null; }, async updateMemoryUsage() { return null; } @@ -121,7 +121,7 @@ test('unpinMemory returns null for non-existent ID but still clears cache', asyn listMemoriesCallCount++; return records; }, - async setPinned(id, pinned) { + async setPinned(_id, _pinned) { return null; }, async updateMemoryUsage() { return null; } diff --git a/test/memory-run-prune-cycle.test.mjs b/test/memory-run-prune-cycle.test.mjs index a9092a3..28b519b 100644 --- a/test/memory-run-prune-cycle.test.mjs +++ b/test/memory-run-prune-cycle.test.mjs @@ -42,8 +42,7 @@ test.before(async () => { await fs.mkdir(memDir, { recursive: true }); // Create initial state - const now = 200_000; - // retentionMs will be 100,000 + // now = 200_000, retentionMs will be 100,000 // cutoff = 100,000 const memories = [ diff --git a/test/nostr-lock.test.mjs b/test/nostr-lock.test.mjs index 127e130..f2c16da 100644 --- a/test/nostr-lock.test.mjs +++ b/test/nostr-lock.test.mjs @@ -1,4 +1,4 @@ -import { test, describe, it } from 'node:test'; +import { describe, it } from 'node:test'; import assert from 'node:assert'; import { spawnSync } from 'node:child_process'; import fs from 'node:fs';