mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-11-03 11:21:53 +00:00
update
This commit is contained in:
1 parent
b5023799d3
commit
1fb668064e
8 files changed
+422
-38
No files matched your search
@@ -33,6 +33,8 @@ npx --no-install torch-lock check --cadence daily
|
||||
npx --no-install torch-lock dashboard --port 4173 --host 127.0.0.1
|
||||
```
|
||||
|
||||
`torch-lock init` and `torch-lock update` now auto-upsert the `## TORCH Memory Integration` block into root-level `AGENTS.md` and `CLAUDE.md` (and create `AGENTS.md` if neither file exists).
|
||||
|
||||
> TORCH is distributed from GitHub tarballs and is not currently published to the npm registry.
|
||||
> Use pinned commit tarballs (`archive/<commit-sha>.tar.gz`) for repeatable installs. Avoid `refs/heads/main.tar.gz` for production rollouts.
|
||||
|
||||
@@ -119,6 +121,9 @@ npx --no-install torch-lock init
|
||||
# Update TORCH configuration and scripts
|
||||
npx --no-install torch-lock update
|
||||
|
||||
# Validate setup and print actionable fixes
|
||||
npx --no-install torch-lock doctor
|
||||
|
||||
# Remove TORCH completely from this project
|
||||
npx --no-install torch-lock remove
|
||||
|
||||
|
||||
+15
-38
@@ -938,9 +938,9 @@
|
||||
</div>
|
||||
|
||||
<p class="install-label" style="margin-top: 1rem;">Copy this happy-path prompt to onboard TORCH:</p>
|
||||
<p class="text-muted" style="margin-top: 0; margin-bottom: 0.75rem; font-size: 0.85rem;">Requires Node.js 22+.</p>
|
||||
<p class="text-muted" style="margin-top: 0; margin-bottom: 0.75rem; font-size: 0.85rem;">Requires Node.js 22+. `init` also hooks TORCH memory instructions into AGENTS.md / CLAUDE.md.</p>
|
||||
<div class="install-code-wrapper multiline">
|
||||
<code id="install-cmd">Hey Jules, can you install and setup TORCH into this repo? Here is the install command:
|
||||
<code id="install-cmd">Hey Jules, can you install and setup TORCH into this repo in one pass? Please run this command:
|
||||
|
||||
npm install https://github.com/PR0M3TH3AN/TORCH/archive/refs/heads/main.tar.gz && npx --no-install torch-lock init</code>
|
||||
<button class="copy-install-btn" onclick="copyToClipboard('install-cmd', this, true)">
|
||||
@@ -1022,27 +1022,6 @@ npm install https://github.com/PR0M3TH3AN/TORCH/archive/refs/heads/main.tar.gz &
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="hook-memory scroll-reveal" style="max-width: 800px; margin: 4rem auto; padding: 0 2rem; text-align: center;">
|
||||
<h2 style="font-size: 2rem; margin-bottom: 1.5rem; color: var(--text-strong);">Hook memory into your existing agents</h2>
|
||||
<p style="font-size: 1.1rem; color: var(--muted); margin-bottom: 2rem; line-height: 1.6;">
|
||||
Add these instructions to your agent's system prompt (e.g. in <code>AGENTS.md</code> or <code>CLAUDE.md</code>) to enable the TORCH memory system.
|
||||
</p>
|
||||
|
||||
<div class="install-code-wrapper multiline" style="margin: 0 auto; text-align: left;">
|
||||
<code id="memory-hook-code">## TORCH Memory Integration
|
||||
You have access to the TORCH memory system.
|
||||
1. READ: Check `.scheduler-memory/latest/${cadence}/memories.md` for past learnings.
|
||||
2. WRITE: Before exiting, save new insights to `memory-update.md` so future runs can learn from this session.</code>
|
||||
<button class="copy-install-btn" onclick="copyToClipboard('memory-hook-code', this)">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||
</svg>
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="meta-prompts scroll-reveal">
|
||||
<h2>Scheduler Meta Prompts</h2>
|
||||
<div class="prompts-grid">
|
||||
@@ -1637,21 +1616,19 @@ You have access to the TORCH memory system.
|
||||
// Update prompt text
|
||||
const installCmd = document.getElementById('install-cmd');
|
||||
let promptText = '';
|
||||
|
||||
if (agent === 'jules') {
|
||||
promptText = "Hey Jules, can you install and setup TORCH into this repo? Here is the install command:\n\nnpm install https://github.com/PR0M3TH3AN/TORCH/archive/refs/heads/main.tar.gz && npx --no-install torch-lock init";
|
||||
} else if (agent === 'codex') {
|
||||
promptText = "Hey Codex, can you install and setup TORCH into this repo? Here is the install command:\n\nnpm install https://github.com/PR0M3TH3AN/TORCH/archive/refs/heads/main.tar.gz && npx --no-install torch-lock init";
|
||||
} else if (agent === 'opencode') {
|
||||
promptText = "Hey OpenCode, can you install and setup TORCH into this repo? Here is the install command:\n\nnpm install https://github.com/PR0M3TH3AN/TORCH/archive/refs/heads/main.tar.gz && npx --no-install torch-lock init";
|
||||
} else if (agent === 'claude') {
|
||||
promptText = "Hey Claude, can you install and setup TORCH into this repo? Here is the install command:\n\nnpm install https://github.com/PR0M3TH3AN/TORCH/archive/refs/heads/main.tar.gz && npx --no-install torch-lock init";
|
||||
} else if (agent === 'gemini') {
|
||||
promptText = "Hey Gemini, can you install and setup TORCH into this repo? Here is the install command:\n\nnpm install https://github.com/PR0M3TH3AN/TORCH/archive/refs/heads/main.tar.gz && npx --no-install torch-lock init";
|
||||
} else if (agent === 'qwen') {
|
||||
promptText = "Hey Qwen, can you install and setup TORCH into this repo? Here is the install command:\n\nnpm install https://github.com/PR0M3TH3AN/TORCH/archive/refs/heads/main.tar.gz && npx --no-install torch-lock init";
|
||||
} else if (agent === 'antigravity') {
|
||||
promptText = "Hey Antigravity, can you install and setup TORCH into this repo? Here is the install command:\n\nnpm install https://github.com/PR0M3TH3AN/TORCH/archive/refs/heads/main.tar.gz && npx --no-install torch-lock init";
|
||||
const agentLabelById = {
|
||||
jules: 'Jules',
|
||||
codex: 'Codex',
|
||||
opencode: 'OpenCode',
|
||||
claude: 'Claude',
|
||||
gemini: 'Gemini',
|
||||
qwen: 'Qwen',
|
||||
antigravity: 'Antigravity',
|
||||
};
|
||||
const installCommand = 'npm install https://github.com/PR0M3TH3AN/TORCH/archive/refs/heads/main.tar.gz && npx --no-install torch-lock init';
|
||||
const agentLabel = agentLabelById[agent];
|
||||
if (agentLabel) {
|
||||
promptText = `Hey ${agentLabel}, can you install and setup TORCH into this repo in one pass? Please run this command:\n\n${installCommand}`;
|
||||
}
|
||||
|
||||
if (installCmd) {
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { detectTorchInstallDir } from './cmd-remove.mjs';
|
||||
|
||||
const MEMORY_HEADING = '## TORCH Memory Integration';
|
||||
const MEMORY_READ_LINE = '`.scheduler-memory/latest/${cadence}/memories.md`';
|
||||
const MEMORY_WRITE_LINE = '`memory-update.md`';
|
||||
|
||||
function parseMajorNodeVersion(version) {
|
||||
const major = Number.parseInt(String(version).split('.')[0], 10);
|
||||
return Number.isFinite(major) ? major : null;
|
||||
}
|
||||
|
||||
function createCheck(id, status, summary, fix = null) {
|
||||
return { id, status, summary, fix };
|
||||
}
|
||||
|
||||
function hasMemoryHook(content) {
|
||||
return content.includes(MEMORY_HEADING)
|
||||
&& content.includes(MEMORY_READ_LINE)
|
||||
&& content.includes(MEMORY_WRITE_LINE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs setup and install diagnostics for a TORCH-enabled repository.
|
||||
*
|
||||
* @param {Object} [opts]
|
||||
* @param {string} [opts.cwd]
|
||||
* @param {string} [opts.nodeVersion]
|
||||
* @returns {{
|
||||
* ok: boolean,
|
||||
* summary: { passed: number, warned: number, failed: number },
|
||||
* checks: Array<{ id: string, status: 'pass'|'warn'|'fail', summary: string, fix: string|null }>
|
||||
* }}
|
||||
*/
|
||||
export function runDoctorChecks(opts = {}) {
|
||||
const cwd = opts.cwd || process.cwd();
|
||||
const nodeVersion = opts.nodeVersion || process.versions.node;
|
||||
const checks = [];
|
||||
|
||||
const nodeMajor = parseMajorNodeVersion(nodeVersion);
|
||||
if (nodeMajor !== null && nodeMajor >= 22) {
|
||||
checks.push(createCheck('node-version', 'pass', `Node.js ${nodeVersion} meets requirement (>= 22).`));
|
||||
} else {
|
||||
checks.push(createCheck(
|
||||
'node-version',
|
||||
'fail',
|
||||
`Node.js ${nodeVersion} is below requirement (>= 22).`,
|
||||
'Install Node.js 22+ and rerun `npx --no-install torch-lock doctor`.',
|
||||
));
|
||||
}
|
||||
|
||||
const installDir = detectTorchInstallDir(cwd);
|
||||
if (!installDir) {
|
||||
checks.push(createCheck(
|
||||
'install-detection',
|
||||
'fail',
|
||||
'No TORCH installation detected.',
|
||||
'Run `npm install <torch-tarball> --force && npx --no-install torch-lock init`.',
|
||||
));
|
||||
} else {
|
||||
const installLabel = installDir === '.' ? 'root install (.)' : `${installDir}/`;
|
||||
checks.push(createCheck('install-detection', 'pass', `Detected TORCH install at ${installLabel}.`));
|
||||
}
|
||||
|
||||
const configPath = path.join(cwd, 'torch-config.json');
|
||||
let config = null;
|
||||
if (!fs.existsSync(configPath)) {
|
||||
checks.push(createCheck(
|
||||
'torch-config',
|
||||
'fail',
|
||||
'Missing torch-config.json in project root.',
|
||||
'Run `npx --no-install torch-lock init --force` or `npx --no-install torch-lock update --force`.',
|
||||
));
|
||||
} else {
|
||||
try {
|
||||
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
checks.push(createCheck('torch-config', 'pass', 'torch-config.json exists and parses as JSON.'));
|
||||
} catch (error) {
|
||||
checks.push(createCheck(
|
||||
'torch-config',
|
||||
'fail',
|
||||
`torch-config.json is invalid JSON: ${error.message}`,
|
||||
'Fix JSON syntax or regenerate with `npx --no-install torch-lock update --force`.',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if (config) {
|
||||
const dailyHandoff = config?.scheduler?.handoffCommandByCadence?.daily;
|
||||
const weeklyHandoff = config?.scheduler?.handoffCommandByCadence?.weekly;
|
||||
if (dailyHandoff && weeklyHandoff) {
|
||||
checks.push(createCheck('handoff-config', 'pass', 'Scheduler handoff commands are configured for daily and weekly.'));
|
||||
} else {
|
||||
checks.push(createCheck(
|
||||
'handoff-config',
|
||||
'fail',
|
||||
'Scheduler handoff command is missing for daily and/or weekly cadence.',
|
||||
'Run `npx --no-install torch-lock update --force` to rehydrate scheduler config.',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if (installDir) {
|
||||
const installRoot = path.resolve(cwd, installDir);
|
||||
const expectedPaths = [
|
||||
path.join(installRoot, 'roster.json'),
|
||||
path.join(installRoot, 'prompts', 'daily'),
|
||||
path.join(installRoot, 'prompts', 'weekly'),
|
||||
path.join(installRoot, 'scripts', 'agent', 'run-selected-prompt.mjs'),
|
||||
];
|
||||
const missing = expectedPaths.filter((targetPath) => !fs.existsSync(targetPath));
|
||||
if (missing.length === 0) {
|
||||
checks.push(createCheck('install-files', 'pass', 'Core TORCH install files are present.'));
|
||||
} else {
|
||||
const missingRel = missing.map((targetPath) => path.relative(cwd, targetPath)).join(', ');
|
||||
checks.push(createCheck(
|
||||
'install-files',
|
||||
'fail',
|
||||
`Missing install artifacts: ${missingRel}`,
|
||||
'Run `npx --no-install torch-lock update --force` to repair copied files.',
|
||||
));
|
||||
}
|
||||
|
||||
const nodeModulesPath = path.join(installRoot, 'node_modules');
|
||||
if (fs.existsSync(nodeModulesPath)) {
|
||||
checks.push(createCheck('install-deps', 'pass', `${path.relative(cwd, nodeModulesPath)} exists.`));
|
||||
} else {
|
||||
checks.push(createCheck(
|
||||
'install-deps',
|
||||
'warn',
|
||||
`${path.relative(cwd, nodeModulesPath)} is missing; TORCH package dependencies may not be installed yet.`,
|
||||
`Run \`npm install --prefix ${installDir === '.' ? '.' : installDir}\`.`,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
const promptFiles = ['AGENTS.md', 'CLAUDE.md']
|
||||
.map((name) => ({ name, filePath: path.join(cwd, name) }))
|
||||
.filter((entry) => fs.existsSync(entry.filePath));
|
||||
|
||||
if (promptFiles.length === 0) {
|
||||
checks.push(createCheck(
|
||||
'memory-hook',
|
||||
'warn',
|
||||
'No AGENTS.md or CLAUDE.md found in repository root.',
|
||||
'Run `npx --no-install torch-lock update --force` to create/update agent prompt hooks.',
|
||||
));
|
||||
} else {
|
||||
const withHook = [];
|
||||
const withoutHook = [];
|
||||
for (const entry of promptFiles) {
|
||||
const content = fs.readFileSync(entry.filePath, 'utf8');
|
||||
if (hasMemoryHook(content)) {
|
||||
withHook.push(entry.name);
|
||||
} else {
|
||||
withoutHook.push(entry.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (withHook.length > 0 && withoutHook.length === 0) {
|
||||
checks.push(createCheck('memory-hook', 'pass', `Memory hook present in: ${withHook.join(', ')}.`));
|
||||
} else if (withHook.length > 0) {
|
||||
checks.push(createCheck(
|
||||
'memory-hook',
|
||||
'warn',
|
||||
`Memory hook present in ${withHook.join(', ')} but missing in ${withoutHook.join(', ')}.`,
|
||||
'Run `npx --no-install torch-lock update --force` to upsert missing prompt hooks.',
|
||||
));
|
||||
} else {
|
||||
checks.push(createCheck(
|
||||
'memory-hook',
|
||||
'fail',
|
||||
`Memory hook missing from: ${withoutHook.join(', ')}.`,
|
||||
'Run `npx --no-install torch-lock update --force` to inject TORCH memory integration.',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
const summary = {
|
||||
passed: checks.filter((check) => check.status === 'pass').length,
|
||||
warned: checks.filter((check) => check.status === 'warn').length,
|
||||
failed: checks.filter((check) => check.status === 'fail').length,
|
||||
};
|
||||
|
||||
return {
|
||||
ok: summary.failed === 0,
|
||||
summary,
|
||||
checks,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a human-readable setup diagnostic report.
|
||||
*
|
||||
* @param {Object} [opts]
|
||||
* @param {string} [opts.cwd]
|
||||
* @param {boolean} [opts.json]
|
||||
* @param {Function} [opts.log]
|
||||
* @returns {ReturnType<typeof runDoctorChecks>}
|
||||
*/
|
||||
export function cmdDoctor(opts = {}) {
|
||||
const {
|
||||
cwd = process.cwd(),
|
||||
json = false,
|
||||
log = console.log,
|
||||
} = opts;
|
||||
|
||||
const report = runDoctorChecks({ cwd, nodeVersion: opts.nodeVersion });
|
||||
if (json) {
|
||||
log(JSON.stringify(report, null, 2));
|
||||
return report;
|
||||
}
|
||||
|
||||
log('TORCH Doctor Report');
|
||||
log(`Root: ${cwd}`);
|
||||
for (const check of report.checks) {
|
||||
const badge = check.status === 'pass' ? '[PASS]' : check.status === 'warn' ? '[WARN]' : '[FAIL]';
|
||||
log(`${badge} ${check.id}: ${check.summary}`);
|
||||
}
|
||||
|
||||
log('');
|
||||
log(`Summary: ${report.summary.passed} passed, ${report.summary.warned} warned, ${report.summary.failed} failed.`);
|
||||
if (report.summary.warned > 0 || report.summary.failed > 0) {
|
||||
log('Recommended fixes:');
|
||||
for (const check of report.checks.filter((item) => item.status !== 'pass' && item.fix)) {
|
||||
log(`- ${check.fix}`);
|
||||
}
|
||||
}
|
||||
|
||||
return report;
|
||||
}
|
||||
@@ -45,6 +45,7 @@ Commands:
|
||||
list [--cadence <daily|weekly>] Print active lock table
|
||||
health --cadence <daily|weekly> Probe relay websocket + publish/read health
|
||||
dashboard [--port <port>] [--host <host>] Serve the dashboard (default: ${DEFAULT_DASHBOARD_PORT})
|
||||
doctor [--json] Validate local TORCH setup and print fixes
|
||||
init [--force] Initialize torch/ directory in current project
|
||||
update [--force] Update torch/ configuration (backups, merges)
|
||||
remove [--force] Remove all TORCH files and configuration from project
|
||||
|
||||
@@ -44,6 +44,7 @@ import { cmdRollback } from './cmd-rollback.mjs';
|
||||
import { cmdCheck } from './cmd-check.mjs';
|
||||
import { cmdList } from './cmd-list.mjs';
|
||||
import { cmdComplete } from './cmd-complete.mjs';
|
||||
import { cmdDoctor } from './cmd-doctor.mjs';
|
||||
|
||||
useWebSocketImplementation(WebSocket);
|
||||
|
||||
@@ -308,6 +309,12 @@ const COMMAND_HANDLERS = {
|
||||
dashboard: async (args) => {
|
||||
await cmdDashboard(args.port, args.host);
|
||||
},
|
||||
doctor: async (args) => {
|
||||
const report = cmdDoctor({ json: args.json });
|
||||
if (!report.ok) {
|
||||
throw new ExitError(2, 'Doctor found setup failures');
|
||||
}
|
||||
},
|
||||
init: async (args) => {
|
||||
await cmdInit(args.force);
|
||||
},
|
||||
|
||||
+63
@@ -54,6 +54,12 @@ const APP_DIRS = ['src', 'bin', 'dashboard', 'landing', 'assets', 'scripts'];
|
||||
|
||||
/** Individual files to sync from package root to install directory. */
|
||||
const APP_FILES = ['package.json', 'build.mjs', 'README.md', 'torch-config.example.json', 'TORCH.md', 'eslint.config.mjs'];
|
||||
const MEMORY_PROMPT_FILES = ['AGENTS.md', 'CLAUDE.md'];
|
||||
const MEMORY_INTEGRATION_HEADING = '## TORCH Memory Integration';
|
||||
const MEMORY_INTEGRATION_BLOCK = `${MEMORY_INTEGRATION_HEADING}
|
||||
You have access to the TORCH memory system.
|
||||
1. READ: Check \`.scheduler-memory/latest/\${cadence}/memories.md\` for past learnings.
|
||||
2. WRITE: Before exiting, save new insights to \`memory-update.md\` so future runs can learn from this session.`;
|
||||
|
||||
/**
|
||||
* Resolves the absolute paths for the installation.
|
||||
@@ -478,6 +484,61 @@ function injectHostScriptsIfNeeded(paths, installDir) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upserts the TORCH memory integration section inside a system prompt file.
|
||||
*
|
||||
* @param {string} content - Existing file content.
|
||||
* @returns {string} Updated file content.
|
||||
*/
|
||||
function upsertMemoryIntegrationBlock(content) {
|
||||
if (content.includes(MEMORY_INTEGRATION_BLOCK)) {
|
||||
return content;
|
||||
}
|
||||
|
||||
const escapedHeading = MEMORY_INTEGRATION_HEADING.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const sectionRegex = new RegExp(`(^|\\n)${escapedHeading}\\n[\\s\\S]*?(?=\\n##\\s|$)`, 'm');
|
||||
|
||||
if (sectionRegex.test(content)) {
|
||||
return content.replace(sectionRegex, (_match, prefix) => `${prefix}${MEMORY_INTEGRATION_BLOCK}`);
|
||||
}
|
||||
|
||||
const trimmed = content.trimEnd();
|
||||
if (!trimmed) {
|
||||
return `${MEMORY_INTEGRATION_BLOCK}\n`;
|
||||
}
|
||||
return `${trimmed}\n\n${MEMORY_INTEGRATION_BLOCK}\n`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures AGENTS/CLAUDE system prompt files include TORCH memory integration.
|
||||
* If neither file exists, creates AGENTS.md with the integration block.
|
||||
*
|
||||
* @param {string} root - Host project root.
|
||||
*/
|
||||
function ensureMemoryPromptHook(root) {
|
||||
const existingTargets = MEMORY_PROMPT_FILES.filter((file) => fs.existsSync(path.join(root, file)));
|
||||
|
||||
if (existingTargets.length === 0) {
|
||||
const defaultPath = path.join(root, 'AGENTS.md');
|
||||
fs.writeFileSync(defaultPath, `${MEMORY_INTEGRATION_BLOCK}\n`, 'utf8');
|
||||
console.log(`Created ${path.relative(root, defaultPath)} with TORCH memory integration.`);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const file of existingTargets) {
|
||||
const filePath = path.join(root, file);
|
||||
const current = fs.readFileSync(filePath, 'utf8');
|
||||
const next = upsertMemoryIntegrationBlock(current);
|
||||
|
||||
if (next !== current) {
|
||||
fs.writeFileSync(filePath, next, 'utf8');
|
||||
console.log(`Updated ${path.relative(root, filePath)} with TORCH memory integration.`);
|
||||
} else {
|
||||
console.log(`TORCH memory integration already present in ${path.relative(root, filePath)}.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main entry point for `torch-lock init`.
|
||||
*
|
||||
@@ -505,6 +566,7 @@ export async function cmdInit(force = false, cwd = process.cwd(), mockAnswers =
|
||||
configureTorch(cwd, paths, installDir, namespace, relays, hashtag);
|
||||
const dashboardUrl = createDashboardLinkFile(paths, namespace, relays, hashtag);
|
||||
injectHostScriptsIfNeeded(paths, installDir);
|
||||
ensureMemoryPromptHook(paths.root);
|
||||
|
||||
console.log('\nInitialization complete.');
|
||||
console.log('You can now customize the files in ' + path.relative(cwd, paths.torchDir) + '/');
|
||||
@@ -712,5 +774,6 @@ export function cmdUpdate(force = false, cwd = process.cwd()) {
|
||||
}
|
||||
}
|
||||
|
||||
ensureMemoryPromptHook(paths.root);
|
||||
console.log('\nUpdate complete.');
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
import { cmdInit } from '../src/ops.mjs';
|
||||
import { runDoctorChecks, cmdDoctor } from '../src/cmd-doctor.mjs';
|
||||
|
||||
const MOCK_CONFIG = {
|
||||
installDir: 'torch',
|
||||
namespace: 'doctor-test-namespace',
|
||||
hashtag: 'doctor-test-hashtag',
|
||||
relays: ['wss://relay.damus.io'],
|
||||
};
|
||||
|
||||
test('runDoctorChecks reports failure when TORCH is not installed', async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'torch-doctor-missing-'));
|
||||
|
||||
const report = runDoctorChecks({ cwd: root, nodeVersion: '22.10.0' });
|
||||
assert.equal(report.ok, false);
|
||||
assert.ok(report.checks.some((check) => check.id === 'install-detection' && check.status === 'fail'));
|
||||
});
|
||||
|
||||
test('runDoctorChecks passes core checks after init', async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'torch-doctor-healthy-'));
|
||||
await cmdInit(false, root, MOCK_CONFIG);
|
||||
|
||||
// Simulate post-init dependency install for health check.
|
||||
await fs.mkdir(path.join(root, 'torch', 'node_modules'), { recursive: true });
|
||||
|
||||
const report = runDoctorChecks({ cwd: root, nodeVersion: '22.10.0' });
|
||||
assert.equal(report.ok, true);
|
||||
assert.ok(report.checks.some((check) => check.id === 'memory-hook' && check.status === 'pass'));
|
||||
assert.ok(report.checks.some((check) => check.id === 'install-files' && check.status === 'pass'));
|
||||
});
|
||||
|
||||
test('runDoctorChecks fails when memory hook is missing from existing AGENTS.md', async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'torch-doctor-hook-missing-'));
|
||||
await cmdInit(false, root, MOCK_CONFIG);
|
||||
|
||||
await fs.writeFile(path.join(root, 'AGENTS.md'), '# AGENTS\nNo memory section.\n', 'utf8');
|
||||
|
||||
const report = runDoctorChecks({ cwd: root, nodeVersion: '22.10.0' });
|
||||
assert.equal(report.ok, false);
|
||||
const memoryCheck = report.checks.find((check) => check.id === 'memory-hook');
|
||||
assert.ok(memoryCheck);
|
||||
assert.equal(memoryCheck.status, 'fail');
|
||||
});
|
||||
|
||||
test('cmdDoctor emits JSON report when requested', async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'torch-doctor-json-'));
|
||||
await cmdInit(false, root, MOCK_CONFIG);
|
||||
|
||||
const logs = [];
|
||||
const report = cmdDoctor({
|
||||
cwd: root,
|
||||
json: true,
|
||||
nodeVersion: '22.10.0',
|
||||
log: (line) => logs.push(String(line)),
|
||||
});
|
||||
|
||||
assert.ok(report.summary.passed >= 1);
|
||||
assert.equal(logs.length, 1);
|
||||
assert.doesNotThrow(() => JSON.parse(logs[0]));
|
||||
});
|
||||
@@ -193,3 +193,37 @@ test('cmdInit creates torch-config.json with random namespace', async () => {
|
||||
'weekly handoff command should be configured for host installs',
|
||||
);
|
||||
});
|
||||
|
||||
test('cmdInit upserts TORCH memory integration into existing AGENTS.md and CLAUDE.md', async () => {
|
||||
const projectRoot = path.join(tempBase, 'project_memory_hook_init');
|
||||
fs.mkdirSync(projectRoot, { recursive: true });
|
||||
|
||||
const agentsPath = path.join(projectRoot, 'AGENTS.md');
|
||||
const claudePath = path.join(projectRoot, 'CLAUDE.md');
|
||||
fs.writeFileSync(agentsPath, '# Team Policy\n\nKeep tests strict.\n', 'utf8');
|
||||
fs.writeFileSync(claudePath, '# Local Guidance\n\nFollow repo conventions.\n', 'utf8');
|
||||
|
||||
await cmdInit(false, projectRoot, MOCK_CONFIG);
|
||||
|
||||
const agentsContent = fs.readFileSync(agentsPath, 'utf8');
|
||||
const claudeContent = fs.readFileSync(claudePath, 'utf8');
|
||||
assert.ok(agentsContent.includes('## TORCH Memory Integration'), 'AGENTS.md should include memory heading');
|
||||
assert.ok(claudeContent.includes('## TORCH Memory Integration'), 'CLAUDE.md should include memory heading');
|
||||
assert.ok(agentsContent.includes('Keep tests strict.'), 'AGENTS.md existing content should be preserved');
|
||||
assert.ok(claudeContent.includes('Follow repo conventions.'), 'CLAUDE.md existing content should be preserved');
|
||||
});
|
||||
|
||||
test('cmdUpdate keeps TORCH memory integration idempotent', async () => {
|
||||
const projectRoot = path.join(tempBase, 'project_memory_hook_update');
|
||||
fs.mkdirSync(projectRoot, { recursive: true });
|
||||
|
||||
const agentsPath = path.join(projectRoot, 'AGENTS.md');
|
||||
fs.writeFileSync(agentsPath, '# Agent Rules\n', 'utf8');
|
||||
|
||||
await cmdInit(false, projectRoot, MOCK_CONFIG);
|
||||
await cmdUpdate(false, projectRoot);
|
||||
|
||||
const agentsContent = fs.readFileSync(agentsPath, 'utf8');
|
||||
const headingMatches = agentsContent.match(/## TORCH Memory Integration/g) ?? [];
|
||||
assert.strictEqual(headingMatches.length, 1, 'Memory integration block should not be duplicated');
|
||||
});
|
||||
Reference in new issue
Block a user