From 11d34be6f13b6907c8b0756a047059a33470aa67 Mon Sep 17 00:00:00 2001 From: oratis Date: Sun, 9 Aug 2026 21:58:39 +0800 Subject: [PATCH 1/3] fix(cli): publish under a scope we actually own MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 0.3.0 renamed the CLI away from `deepcode-cli` because that name belongs to an unrelated project. `@deepcode/cli` was not ours either. The leaf name is unpublished — which is exactly what made it look free. But the `@deepcode` scope holds `@deepcode/tsc` and `@deepcode/dcignore`, and npm rejects a publish into a scope you do not own regardless of whether the individual name is available. A scope is not claimable by publishing into it, so `pnpm publish` would have returned the same 403 the rename was meant to fix. `publish-cli` needs both build jobs to succeed first, so it has still never run — the second wrong name would have surfaced exactly the way the first did. The new name is the repository owner's personal scope, which needs no organisation to exist first. Nothing was ever published under either old name, so no installed package changes and the binary is still `deepcode`. Guard added: every `npm i -g …` in a current document or in CLI source must name the package `apps/cli/package.json` publishes, and the published name may not be either of the two that belong to somebody else. Both renames so far moved some install strings and left others behind. Also rewrites `apps/cli/README.md`, which ships in the package `files` list and is therefore the npm landing page. It described the CLI as an "M0 骨架,命令 入口存在但不能用" and pointed at milestone numbers for when features would arrive. Historical snapshots keep the names that were true when they were written. Co-Authored-By: Claude Opus 5 --- .github/workflows/release.yml | 2 +- CHANGELOG.md | 16 +++++ CONTRIBUTING.md | 2 +- README.md | 4 +- apps/cli/README.md | 44 +++++++++---- apps/cli/package.json | 2 +- apps/cli/src/cli.ts | 2 +- apps/cli/src/commands.ts | 2 +- apps/cli/src/parity-commands.test.ts | 2 +- docs/MIGRATION_FROM_CLAUDE_CODE.md | 2 +- docs/RELEASING.md | 24 +++++-- docs/cli-flags.md | 2 +- docs/quickstart.md | 2 +- .../core/src/config/claude-compat.test.ts | 2 +- scripts/install-cron-daemon.sh | 2 +- scripts/package-name-consistency.test.ts | 66 +++++++++++++++++++ 16 files changed, 146 insertions(+), 30 deletions(-) create mode 100644 scripts/package-name-consistency.test.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 275b596..7e0c309 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -114,7 +114,7 @@ jobs: # Publish CLI to npm # ---------------------------------------------------------------------- publish-cli: - name: Publish @deepcode/cli to npm + name: Publish @oratis/deepcode to npm # Avoid a partial release: do not publish npm until both installable # desktop/editor artifacts have built successfully. needs: [validate, build-vscode, build-mac] diff --git a/CHANGELOG.md b/CHANGELOG.md index 6104501..3db9501 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,6 +88,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 precise failure — a convention passed by word of mouth that had stopped being enforced. They now share `gitSpawnEnv`, and a check fails the build if a test spawns `git` without it. +- **The CLI is published as `@oratis/deepcode`.** 0.3.0 renamed it away from + `deepcode-cli` because that name belongs to an unrelated project — but + `@deepcode/cli` was not ours either. The leaf name is unpublished, which is + what made it look free; the `@deepcode` **scope** holds `@deepcode/tsc` and + `@deepcode/dcignore`, and npm rejects a publish into a scope you do not own. A + scope is not claimable by publishing into it, so `pnpm publish` would have + returned the same 403 the rename was meant to fix. The new name is the + repository owner's personal scope, which needs no organisation to exist first. + Nothing was ever published under either old name, so no installed package + changes. The binary is still `deepcode`. +- A test now asserts that every `npm i -g …` in a current document or in CLI + source names the package `apps/cli/package.json` publishes. Both renames so far + moved some install strings and left others behind. +- `apps/cli/README.md` — the npm landing page — still described the CLI as an + "M0 骨架,命令入口存在但不能用" and pointed at milestone numbers for when + features would arrive. It shipped in the package `files` list. ## [0.3.0] — 2026-08-08 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8a4a8bf..0577432 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,7 +20,7 @@ deepcode/ │ ├── core/ # @deepcode/core — 内核(无 UI 依赖) │ └── shared-ui/ # CLI 与桌面客户端共享类型 ├── apps/ -│ ├── cli/ # @deepcode/cli — npm 包,命令 `deepcode` +│ ├── cli/ # @oratis/deepcode — npm 包,命令 `deepcode` │ └── desktop/ # Mac 客户端(Tauri 2 + Rust + React) ├── docs/ │ ├── DEVELOPMENT_PLAN.md diff --git a/README.md b/README.md index 7bab230..9be3eba 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ DeepCode 让 DeepSeek 可以在本地代码库中执行读取、编辑、命令 ```bash # 1. 装 CLI -npm i -g @deepcode/cli +npm i -g @oratis/deepcode # 2. 设 DeepSeek key(首次启动会引导) deepcode @@ -83,7 +83,7 @@ packages/ core/ # @deepcode/core — agent loop, providers, tools, MCP, sandbox, hooks (UI-agnostic) shared-ui/ # @deepcode/shared-ui — types shared between CLI + Mac client + VS Code apps/ - cli/ # @deepcode/cli — Node.js CLI (npm publishable) + cli/ # @oratis/deepcode — Node.js CLI (npm publishable) desktop/ # @deepcode/desktop — Tauri 2 + React Mac client vscode/ # deepcode — VS Code extension (app-server protocol client) lsp/ # @deepcode/lsp — LSP bridge for Neovim/Emacs/Sublime (v1.1) diff --git a/apps/cli/README.md b/apps/cli/README.md index 9138435..5389449 100644 --- a/apps/cli/README.md +++ b/apps/cli/README.md @@ -1,21 +1,43 @@ -# @deepcode/cli +# @oratis/deepcode -DeepCode CLI — Node.js 一键安装的命令行 AI 编程代理。 +DeepCode CLI —— DeepSeek 驱动的命令行编程 agent,在真实代码库里读文件、改代码、跑命令、 +审阅 diff,并把每一次改动记账。 -## Install (post-v1) +## 安装 ```bash -npm i -g @deepcode/cli +npm i -g @oratis/deepcode deepcode --help ``` -## 当前状态 +需要 Node 22+。首次启动会引导填入 `DEEPSEEK_API_KEY`。 -M0 骨架 — 命令入口存在但不能用。实际功能: +## 用法 -- onboarding + REPL:M2 -- 30+ slash commands:M2 -- 完整 17 个 CLI flags:M2-M5 -- headless `-p` 模式:M8 +```bash +deepcode # 交互式 REPL +deepcode -p "fix the bug in src/auth.ts" # headless 一次性 +deepcode --mode plan # 只读规划,出计划等批准 +deepcode --model deepseek-reasoner --effort high +``` + +工具、权限、沙箱、MCP、skills、plugins、hooks、后台任务与定时任务的完整说明见 +[quickstart](https://github.com/oratis/deepcode/blob/main/docs/quickstart.md) 与 +[CLI flags](https://github.com/oratis/deepcode/blob/main/docs/cli-flags.md)。 + +## 治理 + +- [`deepcode contract`](https://github.com/oratis/deepcode/blob/main/docs/file-contract.md) + —— 路径维度的读/写/执行契约,与既有权限规则按"最严者胜"合成,只能收紧。 +- [`deepcode ledger`](https://github.com/oratis/deepcode/blob/main/docs/change-ledger.md) + —— 变更账本:每次改动配上驱动它的请求和可回滚的检查点。 +- `deepcode doctor` —— 打印运行时能力声明与契约告警。 + +## 关于包名 + +二进制始终是 `deepcode`。npm 上的包名换过两次:`deepcode-cli` 与 `@deepcode/cli` +都属于无关的第三方项目,无法发布。当前且长期的名字是 **`@oratis/deepcode`**。 + +## License -详见 `docs/DEVELOPMENT_PLAN.md` §5 / §5a。 +MIT diff --git a/apps/cli/package.json b/apps/cli/package.json index 7e1c9f2..38caed1 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,5 +1,5 @@ { - "name": "@deepcode/cli", + "name": "@oratis/deepcode", "version": "0.3.0", "description": "DeepCode CLI — DeepSeek-powered AI coding agent for real codebases", "license": "MIT", diff --git a/apps/cli/src/cli.ts b/apps/cli/src/cli.ts index ac67d16..a10a3db 100644 --- a/apps/cli/src/cli.ts +++ b/apps/cli/src/cli.ts @@ -78,7 +78,7 @@ async function main(): Promise { return doctor(); } if (args.upgrade) { - process.stdout.write(`Run: npm i -g @deepcode/cli@latest\n`); + process.stdout.write(`Run: npm i -g @oratis/deepcode@latest\n`); process.stdout.write(`(The Mac client updates itself; only the CLI needs this.)\n`); return 0; } diff --git a/apps/cli/src/commands.ts b/apps/cli/src/commands.ts index 41bd3c8..f5e7e54 100644 --- a/apps/cli/src/commands.ts +++ b/apps/cli/src/commands.ts @@ -1151,7 +1151,7 @@ export const UpgradeCommand: SlashCommand = { run() { return [ `DeepCode CLI v${VERSION}`, - 'Update the CLI: npm i -g @deepcode/cli@latest', + 'Update the CLI: npm i -g @oratis/deepcode@latest', 'The macOS desktop app auto-updates via GitHub Releases.', ]; }, diff --git a/apps/cli/src/parity-commands.test.ts b/apps/cli/src/parity-commands.test.ts index f92ccc0..ce0ecd6 100644 --- a/apps/cli/src/parity-commands.test.ts +++ b/apps/cli/src/parity-commands.test.ts @@ -139,7 +139,7 @@ describe('/upgrade + /privacy-settings', () => { it('/upgrade shows the version + update instructions', async () => { const out = (await reg.match('/upgrade')!.cmd.run([], ctx())).join('\n'); expect(out).toMatch(/DeepCode CLI v\d/); - expect(out).toContain('npm i -g @deepcode/cli@latest'); + expect(out).toContain('npm i -g @oratis/deepcode@latest'); }); it('/privacy-settings shows data locations + the DeepSeek endpoint', async () => { diff --git a/docs/MIGRATION_FROM_CLAUDE_CODE.md b/docs/MIGRATION_FROM_CLAUDE_CODE.md index 670a5d3..41b3b5e 100644 --- a/docs/MIGRATION_FROM_CLAUDE_CODE.md +++ b/docs/MIGRATION_FROM_CLAUDE_CODE.md @@ -25,7 +25,7 @@ The copy below is only needed if you want DeepCode to own its own copies. ```bash # 1. Install DeepCode CLI -npm install -g @deepcode/cli +npm install -g @oratis/deepcode # 2. Set your DeepSeek key mkdir -p ~/.deepcode diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 281a6fc..c632c42 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -31,7 +31,7 @@ ignore red releases. A job that actually _fails_ still blocks the release. | `APPLE_TEAM_ID` | 10-character team ID (from developer.apple.com → membership) | | `CSC_LINK` | Base64-encoded `.p12` of the Developer ID Application cert | | `CSC_KEY_PASSWORD` | Password used when exporting the `.p12` | -| `NPM_TOKEN` | npm access token with `publish` scope for the `@deepcode` scope | +| `NPM_TOKEN` | npm access token able to publish into the `@oratis` scope | ### 2. Export the Developer ID certificate @@ -60,10 +60,22 @@ App-specific passwords → Generate. Save the 16-char password as [npmjs.com](https://www.npmjs.com) → account → Access Tokens → Generate new token → **Automation** (CI-friendly) → save as `NPM_TOKEN`. -The CLI publishes as **`@deepcode/cli`**. The unscoped `deepcode-cli` on npm -belongs to an unrelated project, so the `@deepcode` org must exist and the token -must be able to publish into it. The workflow already passes `--access public`, -which scoped packages need in order not to default to private. +The CLI publishes as **`@oratis/deepcode`** — the personal scope of the account +that owns the repository, so no organisation has to exist first. The token must +be issued by that account. The workflow already passes `--access public`, which +scoped packages need in order not to default to private. + +Two earlier names were dead ends, and both were only discovered by checking the +registry rather than by reading our own `package.json`: + +| Name | Owner | +| -------------- | ---------------------------------------------------------------- | +| `deepcode-cli` | an unrelated Doubao-based CLI (`guocong-bincai/deepcode-cli`) | +| `@deepcode/*` | an unrelated scope holding `@deepcode/tsc`, `@deepcode/dcignore` | + +`@deepcode/cli` itself is unpublished, which is what made it look available — +but npm rejects a publish into a scope you do not own regardless of whether the +individual name is free. A scope is not claimable by publishing into it. ## Releasing @@ -168,7 +180,7 @@ download manually; the "Relaunch to update" flow lights up once the feed exists. ## After a release -- Verify: `npm view @deepcode/cli@` shows the new version +- Verify: `npm view @oratis/deepcode@` shows the new version - Verify: `https://github.com/oratis/deepcode/releases/tag/v` has the DMG and version-matched VSIX attached - Optional: announce in the README / homepage diff --git a/docs/cli-flags.md b/docs/cli-flags.md index d8e80c1..9f62afc 100644 --- a/docs/cli-flags.md +++ b/docs/cli-flags.md @@ -20,7 +20,7 @@ deepcode upgrade # CLI self-update | `-h`, `--help` | Print usage | M2 ✅ | | `-v`, `--version` | Print version | M2 ✅ | | `doctor` | Health check (runtime, credentials, config provenance/trust/issues) | M2 ✅ | -| `upgrade` | Print `npm i -g @deepcode/cli@latest` hint | M2 ✅ | +| `upgrade` | Print `npm i -g @oratis/deepcode@latest` hint | M2 ✅ | | `-p`, `--print ` | Headless one-shot | M8 | ## Session shaping diff --git a/docs/quickstart.md b/docs/quickstart.md index ef339e6..e58fbe7 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -15,7 +15,7 @@ permission, and recovery behavior is still being unified; see the ```bash # 1. Install -npm i -g @deepcode/cli +npm i -g @oratis/deepcode # 2. Start the REPL — the first run walks you through setting your DeepSeek key deepcode diff --git a/packages/core/src/config/claude-compat.test.ts b/packages/core/src/config/claude-compat.test.ts index 8045d2c..d69765b 100644 --- a/packages/core/src/config/claude-compat.test.ts +++ b/packages/core/src/config/claude-compat.test.ts @@ -2,7 +2,7 @@ // // The migration guide asked people to `mv ~/.claude/... ~/.deepcode/...` before // DeepCode would see anything they had. That is five steps of grit in front of -// `npm i -g @deepcode/cli && deepcode`, and it is the first thing a migrating +// `npm i -g @oratis/deepcode && deepcode`, and it is the first thing a migrating // user hits. import { mkdir, mkdtemp, writeFile } from 'node:fs/promises'; diff --git a/scripts/install-cron-daemon.sh b/scripts/install-cron-daemon.sh index a92252e..5ef9bbe 100755 --- a/scripts/install-cron-daemon.sh +++ b/scripts/install-cron-daemon.sh @@ -37,7 +37,7 @@ if [[ -z "$BIN" ]]; then BIN="$ROOT/apps/cli/dist/index.js" else echo "ERROR: could not find a deepcode binary." - echo "Run 'pnpm --filter deepcode-cli build' or set DEEPCODE_BIN=/path/to/deepcode." + echo "Run 'pnpm --filter @oratis/deepcode build' or set DEEPCODE_BIN=/path/to/deepcode." exit 1 fi fi diff --git a/scripts/package-name-consistency.test.ts b/scripts/package-name-consistency.test.ts new file mode 100644 index 0000000..bdaa24f --- /dev/null +++ b/scripts/package-name-consistency.test.ts @@ -0,0 +1,66 @@ +// The name we tell people to install must be the name we publish. +// +// The CLI has been renamed twice, and both times some of the install strings +// moved while others stayed. A rename that touches `package.json` but not the +// `/upgrade` hint ships a working package alongside instructions for a package +// that belongs to somebody else — which is worse than either name alone. +// +// So: every `npm i -g ` in a current document or in CLI source must name +// the package `apps/cli/package.json` publishes. + +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { describe, expect, it } from 'vitest'; + +const root = resolve(import.meta.dirname, '..'); +const read = (path: string): string => readFileSync(resolve(root, path), 'utf8'); + +const publishedName = (): string => JSON.parse(read('apps/cli/package.json')).name as string; + +// Historical snapshots (MORNING_REPORT, DEVELOPMENT_PLAN, HANDOFF, +// BEHAVIOR_PARITY, release-artifacts) deliberately keep the names that were +// true when they were written, so they are not scanned. scripts/check-docs.mjs +// is what keeps them marked as historical. +const scanned = [ + 'README.md', + 'CONTRIBUTING.md', + 'apps/cli/README.md', + 'docs/quickstart.md', + 'docs/RELEASING.md', + 'docs/MIGRATION_FROM_CLAUDE_CODE.md', + 'docs/cli-flags.md', + 'apps/cli/src/cli.ts', + 'apps/cli/src/commands.ts', +]; + +const installCommand = /npm (?:i|install) -g\s+(@?[\w./-]+?)(?:@latest)?(?=[\s`'"\\]|$)/g; + +describe('published package name', () => { + it('is what every install instruction names', () => { + const expected = publishedName(); + const wrong: string[] = []; + + for (const path of scanned) { + const body = read(path); + for (const match of body.matchAll(installCommand)) { + if (match[1] !== expected) wrong.push(`${path}: ${match[0]}`); + } + } + + expect(wrong).toEqual([]); + }); + + it('is not one of the names that belong to somebody else', () => { + // Checked against the registry, not against availability of the leaf name: + // `@deepcode/cli` is unpublished, which is exactly why it looked free — but + // the `@deepcode` scope holds `@deepcode/tsc` and `@deepcode/dcignore`, and + // npm rejects a publish into a scope you do not own. `deepcode-cli` is an + // unrelated Doubao-based CLI. Both would 403 at `pnpm publish`. + expect(['deepcode-cli', '@deepcode/cli']).not.toContain(publishedName()); + }); + + it('is reached by a binary still called deepcode', () => { + const pkg = JSON.parse(read('apps/cli/package.json')) as { bin: Record }; + expect(Object.keys(pkg.bin)).toEqual(['deepcode']); + }); +}); From 0ba2d745af43136a8e15c77b090fa384c21a3be1 Mon Sep 17 00:00:00 2001 From: oratis Date: Sun, 9 Aug 2026 23:29:22 +0800 Subject: [PATCH 2/3] test(cli): scan for stale install strings by exclusion, not by allowlist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The guard listed nine files to check. That list has to be extended by whoever adds the next document, and it says nothing when they forget — the same silent-drift shape the check exists to catch. Not hypothetical: this rename hand-edited install strings in packages/core/src/config/claude-compat.test.ts and apps/cli/src/parity-commands.test.ts, and neither was on the list. A third rename would have left both behind exactly as the first two did. Now every .md/.ts/.tsx/.mjs/.sh in the repository is scanned except an explicit set of historical snapshots — the same set scripts/check-docs.mjs already marks as such, plus CHANGELOG entries for shipped releases. A second test pins the walker itself, so a skip rule that swallowed docs/ or apps/ cannot leave the check passing over nothing. Verified by pointing claude-compat.test.ts at the old name: the check names the file and fails. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 6 +- scripts/package-name-consistency.test.ts | 75 +++++++++++++++++------- 2 files changed, 60 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3db9501..2ff7e2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,7 +100,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 changes. The binary is still `deepcode`. - A test now asserts that every `npm i -g …` in a current document or in CLI source names the package `apps/cli/package.json` publishes. Both renames so far - moved some install strings and left others behind. + moved some install strings and left others behind. It scans the whole + repository minus an explicit list of historical snapshots, rather than an + allowlist of the documents somebody thought of — an allowlist has to be + extended by whoever adds the next document, and stays silent when they forget, + which is the same shape as the bug it is there to catch. - `apps/cli/README.md` — the npm landing page — still described the CLI as an "M0 骨架,命令入口存在但不能用" and pointed at milestone numbers for when features would arrive. It shipped in the package `files` list. diff --git a/scripts/package-name-consistency.test.ts b/scripts/package-name-consistency.test.ts index bdaa24f..ab02d7a 100644 --- a/scripts/package-name-consistency.test.ts +++ b/scripts/package-name-consistency.test.ts @@ -8,8 +8,8 @@ // So: every `npm i -g ` in a current document or in CLI source must name // the package `apps/cli/package.json` publishes. -import { readFileSync } from 'node:fs'; -import { resolve } from 'node:path'; +import { lstatSync, readdirSync, readFileSync } from 'node:fs'; +import { join, relative, resolve } from 'node:path'; import { describe, expect, it } from 'vitest'; const root = resolve(import.meta.dirname, '..'); @@ -17,21 +17,46 @@ const read = (path: string): string => readFileSync(resolve(root, path), 'utf8') const publishedName = (): string => JSON.parse(read('apps/cli/package.json')).name as string; -// Historical snapshots (MORNING_REPORT, DEVELOPMENT_PLAN, HANDOFF, -// BEHAVIOR_PARITY, release-artifacts) deliberately keep the names that were -// true when they were written, so they are not scanned. scripts/check-docs.mjs -// is what keeps them marked as historical. -const scanned = [ - 'README.md', - 'CONTRIBUTING.md', - 'apps/cli/README.md', - 'docs/quickstart.md', - 'docs/RELEASING.md', - 'docs/MIGRATION_FROM_CLAUDE_CODE.md', - 'docs/cli-flags.md', - 'apps/cli/src/cli.ts', - 'apps/cli/src/commands.ts', -]; +// Everything is scanned unless it is listed here, rather than nothing being +// scanned unless it is listed there. +// +// An allowlist has to be extended by whoever adds the next document, and the +// failure when they forget is silent — which is the same shape as the bug this +// file exists to catch. It is also not hypothetical: the rename this test +// shipped with had to hand-edit install strings in +// `packages/core/src/config/claude-compat.test.ts` and +// `apps/cli/src/parity-commands.test.ts`, neither of which an allowlist built +// from the documents anybody thought of would have contained. +// +// Historical snapshots deliberately keep the names that were true when they +// were written; `scripts/check-docs.mjs` is what keeps them marked as such, and +// this is the same list. CHANGELOG entries for shipped releases are history too. +const historical = new Set([ + 'CHANGELOG.md', + 'MORNING_REPORT.md', + 'docs/HANDOFF.md', + 'docs/BEHAVIOR_PARITY.md', + 'docs/DEVELOPMENT_PLAN.md', +]); +const skipDirs = new Set(['node_modules', 'dist', 'target', 'out', '.git', 'release-artifacts']); +const scannedExtensions = ['.md', '.ts', '.tsx', '.mjs', '.sh']; + +function scannedFiles(dir: string, found: string[] = []): string[] { + for (const entry of readdirSync(dir)) { + if (skipDirs.has(entry)) continue; + const path = join(dir, entry); + // lstat, not stat: a symlink into a skipped tree must not be followed back + // in, and a broken one must not throw. + const stats = lstatSync(path, { throwIfNoEntry: false }); + if (!stats) continue; + if (stats.isDirectory()) scannedFiles(path, found); + else if (scannedExtensions.some((ext) => entry.endsWith(ext))) { + const rel = relative(root, path); + if (!historical.has(rel)) found.push(rel); + } + } + return found; +} const installCommand = /npm (?:i|install) -g\s+(@?[\w./-]+?)(?:@latest)?(?=[\s`'"\\]|$)/g; @@ -40,9 +65,8 @@ describe('published package name', () => { const expected = publishedName(); const wrong: string[] = []; - for (const path of scanned) { - const body = read(path); - for (const match of body.matchAll(installCommand)) { + for (const path of scannedFiles(root)) { + for (const match of read(path).matchAll(installCommand)) { if (match[1] !== expected) wrong.push(`${path}: ${match[0]}`); } } @@ -50,6 +74,17 @@ describe('published package name', () => { expect(wrong).toEqual([]); }); + it('scans the files that actually carry install strings', () => { + // Guards the walker itself: a skip rule that quietly swallowed `docs/` or + // `apps/` would leave the check above passing over nothing at all. + const scanned = scannedFiles(root); + expect(scanned).toContain('README.md'); + expect(scanned).toContain('docs/quickstart.md'); + expect(scanned).toContain('apps/cli/src/cli.ts'); + expect(scanned).toContain('packages/core/src/config/claude-compat.test.ts'); + expect(scanned).not.toContain('docs/DEVELOPMENT_PLAN.md'); + }); + it('is not one of the names that belong to somebody else', () => { // Checked against the registry, not against availability of the leaf name: // `@deepcode/cli` is unpublished, which is exactly why it looked free — but From f0c9832a809fb0c4ca04284c6040051c5a9794a3 Mon Sep 17 00:00:00 2001 From: oratis Date: Sun, 9 Aug 2026 23:55:54 +0800 Subject: [PATCH 3/3] docs: note in the v0.3.0 report that @deepcode/cli was also not ours MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The widened guard caught this the moment #251 landed on main: §7.1 of the delivery report tells readers to run `npm i -g @deepcode/cli`, which would 403 for the same reason the name it replaced did. The report is a dated snapshot and §7.1 is a record of what was decided at the time, so the decision stays on the page and the correction is appended to it rather than rewritten over it. The file joins the historical set for the same reason the other snapshots are in it. That the check found this by itself, on a document nobody would have thought to add to an allowlist, is the argument for scanning by exclusion. Co-Authored-By: Claude Opus 5 --- docs/V0.3.0_REPORT.md | 6 ++++++ scripts/package-name-consistency.test.ts | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/docs/V0.3.0_REPORT.md b/docs/V0.3.0_REPORT.md index 67cf1b9..41673e9 100644 --- a/docs/V0.3.0_REPORT.md +++ b/docs/V0.3.0_REPORT.md @@ -188,6 +188,12 @@ committed 哈希能防住"nodejs.org 被攻陷",构建时抓 `SHASUMS256.txt` 历史快照文档(MORNING_REPORT / DEVELOPMENT_PLAN / HANDOFF / BEHAVIOR_PARITY)保留旧名, 它们记录的是写下时为真的事。 +> **后续更正(#253)**:`@deepcode/cli` 同样不是我们的。leaf name 未被发布过, +> 这正是它看起来可用的原因——但 `@deepcode` **scope** 下有 `@deepcode/tsc` 与 +> `@deepcode/dcignore`,npm 不允许发布进你不拥有的 scope,scope 也无法靠发布占用。 +> 本节记录的是当时的决定;实际发布名是 **`@oratis/deepcode`**,二进制仍为 `deepcode`。 +> 两个旧名都从未发布过,所以没有任何已安装的包受影响。 + > typecheck 抓到一个真问题:`/npm i -g @deepcode/cli@latest/` 里 scope 的 `/` 会提前终结正则字面量。 > 已改为 `toContain`。带 scope 的重命名撞上正则,正是全局替换会漏掉的那类。 diff --git a/scripts/package-name-consistency.test.ts b/scripts/package-name-consistency.test.ts index ab02d7a..1ed3721 100644 --- a/scripts/package-name-consistency.test.ts +++ b/scripts/package-name-consistency.test.ts @@ -37,6 +37,10 @@ const historical = new Set([ 'docs/HANDOFF.md', 'docs/BEHAVIOR_PARITY.md', 'docs/DEVELOPMENT_PLAN.md', + // A dated delivery report for one release. Its §7.1 records the rename that + // was decided at the time, and that record should keep saying what was + // decided — with a note pointing at the correction, which is what it has. + 'docs/V0.3.0_REPORT.md', ]); const skipDirs = new Set(['node_modules', 'dist', 'target', 'out', '.git', 'release-artifacts']); const scannedExtensions = ['.md', '.ts', '.tsx', '.mjs', '.sh'];