From 1266eb16059574edc2b509d1600ba1ca7a4440be Mon Sep 17 00:00:00 2001 From: qiansc Date: Fri, 21 Aug 2026 18:00:34 +0800 Subject: [PATCH] fix(v0.6.10): normalize repository recovery identity --- CHANGELOG.md | 3 + bun.lock | 18 ++--- package.json | 2 +- .../context-workflow/provider.yaml | 2 +- packages/context-cli/package.json | 2 +- .../projectRepoSourceRecoveryV062.test.ts | 65 +++++++++++++++++++ .../src/project/repoSourceRecovery.ts | 34 +++++++--- packages/context/package.json | 2 +- packages/core/package.json | 2 +- packages/dev-cli/package.json | 2 +- packages/extract-go/package.json | 2 +- packages/extract-rush/package.json | 2 +- packages/extract-ts/package.json | 2 +- packages/extract/package.json | 2 +- packages/tui/package.json | 2 +- 15 files changed, 112 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7868bf3..93aa221 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to Context are documented here. ## Unreleased +## 0.6.10 - 2026-08-21 + +- Made repository recovery identity transport-independent by matching the full namespace/repository path while preserving pinned-commit and subpath validation. - Added Gate-owned managed fast paths that preserve ordinary inspection and resolution capabilities while skipping redundant HTML review surfaces and dialogue resources under current-conversation authority. - Added ordinary-mode guidance after workspace creation and source capture that explains the review/fully-managed trade-off before later approval rounds. diff --git a/bun.lock b/bun.lock index 51cb4b1..6c5d4a2 100644 --- a/bun.lock +++ b/bun.lock @@ -17,7 +17,7 @@ }, "packages/context": { "name": "@c4a/context", - "version": "0.6.9", + "version": "0.6.10", "dependencies": { "yaml": "^2.5.1", "zod": "^3.23.8", @@ -25,7 +25,7 @@ }, "packages/context-cli": { "name": "@c4a/context-cli", - "version": "0.6.9", + "version": "0.6.10", "bin": { "context": "dist/cli.js", }, @@ -56,7 +56,7 @@ }, "packages/core": { "name": "@c4a/core", - "version": "0.6.9", + "version": "0.6.10", "dependencies": { "picomatch": "^4.0.4", "yaml": "^2.4.5", @@ -68,7 +68,7 @@ }, "packages/dev-cli": { "name": "@c4a/dev-cli", - "version": "0.6.9", + "version": "0.6.10", "dependencies": { "@c4a/core": "workspace:*", "@c4a/tui": "workspace:*", @@ -79,7 +79,7 @@ }, "packages/extract": { "name": "@c4a/extract", - "version": "0.6.9", + "version": "0.6.10", "bin": { "c4a-extract-code": "./dist/bin/c4a-extract-code.js", }, @@ -91,7 +91,7 @@ }, "packages/extract-go": { "name": "@c4a/extract-go", - "version": "0.6.9", + "version": "0.6.10", "dependencies": { "web-tree-sitter": "^0.25.10", }, @@ -102,7 +102,7 @@ }, "packages/extract-rush": { "name": "@c4a/extract-rush", - "version": "0.6.9", + "version": "0.6.10", "dependencies": { "typescript": "^5.5.4", "yaml": "^2.5.1", @@ -110,7 +110,7 @@ }, "packages/extract-ts": { "name": "@c4a/extract-ts", - "version": "0.6.9", + "version": "0.6.10", "dependencies": { "typescript": "^5.5.4", "web-tree-sitter": "^0.20.8", @@ -121,7 +121,7 @@ }, "packages/tui": { "name": "@c4a/tui", - "version": "0.6.9", + "version": "0.6.10", "dependencies": { "ink": "^5.0.0", "react": "^18.3.1", diff --git a/package.json b/package.json index fe1b1f3..98b15ed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "context", - "version": "0.6.9", + "version": "0.6.10", "packageManager": "bun@1.3.9", "repository": { "type": "git", diff --git a/packages/context-cli/context-workflow/provider.yaml b/packages/context-cli/context-workflow/provider.yaml index 1a8ee06..ffa6168 100644 --- a/packages/context-cli/context-workflow/provider.yaml +++ b/packages/context-cli/context-workflow/provider.yaml @@ -1,6 +1,6 @@ schema: agent-graph.provider.v1 id: c4a/context -version: 0.6.9 +version: 0.6.10 name: Context workflow description: Internal work contract for Context knowledge workspaces. graphs: diff --git a/packages/context-cli/package.json b/packages/context-cli/package.json index fa278b8..cd8f39f 100644 --- a/packages/context-cli/package.json +++ b/packages/context-cli/package.json @@ -1,7 +1,7 @@ { "name": "@c4a/context-cli", "description": "Local runtime and Agent integration for traceable knowledge production", - "version": "0.6.9", + "version": "0.6.10", "type": "module", "license": "MIT", "engines": { diff --git a/packages/context-cli/src/__tests__/projectRepoSourceRecoveryV062.test.ts b/packages/context-cli/src/__tests__/projectRepoSourceRecoveryV062.test.ts index b8795d2..f877746 100644 --- a/packages/context-cli/src/__tests__/projectRepoSourceRecoveryV062.test.ts +++ b/packages/context-cli/src/__tests__/projectRepoSourceRecoveryV062.test.ts @@ -67,6 +67,38 @@ async function fixture(): Promise<{ } describe("repository source recovery", () => { + test("groups transport aliases by repository path and pinned commit", async () => { + const input = await fixture(); + await writeRepoRegistry(input.project, { + repos: [ + { + name: "20260813/alpha", + namespace: "20260813", + module: "alpha", + subpath: "packages/alpha", + git: { + remote: "git@source.example.com:example/product-monorepo.git", + ref: input.commit, + }, + }, + { + name: "20260813/beta", + namespace: "20260813", + module: "beta", + subpath: "packages/beta", + git: { + remote: "https://mirror.example.net/example/product-monorepo/", + ref: input.commit, + }, + }, + ], + }); + + const plan = await repositoryRecoveryPlan({ projectRoot: input.project }); + expect(plan.groups).toHaveLength(1); + expect(plan.groups[0]?.sources).toEqual(["20260813/alpha", "20260813/beta"]); + }); + test("groups logical modules and restores them from one explicit local checkout", async () => { const input = await fixture(); const plan = await repositoryRecoveryPlan({ projectRoot: input.project }); @@ -100,6 +132,39 @@ describe("repository source recovery", () => { expect(readyPlan.next_action).toBeNull(); }); + test("accepts a local checkout whose origin uses another transport host", async () => { + const input = await fixture(); + git(input.checkout, [ + "remote", + "set-url", + "origin", + "ssh://mirror-user@mirror.example.net:29418/example/product-monorepo.git", + ]); + + const result = await restoreRepositorySources({ + projectRoot: input.project, + payload: { + schema: "context.repository-source-recovery.v1", + repositories: [{ source: "20260813/alpha", mode: "local", path: input.checkout }], + }, + }); + + expect(result.restored[0]).toEqual(expect.objectContaining({ mode: "local", ready: true })); + }); + + test("rejects a local checkout with a different repository path", async () => { + const input = await fixture(); + git(input.checkout, ["remote", "set-url", "origin", "git@mirror.example.net:other/product-monorepo.git"]); + + await expect(restoreRepositorySources({ + projectRoot: input.project, + payload: { + schema: "context.repository-source-recovery.v1", + repositories: [{ source: "20260813/alpha", mode: "local", path: input.checkout }], + }, + })).rejects.toThrow("local repository origin does not match the registered source"); + }); + test("clones the pinned commit into the disposable repository cache", async () => { const input = await fixture(); const bare = join(input.root, "origin.git"); diff --git a/packages/context-cli/src/project/repoSourceRecovery.ts b/packages/context-cli/src/project/repoSourceRecovery.ts index 99dbf78..e010614 100644 --- a/packages/context-cli/src/project/repoSourceRecovery.ts +++ b/packages/context-cli/src/project/repoSourceRecovery.ts @@ -88,16 +88,26 @@ export function parseRepositoryRecoveryPayload(value: unknown): RepositoryRecove return { schema: RECOVERY_SCHEMA, repositories }; } -function canonicalRemote(value: string): string { +function trimRepositorySuffix(value: string): string { + return value.trim().replace(/^\/+|\/+$/gu, "").replace(/\.git$/iu, ""); +} + +function repositoryPathIdentity(value: string): string { const trimmed = value.trim().replace(/\/+$/u, "").replace(/\.git$/iu, ""); - const scp = /^(?:[^@]+@)?([^:]+):(.+)$/u.exec(trimmed); - if (scp !== null) return `${scp[1]?.toLowerCase()}/${scp[2]}`; - try { - const url = new URL(trimmed); - return `${url.hostname.toLowerCase()}${url.pathname}`.replace(/\/+$/u, "").replace(/\.git$/iu, ""); - } catch { - return trimmed; + const windowsPath = /^[a-z]:[\\/]/iu.test(trimmed); + const urlSyntax = /^[a-z][a-z0-9+.-]*:\/\//iu.test(trimmed); + if (!windowsPath && urlSyntax) { + try { + const url = new URL(trimmed); + if (url.hostname.length > 0) return trimRepositorySuffix(url.pathname); + return url.pathname.replace(/\/+$/u, "").replace(/\.git$/iu, ""); + } catch { + return trimmed; + } } + const scp = windowsPath ? null : /^(?:[^@/:]+@)?[^/:]+:(.+)$/u.exec(trimmed); + if (scp?.[1] !== undefined) return trimRepositorySuffix(scp[1]); + return trimmed; } function repositorySlug(remote: string): string { @@ -111,7 +121,7 @@ function groupId(source: Pick): string { } function groupKey(source: Pick): string { - return `${canonicalRemote(source.git.remote)}\u0000${source.git.ref.toLowerCase()}`; + return `${repositoryPathIdentity(source.git.remote)}\u0000${source.git.ref.toLowerCase()}`; } function suggestedCloneTarget(source: Pick): string { @@ -217,10 +227,14 @@ async function verifyCheckout(input: { subpaths: readonly string[]; }): Promise { const actualRemote = await git(input.checkout, ["remote", "get-url", "origin"]); - if (canonicalRemote(actualRemote) !== canonicalRemote(input.remote)) { + const expectedRepository = repositoryPathIdentity(input.remote); + const actualRepository = repositoryPathIdentity(actualRemote); + if (actualRepository !== expectedRepository) { throw userInputError("local repository origin does not match the registered source", { expected_remote: input.remote, actual_remote: actualRemote, + expected_repository: expectedRepository, + actual_repository: actualRepository, checkout: input.checkout, }); } diff --git a/packages/context/package.json b/packages/context/package.json index 75cacd2..d267678 100644 --- a/packages/context/package.json +++ b/packages/context/package.json @@ -1,7 +1,7 @@ { "name": "@c4a/context", "description": "Declarative SDK for Context knowledge sources, workflows, review, and package outputs", - "version": "0.6.9", + "version": "0.6.10", "type": "module", "license": "MIT", "engines": { diff --git a/packages/core/package.json b/packages/core/package.json index 30b16ff..4ef7508 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@c4a/core", "description": "Shared extraction types, schemas, and utilities for Context", - "version": "0.6.9", + "version": "0.6.10", "type": "module", "license": "MIT", "engines": { diff --git a/packages/dev-cli/package.json b/packages/dev-cli/package.json index 9b2b24f..ad96110 100644 --- a/packages/dev-cli/package.json +++ b/packages/dev-cli/package.json @@ -1,7 +1,7 @@ { "name": "@c4a/dev-cli", "description": "Developer menu for Context build, verification, link, and release preparation", - "version": "0.6.9", + "version": "0.6.10", "type": "module", "license": "MIT", "repository": { diff --git a/packages/extract-go/package.json b/packages/extract-go/package.json index 6d7028c..12b5f91 100644 --- a/packages/extract-go/package.json +++ b/packages/extract-go/package.json @@ -1,7 +1,7 @@ { "name": "@c4a/extract-go", "description": "Go extraction plugin and structural index for Context", - "version": "0.6.9", + "version": "0.6.10", "type": "module", "license": "MIT", "engines": { diff --git a/packages/extract-rush/package.json b/packages/extract-rush/package.json index a6aeffd..5a5ee22 100644 --- a/packages/extract-rush/package.json +++ b/packages/extract-rush/package.json @@ -1,7 +1,7 @@ { "name": "@c4a/extract-rush", "description": "Rush workspace structural index for Context", - "version": "0.6.9", + "version": "0.6.10", "type": "module", "license": "MIT", "engines": { diff --git a/packages/extract-ts/package.json b/packages/extract-ts/package.json index 6343cfb..e7b735a 100644 --- a/packages/extract-ts/package.json +++ b/packages/extract-ts/package.json @@ -1,7 +1,7 @@ { "name": "@c4a/extract-ts", "description": "TypeScript extraction plugin for the Context ExtractionResult v2 contract", - "version": "0.6.9", + "version": "0.6.10", "type": "module", "license": "MIT", "engines": { diff --git a/packages/extract/package.json b/packages/extract/package.json index 4b576ed..5e640d4 100644 --- a/packages/extract/package.json +++ b/packages/extract/package.json @@ -1,7 +1,7 @@ { "name": "@c4a/extract", "description": "Language-plugin framework and repository runner for Context code evidence", - "version": "0.6.9", + "version": "0.6.10", "type": "module", "license": "MIT", "engines": { diff --git a/packages/tui/package.json b/packages/tui/package.json index 45ef6ff..b0f4857 100644 --- a/packages/tui/package.json +++ b/packages/tui/package.json @@ -1,7 +1,7 @@ { "name": "@c4a/tui", "description": "Shared terminal UI components (Ink + React) for Context development tools", - "version": "0.6.9", + "version": "0.6.10", "type": "module", "license": "MIT", "repository": {