chore: code quality, CI/CD, and DX improvements

Phase 3 — Code Quality:
- Deduplicate nowUnix() in lock-ops.mjs (import from utils.mjs)
- Promote ESLint no-unused-vars to error, fix all 46 violations
- Remove dead code: unused imports, variables, and catch bindings
- Add /* global marked */ for CDN-loaded library in dashboard

Phase 4 — CI/CD:
- Add Node.js version matrix (22, 23) to CI workflow
- Add npm audit security scan step to CI
- Add innerHTML usage check to CI pipeline
- Enable Dependabot for npm and GitHub Actions dependencies
- Create PR template

Phase 5 — Developer Experience:
- Create .env.example documenting all environment variables
- Expand CONTRIBUTING.md with commit conventions, agent/prompt
  creation guide, architecture overview, and test integrity
- Add system architecture diagram with Mermaid (docs/architecture/)

https://claude.ai/code/session_01GfwUNbgeMgXreFDbsNQMsS
This commit is contained in:
Claude committed 2026-02-19 21:03:30 +00:00
1 parent e842ec069a
commit 639ad6877d
33 files changed
+336 -129

No files matched your search

+47
View File
@@ -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
+17
View File
@@ -0,0 +1,17 @@
## Summary
<!-- 1-3 bullet points describing what this PR does and why -->
-
## Test plan
<!-- How was this tested? -->
- [ ] `npm run lint` passes
- [ ] `npm test` passes
- [ ] Manual verification (if applicable)
## Related issues
<!-- Link related issues: Fixes #123, Closes #456 -->
+12
View File
@@ -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
+9 -2
View File
@@ -9,14 +9,21 @@ on:
jobs: jobs:
build-and-test: build-and-test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22, 23]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Use Node.js 22 - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 22 node-version: ${{ matrix.node-version }}
cache: 'npm' cache: 'npm'
- run: npm ci - run: npm ci
- run: npm run build - run: npm run build
- run: npm run lint - 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 - run: npm test
+61 -8
View File
@@ -16,11 +16,17 @@ Thank you for your interest in contributing to TORCH!
npm install npm install
``` ```
3. Copy the environment template (optional):
```bash
cp .env.example .env
```
## Development Workflow ## Development Workflow
### Building ### 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 ```bash
npm run build npm run build
@@ -28,32 +34,79 @@ npm run build
### Testing ### Testing
Run the test suite to ensure your changes don't break existing functionality. Run the full test suite:
```bash ```bash
npm test npm test
``` ```
For faster feedback loop on unit tests (excluding integration): For faster feedback on lock-backend unit tests:
```bash ```bash
npm run test:unit:lock-backend npm run test:unit:lock-backend
``` ```
For integration/resilience tests:
```bash
npm run test:extended-main
```
### Linting ### Linting
Ensure your code follows the project's style guidelines. Ensure your code follows the project's style guidelines:
```bash ```bash
npm run lint 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: `<agent-name>.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 ## Pull Requests
Please follow the repository conventions for Pull Requests. - Keep PR titles short (under 70 characters)
- Include a summary of changes and test plan in the description
> **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. - Ensure `npm run lint` and `npm test` pass before submitting
- Reference related issues where applicable
## Reporting Issues ## 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.
+3 -5
View File
@@ -1,5 +1,6 @@
/* global marked */
import { KIND_APP_DATA } from '../src/constants.mjs'; import { KIND_APP_DATA } from '../src/constants.mjs';
import { createElement, escapeHtml } from './domUtils.js'; import { createElement } from './domUtils.js';
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// Modal Logic // Modal Logic
@@ -168,7 +169,7 @@ function parseDashboardConfig(torchConfig = {}) {
let localPrefs = {}; let localPrefs = {};
try { try {
localPrefs = JSON.parse(localStorage.getItem('torch_dashboard_prefs')) || {}; localPrefs = JSON.parse(localStorage.getItem('torch_dashboard_prefs')) || {};
} catch {} } catch { /* ignore malformed localStorage */ }
const dashboardConfig = torchConfig.dashboard || {}; const dashboardConfig = torchConfig.dashboard || {};
const lockConfig = torchConfig.nostrLock || {}; const lockConfig = torchConfig.nostrLock || {};
@@ -218,8 +219,6 @@ function parseDashboardConfig(torchConfig = {}) {
let DASHBOARD_CONFIG = parseDashboardConfig({}); let DASHBOARD_CONFIG = parseDashboardConfig({});
let RELAYS = DASHBOARD_CONFIG.relays; let RELAYS = DASHBOARD_CONFIG.relays;
let D_TAG_PREFIX = `${DASHBOARD_CONFIG.namespace}-lock/`;
const LOCK_EVENT_KIND = KIND_APP_DATA; const LOCK_EVENT_KIND = KIND_APP_DATA;
let HASHTAG = DASHBOARD_CONFIG.hashtag; let HASHTAG = DASHBOARD_CONFIG.hashtag;
@@ -787,7 +786,6 @@ async function bootstrap() {
const torchConfig = await loadTorchConfigFile(); const torchConfig = await loadTorchConfigFile();
DASHBOARD_CONFIG = parseDashboardConfig(torchConfig); DASHBOARD_CONFIG = parseDashboardConfig(torchConfig);
RELAYS = DASHBOARD_CONFIG.relays; RELAYS = DASHBOARD_CONFIG.relays;
D_TAG_PREFIX = `${DASHBOARD_CONFIG.namespace}-lock/`;
HASHTAG = DASHBOARD_CONFIG.hashtag; HASHTAG = DASHBOARD_CONFIG.hashtag;
cadenceFilter.value = DASHBOARD_CONFIG.defaultCadenceView; cadenceFilter.value = DASHBOARD_CONFIG.defaultCadenceView;
+157
View File
@@ -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<br/>Command Dispatch"]
LockOps["src/lock-ops.mjs<br/>Lock Publisher / Query"]
Health["src/relay-health.mjs<br/>Relay Health Monitor"]
Scheduler["scripts/agent/<br/>run-scheduler-cycle.mjs"]
end
subgraph Nostr["Nostr Network"]
R1["Relay 1<br/>relay.damus.io"]
R2["Relay 2<br/>nos.lol"]
R3["Relay 3<br/>relay.primal.net"]
end
subgraph Dashboard["Dashboard"]
Server["src/dashboard.mjs<br/>HTTP Server"]
UI["dashboard/index.html<br/>+ 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<br/>(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<br/>(roster.json)"]
LoadRoster --> Filter["Filter by cadence<br/>(daily/weekly)"]
Filter --> CheckPaused{"Agent<br/>paused?"}
CheckPaused -->|Yes| Skip["Skip agent"]
CheckPaused -->|No| CheckCompleted{"Already<br/>completed<br/>today?"}
CheckCompleted -->|Yes| Skip
CheckCompleted -->|No| CheckLock{"Lock<br/>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<br/>agents?"}
Next -->|Yes| CheckPaused
Next -->|No| End["Cycle Complete"]
```
## Memory Subsystem
```mermaid
flowchart LR
Events["Runtime Events"] --> Ingestor
Ingestor --> |"Chunk + Summarize"| Store["Memory Store<br/>(JSON file)"]
Store --> Retriever
Retriever --> |"Rank: semantic +<br/>importance + recency"| Results["Ranked Memories"]
Scheduler["Maintenance<br/>Scheduler"] --> Pruner
Pruner --> |"TTL / merge /<br/>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)
```
+1 -1
View File
@@ -16,7 +16,7 @@ export default [
sourceType: "module", sourceType: "module",
}, },
rules: { rules: {
"no-unused-vars": "warn", "no-unused-vars": ["error", { "argsIgnorePattern": "^_", "caughtErrorsIgnorePattern": "^_" }],
"no-console": "off", "no-console": "off",
}, },
}, },
-64
View File
@@ -17,7 +17,6 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@playwright/test": "^1.58.2",
"eslint": "^10.0.0", "eslint": "^10.0.0",
"globals": "^17.3.0" "globals": "^17.3.0"
}, },
@@ -260,22 +259,6 @@
"url": "https://paulmillr.com/funding/" "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": { "node_modules/@scure/base": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz",
@@ -691,21 +674,6 @@
"dev": true, "dev": true,
"license": "ISC" "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": { "node_modules/glob-parent": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -988,38 +956,6 @@
"node": ">=8" "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": { "node_modules/prelude-ls": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+1 -1
View File
@@ -1,5 +1,5 @@
import { createLifecyclePlan } from '../src/services/memory/pruner.js'; import { createLifecyclePlan } from '../src/services/memory/pruner.js';
import crypto from 'node:crypto';
const COUNT = 30000; const COUNT = 30000;
const DUPLICATE_WINDOW_MS = 1000 * 60 * 60; // 1 hour const DUPLICATE_WINDOW_MS = 1000 * 60 * 60; // 1 hour
+1 -4
View File
@@ -16,11 +16,8 @@ const getArg = (key) => {
if (flagIdx !== -1 && ARGS[flagIdx + 1] && !ARGS[flagIdx + 1].startsWith('--')) return ARGS[flagIdx + 1]; if (flagIdx !== -1 && ARGS[flagIdx + 1] && !ARGS[flagIdx + 1].startsWith('--')) return ARGS[flagIdx + 1];
return null; return null;
}; };
const hasArg = (key) => ARGS.includes(`--${key}`);
const RELAYS = (getArg('relays') || process.env.RELAY_URLS || '').split(',').filter(Boolean); const RELAYS = (getArg('relays') || process.env.RELAY_URLS || '').split(',').filter(Boolean);
const SERVE_CMD = getArg('serve') || 'npx'; const SERVE_CMD = getArg('serve') || 'npx';
const CONFIRM_PUBLIC = hasArg('confirm-public');
const OUT_DIR = getArg('out') || 'artifacts'; const OUT_DIR = getArg('out') || 'artifacts';
// --- Logging --- // --- Logging ---
@@ -45,7 +42,7 @@ async function waitForServer(url, timeoutMs = 10000) {
try { try {
const res = await fetch(url); const res = await fetch(url);
if (res.ok) return true; if (res.ok) return true;
} catch (e) { } catch (_e) {
// ignore // ignore
} }
await sleep(500); await sleep(500);
+3 -3
View File
@@ -22,7 +22,7 @@ async function waitForServer() {
req.end(); req.end();
}); });
return; return;
} catch (e) { } catch (_e) {
await sleep(100); await sleep(100);
} }
} }
@@ -59,8 +59,8 @@ async function benchmark() {
if (current >= REQUESTS) break; if (current >= REQUESTS) break;
try { try {
await makeRequest(); await makeRequest();
} catch (e) { } catch (_e) {
// console.error(e); // console.error(_e);
} }
} }
}; };
+1 -1
View File
@@ -96,7 +96,7 @@ async function runBenchmark() {
duplicateWindowMs: 1000 * 60 * 60, // 1 hour duplicateWindowMs: 1000 * 60 * 60, // 1 hour
similarityThreshold: 0.99, similarityThreshold: 0.99,
getEmbedding: getEmbeddingWrapper, getEmbedding: getEmbeddingWrapper,
generateSummary: async (prompt) => { generateSummary: async (_prompt) => {
await delay(LATENCY_MS); await delay(LATENCY_MS);
return JSON.stringify({ return JSON.stringify({
summary: 'Condensed summary based on prompt.', summary: 'Condensed summary based on prompt.',
+1 -1
View File
@@ -31,7 +31,7 @@ async function runTests(i) {
child.stdout.on('data', (d) => stdout += d.toString()); child.stdout.on('data', (d) => stdout += d.toString());
child.on('close', (code) => { child.on('close', (_code) => {
const lines = stdout.split('\n'); const lines = stdout.split('\n');
for (const line of lines) { for (const line of lines) {
if (line.startsWith('not ok ') && !line.includes('# skip') && !line.includes('# todo')) { if (line.startsWith('not ok ') && !line.includes('# skip') && !line.includes('# todo')) {
+1 -1
View File
@@ -12,7 +12,7 @@ function getFiles(dir, files = []) {
files.push(name); files.push(name);
} }
} }
} catch (e) { } catch (_e) {
// ignore if dir doesn't exist // ignore if dir doesn't exist
} }
return files; return files;
+1 -2
View File
@@ -1,7 +1,6 @@
import { createProposal, listProposals, applyProposal, rejectProposal, getProposal } from './services/governance/index.js'; import { createProposal, listProposals, applyProposal, rejectProposal, getProposal } from './services/governance/index.js';
import { ExitError } from './errors.mjs'; import { ExitError } from './errors.mjs';
import fs from 'node:fs/promises'; import fs from 'node:fs/promises';
import path from 'node:path';
export async function cmdProposal(subcommand, args = {}) { export async function cmdProposal(subcommand, args = {}) {
switch (subcommand) { switch (subcommand) {
@@ -30,7 +29,7 @@ async function handleCreate({ agent, target, contentFile, reason }) {
let newContent; let newContent;
try { try {
newContent = await fs.readFile(contentFile, 'utf8'); newContent = await fs.readFile(contentFile, 'utf8');
} catch (e) { } catch (_e) {
console.error(`Failed to read content file: ${contentFile}`); console.error(`Failed to read content file: ${contentFile}`);
throw new ExitError(1, 'File read error'); throw new ExitError(1, 'File read error');
} }
+1 -11
View File
@@ -9,19 +9,13 @@ import {
} from './torch-config.mjs'; } from './torch-config.mjs';
import { import {
KIND_APP_DATA, KIND_APP_DATA,
DEFAULT_RETRY_ATTEMPTS,
DEFAULT_RETRY_BASE_DELAY_MS,
DEFAULT_RETRY_CAP_DELAY_MS,
DEFAULT_ROLLING_WINDOW_SIZE, DEFAULT_ROLLING_WINDOW_SIZE,
DEFAULT_FAILURE_THRESHOLD, DEFAULT_FAILURE_THRESHOLD,
DEFAULT_QUARANTINE_COOLDOWN_MS, DEFAULT_QUARANTINE_COOLDOWN_MS,
DEFAULT_MAX_QUARANTINE_COOLDOWN_MS, DEFAULT_MAX_QUARANTINE_COOLDOWN_MS,
DEFAULT_SNAPSHOT_INTERVAL_MS, DEFAULT_SNAPSHOT_INTERVAL_MS,
} from './constants.mjs'; } from './constants.mjs';
import { nowUnix } from './utils.mjs';
function nowUnix() {
return Math.floor(Date.now() / 1000);
}
/** /**
* Parses a raw Nostr event into a structured lock object. * Parses a raw Nostr event into a structured lock object.
@@ -630,10 +624,6 @@ export class LockPublisher {
async publish() { async publish() {
const { const {
poolFactory = () => new SimplePool(), poolFactory = () => new SimplePool(),
getPublishTimeoutMsFn = getPublishTimeoutMs,
getMinSuccessfulRelayPublishesFn = getMinSuccessfulRelayPublishes,
getRelayFallbacksFn = getRelayFallbacks,
getMinActiveRelayPoolFn = getMinActiveRelayPool,
retryAttempts = 4, retryAttempts = 4,
retryBaseDelayMs = 500, retryBaseDelayMs = 500,
retryCapDelayMs = 8_000, retryCapDelayMs = 8_000,
+1 -1
View File
@@ -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$/); const match = file.match(/^(\d{4}-\d{2}-\d{2})T.*__([a-zA-Z0-9-_]+)__(.*)\.md$/);
if (!match) continue; if (!match) continue;
const [_, datePart, agent, status] = match; const [, datePart, agent, status] = match;
if (status === 'completed') { if (status === 'completed') {
if (cadence === 'daily' && datePart === today) { if (cadence === 'daily' && datePart === today) {
+1 -1
View File
@@ -381,7 +381,7 @@ export function cmdUpdate(force = false, cwd = process.cwd()) {
if (pkg.name === 'torch-lock') { if (pkg.name === 'torch-lock') {
installDirName = '.'; installDirName = '.';
} }
} catch (e) { } catch (_e) {
// Ignore error if package.json is missing or invalid // Ignore error if package.json is missing or invalid
} }
} }
+1 -1
View File
@@ -25,7 +25,7 @@ async function run() {
const res = await loadTorchConfig(); const res = await loadTorchConfig();
// Use res to prevent dead code elimination if JIT is smart (though side effects exist) // Use res to prevent dead code elimination if JIT is smart (though side effects exist)
if (!res) throw new Error("Failed"); if (!res) throw new Error("Failed");
} catch (e) { } catch (_e) {
// ignore // ignore
} }
} }
+4 -4
View File
@@ -30,7 +30,7 @@ export async function listProposals() {
id: entry.name, id: entry.name,
...meta ...meta
}); });
} catch (e) { } catch (_e) {
// Ignore invalid proposals // Ignore invalid proposals
} }
} }
@@ -107,7 +107,7 @@ export async function createProposal({ agent, target, newContent, reason }) {
console.error('Diff generation failed:', e.message); console.error('Diff generation failed:', e.message);
} }
} }
} catch (err) { } catch (_err) {
diff = '(New File)'; diff = '(New File)';
} }
@@ -187,7 +187,7 @@ export async function applyProposal(id) {
// User requested <path>/<hash>.md. // User requested <path>/<hash>.md.
const archivePath = path.join(archiveDir, `${path.basename(meta.target, '.md')}_${hash}.md`); const archivePath = path.join(archiveDir, `${path.basename(meta.target, '.md')}_${hash}.md`);
await fs.writeFile(archivePath, oldContent); await fs.writeFile(archivePath, oldContent);
} catch (e) { } catch (_e) {
// If file didn't exist, nothing to archive // 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'); sourceContent = await fs.readFile(path.join(archiveDir, match), 'utf8');
} }
} }
} catch (e) { } catch (_e) {
// Archive lookup failed // Archive lookup failed
} }
+1 -1
View File
@@ -14,7 +14,7 @@ const VECTOR_BACKEND_ENV = 'MEMORY_VECTOR_BACKEND';
* @param {string} text * @param {string} text
* @returns {number[]} * @returns {number[]}
*/ */
function fallbackEmbedding(text) { function fallbackEmbedding(_text) {
// Disabled as per configuration: Jules uses text-based memory, not vector embeddings. // Disabled as per configuration: Jules uses text-based memory, not vector embeddings.
return []; return [];
} }
+1 -1
View File
@@ -7,7 +7,7 @@ import { getMemoryPruneMode, isMemoryIngestEnabled } from './feature-flags.js';
import fs from 'node:fs'; import fs from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import util from 'node:util'; import util from 'node:util';
import { ensureDir } from '../../utils.mjs';
const MEMORY_FILE_PATH = path.join(process.cwd(), '.scheduler-memory', 'memory-store.json'); const MEMORY_FILE_PATH = path.join(process.cwd(), '.scheduler-memory', 'memory-store.json');
const debug = util.debuglog('torch-memory'); const debug = util.debuglog('torch-memory');
+1 -1
View File
@@ -1,4 +1,4 @@
import { test, describe, it } from 'node:test'; import { describe, it } from 'node:test';
import assert from 'node:assert'; import assert from 'node:assert';
import { cmdList } from '../src/cmd-list.mjs'; import { cmdList } from '../src/cmd-list.mjs';
+1 -3
View File
@@ -40,8 +40,6 @@ test('Dashboard Configuration Exposure Security', async (t) => {
// Override env to use this config // Override env to use this config
const originalEnvPath = process.env.TORCH_CONFIG_PATH; const originalEnvPath = process.env.TORCH_CONFIG_PATH;
const originalAuth = process.env.TORCH_DASHBOARD_AUTH;
process.env.TORCH_CONFIG_PATH = tempConfigPath; process.env.TORCH_CONFIG_PATH = tempConfigPath;
// Disable auth via env so we can access the endpoint without credentials // Disable auth via env so we can access the endpoint without credentials
// The vulnerability is about exposure of *other* fields. // The vulnerability is about exposure of *other* fields.
@@ -93,7 +91,7 @@ test('Dashboard Configuration Exposure Security', async (t) => {
let exposedConfig; let exposedConfig;
try { try {
exposedConfig = JSON.parse(responseBody); exposedConfig = JSON.parse(responseBody);
} catch (e) { } catch (_e) {
assert.fail(`Failed to parse JSON response: ${responseBody}`); assert.fail(`Failed to parse JSON response: ${responseBody}`);
} }
-2
View File
@@ -99,7 +99,6 @@ describe('src/lib.mjs', () => {
}); });
describe('cmdLock', () => { describe('cmdLock', () => {
const mockPublishLock = async () => {};
const mockGenerateSecretKey = () => new Uint8Array(32); const mockGenerateSecretKey = () => new Uint8Array(32);
const mockGetPublicKey = () => 'mock-pubkey'; const mockGetPublicKey = () => 'mock-pubkey';
const mockFinalizeEvent = (t) => ({ ...t, id: 'mock-id', sig: 'mock-sig' }); const mockFinalizeEvent = (t) => ({ ...t, id: 'mock-id', sig: 'mock-sig' });
@@ -353,7 +352,6 @@ describe('src/lib.mjs', () => {
}); });
describe('cmdComplete', () => { describe('cmdComplete', () => {
const mockPublishLock = async () => {};
const mockGenerateSecretKey = () => new Uint8Array(32); const mockGenerateSecretKey = () => new Uint8Array(32);
const mockGetPublicKey = () => 'mock-pubkey'; const mockGetPublicKey = () => 'mock-pubkey';
const mockFinalizeEvent = (t) => ({ ...t, id: 'mock-id-complete' }); const mockFinalizeEvent = (t) => ({ ...t, id: 'mock-id-complete' });
+1 -1
View File
@@ -138,7 +138,7 @@ test('runPruneCycle respects prune feature-flag modes', async () => {
test('markMemoryMerged delegates to repository and clears cache', async () => { test('markMemoryMerged delegates to repository and clears cache', async () => {
let callCount = 0; let callCount = 0;
const repository = { const repository = {
async markMerged(id, mergedInto) { async markMerged(id, _mergedInto) {
callCount++; callCount++;
return id === 'exists'; return id === 'exists';
} }
-1
View File
@@ -6,7 +6,6 @@ import {
formatMemoryBlock, formatMemoryBlock,
formatMemoriesForPrompt, formatMemoriesForPrompt,
renderPromptWithMemoryContext, renderPromptWithMemoryContext,
isMemoryRetrievalEnabled
} from '../src/services/memory/formatter.js'; } from '../src/services/memory/formatter.js';
function createMemory(overrides = {}) { function createMemory(overrides = {}) {
+1 -1
View File
@@ -110,7 +110,7 @@ test('getRelevantMemories passes correct parameters to ranker', async (t) => {
await getRelevantMemories(params); await getRelevantMemories(params);
assert.equal(ranker.mock.callCount(), 1); 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.agent_id, 'agent-1');
assert.equal(queryParams.query, 'my query'); assert.equal(queryParams.query, 'my query');
+2 -2
View File
@@ -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 assert from 'node:assert/strict';
import { ingestEvents, getRelevantMemories } from '../src/services/memory/index.js'; import { ingestEvents, getRelevantMemories } from '../src/services/memory/index.js';
@@ -11,7 +11,7 @@ describe('ingestEvents', () => {
let linkedEmbeddings = new Map(); let linkedEmbeddings = new Map();
const mockOptions = { 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 }), emitTelemetry: (event, payload) => telemetryEvents.push({ event, payload }),
metrics: { metrics: {
emit: (metric, payload) => metricsEvents.push({ metric, payload }), emit: (metric, payload) => metricsEvents.push({ metric, payload }),
+2 -2
View File
@@ -52,7 +52,7 @@ test('pinMemory returns null for non-existent ID but still clears cache', async
listMemoriesCallCount++; listMemoriesCallCount++;
return records; return records;
}, },
async setPinned(id, pinned) { async setPinned(_id, _pinned) {
return null; return null;
}, },
async updateMemoryUsage() { return null; } async updateMemoryUsage() { return null; }
@@ -121,7 +121,7 @@ test('unpinMemory returns null for non-existent ID but still clears cache', asyn
listMemoriesCallCount++; listMemoriesCallCount++;
return records; return records;
}, },
async setPinned(id, pinned) { async setPinned(_id, _pinned) {
return null; return null;
}, },
async updateMemoryUsage() { return null; } async updateMemoryUsage() { return null; }
+1 -2
View File
@@ -42,8 +42,7 @@ test.before(async () => {
await fs.mkdir(memDir, { recursive: true }); await fs.mkdir(memDir, { recursive: true });
// Create initial state // Create initial state
const now = 200_000; // now = 200_000, retentionMs will be 100,000
// retentionMs will be 100,000
// cutoff = 100,000 // cutoff = 100,000
const memories = [ const memories = [
+1 -1
View File
@@ -1,4 +1,4 @@
import { test, describe, it } from 'node:test'; import { describe, it } from 'node:test';
import assert from 'node:assert'; import assert from 'node:assert';
import { spawnSync } from 'node:child_process'; import { spawnSync } from 'node:child_process';
import fs from 'node:fs'; import fs from 'node:fs';