From af6e55c7e1b4de63fb6c5b82122c8a01bb121ec6 Mon Sep 17 00:00:00 2001 From: RemoteCTO <768254+RemoteCTO@users.noreply.github.com> Date: Fri, 13 Feb 2026 11:38:16 +0400 Subject: [PATCH 1/4] Run default report when invoked with no arguments claudelog with no args now runs a report using configurable defaults (defaultReport in config.json, fallback --week) instead of printing usage. Bump to 0.3.0. --- .claude-plugin/plugin.json | 2 +- README.md | 23 +++- bin/claudelog | 79 ++++++++++---- config.example.json | 3 +- lib/config.mjs | 18 ++++ package-lock.json | 7 +- package.json | 2 +- test/bin/claudelog.test.mjs | 210 +++++++++++++++++++++++++++--------- test/lib/config.test.mjs | 38 +++++++ 9 files changed, 299 insertions(+), 83 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 067b8d4..fafc7ec 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "timelog", - "version": "0.2.0", + "version": "0.3.0", "description": "Automatic time tracking for Claude Code sessions. Logs project, ticket, and prompt data as JSONL for timesheet reconstruction.", "license": "Apache-2.0", "keywords": [ diff --git a/README.md b/README.md index 7cc07d4..4e111a6 100644 --- a/README.md +++ b/README.md @@ -98,12 +98,24 @@ and backfills from any terminal — no active Claude Code session needed. ```bash -claudelog report --week --by-project -claudelog report --month --timesheet --json -claudelog backfill -claudelog --help +claudelog # default report (--week) +claudelog report --month --timesheet # explicit flags +claudelog backfill # import history +claudelog --help # show usage ``` +Running `claudelog` with no arguments runs +a default report. Configure the default via +`defaultReport` in `config.json`: + +```json +{ + "defaultReport": ["--month", "--timesheet"] +} +``` + +Falls back to `["--week"]` if not set. + ### Adding to PATH The plugin installs to a versioned cache @@ -204,7 +216,8 @@ settings are optional. ], "projectSource": "git-root", "projectPattern": null, - "breakThreshold": 1800 + "breakThreshold": 1800, + "defaultReport": ["--week"] } ``` diff --git a/bin/claudelog b/bin/claudelog index 2d58504..d6a8dd1 100755 --- a/bin/claudelog +++ b/bin/claudelog @@ -1,53 +1,90 @@ #!/usr/bin/env node import { execFileSync } from 'node:child_process'; +import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; +import { homedir } from 'node:os'; const ROOT = dirname( dirname(fileURLToPath(import.meta.url)) ); -const COMMANDS = new Set(['report', 'backfill']); +const COMMANDS = new Set([ + 'report', 'backfill', +]); -const USAGE = `Usage: claudelog [options] +const DEFAULT_ARGS = ['--week']; + +const USAGE = `Usage: claudelog [command] [options] Commands: report Generate time reports backfill Import historical transcripts +With no arguments, runs a default report +(configurable via defaultReport in +config.json). + Run claudelog --help for details. Examples: + claudelog claudelog report --week --by-project claudelog report --month --timesheet claudelog backfill`; +function loadDefaultReport() { + const dir = + process.env.CLAUDE_TIMELOG_DIR || + join(homedir(), '.claude', 'timelog'); + try { + const raw = readFileSync( + join(dir, 'config.json'), 'utf8' + ); + const cfg = JSON.parse(raw); + if (Array.isArray(cfg.defaultReport)) { + return cfg.defaultReport; + } + } catch { + // No config or invalid JSON — use default + } + return DEFAULT_ARGS; +} + +function dispatch(script, args) { + try { + execFileSync( + process.execPath, + [script, ...args], + { stdio: 'inherit' } + ); + } catch (err) { + process.exit(err.status ?? 1); + } +} + const cmd = process.argv[2]; -if (!cmd || cmd === '--help' || cmd === 'help') { - const out = cmd ? process.stdout : process.stderr; - out.write(USAGE + '\n'); - process.exit(cmd ? 0 : 1); +if (cmd === '--help' || cmd === 'help') { + process.stdout.write(USAGE + '\n'); + process.exit(0); } -if (!COMMANDS.has(cmd)) { +if (!cmd) { + const args = loadDefaultReport(); + dispatch( + join(ROOT, 'scripts', 'report.mjs'), + args + ); +} else if (COMMANDS.has(cmd)) { + dispatch( + join(ROOT, 'scripts', `${cmd}.mjs`), + process.argv.slice(3) + ); +} else { process.stderr.write( `Unknown command: ${cmd}\n\n${USAGE}\n` ); process.exit(1); } - -const script = join( - ROOT, 'scripts', `${cmd}.mjs` -); - -try { - execFileSync( - process.execPath, - [script, ...process.argv.slice(3)], - { stdio: 'inherit' } - ); -} catch (err) { - process.exit(err.status ?? 1); -} diff --git a/config.example.json b/config.example.json index 82538e7..3301d5c 100644 --- a/config.example.json +++ b/config.example.json @@ -4,5 +4,6 @@ "#(\\d+)" ], "projectSource": "git-root", - "projectPattern": "projects/(?:active/)?([^/]+)" + "projectPattern": "projects/(?:active/)?([^/]+)", + "defaultReport": ["--week"] } diff --git a/lib/config.mjs b/lib/config.mjs index 384066e..9893c83 100644 --- a/lib/config.mjs +++ b/lib/config.mjs @@ -75,6 +75,24 @@ function validateConfig(cfg) { DEFAULT_CONFIG.projectSource; } + // defaultReport: array of strings + if ('defaultReport' in result) { + if ( + !Array.isArray(result.defaultReport) + ) { + console.error( + 'timelog: defaultReport must be ' + + 'an array. Ignoring.' + ); + delete result.defaultReport; + } else { + result.defaultReport = + result.defaultReport.filter( + (a) => typeof a === 'string' + ); + } + } + // projectPattern: reject ReDoS patterns if ( result.projectPattern && diff --git a/package-lock.json b/package-lock.json index 4da0e5d..8f057d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,16 @@ { "name": "claude-code-timelog", - "version": "0.2.0", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "claude-code-timelog", - "version": "0.2.0", + "version": "0.3.0", "license": "Apache-2.0", + "bin": { + "claudelog": "bin/claudelog" + }, "devDependencies": { "eslint": "^9" }, diff --git a/package.json b/package.json index 6979e07..3c64379 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "claude-code-timelog", - "version": "0.2.0", + "version": "0.3.0", "type": "module", "bin": { "claudelog": "bin/claudelog" diff --git a/test/bin/claudelog.test.mjs b/test/bin/claudelog.test.mjs index 82ad325..4839eee 100644 --- a/test/bin/claudelog.test.mjs +++ b/test/bin/claudelog.test.mjs @@ -1,6 +1,11 @@ -import { describe, it } from 'node:test'; +import { + describe, it, before, after, +} from 'node:test'; import assert from 'node:assert/strict'; import { spawnSync } from 'node:child_process'; +import { + mkdirSync, writeFileSync, rmSync, +} from 'node:fs'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -9,70 +14,171 @@ const BIN = join( '..', '..', '..', 'bin', 'claudelog' ); -const ENV = { - ...process.env, - CLAUDE_TIMELOG_DIR: '/tmp/claudelog-test', -}; +const TEST_DIR = join( + '/tmp', 'claudelog-test-' + process.pid +); -function run(...args) { - return spawnSync('node', [BIN, ...args], { - encoding: 'utf8', - env: ENV, - timeout: 10000, - }); +function makeEnv(dir) { + return { + ...process.env, + CLAUDE_TIMELOG_DIR: dir || TEST_DIR, + }; +} + +function run(args, env) { + return spawnSync( + 'node', [BIN, ...args], + { + encoding: 'utf8', + env: env || makeEnv(), + timeout: 10000, + } + ); } describe('bin/claudelog', () => { - it('prints usage with no arguments', () => { - const r = run(); - assert.equal(r.status, 1); - assert.match(r.stderr, /usage:/i); - assert.match(r.stderr, /claudelog/); - assert.match(r.stderr, /report/); - assert.match(r.stderr, /backfill/); + before(() => { + mkdirSync(TEST_DIR, { recursive: true }); }); - it('prints usage with --help', () => { - const r = run('--help'); - assert.equal(r.status, 0); - assert.match(r.stdout, /usage:/i); - assert.match(r.stdout, /report/); - assert.match(r.stdout, /backfill/); + after(() => { + rmSync(TEST_DIR, { + recursive: true, force: true, + }); }); - it('prints usage with help subcommand', () => { - const r = run('help'); - assert.equal(r.status, 0); - assert.match(r.stdout, /usage:/i); - }); + describe('help', () => { + it('prints usage with --help', () => { + const r = run(['--help']); + assert.equal(r.status, 0); + assert.match(r.stdout, /usage:/i); + assert.match(r.stdout, /report/); + assert.match(r.stdout, /backfill/); + }); - it('rejects unknown subcommands', () => { - const r = run('nonsense'); - assert.equal(r.status, 1); - assert.match(r.stderr, /unknown command/i); - }); + it('prints usage with help subcommand', () => { + const r = run(['help']); + assert.equal(r.status, 0); + assert.match(r.stdout, /usage:/i); + }); - it('runs report --help without error', () => { - const r = run('report', '--help'); - assert.equal(r.status, 0); - assert.match(r.stdout, /usage:/i); + it('mentions default report in usage', () => { + const r = run(['--help']); + assert.match( + r.stdout, /no arguments/i + ); + }); }); - it('forwards exit code from subcommand', () => { - // Far-future range guarantees no data - const r = run( - 'report', - '--from', '2099-01-01', - '--to', '2099-01-07' - ); - assert.notEqual(r.status, 0); - assert.match( - r.stderr, /no timelog data/i - ); + describe('default report (no arguments)', () => { + it('runs report instead of usage', () => { + // With no data, report exits 1 with + // "no timelog data" — NOT usage text + const dir = join(TEST_DIR, 'empty'); + mkdirSync(dir, { recursive: true }); + const r = run([], makeEnv(dir)); + assert.match( + r.stderr, /no timelog data/i + ); + assert.doesNotMatch( + r.stderr, /usage:/i + ); + }); + + it('uses --week by default', () => { + // report.mjs mentions the date range + // in its "no data" message + const dir = join(TEST_DIR, 'week'); + mkdirSync(dir, { recursive: true }); + const r = run([], makeEnv(dir)); + // Default --week shows date range + assert.match( + r.stderr, /no timelog data/i + ); + }); + + it('uses defaultReport from config', () => { + const dir = join(TEST_DIR, 'custom'); + mkdirSync(dir, { recursive: true }); + writeFileSync( + join(dir, 'config.json'), + JSON.stringify({ + defaultReport: [ + '--from', '2099-01-01', + '--to', '2099-01-07', + ], + }) + ); + const r = run([], makeEnv(dir)); + // The custom date range appears in + // the "no data" error message + assert.match( + r.stderr, + /2099/ + ); + }); + + it('ignores invalid defaultReport', () => { + const dir = join( + TEST_DIR, 'bad-config' + ); + mkdirSync(dir, { recursive: true }); + writeFileSync( + join(dir, 'config.json'), + JSON.stringify({ + defaultReport: 'not-an-array', + }) + ); + // Falls back to --week, still runs + const r = run([], makeEnv(dir)); + assert.match( + r.stderr, /no timelog data/i + ); + assert.doesNotMatch( + r.stderr, /usage:/i + ); + }); }); - it('runs backfill without error', () => { - const r = run('backfill'); - assert.equal(r.status, 0); + describe('explicit subcommands', () => { + it('rejects unknown subcommands', () => { + const r = run(['nonsense']); + assert.equal(r.status, 1); + assert.match( + r.stderr, /unknown command/i + ); + }); + + it('runs report --help', () => { + const r = run(['report', '--help']); + assert.equal(r.status, 0); + assert.match(r.stdout, /usage:/i); + }); + + it('forwards flags to report', () => { + const r = run([ + 'report', + '--from', '2099-01-01', + '--to', '2099-01-07', + ]); + assert.notEqual(r.status, 0); + assert.match( + r.stderr, /no timelog data/i + ); + }); + + it('forwards exit code', () => { + const r = run([ + 'report', + '--from', '2099-01-01', + '--to', '2099-01-07', + ]); + assert.notEqual(r.status, 0); + }); + + it('runs backfill without error', () => { + const r = run(['backfill']); + assert.equal(r.status, 0); + }); }); }); diff --git a/test/lib/config.test.mjs b/test/lib/config.test.mjs index b6c0b50..827558e 100644 --- a/test/lib/config.test.mjs +++ b/test/lib/config.test.mjs @@ -557,6 +557,44 @@ describe('lib/config', () => { }); }); + describe('defaultReport', () => { + it('accepts valid array', () => { + const cfg = validateConfig({ + ...DEFAULT_CONFIG, + defaultReport: [ + '--week', '--timesheet', + ], + }); + assert.deepEqual( + cfg.defaultReport, + ['--week', '--timesheet'] + ); + }); + + it('rejects non-array', () => { + const cfg = validateConfig({ + ...DEFAULT_CONFIG, + defaultReport: '--week', + }); + assert.strictEqual( + cfg.defaultReport, undefined + ); + }); + + it('filters non-string entries', + () => { + const cfg = validateConfig({ + ...DEFAULT_CONFIG, + defaultReport: [ + '--week', 42, null, + ], + }); + assert.deepEqual( + cfg.defaultReport, ['--week'] + ); + }); + }); + describe('projectPattern', () => { it('rejects nested quantifiers', () => { From 56e209fca14d41beab78e15cedb1c0fd18685f97 Mon Sep 17 00:00:00 2001 From: RemoteCTO <768254+RemoteCTO@users.noreply.github.com> Date: Fri, 13 Feb 2026 13:05:52 +0400 Subject: [PATCH 2/4] Add contribution guidelines for open source contributors --- CONTRIBUTING.md | 133 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..1665feb --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,133 @@ +# Contributing + +Thanks for your interest in contributing to +Claude Code Timelog. This guide covers setup, +standards, and how to submit changes. + +## Getting started + +```bash +git clone https://github.com/RemoteCTO/claude-code-timelog.git +cd claude-code-timelog +npm install +``` + +Run the test suite and linter to verify +everything works: + +```bash +npm test +npm run lint +``` + +Tests use Node's built-in test runner and +run across Node 18, 20, and 22 in CI. + +## Project structure + +``` +hooks/ Claude Code hook handlers +scripts/ CLI scripts (report, backfill) +lib/ Shared library code +bin/ CLI entry point (claudelog) +commands/ Plugin command definitions +test/ Tests (mirrors source layout) +``` + +**Hooks** run inside Claude Code sessions. +**Scripts** run standalone from the terminal. +**lib/** is shared between both. + +## Code style + +- **80 character line limit** (enforced by + ESLint and editorconfig) +- ESM modules (`.mjs` extension, `import`/ + `export`) +- `prefer-const`, `no-var` +- 2-space indentation +- LF line endings +- Trailing newline at end of file + +The full config is in `eslint.config.mjs` +and `.editorconfig`. Most editors pick these +up automatically. + +## Writing tests + +Tests live in `test/` and mirror the source +tree: + +``` +lib/config.mjs → test/lib/config.test.mjs +scripts/report.mjs → test/scripts/report.test.mjs +bin/claudelog → test/bin/claudelog.test.mjs +``` + +Use Node's built-in `node:test` and +`node:assert` modules: + +```js +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; + +describe('feature', () => { + it('does the thing', () => { + assert.strictEqual(actual, expected); + }); +}); +``` + +Run a single test file during development: + +```bash +node --test test/lib/config.test.mjs +``` + +## Submitting changes + +1. Fork the repository +2. Create a feature branch from `main` +3. Make your changes with tests +4. Ensure `npm test` and `npm run lint` pass +5. Open a pull request against `main` + +CI runs automatically on pull requests. +Both lint and test jobs must pass. + +### Good pull requests + +- **One concern per PR** — a bug fix, a + feature, a refactor. Not all three. +- **Tests included** — new behaviour needs + tests; bug fixes need a regression test. +- **Descriptive commit messages** — explain + *why*, not just *what*. + +### Reporting bugs + +Open an issue with: + +- What you expected to happen +- What actually happened +- Steps to reproduce +- Node version (`node -v`) +- Claude Code version + +## Environment variables + +These are useful during development: + +| Variable | Purpose | +|----------|---------| +| `CLAUDE_TIMELOG_DIR` | Override log directory | + +Set `CLAUDE_TIMELOG_DIR` to a temp directory +when running tests or experimenting to avoid +polluting your real timelog data. + +## Licence + +By contributing, you agree that your +contributions will be licensed under the +Apache 2.0 licence. From fa4837f9e913f789931aab2200ccf4963be3f0b2 Mon Sep 17 00:00:00 2001 From: RemoteCTO <768254+RemoteCTO@users.noreply.github.com> Date: Fri, 13 Feb 2026 13:11:15 +0400 Subject: [PATCH 3/4] Add community files for open source contributors Issue templates, PR template, security policy, changelog, and updated contributing guide to match project conventions. --- .github/ISSUE_TEMPLATE/bug_report.md | 42 ++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 24 +++++ .github/PULL_REQUEST_TEMPLATE.md | 16 +++ CHANGELOG.md | 59 +++++++++++ CONTRIBUTING.md | 122 +++++++++------------- SECURITY.md | 34 ++++++ 6 files changed, 222 insertions(+), 75 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 CHANGELOG.md create mode 100644 SECURITY.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..3c544fe --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,42 @@ +--- +name: Bug Report +about: Report a bug in claude-code-timelog +labels: bug +--- + +## Describe the Bug + +A clear description of what the bug is. + +## Steps to Reproduce + +1. Step one +2. Step two +3. Step three + +## Expected Behaviour + +What you expected to happen. + +## Environment + +- OS: [e.g., macOS 14.2, Ubuntu 22.04] +- Node.js version: [e.g., 18.19.0, 22.0.0] +- Claude Code version: [e.g., 1.0.33] +- Plugin version: [e.g., 0.3.0] + +## Logs + +If applicable, include: + +- Report output or error messages +- Contents of `~/.claude/timelog/` (redact + any sensitive prompt text) + +``` +Paste logs here +``` + +## Additional Context + +Any other context about the problem. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..49cbb26 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,24 @@ +--- +name: Feature Request +about: Suggest a new feature for claude-code-timelog +labels: enhancement +--- + +## Problem Description + +Describe the problem this feature would solve. +What are you trying to accomplish? + +## Proposed Solution + +Describe your proposed solution. How would +this feature work? + +## Alternatives Considered + +What alternative solutions or workarounds +have you considered? + +## Additional Context + +Any other context, screenshots, or examples. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..7d4f559 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,16 @@ +## Description + +Describe the changes in this pull request. + +## Checklist + +- [ ] Description of changes provided above +- [ ] Tests added or updated for new behaviour +- [ ] `npm test` passes +- [ ] `npm run lint` passes +- [ ] `CHANGELOG.md` updated under `[Unreleased]` +- [ ] Documentation updated if needed (README) + +## Related Issues + +Fixes #(issue number) diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a6238ab --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,59 @@ +# Changelog + +All notable changes to claude-code-timelog are +documented here. Format follows +[Keep a Changelog][kac]. + +[kac]: https://keepachangelog.com/en/1.1.0/ + +## [Unreleased] + +### Added + +- Default report: `claudelog` with no arguments + runs a weekly report instead of printing usage. + Configurable via `defaultReport` in config.json. +- `CONTRIBUTING.md` with setup, code style, + testing, and submission guidelines. +- `SECURITY.md` with vulnerability reporting + instructions. +- Issue templates for bug reports and feature + requests. +- Pull request template with checklist. +- `CHANGELOG.md` (this file). + +## [0.2.0] — 2026-02-12 + +### Added + +- `claudelog` CLI command for running reports + and backfills from any terminal without an + active Claude Code session. +- `bin` field in package.json. + +## [0.1.0] — 2026-02-11 + +Initial release. + +### Added + +- Automatic time tracking via Claude Code hooks + (SessionStart, UserPromptSubmit, Stop). +- JSONL log files (one per day) with session, + project, ticket, and prompt data. +- Report generation with multiple views: + default (day x project x ticket), timesheet, + by-project, by-ticket, by-model, by-day. +- Date range filters: `--week`, `--month`, + `--from`/`--to`. +- Project and ticket filters. +- JSON output mode (`--json`). +- Backfill from existing Claude Code session + transcripts. +- Configurable project detection via + `projectPattern` regex. +- Configurable ticket patterns with + Jira/Linear/GitHub support. +- Break detection with configurable threshold. +- Event-level aggregation for accurate + mid-session project/ticket switching. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1665feb..4ccba70 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,28 +1,20 @@ -# Contributing +# Contributing to claude-code-timelog -Thanks for your interest in contributing to -Claude Code Timelog. This guide covers setup, -standards, and how to submit changes. +## Prerequisites -## Getting started +- Node.js 18 or later +- Claude Code (for testing hooks) + +## Setup ```bash git clone https://github.com/RemoteCTO/claude-code-timelog.git cd claude-code-timelog npm install -``` - -Run the test suite and linter to verify -everything works: - -```bash npm test npm run lint ``` -Tests use Node's built-in test runner and -run across Node 18, 20, and 22 in CI. - ## Project structure ``` @@ -40,91 +32,71 @@ test/ Tests (mirrors source layout) ## Code style -- **80 character line limit** (enforced by - ESLint and editorconfig) -- ESM modules (`.mjs` extension, `import`/ - `export`) +Code style is enforced by ESLint and +editorconfig: + +- 80-character line limit +- ESM modules (`.mjs` extension) - `prefer-const`, `no-var` - 2-space indentation - LF line endings -- Trailing newline at end of file - -The full config is in `eslint.config.mjs` -and `.editorconfig`. Most editors pick these -up automatically. -## Writing tests +Run `npm run lint` before submitting changes. -Tests live in `test/` and mirror the source -tree: +## Testing -``` -lib/config.mjs → test/lib/config.test.mjs -scripts/report.mjs → test/scripts/report.test.mjs -bin/claudelog → test/bin/claudelog.test.mjs -``` - -Use Node's built-in `node:test` and -`node:assert` modules: - -```js -import { describe, it } from 'node:test'; -import assert from 'node:assert/strict'; +Tests use Node's native test runner +(`node --test`). -describe('feature', () => { - it('does the thing', () => { - assert.strictEqual(actual, expected); - }); -}); -``` +- Write tests first (TDD) +- Test behaviour, not implementation +- Use real objects, not mocks +- Keep tests focused and independent -Run a single test file during development: +Run tests with `npm test`. Run a single file: ```bash node --test test/lib/config.test.mjs ``` -## Submitting changes +Use `CLAUDE_TIMELOG_DIR` to point at a temp +directory during development to avoid polluting +your real timelog data. + +## Pull request process 1. Fork the repository 2. Create a feature branch from `main` -3. Make your changes with tests -4. Ensure `npm test` and `npm run lint` pass -5. Open a pull request against `main` - -CI runs automatically on pull requests. -Both lint and test jobs must pass. - -### Good pull requests +3. Make your changes +4. Write or update tests +5. Update `CHANGELOG.md` under an `[Unreleased]` + heading (see [Keep a Changelog][kac]) +6. Run `npm test` and `npm run lint` +7. Submit a pull request -- **One concern per PR** — a bug fix, a - feature, a refactor. Not all three. -- **Tests included** — new behaviour needs - tests; bug fixes need a regression test. -- **Descriptive commit messages** — explain - *why*, not just *what*. +[kac]: https://keepachangelog.com/en/1.1.0/ -### Reporting bugs +Keep PRs focused on a single change. Include +clear descriptions of what changed and why. -Open an issue with: +## Commit messages -- What you expected to happen -- What actually happened -- Steps to reproduce -- Node version (`node -v`) -- Claude Code version +Follow conventional commit format where +appropriate: -## Environment variables +- `feat:` new features +- `fix:` bug fixes +- `docs:` documentation changes +- `test:` test additions or changes +- `refactor:` code changes without behaviour + changes -These are useful during development: +Keep commit messages concise and descriptive. -| Variable | Purpose | -|----------|---------| -| `CLAUDE_TIMELOG_DIR` | Override log directory | +## Questions -Set `CLAUDE_TIMELOG_DIR` to a temp directory -when running tests or experimenting to avoid -polluting your real timelog data. +Open an issue for questions or clarifications +before starting significant changes. ## Licence diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..9e6e29a --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,34 @@ +# Security Policy + +## Reporting a Vulnerability + +If you discover a security vulnerability in +claude-code-timelog, please report it via +GitHub Security Advisories: + +https://github.com/RemoteCTO/claude-code-timelog/security/advisories/new + +Do **not** open a public issue for security +vulnerabilities. + +Alternatively, email security reports to: +edward@bannermedia.ltd + +## What to Include + +- Description of the vulnerability +- Steps to reproduce +- Potential impact +- Suggested fix (if you have one) + +## Response Time + +We aim to respond to security reports within +48 hours and provide a fix or mitigation plan +within one week for critical vulnerabilities. + +## Disclosure Policy + +Please allow us reasonable time to address the +vulnerability before public disclosure. We will +coordinate disclosure timing with you. From bf7d1575f5200121fe75742e72110f3deca28f5f Mon Sep 17 00:00:00 2001 From: RemoteCTO <768254+RemoteCTO@users.noreply.github.com> Date: Fri, 13 Feb 2026 13:13:48 +0400 Subject: [PATCH 4/4] Link marketplace repo from install instructions --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4e111a6..88120eb 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ external services. ## Quick start -### From marketplace (recommended) +### From [marketplace][mkt] (recommended) ``` /plugin marketplace add RemoteCTO/claude-plugins-marketplace @@ -45,6 +45,8 @@ git clone https://github.com/RemoteCTO/claude-code-timelog.git claude --plugin-dir ./claude-code-timelog ``` +[mkt]: https://github.com/RemoteCTO/claude-plugins-marketplace + That's it. The plugin starts logging immediately with sensible defaults: