diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69aa6f8..01635e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,6 @@ jobs: - name: Verify committed action bundle is up to date run: | if ! git diff --exit-code -- packages/action/dist; then - echo "::error::packages/action/dist is out of date - run 'npm run build -w @seal/action' and commit the result." + echo "::error::packages/action/dist is out of date - run 'npm run build -w @docmend/action' and commit the result." exit 1 fi diff --git a/.gitignore b/.gitignore index 4dfc0ea..2eadbe5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ dist/ .env.local .DS_Store -# @seal/action ships as a GitHub Action, consumed directly from git with +# @docmend/action ships as a GitHub Action, consumed directly from git with # no install/build step, so its bundled entry point must be committed. !packages/action/dist/ !packages/action/dist/** diff --git a/README.md b/README.md index 86d4137..8f7db89 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# seal +# docmend -**Automated documentation healing.** seal watches your codebase, detects when a code change makes your docs inaccurate, and either fixes the stale section automatically or flags it for human review — as a local git hook, a GitHub Action, or both. +**Automated documentation healing.** docmend watches your codebase, detects when a code change makes your docs inaccurate, and either fixes the stale section automatically or flags it for human review — as a local git hook, a GitHub Action, or both. -Every team's docs drift out of sync with the code. seal closes that gap automatically, using an LLM to understand *what* changed and whether it actually invalidates what's written about it — not just that a file touched a function that happens to be mentioned somewhere. +Every team's docs drift out of sync with the code. docmend closes that gap automatically, using an LLM to understand *what* changed and whether it actually invalidates what's written about it — not just that a file touched a function that happens to be mentioned somewhere. ## How it works @@ -13,25 +13,25 @@ Every team's docs drift out of sync with the code. seal closes that gap automati ## Installing -### As a local git hook (`@seal/cli`) +### As a local git hook (`@docmend/cli`) ```console -$ npm install --save-dev @seal/cli -$ npx seal init # installs a pre-commit hook -$ SEAL_GEMINI_API_KEY=... npx seal index # one-time: build the code-to-docs graph +$ npm install --save-dev @docmend/cli +$ npx docmend init # installs a pre-commit hook +$ DOCMEND_GEMINI_API_KEY=... npx docmend index # one-time: build the code-to-docs graph ``` -From then on, `seal check` runs automatically on every commit: it diffs your staged changes, checks any linked docs for staleness, and — for high-confidence fixes — rewrites the doc and re-stages it alongside your change. Anything uncertain is reported, never silently applied. +From then on, `docmend check` runs automatically on every commit: it diffs your staged changes, checks any linked docs for staleness, and — for high-confidence fixes — rewrites the doc and re-stages it alongside your change. Anything uncertain is reported, never silently applied. - Non-blocking by default. Pass `--strict` to block the commit when something needs review. -- `SEAL_SKIP=1 git commit ...` bypasses the check entirely. -- Re-run `seal index` after significant changes to refresh the cached graph (it's read fresh; nothing rebuilds automatically on every commit — that's what keeps the hook fast). -- `.sealignore` (gitignore-style syntax) excludes paths from the *changed-file* check. +- `DOCMEND_SKIP=1 git commit ...` bypasses the check entirely. +- Re-run `docmend index` after significant changes to refresh the cached graph (it's read fresh; nothing rebuilds automatically on every commit — that's what keeps the hook fast). +- `.docmendignore` (gitignore-style syntax) excludes paths from the *changed-file* check. -### As a GitHub Action (`@seal/action`) +### As a GitHub Action (`@docmend/action`) ```yaml -# .github/workflows/seal.yml +# .github/workflows/docmend.yml on: pull_request: @@ -40,13 +40,13 @@ permissions: pull-requests: write jobs: - seal: + docmend: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # need the PR's base commit available, not just the head - - uses: dakshcodez/seal@v1 + - uses: dakshcodez/docmend@v1 with: gemini-api-key: ${{ secrets.GEMINI_API_KEY }} # confidence-threshold: '0.8' # optional, default shown @@ -57,7 +57,7 @@ On every PR, the Action diffs the base against the head commit, and for validate ## Configuration -Both the CLI and the Action read the same underlying pipeline. The CLI takes `SEAL_GEMINI_API_KEY` (or `GEMINI_API_KEY`) from the environment; the Action takes `gemini-api-key` as an input, matching the `action.yml` inputs (`confidence-threshold`, `auto-merge`, `github-token`). +Both the CLI and the Action read the same underlying pipeline. The CLI takes `DOCMEND_GEMINI_API_KEY` (or `GEMINI_API_KEY`) from the environment; the Action takes `gemini-api-key` as an input, matching the `action.yml` inputs (`confidence-threshold`, `auto-merge`, `github-token`). ## Real-world accuracy @@ -71,9 +71,9 @@ TypeScript throughout, npm workspaces monorepo. Gemini (`gemini-3.7-flash` / `ge ``` packages/ - core/ @seal/core - shared pipeline: parsing, link graph, change detection, doc repair - cli/ @seal/cli - npm package, the local git hook - action/ @seal/action - GitHub Action + core/ @docmend/core - shared pipeline: parsing, link graph, change detection, doc repair + cli/ @docmend/cli - npm package, the local git hook + action/ @docmend/action - GitHub Action ``` ## License diff --git a/TESTING.md b/TESTING.md index c84560a..11adc9d 100644 --- a/TESTING.md +++ b/TESTING.md @@ -1,12 +1,12 @@ # Real-world accuracy test -This documents a real-world test of seal against a fork of [tj/commander.js](https://github.com/tj/commander.js) (a real, actively-maintained, well-documented JavaScript library — chosen over the originally-suggested FastAPI/Pydantic for its much smaller size, to keep the test's embedding-API cost and runtime tractable on a free-tier key). +This documents a real-world test of docmend against a fork of [tj/commander.js](https://github.com/tj/commander.js) (a real, actively-maintained, well-documented JavaScript library — chosen over the originally-suggested FastAPI/Pydantic for its much smaller size, to keep the test's embedding-API cost and runtime tractable on a free-tier key). ## Methodology 1. Forked `tj/commander.js` to `dakshcodez/commander.js` and cloned it locally. -2. Ran `seal index` for real (live Gemini API, `gemini-3.7-flash`/`gemini-embedding-2` for indexing, `gemini-3.5-flash-lite` for the staleness/repair calls during this test session specifically to work around `3.7-flash`'s demand-driven `503`s — the shipped default remains `3.7-flash`). Indexed the full repo: **232 code chunks, 365 doc sections** (before the changelog-exclusion fix described below), producing 194 links (178 heuristic, 16 embedding). -3. Made three deliberate, targeted code changes and staged them, then ran `seal check` for real against the live pipeline. +2. Ran `docmend index` for real (live Gemini API, `gemini-3.7-flash`/`gemini-embedding-2` for indexing, `gemini-3.5-flash-lite` for the staleness/repair calls during this test session specifically to work around `3.7-flash`'s demand-driven `503`s — the shipped default remains `3.7-flash`). Indexed the full repo: **232 code chunks, 365 doc sections** (before the changelog-exclusion fix described below), producing 194 links (178 heuristic, 16 embedding). +3. Made three deliberate, targeted code changes and staged them, then ran `docmend check` for real against the live pipeline. 4. Inspected the actual generated corrections and verdicts against expectations. ## Deliberate test cases @@ -31,6 +31,6 @@ Also observed, not fixed: the correction pass occasionally touched unrelated for ## Known limitations of this test - The changelog-exclusion fix (bug #3 above) is verified at the unit level (the file-matching pattern was tested directly against 7 real-world filename cases, all correct) but **not re-validated end-to-end against the live API** — the free tier's *daily* embedding quota (1000 requests/day) was exhausted while re-indexing to confirm it, and daily quotas don't reset within a session. A full end-to-end re-run is a natural follow-up once quota is available. -- This test exercised the `@seal/cli` path (`seal index` + `seal check`) only, not the GitHub Action — the Action's `dist/index.js` isn't bundled for standalone execution yet (see the publish-phase task). +- This test exercised the `@docmend/cli` path (`docmend index` + `docmend check`) only, not the GitHub Action — the Action's `dist/index.js` isn't bundled for standalone execution yet (see the publish-phase task). - One real repository, one language (JavaScript), a handful of deliberate cases — not a statistically rigorous accuracy benchmark, but real signal from real code and real docs rather than synthetic fixtures. -- The new `isTestFile`/`isChangelogFile` exclusions are unconditional — there's no way to opt a specific file back in short of renaming it. `.sealignore` exists as a user-facing override mechanism, but it's currently only wired into `check.ts`'s changed-file filtering, not into the doc/code indexing walk itself (a pre-existing gap, not introduced by this fix). A team that genuinely wants a file named `HISTORY.md` treated as living documentation has no escape hatch today. +- The new `isTestFile`/`isChangelogFile` exclusions are unconditional — there's no way to opt a specific file back in short of renaming it. `.docmendignore` exists as a user-facing override mechanism, but it's currently only wired into `check.ts`'s changed-file filtering, not into the doc/code indexing walk itself (a pre-existing gap, not introduced by this fix). A team that genuinely wants a file named `HISTORY.md` treated as living documentation has no escape hatch today. diff --git a/package-lock.json b/package-lock.json index 44d622d..f172cba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "seal", + "name": "docmend", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "seal", + "name": "docmend", "version": "0.1.0", "workspaces": [ "packages/*" @@ -99,6 +99,18 @@ "integrity": "sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==", "license": "MIT" }, + "node_modules/@docmend/action": { + "resolved": "packages/action", + "link": true + }, + "node_modules/@docmend/cli": { + "resolved": "packages/cli", + "link": true + }, + "node_modules/@docmend/core": { + "resolved": "packages/core", + "link": true + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", @@ -1135,18 +1147,6 @@ "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", "license": "BSD-3-Clause" }, - "node_modules/@seal/action": { - "resolved": "packages/action", - "link": true - }, - "node_modules/@seal/cli": { - "resolved": "packages/cli", - "link": true - }, - "node_modules/@seal/core": { - "resolved": "packages/core", - "link": true - }, "node_modules/@types/esrecurse": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", @@ -3448,12 +3448,12 @@ } }, "packages/action": { - "name": "@seal/action", + "name": "@docmend/action", "version": "0.1.0", "dependencies": { "@actions/core": "^3.0.1", "@actions/github": "^9.1.1", - "@seal/core": "*" + "@docmend/core": "*" }, "devDependencies": { "esbuild": "^0.28.2" @@ -3463,21 +3463,21 @@ } }, "packages/cli": { - "name": "@seal/cli", + "name": "@docmend/cli", "version": "0.1.0", "license": "MIT", "dependencies": { - "@seal/core": "^0.1.0" + "@docmend/core": "^0.1.0" }, "bin": { - "seal": "dist/index.js" + "docmend": "dist/index.js" }, "engines": { "node": ">=20" } }, "packages/core": { - "name": "@seal/core", + "name": "@docmend/core", "version": "0.1.0", "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 288be73..08c478c 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "seal", + "name": "docmend", "private": true, "version": "0.1.0", "description": "Automated documentation healing for your codebase", @@ -11,8 +11,8 @@ "node": ">=20" }, "scripts": { - "build": "npm run build -w @seal/core && npm run build -w @seal/cli && npm run build -w @seal/action", - "typecheck": "npm run build -w @seal/core && npm run typecheck -w @seal/cli && npm run typecheck -w @seal/action", + "build": "npm run build -w @docmend/core && npm run build -w @docmend/cli && npm run build -w @docmend/action", + "typecheck": "npm run build -w @docmend/core && npm run typecheck -w @docmend/cli && npm run typecheck -w @docmend/action", "lint": "eslint ." }, "devDependencies": { diff --git a/packages/action/action.yml b/packages/action/action.yml index eddc49c..b2a1e6c 100644 --- a/packages/action/action.yml +++ b/packages/action/action.yml @@ -1,4 +1,4 @@ -name: 'seal - Automated Documentation Healing' +name: 'docmend - Automated Documentation Healing' description: 'Detects code changes that make documentation stale, auto-fixes high-confidence cases via a PR, and flags the rest for human review.' inputs: gemini-api-key: @@ -13,7 +13,7 @@ inputs: required: false default: '0.8' auto-merge: - description: 'Automatically merge the auto-fix PR seal opens.' + description: 'Automatically merge the auto-fix PR docmend opens.' required: false default: 'false' outputs: diff --git a/packages/action/dist/index.js b/packages/action/dist/index.js index 01d4451..7988690 100644 --- a/packages/action/dist/index.js +++ b/packages/action/dist/index.js @@ -199607,9 +199607,9 @@ var GeminiClient = class { generationModel; embeddingModel; constructor(options = {}) { - const apiKey = options.apiKey ?? process.env.SEAL_GEMINI_API_KEY ?? process.env.GEMINI_API_KEY; + const apiKey = options.apiKey ?? process.env.DOCMEND_GEMINI_API_KEY ?? process.env.GEMINI_API_KEY; if (!apiKey) { - throw new Error("Gemini API key not found. Set SEAL_GEMINI_API_KEY (or GEMINI_API_KEY), or pass apiKey explicitly."); + throw new Error("Gemini API key not found. Set DOCMEND_GEMINI_API_KEY (or GEMINI_API_KEY), or pass apiKey explicitly."); } this.client = new GoogleGenAI2({ apiKey }); this.generationModel = options.generationModel ?? DEFAULT_GENERATION_MODEL; @@ -204010,7 +204010,7 @@ async function buildEmbeddingLinks(chunks, sections, llm, options) { for (const chunk of chunks) { const vector = await llm.embed(chunkEmbeddingText(chunk)); if (seenChunkIds.has(chunk.id)) { - console.warn(`seal: duplicate chunk id "${chunk.id}" - only the last one is linkable in the embedding index.`); + console.warn(`docmend: duplicate chunk id "${chunk.id}" - only the last one is linkable in the embedding index.`); } seenChunkIds.add(chunk.id); await index.upsertItem({ id: chunk.id, vector }); @@ -204158,7 +204158,7 @@ async function hasUnstagedChanges(cwd, path2) { return stdout.trim().length > 0; } -// ../core/dist/config/sealignore.js +// ../core/dist/config/docmendignore.js import { readFile as readFile4 } from "node:fs/promises"; import { join as join2 } from "node:path"; var SPECIAL_CHARS = ".+^${}()|[]\\"; @@ -204195,7 +204195,7 @@ function globToRegExp(pattern) { async function loadIgnorePatterns(rootDir) { let raw; try { - raw = await readFile4(join2(rootDir, ".sealignore"), "utf8"); + raw = await readFile4(join2(rootDir, ".docmendignore"), "utf8"); } catch { return []; } @@ -204391,7 +204391,7 @@ async function detectChanges(options) { // ../core/dist/repair/generate.js var AUTO_FIX_THRESHOLD = 0.8; -var LOW_CONFIDENCE_MARKER = "\n"; +var LOW_CONFIDENCE_MARKER = "\n"; var SYSTEM_INSTRUCTION2 = `You are a precise technical documentation editor. You will be given a documentation section, the new version of the code it describes, and a diagnosis of what is now inaccurate. Rewrite ONLY the parts of the section that are inaccurate given the new code. Preserve the original style, tone, structure, and any content that is still accurate - do not rewrite parts that don't need to change. Respond with ONLY a JSON object of the form {"correctedContent": string, "confidence": number, "rationale": string}. No markdown, no code fences, no extra text. @@ -204603,7 +204603,7 @@ var BOT_NAME = "github-actions[bot]"; var BOT_EMAIL = "41898282+github-actions[bot]@users.noreply.github.com"; async function createFixPr(cwd, octokit, graph, ctx, autoFixResults, autoMerge) { if (autoFixResults.length === 0) return null; - const branchName = `seal/doc-fixes-${Date.now()}`; + const branchName = `docmend/doc-fixes-${Date.now()}`; await configureGitIdentity(cwd, BOT_NAME, BOT_EMAIL); await createBranch(cwd, branchName, ctx.headSha); const appliedSectionIds = []; @@ -204618,10 +204618,10 @@ async function createFixPr(cwd, octokit, graph, ctx, autoFixResults, autoMerge) await checkoutRef(cwd, ctx.headSha); return null; } - await commit(cwd, "docs: auto-fix stale documentation via seal"); + await commit(cwd, "docs: auto-fix stale documentation via docmend"); await push(cwd, branchName); const body2 = [ - "Automated documentation fixes from seal for this PR.", + "Automated documentation fixes from docmend for this PR.", "", ...appliedSectionIds.map((id) => `- \`${id}\``) ].join("\n"); @@ -204653,12 +204653,12 @@ async function createFixPr(cwd, octokit, graph, ctx, autoFixResults, autoMerge) function loadPrContext() { if (context2.eventName !== "pull_request") { throw new Error( - `seal action must be triggered by a "pull_request" event, got "${context2.eventName}". Using "pull_request_target" here would run untrusted PR content with this repo's privileged token.` + `docmend action must be triggered by a "pull_request" event, got "${context2.eventName}". Using "pull_request_target" here would run untrusted PR content with this repo's privileged token.` ); } const pr = context2.payload.pull_request; if (!pr) { - throw new Error("seal action must be triggered by a pull_request event"); + throw new Error("docmend action must be triggered by a pull_request event"); } const baseSha = pr.base?.sha; const headSha = pr.head?.sha; @@ -204683,18 +204683,18 @@ async function run2() { const cwd = process.cwd(); const octokit = getOctokit(config.githubToken); const llm = new GeminiClient({ apiKey: config.apiKey }); - info2("seal: building code-to-docs link graph..."); + info2("docmend: building code-to-docs link graph..."); const chunks = await parseCodebase(cwd); const sections = await parseDocs(cwd); const graph = await buildLinkGraph(chunks, sections, { llm, - embeddingIndexPath: join4(cwd, ".seal", "vectra-index") + embeddingIndexPath: join4(cwd, ".docmend", "vectra-index") }); const changedFiles = await getDiffBetweenRefs(cwd, ctx.baseSha, ctx.headSha); const ignorePatterns = await loadIgnorePatterns(cwd); const relevantFiles = changedFiles.filter((file) => !isIgnored(file.path, ignorePatterns)); if (relevantFiles.length === 0) { - info2("seal: no relevant changes in this PR."); + info2("docmend: no relevant changes in this PR."); setOutput("stale-sections-found", 0); setOutput("corrections-generated", 0); return; @@ -204708,7 +204708,7 @@ async function run2() { const staleVerdicts = verdicts.filter((verdict) => verdict.stale); setOutput("stale-sections-found", staleVerdicts.length); if (staleVerdicts.length === 0) { - info2("seal: docs look accurate for this PR."); + info2("docmend: docs look accurate for this PR."); setOutput("corrections-generated", 0); return; } @@ -204729,10 +204729,10 @@ async function run2() { fixPr = await createFixPr(cwd, octokit, graph, ctx, autoFixResults, config.autoMerge); } catch (error2) { fixPrError = error2 instanceof Error ? error2.message : String(error2); - warning(`seal: could not open the auto-fix PR - ${fixPrError}`); + warning(`docmend: could not open the auto-fix PR - ${fixPrError}`); } await postSummaryComment(octokit, ctx, { verdicts, reviewNeeded, failed, fixPr, fixPrError }); - info2("seal: doc check complete."); + info2("docmend: doc check complete."); } run2().catch((error2) => { setFailed(error2 instanceof Error ? error2.message : String(error2)); diff --git a/packages/action/package.json b/packages/action/package.json index 1773258..a3c74ff 100644 --- a/packages/action/package.json +++ b/packages/action/package.json @@ -1,8 +1,8 @@ { - "name": "@seal/action", + "name": "@docmend/action", "version": "0.1.0", "private": true, - "description": "seal GitHub Action - automated documentation healing CI integration", + "description": "docmend GitHub Action - automated documentation healing CI integration", "type": "module", "main": "dist/index.js", "files": [ @@ -15,7 +15,7 @@ "dependencies": { "@actions/core": "^3.0.1", "@actions/github": "^9.1.1", - "@seal/core": "*" + "@docmend/core": "*" }, "engines": { "node": ">=20" diff --git a/packages/action/scripts/copy-wasm.mjs b/packages/action/scripts/copy-wasm.mjs index c86a912..58e348d 100644 --- a/packages/action/scripts/copy-wasm.mjs +++ b/packages/action/scripts/copy-wasm.mjs @@ -3,9 +3,9 @@ import { createRequire } from 'node:module'; import { basename, join } from 'node:path'; import { fileURLToPath } from 'node:url'; -// Resolved from @seal/core's own location (not this script's) so it works +// Resolved from @docmend/core's own location (not this script's) so it works // regardless of where npm happened to hoist these transitive dependencies. -const coreEntry = fileURLToPath(import.meta.resolve('@seal/core')); +const coreEntry = fileURLToPath(import.meta.resolve('@docmend/core')); const coreRequire = createRequire(coreEntry); const wasmFiles = [ diff --git a/packages/action/src/comment.ts b/packages/action/src/comment.ts index 26f7c1e..fd0acbd 100644 --- a/packages/action/src/comment.ts +++ b/packages/action/src/comment.ts @@ -1,5 +1,5 @@ import type { GitHub } from '@actions/github/lib/utils'; -import type { RepairResult, StalenessVerdict } from '@seal/core'; +import type { RepairResult, StalenessVerdict } from '@docmend/core'; import type { FixPrResult } from './fix-pr.js'; import type { PrContext } from './pr-context.js'; diff --git a/packages/action/src/fix-pr.ts b/packages/action/src/fix-pr.ts index 570bcaf..39093d6 100644 --- a/packages/action/src/fix-pr.ts +++ b/packages/action/src/fix-pr.ts @@ -1,6 +1,6 @@ import type { GitHub } from '@actions/github/lib/utils'; -import type { LinkGraph, RepairResult } from '@seal/core'; -import { applyCorrectionToRepo, checkoutRef, commit, configureGitIdentity, createBranch, push } from '@seal/core'; +import type { LinkGraph, RepairResult } from '@docmend/core'; +import { applyCorrectionToRepo, checkoutRef, commit, configureGitIdentity, createBranch, push } from '@docmend/core'; import type { PrContext } from './pr-context.js'; const BOT_NAME = 'github-actions[bot]'; @@ -24,7 +24,7 @@ export async function createFixPr( ): Promise { if (autoFixResults.length === 0) return null; - const branchName = `seal/doc-fixes-${Date.now()}`; + const branchName = `docmend/doc-fixes-${Date.now()}`; await configureGitIdentity(cwd, BOT_NAME, BOT_EMAIL); // actions/checkout leaves a pull_request event in detached HEAD at the PR // head SHA - the branch name itself usually isn't available as a local @@ -50,11 +50,11 @@ export async function createFixPr( return null; } - await commit(cwd, 'docs: auto-fix stale documentation via seal'); + await commit(cwd, 'docs: auto-fix stale documentation via docmend'); await push(cwd, branchName); const body = [ - 'Automated documentation fixes from seal for this PR.', + 'Automated documentation fixes from docmend for this PR.', '', ...appliedSectionIds.map((id) => `- \`${id}\``), ].join('\n'); diff --git a/packages/action/src/index.ts b/packages/action/src/index.ts index c1d8b73..046c1e8 100644 --- a/packages/action/src/index.ts +++ b/packages/action/src/index.ts @@ -13,7 +13,7 @@ import { readFileAtRef, repairStaleDocs, resolveFileChanges, -} from '@seal/core'; +} from '@docmend/core'; import { postSummaryComment } from './comment.js'; import { loadConfig } from './config.js'; import { createFixPr } from './fix-pr.js'; @@ -27,12 +27,12 @@ async function run(): Promise { const octokit = getOctokit(config.githubToken); const llm = new GeminiClient({ apiKey: config.apiKey }); - core.info('seal: building code-to-docs link graph...'); + core.info('docmend: building code-to-docs link graph...'); const chunks = await parseCodebase(cwd); const sections = await parseDocs(cwd); const graph = await buildLinkGraph(chunks, sections, { llm, - embeddingIndexPath: join(cwd, '.seal', 'vectra-index'), + embeddingIndexPath: join(cwd, '.docmend', 'vectra-index'), }); const changedFiles = await getDiffBetweenRefs(cwd, ctx.baseSha, ctx.headSha); @@ -40,7 +40,7 @@ async function run(): Promise { const relevantFiles = changedFiles.filter((file) => !isIgnored(file.path, ignorePatterns)); if (relevantFiles.length === 0) { - core.info('seal: no relevant changes in this PR.'); + core.info('docmend: no relevant changes in this PR.'); core.setOutput('stale-sections-found', 0); core.setOutput('corrections-generated', 0); return; @@ -58,7 +58,7 @@ async function run(): Promise { core.setOutput('stale-sections-found', staleVerdicts.length); if (staleVerdicts.length === 0) { - core.info('seal: docs look accurate for this PR.'); + core.info('docmend: docs look accurate for this PR.'); core.setOutput('corrections-generated', 0); return; } @@ -86,12 +86,12 @@ async function run(): Promise { fixPr = await createFixPr(cwd, octokit, graph, ctx, autoFixResults, config.autoMerge); } catch (error) { fixPrError = error instanceof Error ? error.message : String(error); - core.warning(`seal: could not open the auto-fix PR - ${fixPrError}`); + core.warning(`docmend: could not open the auto-fix PR - ${fixPrError}`); } await postSummaryComment(octokit, ctx, { verdicts, reviewNeeded, failed, fixPr, fixPrError }); - core.info('seal: doc check complete.'); + core.info('docmend: doc check complete.'); } run().catch((error: unknown) => { diff --git a/packages/action/src/pr-context.ts b/packages/action/src/pr-context.ts index 0dc12d7..d7c388d 100644 --- a/packages/action/src/pr-context.ts +++ b/packages/action/src/pr-context.ts @@ -16,7 +16,7 @@ export function loadPrContext(): PrContext { // opening a PR) assumes standard pull_request trust semantics. if (context.eventName !== 'pull_request') { throw new Error( - `seal action must be triggered by a "pull_request" event, got "${context.eventName}". ` + + `docmend action must be triggered by a "pull_request" event, got "${context.eventName}". ` + 'Using "pull_request_target" here would run untrusted PR content with this repo\'s privileged token.', ); } @@ -26,7 +26,7 @@ export function loadPrContext(): PrContext { | undefined; if (!pr) { - throw new Error('seal action must be triggered by a pull_request event'); + throw new Error('docmend action must be triggered by a pull_request event'); } const baseSha = pr.base?.sha; diff --git a/packages/cli/README.md b/packages/cli/README.md index fac9f5f..c6f1503 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -1,33 +1,33 @@ -# @seal/cli +# @docmend/cli Automated documentation healing, as a local git hook. Detects when a code change makes your Markdown docs stale, auto-fixes high-confidence cases, and flags the rest for review — before you commit. ## Install ```console -$ npm install --save-dev @seal/cli -$ npx seal init # installs a pre-commit hook -$ SEAL_GEMINI_API_KEY=... npx seal index # one-time: build the code-to-docs graph +$ npm install --save-dev @docmend/cli +$ npx docmend init # installs a pre-commit hook +$ DOCMEND_GEMINI_API_KEY=... npx docmend index # one-time: build the code-to-docs graph ``` ## Usage -`seal check` runs automatically on every commit via the installed hook. It diffs your staged changes, checks any linked docs for staleness, and rewrites + re-stages high-confidence fixes. Everything else is reported, never silently applied. +`docmend check` runs automatically on every commit via the installed hook. It diffs your staged changes, checks any linked docs for staleness, and rewrites + re-stages high-confidence fixes. Everything else is reported, never silently applied. ```console -$ npx seal check # non-blocking by default -$ npx seal check --strict # exit non-zero if anything needs review -$ SEAL_SKIP=1 git commit # bypass the check entirely +$ npx docmend check # non-blocking by default +$ npx docmend check --strict # exit non-zero if anything needs review +$ DOCMEND_SKIP=1 git commit # bypass the check entirely ``` -Re-run `npx seal index` after significant changes to refresh the cached code-to-docs graph — it's read fresh on every `check`, not rebuilt automatically, which is what keeps the hook fast. +Re-run `npx docmend index` after significant changes to refresh the cached code-to-docs graph — it's read fresh on every `check`, not rebuilt automatically, which is what keeps the hook fast. ## Configuration -- `SEAL_GEMINI_API_KEY` (or `GEMINI_API_KEY`) — required. -- `.sealignore` in your repo root — gitignore-style syntax, excludes paths from the changed-file check. +- `DOCMEND_GEMINI_API_KEY` (or `GEMINI_API_KEY`) — required. +- `.docmendignore` in your repo root — gitignore-style syntax, excludes paths from the changed-file check. -Full documentation: [github.com/dakshcodez/seal](https://github.com/dakshcodez/seal) +Full documentation: [github.com/dakshcodez/docmend](https://github.com/dakshcodez/docmend) ## License diff --git a/packages/cli/package.json b/packages/cli/package.json index 38fc091..b2b172f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,17 +1,17 @@ { - "name": "@seal/cli", + "name": "@docmend/cli", "version": "0.1.0", - "description": "seal CLI - automated documentation healing as a local git hook", + "description": "docmend CLI - automated documentation healing as a local git hook", "type": "module", "license": "MIT", - "homepage": "https://github.com/dakshcodez/seal#readme", + "homepage": "https://github.com/dakshcodez/docmend#readme", "repository": { "type": "git", - "url": "git+https://github.com/dakshcodez/seal.git", + "url": "git+https://github.com/dakshcodez/docmend.git", "directory": "packages/cli" }, "bugs": { - "url": "https://github.com/dakshcodez/seal/issues" + "url": "https://github.com/dakshcodez/docmend/issues" }, "keywords": [ "documentation", @@ -23,7 +23,7 @@ "developer-tools" ], "bin": { - "seal": "dist/index.js" + "docmend": "dist/index.js" }, "main": "dist/index.js", "files": [ @@ -34,7 +34,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@seal/core": "^0.1.0" + "@docmend/core": "^0.1.0" }, "engines": { "node": ">=20" diff --git a/packages/cli/src/commands/build-index.ts b/packages/cli/src/commands/build-index.ts index ce94a80..aff134c 100644 --- a/packages/cli/src/commands/build-index.ts +++ b/packages/cli/src/commands/build-index.ts @@ -1,6 +1,6 @@ import { join } from 'node:path'; -import type { LLMClient } from '@seal/core'; -import { GeminiClient, buildLinkGraph, parseCodebase, parseDocs, saveLinkGraph } from '@seal/core'; +import type { LLMClient } from '@docmend/core'; +import { GeminiClient, buildLinkGraph, parseCodebase, parseDocs, saveLinkGraph } from '@docmend/core'; export interface BuildIndexOptions { cwd: string; @@ -10,7 +10,7 @@ export interface BuildIndexOptions { export async function buildIndex(options: BuildIndexOptions): Promise { if (!options.llm && !options.apiKey) { - console.error('seal: no Gemini API key found. Set SEAL_GEMINI_API_KEY or GEMINI_API_KEY.'); + console.error('docmend: no Gemini API key found. Set DOCMEND_GEMINI_API_KEY or GEMINI_API_KEY.'); return 1; } @@ -20,10 +20,10 @@ export async function buildIndex(options: BuildIndexOptions): Promise { const graph = await buildLinkGraph(chunks, sections, { llm, - embeddingIndexPath: join(options.cwd, '.seal', 'vectra-index'), + embeddingIndexPath: join(options.cwd, '.docmend', 'vectra-index'), }); - await saveLinkGraph(graph, join(options.cwd, '.seal', 'link-graph.json')); - console.log(`seal: indexed ${chunks.length} code chunks and ${sections.length} doc sections.`); + await saveLinkGraph(graph, join(options.cwd, '.docmend', 'link-graph.json')); + console.log(`docmend: indexed ${chunks.length} code chunks and ${sections.length} doc sections.`); return 0; } diff --git a/packages/cli/src/commands/check.ts b/packages/cli/src/commands/check.ts index d502b18..7c6bcf5 100644 --- a/packages/cli/src/commands/check.ts +++ b/packages/cli/src/commands/check.ts @@ -1,5 +1,5 @@ import { join } from 'node:path'; -import type { LinkGraph, LLMClient } from '@seal/core'; +import type { LinkGraph, LLMClient } from '@docmend/core'; import { GeminiClient, applyCorrectionToRepo, @@ -13,7 +13,7 @@ import { readStagedFile, repairStaleDocs, resolveFileChanges, -} from '@seal/core'; +} from '@docmend/core'; export interface CheckOptions { cwd: string; @@ -22,10 +22,10 @@ export interface CheckOptions { llm?: LLMClient; } -const LINK_GRAPH_PATH = ['.seal', 'link-graph.json']; +const LINK_GRAPH_PATH = ['.docmend', 'link-graph.json']; function resolveApiKey(): string | undefined { - return process.env.SEAL_GEMINI_API_KEY ?? process.env.GEMINI_API_KEY; + return process.env.DOCMEND_GEMINI_API_KEY ?? process.env.GEMINI_API_KEY; } async function loadGraph(cwd: string): Promise { @@ -40,7 +40,7 @@ async function runCheck(options: CheckOptions): Promise { const apiKey = resolveApiKey(); if (!options.llm && !apiKey) { const message = - 'seal: no Gemini API key found (set SEAL_GEMINI_API_KEY or GEMINI_API_KEY) - skipping doc check.'; + 'docmend: no Gemini API key found (set DOCMEND_GEMINI_API_KEY or GEMINI_API_KEY) - skipping doc check.'; if (options.strict) { console.error(message); return 1; @@ -51,7 +51,7 @@ async function runCheck(options: CheckOptions): Promise { const graph = await loadGraph(options.cwd); if (!graph) { - console.warn('seal: no cached link graph found. Run "seal index" first - skipping doc check.'); + console.warn('docmend: no cached link graph found. Run "docmend index" first - skipping doc check.'); return 0; } @@ -63,7 +63,7 @@ async function runCheck(options: CheckOptions): Promise { const relevantFiles = changedFiles.filter((file) => !isIgnored(file.path, ignorePatterns)); if (relevantFiles.length === 0) { - console.log('seal: no relevant changes, nothing to check.'); + console.log('docmend: no relevant changes, nothing to check.'); return 0; } @@ -85,7 +85,7 @@ async function runCheck(options: CheckOptions): Promise { const staleVerdicts = verdicts.filter((verdict) => verdict.stale); if (staleVerdicts.length === 0) { - console.log('seal: docs look accurate for these changes.'); + console.log('docmend: docs look accurate for these changes.'); return 0; } @@ -98,7 +98,7 @@ async function runCheck(options: CheckOptions): Promise { for (const result of repairResults) { if (!result.correction) { failed += 1; - console.error(`seal: could not repair "${result.sectionId}" - ${result.error}`); + console.error(`docmend: could not repair "${result.sectionId}" - ${result.error}`); continue; } @@ -111,23 +111,23 @@ async function runCheck(options: CheckOptions): Promise { ); if (outcome === 'applied') { autoFixed += 1; - console.log(`seal: auto-fixed "${result.sectionId}"`); + console.log(`docmend: auto-fixed "${result.sectionId}"`); continue; } if (outcome === 'skipped-partial-stage') { console.warn( - `seal: "${result.sectionId}" has unstaged changes in the same file - skipping auto-fix to avoid staging unintended changes. Needs manual review.`, + `docmend: "${result.sectionId}" has unstaged changes in the same file - skipping auto-fix to avoid staging unintended changes. Needs manual review.`, ); } else { - console.warn(`seal: could not locate "${result.sectionId}" in its current file - flagging for review instead.`); + console.warn(`docmend: could not locate "${result.sectionId}" in its current file - flagging for review instead.`); } } needsReview += 1; - console.log(`seal: "${result.sectionId}" needs manual review - ${result.correction.rationale}`); + console.log(`docmend: "${result.sectionId}" needs manual review - ${result.correction.rationale}`); } - console.log(`seal: ${autoFixed} auto-fixed, ${needsReview} need review, ${failed} failed.`); + console.log(`docmend: ${autoFixed} auto-fixed, ${needsReview} need review, ${failed} failed.`); if (options.strict && (needsReview > 0 || failed > 0)) { return 1; @@ -136,15 +136,15 @@ async function runCheck(options: CheckOptions): Promise { } export async function check(options: CheckOptions): Promise { - if (process.env.SEAL_SKIP) { - console.log('seal: SEAL_SKIP set, skipping doc check.'); + if (process.env.DOCMEND_SKIP) { + console.log('docmend: DOCMEND_SKIP set, skipping doc check.'); return 0; } try { return await runCheck(options); } catch (error) { - const message = `seal: doc check failed unexpectedly - ${error instanceof Error ? error.message : String(error)}`; + const message = `docmend: doc check failed unexpectedly - ${error instanceof Error ? error.message : String(error)}`; if (options.strict) { console.error(message); return 1; diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 40e4d9e..dc7fd04 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -1,7 +1,7 @@ import { access, chmod, constants, mkdir, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; -const HOOK_SCRIPT = '#!/bin/sh\nnpx seal check\n'; +const HOOK_SCRIPT = '#!/bin/sh\nnpx docmend check\n'; async function fileExists(path: string): Promise { return access(path, constants.F_OK).then( @@ -13,7 +13,7 @@ async function fileExists(path: string): Promise { export async function init(cwd: string): Promise { const gitDir = join(cwd, '.git'); if (!(await fileExists(gitDir))) { - console.error(`seal: ${cwd} does not look like a git repository (no .git found).`); + console.error(`docmend: ${cwd} does not look like a git repository (no .git found).`); return 1; } @@ -23,13 +23,13 @@ export async function init(cwd: string): Promise { if (await fileExists(hookPath)) { console.error( - `seal: ${hookPath} already exists - not overwriting it. Add "npx seal check" to it manually instead.`, + `docmend: ${hookPath} already exists - not overwriting it. Add "npx docmend check" to it manually instead.`, ); return 1; } await writeFile(hookPath, HOOK_SCRIPT, 'utf8'); await chmod(hookPath, 0o755); - console.log(`seal: installed pre-commit hook at ${hookPath}`); + console.log(`docmend: installed pre-commit hook at ${hookPath}`); return 0; } diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 28f0d08..5c617dc 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -19,7 +19,7 @@ async function main(): Promise { return init(cwd); case 'index': { - const apiKey = process.env.SEAL_GEMINI_API_KEY ?? process.env.GEMINI_API_KEY; + const apiKey = process.env.DOCMEND_GEMINI_API_KEY ?? process.env.GEMINI_API_KEY; return buildIndex({ cwd, apiKey }); } @@ -43,6 +43,6 @@ main() process.exitCode = code; }) .catch((error) => { - console.error('seal: unexpected error:', error instanceof Error ? error.message : error); + console.error('docmend: unexpected error:', error instanceof Error ? error.message : error); process.exitCode = 1; }); diff --git a/packages/core/package.json b/packages/core/package.json index 37a2641..3aecc33 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,17 +1,17 @@ { - "name": "@seal/core", + "name": "@docmend/core", "version": "0.1.0", - "description": "Shared core logic for seal: code/doc parsing, link graph, staleness detection, doc repair engine", + "description": "Shared core logic for docmend: code/doc parsing, link graph, staleness detection, doc repair engine", "type": "module", "license": "MIT", - "homepage": "https://github.com/dakshcodez/seal#readme", + "homepage": "https://github.com/dakshcodez/docmend#readme", "repository": { "type": "git", - "url": "git+https://github.com/dakshcodez/seal.git", + "url": "git+https://github.com/dakshcodez/docmend.git", "directory": "packages/core" }, "bugs": { - "url": "https://github.com/dakshcodez/seal/issues" + "url": "https://github.com/dakshcodez/docmend/issues" }, "keywords": [ "documentation", diff --git a/packages/core/src/config/sealignore.ts b/packages/core/src/config/docmendignore.ts similarity index 95% rename from packages/core/src/config/sealignore.ts rename to packages/core/src/config/docmendignore.ts index 35e7760..e6065d6 100644 --- a/packages/core/src/config/sealignore.ts +++ b/packages/core/src/config/docmendignore.ts @@ -39,7 +39,7 @@ function globToRegExp(pattern: string): RegExp { export async function loadIgnorePatterns(rootDir: string): Promise { let raw: string; try { - raw = await readFile(join(rootDir, '.sealignore'), 'utf8'); + raw = await readFile(join(rootDir, '.docmendignore'), 'utf8'); } catch { return []; } diff --git a/packages/core/src/config/index.ts b/packages/core/src/config/index.ts index fa772ab..6e0a548 100644 --- a/packages/core/src/config/index.ts +++ b/packages/core/src/config/index.ts @@ -1 +1 @@ -export { isIgnored, loadIgnorePatterns } from './sealignore.js'; +export { isIgnored, loadIgnorePatterns } from './docmendignore.js'; diff --git a/packages/core/src/linkgraph/embedding.ts b/packages/core/src/linkgraph/embedding.ts index 89f6c73..af55e6c 100644 --- a/packages/core/src/linkgraph/embedding.ts +++ b/packages/core/src/linkgraph/embedding.ts @@ -45,7 +45,7 @@ export async function buildEmbeddingLinks( // Still surfaced as a warning, since it means one of the two silently // becomes unlinkable rather than crashing loudly like insertItem did. if (seenChunkIds.has(chunk.id)) { - console.warn(`seal: duplicate chunk id "${chunk.id}" - only the last one is linkable in the embedding index.`); + console.warn(`docmend: duplicate chunk id "${chunk.id}" - only the last one is linkable in the embedding index.`); } seenChunkIds.add(chunk.id); await index.upsertItem({ id: chunk.id, vector }); diff --git a/packages/core/src/llm/gemini-client.ts b/packages/core/src/llm/gemini-client.ts index ef58e98..cf13033 100644 --- a/packages/core/src/llm/gemini-client.ts +++ b/packages/core/src/llm/gemini-client.ts @@ -48,10 +48,10 @@ export class GeminiClient implements LLMClient { private readonly embeddingModel: string; constructor(options: GeminiClientOptions = {}) { - const apiKey = options.apiKey ?? process.env.SEAL_GEMINI_API_KEY ?? process.env.GEMINI_API_KEY; + const apiKey = options.apiKey ?? process.env.DOCMEND_GEMINI_API_KEY ?? process.env.GEMINI_API_KEY; if (!apiKey) { throw new Error( - 'Gemini API key not found. Set SEAL_GEMINI_API_KEY (or GEMINI_API_KEY), or pass apiKey explicitly.', + 'Gemini API key not found. Set DOCMEND_GEMINI_API_KEY (or GEMINI_API_KEY), or pass apiKey explicitly.', ); } diff --git a/packages/core/src/repair/generate.ts b/packages/core/src/repair/generate.ts index 2b68288..c7e34e8 100644 --- a/packages/core/src/repair/generate.ts +++ b/packages/core/src/repair/generate.ts @@ -4,7 +4,7 @@ import { parseJsonResponse } from '../llm/index.js'; import type { Correction, CorrectionMode } from './types.js'; const AUTO_FIX_THRESHOLD = 0.8; -const LOW_CONFIDENCE_MARKER = '\n'; +const LOW_CONFIDENCE_MARKER = '\n'; const SYSTEM_INSTRUCTION = `You are a precise technical documentation editor. You will be given a documentation section, the new version of the code it describes, and a diagnosis of what is now inaccurate. Rewrite ONLY the parts of the section that are inaccurate given the new code. Preserve the original style, tone, structure, and any content that is still accurate - do not rewrite parts that don't need to change.