Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .claude/skills/code-dedup/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ description: Searches for duplicate code, duplicate tests, and dead code, then s

# Code Dedup

Carefully search for duplicate code, duplicate tests, and dead code across the Diffly repo. Merge duplicates and delete dead code — but only when test coverage proves the change is safe.
Carefully search for duplicate code, duplicate tests, and dead code across the Diffr repo. Merge duplicates and delete dead code — but only when test coverage proves the change is safe.

## Prerequisites — hard gate

Before touching ANY code, verify these conditions. If any fail, stop and report why.

1. Run `make test` — all tests must pass. If tests fail, stop. Do not dedup a broken codebase.
2. Run `make test` — tests are fail-fast AND enforce the coverage threshold from `coverage-thresholds.json`. If anything fails, stop and fix it before deduping.
3. Verify the project uses **static typing**. TypeScript with `tsconfig.json` `"strict": true` is required (which Diffly enforces — see CLAUDE.md). If `strict` is off, STOP and refuse.
3. Verify the project uses **static typing**. TypeScript with `tsconfig.json` `"strict": true` is required (which Diffr enforces — see CLAUDE.md). If `strict` is off, STOP and refuse.

## Steps

Expand Down Expand Up @@ -43,7 +43,7 @@ Before deciding what to touch, understand what is tested.
Search for code that is never called, never imported, never referenced.

1. Look for unused exports, unused functions, unused classes, unused variables.
2. Check TypeScript: `noUnusedLocals`/`noUnusedParameters` are already enabled in Diffly's `tsconfig.json`. Look for unexported helpers with zero references.
2. Check TypeScript: `noUnusedLocals`/`noUnusedParameters` are already enabled in Diffr's `tsconfig.json`. Look for unexported helpers with zero references.
3. For each candidate: **grep the entire codebase** (including `src/test/`, `package.json` `contributes`, `scripts/`, configs) for references. Only mark as dead if truly zero references.
4. List all dead code found with file paths and line numbers. Do NOT delete yet.

Expand All @@ -54,7 +54,7 @@ Search for code blocks that do the same thing in multiple places.
1. Look for functions/methods with identical or near-identical logic.
2. Look for copy-pasted blocks (same structure, maybe different variable names).
3. Look for multiple implementations of the same algorithm or pattern.
4. Check across module boundaries — Diffly's layered architecture (`src/git/`, `src/ui/`, `src/providers/`, `src/commands/`) is a natural place for accidental duplication.
4. Check across module boundaries — Diffr's layered architecture (`src/git/`, `src/ui/`, `src/providers/`, `src/commands/`) is a natural place for accidental duplication.
5. For each duplicate pair: note both locations, what they do, and how they differ (if at all).
6. List all duplicates found. Do NOT merge yet.

Expand Down Expand Up @@ -101,7 +101,7 @@ For each change, follow this cycle: **change → test → verify coverage → co

- **No test coverage = do not touch.** If a file has no tests covering it, leave it alone entirely. You cannot safely dedup what you cannot verify.
- **Coverage must not drop.** If removing or merging code causes coverage to decrease, revert and investigate. The coverage floor from Step 1 is sacred.
- **Strict TypeScript only.** Diffly enforces `strict: true`. If that ever changes, refuse to dedup until it's re-enabled.
- **Strict TypeScript only.** Diffr enforces `strict: true`. If that ever changes, refuse to dedup until it's re-enabled.
- **One change at a time.** Make one dedup change, run tests, verify coverage. Never batch multiple dedup changes before testing.
- **When in doubt, leave it.** If two code blocks look similar but you're not 100% sure they're functionally identical, leave both. False dedup is worse than duplication.
- **Preserve public API surface.** Do not change exported function signatures, command IDs, URI scheme, or `package.json` `contributes` keys that the extension exposes.
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/fix-bug/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ You MUST follow this exact workflow. Do NOT skip steps. Do NOT fix the bug befor
- Write a test that **directly exercises the buggy behavior**
- The test must assert the **correct/expected** behavior — so it FAILS against the current broken code
- The test name should clearly describe the bug (e.g., `test_orange_color_not_applied_to_head`)
- Use the project's existing test framework and conventions (Diffly uses mocha for unit and `@vscode/test-electron` for E2E — pick the right tier per CLAUDE.md)
- Use the project's existing test framework and conventions (Diffr uses mocha for unit and `@vscode/test-electron` for E2E — pick the right tier per CLAUDE.md)

## Step 3: Run the Test — Confirm It FAILS

Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/spec-check/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ argument-hint: "[optional spec ID or filename filter]"

Audit spec/plan documents against the codebase. Ensures every spec section has implementing code, tests, and that the code logic matches the spec.

In Diffly, the primary spec lives at [docs/specs/spec.md](../../../docs/specs/spec.md) and the live plan/TODO list at [docs/plans/plan.md](../../../docs/plans/plan.md).
In Diffr, the primary spec lives at [docs/specs/spec.md](../../../docs/specs/spec.md) and the live plan/TODO list at [docs/plans/plan.md](../../../docs/plans/plan.md).

## Arguments

Expand Down
8 changes: 4 additions & 4 deletions .claude/skills/upgrade-packages/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
---
name: upgrade-packages
description: Upgrade all dependencies/packages to their latest versions. Diffly is TypeScript/Node — use when the user says "upgrade packages", "update dependencies", "bump versions", "update packages", or "upgrade deps".
description: Upgrade all dependencies/packages to their latest versions. Diffr is TypeScript/Node — use when the user says "upgrade packages", "update dependencies", "bump versions", "update packages", or "upgrade deps".
argument-hint: "[--check-only] [--major] [package-name]"
---
<!-- agent-pmo:74cf183 -->

# Upgrade Packages

Upgrade Diffly's npm dependencies to their latest compatible (or latest major, if `--major`) versions.
Upgrade Diffr's npm dependencies to their latest compatible (or latest major, if `--major`) versions.

## Arguments

Expand All @@ -17,7 +17,7 @@ Upgrade Diffly's npm dependencies to their latest compatible (or latest major, i

## Step 1 — Detect package manager

Diffly uses **npm** (package manifest: `package.json`, lockfile: `package-lock.json`). If for some reason the repo has migrated to yarn or pnpm, adapt accordingly (`yarn outdated`/`yarn up` or `pnpm outdated`/`pnpm update`).
Diffr uses **npm** (package manifest: `package.json`, lockfile: `package-lock.json`). If for some reason the repo has migrated to yarn or pnpm, adapt accordingly (`yarn outdated`/`yarn up` or `pnpm outdated`/`pnpm update`).

If `package.json` is missing, stop and tell the user.

Expand Down Expand Up @@ -47,7 +47,7 @@ npm update # semver-compatible (within package.json r
npx npm-check-updates -u && npm install # bump package.json to latest majors
```

### Diffly-specific cautions
### Diffr-specific cautions

- `@types/vscode` and `@types/node` must remain compatible with the `engines.vscode` minimum declared in `package.json`. A major bump that requires a newer VSCode runtime is a breaking change to consumers.
- `@vscode/test-electron`, `mocha`, `c8`, and `vsce` (`@vscode/vsce`) are tightly coupled to the extension test/release pipeline — review their changelogs before bumping.
Expand Down
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"_agent_pmo": "74cf183",
"name": "Diffly (Node.js)",
"name": "Diffr (Node.js)",
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-20",
"remoteUser": "vscode",
"postCreateCommand": "make setup",
Expand Down
16 changes: 10 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# agent-pmo:74cf183
# Release Diffly on a v* tag: build → package one universal VSIX → publish to the
# Release Diffr on a v* tag: build → package one universal VSIX → publish to the
# VS Code Marketplace → attach it to the GitHub release. One sequential job.
# Pure-TS extension (no native binaries) → a single platform-agnostic VSIX.

Expand Down Expand Up @@ -32,25 +32,29 @@ jobs:
- name: Set version from tag
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV"

- name: Stamp version into every carrier
run: |
node scripts/stamp-release-version.mjs "$VERSION"
node scripts/verify-versions.mjs "$VERSION"

- name: Build and package universal VSIX
run: |
npm version "$VERSION" --no-git-tag-version --allow-same-version
npm run build
npx vsce package --out "diffly-$VERSION.vsix"
npx vsce package --out "diffr-$VERSION.vsix"

- name: Publish to VS Code Marketplace
env:
VSCE_PAT: ${{ secrets.VSCODE_MARKETPLACE_PAT }}
run: |
if [ -z "$VSCE_PAT" ]; then
echo "::error::VSCODE_MARKETPLACE_PAT not accessible. Add Diffly to the secret's allowed repositories under Org Settings → Secrets and variables → Actions."
echo "::error::VSCODE_MARKETPLACE_PAT not accessible. Add Diffr to the secret's allowed repositories under Org Settings → Secrets and variables → Actions."
exit 1
fi
npx vsce publish --packagePath "diffly-$VERSION.vsix" --allow-proposed-apis contribSourceControlHistoryItemMenu
npx vsce publish --packagePath "diffr-$VERSION.vsix" --allow-proposed-apis contribSourceControlHistoryItemMenu

- name: Attach VSIX to GitHub release
uses: softprops/action-gh-release@v2
with:
files: diffly-*.vsix
files: diffr-*.vsix
generate_release_notes: true
prerelease: ${{ contains(github.ref_name, '-') }}
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

Initial release — pick two things and diff them, entirely from VS Code's existing context menus.

[0.1.0]: https://github.com/Nimblesite/Diffly/releases/tag/v0.1.0
[0.1.0]: https://github.com/Nimblesite/Diffr/releases/tag/v0.1.0
20 changes: 10 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Diffly — Agent Instructions
# Diffr — Agent Instructions

<!-- agent-pmo:74cf183 -->

Expand All @@ -11,13 +11,13 @@ Call out irrelevant context before proceeding. Bloat degrades reasoning. ⚠️
⚠️ **CRITICAL: THIS CODEBASE RECEIVES A GRADE OF A+.** WE DON'T ALLOW BAD CODE. NOT EVEN FOR ONE LINE. CODE MUST PASS REVIEW AT Google / Meta / Microsoft. ANYTHING LESS IS ⛔️ ILLEGAL AND MUST BE FIXED IMMEDIATELY.⚠️

⚠️ **NEW VIEWS, ACTIVITY-BAR ICONS, SIDEBARS, TREE PROVIDERS, OR WEBVIEWS ARE ⛔️ ILLEGAL.**
Diffly is **context-menu only**. Every feature hangs off VSCode's existing SCM history, SCM resource state, editor title, and explorer menus — plus a small set of palette commands. If a feature needs a new panel to exist, the feature is wrong.⚠️
Diffr is **context-menu only**. Every feature hangs off VSCode's existing SCM history, SCM resource state, editor title, and explorer menus — plus a small set of palette commands. If a feature needs a new panel to exist, the feature is wrong.⚠️

Full design + execution plan: [spec.md](spec.md).

## Project Overview

**Diffly** is a VSCode extension that does exactly one thing: **pick two things and diff them** against a git repository. Side A is a commit; Side B is another commit, the working copy, the index, or a branch/tag (resolved to a commit). It shells out to `git`, hands two URIs to VSCode's built-in `vscode.diff`, and uses a multi-step QuickPick for browsing many changed files. No custom renderer, no custom view.
**Diffr** is a VSCode extension that does exactly one thing: **pick two things and diff them** against a git repository. Side A is a commit; Side B is another commit, the working copy, the index, or a branch/tag (resolved to a commit). It shells out to `git`, hands two URIs to VSCode's built-in `vscode.diff`, and uses a multi-step QuickPick for browsing many changed files. No custom renderer, no custom view.

**Primary language:** TypeScript (pure — Rust LSP was considered and rejected; LSP is for _language_ semantics, not diffing)
**Build command:** `make ci`
Expand All @@ -32,27 +32,27 @@ There are 7 standard make targets: `build`, `test`, `lint`, `fmt`, `clean`, `ci`
context-menu / palette command
→ ui/* QuickPick (CommitPicker | SideBPicker | RefPicker | FilePicker)
→ git/GitRepo → git/GitRunner (subprocess)
→ providers/DifflyContentProvider (TextDocumentContentProvider for diffly://)
→ providers/DiffrContentProvider (TextDocumentContentProvider for diffr://)
→ vscode.commands.executeCommand('vscode.diff', leftUri, rightUri, title)
```

- **`src/git/`** — pure logic. No `vscode` imports. Subprocess wrapper + NUL-delimited porcelain parsers. Trivially portable to IntelliJ/Kotlin if anyone ever wants that.
- **`src/providers/DifflyContentProvider.ts`** — `TextDocumentContentProvider` for scheme `diffly`. URI parse + GitRepo call. Pure dispatch.
- **`src/providers/DiffrContentProvider.ts`** — `TextDocumentContentProvider` for scheme `diffr`. URI parse + GitRepo call. Pure dispatch.
- **`src/ui/`** — `vscode.window.createQuickPick<T>()` wrappers. Each returns `Result<T, Cancelled>`. FilePicker stays open after selection (`ignoreFocusOut: true`) so a single comparison drives many diffs.
- **`src/commands/`** — one file per command. Glue only; logic lives in `git/` and `ui/`.
- **`src/extension.ts`** — activate/deactivate; command + provider registration ONLY. No business logic.
- **Global state** lives in exactly one file: `src/state.ts` (Memento wrapper for last comparison). Nothing escapes it.

## Hard Rules (no exceptions, NON-NEGOTIABLE)

- **NO git commands from the agent.** No `git add`, `commit`, `push`, `checkout`, `merge`, `rebase`. CI and GitHub Actions handle git. (Diffly itself shells out to `git` at runtime — that's the product. The _agent_ doesn't drive git in the dev loop.)
- **NO git commands from the agent.** No `git add`, `commit`, `push`, `checkout`, `merge`, `rebase`. CI and GitHub Actions handle git. (Diffr itself shells out to `git` at runtime — that's the product. The _agent_ doesn't drive git in the dev loop.)
- **NO new views, sidebars, activity-bar icons, tree providers, or webviews.** Context menus + palette commands only. Browsing many files is a QuickPick, not a panel.
- **NO THROWING EXCEPTIONS for control flow.** Return `Result<T,E>` via a discriminated union. Panics are bugs.
- **NO REGEX on structured data.** Git porcelain output is parsed via NUL-delimited splits (`-z` flag everywhere). Never regex over JSON, YAML, source code, or git output.
- **NO PLACEHOLDERS.** If something isn't implemented, leave a loud compilation error with TODO. Silent no-ops = ⛔️ ILLEGAL.
- **Functions < 20 lines.** Refactor aggressively.
- **Files < 450 lines.** Extract modules when over.
- **ZERO DUPLICATION.** Search before writing. Move code, don't copy. Diffly detects diffs — its own codebase must be exemplary.
- **ZERO DUPLICATION.** Search before writing. Move code, don't copy. Diffr detects diffs — its own codebase must be exemplary.
- **TypeScript strict mode.** `tsconfig.json` has `"strict": true`. No `any`. No `!` (non-null assertion) — use optional chaining or explicit guards. No `// @ts-ignore` / `@ts-nocheck`. No `as Type` casts without a comment explaining safety. All function params and return types annotated.
- **No suppressing linter warnings.** Fix the code, not the linter.
- **Decouple providers from the VSCode SDK.** `src/git/` and `src/ui/uri.ts` have ZERO `vscode` imports. SDK-bound modules are thin shells around pure logic.
Expand Down Expand Up @@ -104,7 +104,7 @@ Do not write assertions that guard against AI / taxonomy strings. Assert on **po
⛔️ BAD

```typescript
assert.doesNotMatch(label, /\[diffly-internal-tag\]/);
assert.doesNotMatch(label, /\[diffr-internal-tag\]/);
```

✅ GOOD
Expand All @@ -127,7 +127,7 @@ Two audiences. Write for the right one.
## Repo Structure

```
Diffly/
Diffr/
├── .github/workflows/{ci.yml, release.yml}
├── .vscode-test.mjs
├── Makefile # 7 standard targets + `package`
Expand All @@ -154,7 +154,7 @@ Diffly/
│ │ ├── parsers.ts # NUL-delimited porcelain parsers
│ │ └── types.ts
│ ├── providers/
│ │ └── DifflyContentProvider.ts
│ │ └── DiffrContentProvider.ts
│ ├── ui/
│ │ ├── CommitPicker.ts
│ │ ├── RefPicker.ts
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# agent-pmo:74cf183
# =============================================================================
# Standard Makefile — Diffly (VSCode extension, TypeScript)
# Standard Makefile — Diffr (VSCode extension, TypeScript)
# Cross-platform: Linux, macOS, Windows (via GNU Make)
# =============================================================================

Expand Down
Loading
Loading