Skip to content

fix(import): stop same-named --dir checkouts overwriting each other, and keep AI components on --skip-enrich - #1440

Merged
SaulMoro merged 18 commits into
Tencent:mainfrom
hz89Dev:fix/import-dir-slug-collision
Oct 27, 2026
Merged

SaulMoro merged 18 commits into
Tencent:mainfrom
hz89Dev:fix/import-dir-slug-collision

Conversation

@hz89Dev

@hz89Dev hz89Dev commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

teamai import --dir /a/svc followed by --dir /b/svc wrote both codebases to teamwiki/evidence/code/svc/, so the second import erased the first, curated docs included. A daily import --skip-enrich --incremental also replaced the AI-written _manifest.json with the static fallback. Now a checkout with an origin gets the slug --from-repo and ci extract-mr already use, an import never replaces evidence that names another source, and --skip-enrich keeps the AI component entries.

 import --dir <dir>
-  slug = repo name (checkout root) | directory name
+  slug = <provider>__<owner>__<repo>   when dir is a checkout root with an origin
+       | repo name | directory name    otherwise
+  evidence/code/<old name>/ names the same repo → move it to <slug>/ (warn if <slug>/ exists)
+  evidence/code/<old name>/ names no repository → leave it, warn
+  <old name>/ holds no evidence, roles/projects list it under resources.wiki → warn which entries to change

 importCodebase (every import source)
+  published source-manifest.json names another repo, no remote, or another subdirectory
+    → refuse: "teamwiki/evidence/code/<slug>/ holds the evidence of …. Not replacing it."
   same source → carry docs/ and _domains.json
+               and, under --skip-enrich, the published component entries and edges

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature causing existing behavior to change)
  • Documentation only
  • Refactor / internal cleanup

The --dir slug for a checkout with an origin changes (svc becomes github__acme__svc), and an import over another source's evidence now fails instead of replacing it.

Test Plan

  • npx tsc --noEmit passes
  • npm run lint passes
  • npx vitest run passes: 16623 passed. 7 timeouts in workflow-local-driver, workflow-reconciliation and workflow-settle-cost, which this change does not touch, failed locally; those three files pass 153/153 when run on their own. CI is the check for those.
  • Added/updated tests for the change

New tests, each seen failing before its fix:

  • anchors.test.ts: two checkouts named svc with different origins get github__acme__svc and github__other__svc. A subdirectory keeps its own name.
  • import-codebase.test.ts:
    • refuses an origin-less checkout over a remote repo's evidence, another repo under the same slug, and a same-named subdirectory of the same repo
    • treats SSH and HTTPS clones of one repo as the same source
    • moves directory-named evidence to the remote slug, or warns when the remote slug already has evidence
    • leaves directory-named evidence that names no repository in place, with a warning: published before the per-codebase source manifest, or before the checkout had an origin
    • keeps AI components on --skip-enrich, with the docPath of the slug they are published under
  • import-dir.test.ts: import --dir names the role and project whose resources.wiki still lists the old slug, and leaves that entry alone while the old slug still holds another repo's evidence.

Two existing tests now expect the refusal, because those imports used to replace another source's evidence:

#1427's other origin-less tests pass unchanged on publishedSource.

Real CLI. npm run build, then the built dist/cli.js with an isolated HOME, --output, local git fixtures and a stub claude on PATH for AI enrichment. Same script against a build of main (703d1c9) and of this branch (d0323e8):

Scenario main this branch
--dir /a/svc (acme), then --dir /b/svc (other) one svc/, A's curated file gone github__acme__svc/ and github__other__svc/, A's file kept
Evidence published as svc/ by main, then --dir /a/svc stays svc/ Moved teamwiki/evidence/code/svc/ to teamwiki/evidence/code/github__acme__svc/, curated doc kept
Origin-less /c/svc over svc/ that acme/svc published replaced, repoUrl becomes undefined exit 1, teamwiki/evidence/code/svc/ holds the evidence of git@github.com:acme/svc.git, and this checkout has no origin remote. Not replacing it. …, repoUrl unchanged
AI import, then --skip-enrich --incremental after one change auth: category: component, EXTRACTED, no responsibilities auth: category: service, INFERRED, responsibilities: ["Signs users in"]
Published over HTTPS, re-imported from an SSH clone (not run on main) same slug, curated doc kept
--dir repo/apps/api, then --dir repo/libs/api (not run on main) exit 1, … holds the evidence of apps/api in git@github.com:acme/svc.git, and this checkout is libs/api in git@github.com:acme/svc.git. Not replacing it. …
AI import published as svc/ by main, then --skip-enrich (6be073d) (not run on main) Moved … svc/ to … github__acme__svc/, auth stays INFERRED with docPath: evidence/code/github__acme__svc/auth.md
Origin-less /a/svc re-imported after a change (cad9e39) (not run on main) svc/, curated doc kept
Then origin-less /b/svc (cad9e39) (not run on main) exit 0, svc/ replaced, curated doc not carried

| svc/ published by main with its source-manifest.json removed (as 0.27.0 writes it), then the checkout with an origin (ad4842e) | (not run on main) | github__acme__svc/ beside svc/, warning teamwiki/evidence/code/svc/ names no repository, so it was left in place. … |
| svc/ published by the origin-less checkout, then its origin added (ad4842e) | (not run on main) | same warning, svc/ kept |
| Origin removed from a checkout whose repo published svc/, then added back (ad4842e) | (not run on main) | exit 1, … has no origin remote. Not replacing it. If this checkout is that repository, add its origin remote back and import it again; …; after adding it back, Moved … svc/ to … github__acme__svc/ |

The first six rows were run again on ad4842e with identical output, apart from the new last sentence of the "no origin remote" refusal.

The resources.wiki warning needs a team repo with roles, so it is covered by the import --dir test only, not the real-CLI run. Providers other than local git fixtures and agents other than the stub were not run locally.

Related Issues

Refs #1422: gap 2, and what #1417 left of gap 1. Gap 3 landed in #1427, gap 4 is #1425.

Notes for Reviewers

  • fix(import): recognise an origin-less --dir checkout by a hash of its git root #1427's localRootHash comparison moved from sameRepository into publishedSource. CodebaseSourceSchema now parses a manifest as a remote (repoUrl) or a local root (localRootHash). Two origin-less checkouts are the same source when the hash and the directory match. A differing hash stays unknown: that covers a checkout moved to another path, so the import goes through and only the curated docs are left behind. A different origin-less checkout with the same name therefore still replaces the evidence; nothing distinguishes it from a moved one.
  • manifestMatchesSource still compares scheme-sensitive identities, so switching a clone between HTTPS and SSH costs one full extraction before increments resume.
  • The refusal lives in importCodebase, so it also guards --from-repo and ci extract-mr. Their slugs come from the URL, so it fires there only when two hosts share an owner/repo path.
  • A team that also runs ci extract-mr already had both svc/ and github__acme__svc/. The next --dir import leaves svc/ in place and warns, so curated docs there are not overwritten.

@Smilewithoutfalling

Copy link
Copy Markdown
Contributor

Two heads-ups before either of us has to rebase. #1427 predates this PR and changes the same function.

1. sameRepository is the collision point. #1427 adds a branch inside it (origin-less → compare a hash of the git root) and rewrites the comment above it; this PR deletes the function, replaces it with publishedSource, and rewrites the call site. The second to land conflicts textually either way. There is also a behavioural contradiction, not only a textual one:

published evidence staged checkout this PR #1427 as pushed
names a remote origin-less different → refuse (your real-CLI row 3) replace the docs, asserted in replaces the docs when the published manifest names a remote

One of those two tests has to change once the order is decided. I have no attachment to mine: refusing is the stricter reading, and if this lands first I will rewrite that case.

2. Your note on where the origin-less comparison belongs is right, and the field is already in the file you read. CodebaseSourceSchema requires repoUrl: z.string().min(1), so an origin-less source-manifest.json fails safeParse, readCodebaseSource returns null, and the pair really does fall through to unknown — I checked that against the branch rather than the description. #1427 writes sha256(realpath(git root)) as localRootHash into that same per-codebase source-manifest.json, so after this lands the move into publishedSource is small: keep the extra key in the schema, and treat two origin-less manifests with equal localRootHash as same instead of unknown.

3. One correction to sameDir. For a --dir checkout that is the repo root, the manifest carries sourceSubdir: "", not an absent key — sourceRepositoryMetadata sets it to the empty relative path and manifestObject.sourceSubdir is written whenever it is not undefined. So published.sourceSubdir === undefined never fires there. That is harmless for the check as written (both sides are "" and compare equal), but if unknown is ever split further, absent and "" need to be treated as the same value.

4. sameDir is the check #1427 was missing. A review on #1427 found that service-a/svc and service-b/svc of one origin-less repo hash to the same root, so the root alone carried the first one's curated docs into the second. #1427 now compares sourceSubdir next to the hash — the same condition you already have, reached from the other side.

@hz89Dev

hz89Dev commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Agreed on all four. Whichever lands second takes the conflict, and I'm fine with either order.

  1. For "published names a remote, staged is origin-less", refusing is intended here: that is the gap 2 case where the old code replaced another repo's evidence (the third real-CLI row). If fix(import): recognise an origin-less --dir checkout by a hash of its git root #1427 lands first, I'll rebase, port its localRootHash branch into publishedSource, and keep the refusal for that row.
  2. One detail for the port: CodebaseSourceSchema is a plain z.object, so it strips keys it doesn't declare. localRootHash has to be added to the schema, and repoUrl has to become optional, or origin-less manifests still parse to null. With that in place, two origin-less manifests with equal localRootHash and equal sourceSubdir are same. I would leave differing hashes as unknown rather than different: moving a checkout also changes its root hash, and a plain move should not be refused.
  3. Right, a root checkout writes sourceSubdir: "", and the undefined branch only covers manifests from before that key existed. Absent and "" compare equal today only because both sides of a fresh import write the key. If unknown is split further, normalising absent to "" belongs in readCodebaseSource.
  4. Good, then both sides converge on the same repo + sourceSubdir condition.

@Smilewithoutfalling

Copy link
Copy Markdown
Contributor

Thanks, that all lands. Three answers so the port does not hit a surprise.

On (2) — the schema note is right, but it is yours to make, not mine. #1427 reads the manifest through readStoredSourceManifest (src/codebase-extract.ts:605), which is a bare JSON.parse plus a type assertion and no zod, so localRootHash is not stripped on that path. In publishedSource it will be: the field has to be declared in CodebaseSourceSchema and repoUrl has to become optional, or an origin-less manifest parses to null and the branch never runs. I would not have caught that from my side.

On (3) — I measured the move case on the built CLI before answering. Same checkout copied to another path under the same basename, then --incremental:

[extract] no matching source baseline, running full extraction
  Files: 3
localRootHash  bedb8b90…  ->  24cb91f7…   (= sha256(realpath(new path)))
docs/curated-by-ai.md: gone

So in #1427 a differing hash is different, not unknown — but there is no refusal path there, so the cost is one full run plus the replaced docs, not a rejected import. I kept different on purpose: a moved checkout is indistinguishable from another checkout that happens to share the basename, and only the second reading is the gap 2 bug you are fixing. It is now written up in the PR body as a deliberate trade-off rather than left implicit.

On (1) — agreed, the refusal is yours. If #1440 lands first I will rebase and let your publishedSource reading win on that row; my case 9 goes. Right now #1427 is still waiting on SaulMoro re-reviewing ee188f88, so no order is blocked from my side.

@Walternater

Copy link
Copy Markdown
Contributor

I independently ran this head (built CLI, --dir + --output, isolated dirs) to make the review decision easier.

Verified

  • Two same-basename checkouts with different origins (/a/svc → github.com/acme/svc, /b/svc → github.com/other/svc) now land in github__acme__svc and github__other__svc; the first repo's overview.md and curated docs survive the second import.
  • A real auth/ + billing/ source, imported with --skip-enrich: the published AI metadata for auth (including responsibilities) survives the next --skip-enrich --incremental; billing/ is deleted, its docs/billing.md is pruned, and docs/auth.md stays.
  • tsc and 51 targeted tests (import-codebase, import-dir, anchors) pass on this head; CI is green and the PR is MERGEABLE.

Remaining split, not a blocker here

Two origin-less checkouts with the same basename still replace each other, because there is no source identity to compare. That is the #1427 gap (localRootHash). So I would land this one first: it is broader (gap 1 + gap 2 with an origin), green, and #1427 can then rebase its localRootHash branch into publishedSource as you two already agreed. In that rebase, the published names a remote / staged is origin-less row should become a refusal, not the current “replace the docs” assertion.

@SaulMoro @jeff-r2026 could one of you take the review? The only remaining gate is review required; there is no conflict with main.

@github-actions

Copy link
Copy Markdown
  • [P1 blocking] Preserve the new docPath when merging components — src/import-codebase.ts:76. After an AI-enriched import under svc, importing with --skip-enrich migrates that directory to github__acme__svc, but { ...previous, entrypoints: ... } retains paths such as evidence/code/svc/auth.md. The old directory no longer exists, so the published manifest points to missing pages. Keep the staged component’s docPath alongside its entrypoints.

The PR description documents sufficient testing: a build followed by representative real-CLI runs, with outcomes and deferred coverage stated. No testing-record finding. No PR code was run, built, or installed during this review.

@hz89Dev
hz89Dev force-pushed the fix/import-dir-slug-collision branch from d0323e8 to 6be073d Compare October 27, 2026 03:42
@hz89Dev

hz89Dev commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Fixed in 6be073d. keepEnrichedComponents now takes docPath from the fresh extraction along with entrypoints, so a kept component carries the slug it is published under. A new test in import-codebase.test.ts covers this: an AI import as svc, then --skip-enrich as github__acme__svc with legacySlug: 'svc'. It failed on the old merge with evidence/code/svc/auth.md.

On impact: nothing in src/ reads docPath, and the page an AI import writes is docs/<component>.md or modules/<component>.md, not <slug>/<component>.md. So the stale value was an inconsistent manifest field rather than a broken link in a compiled page. I checked it on the built CLI as well: auth is now INFERRED with docPath: evidence/code/github__acme__svc/auth.md, and that row is in the PR body.

@github-actions

Copy link
Copy Markdown

No new findings in the specified PR diff.

  • Resolved: The earlier docPath finding is fixed: preserved components now use the staged docPath and entrypoints, with a regression test covering slug migration.
  • Testing: The PR description documents a build and representative real-CLI verification, including the migration fix. Deferred provider/agent coverage is explicitly stated; no testing-record finding.

No PR code was run, built, or installed during this review.

@hz89Dev

hz89Dev commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

#1427 landed first, so I rebased and ported its comparison in cad9e39. CodebaseSourceSchema parses a manifest as a remote (repoUrl) or a local root (localRootHash), and publishedSource treats two origin-less checkouts as the same source when the hash and sourceSubdir match. A differing hash is unknown, which behaves like your false: the import goes through and the docs are not carried.

codebase-incremental-no-origin.test.ts passes unchanged apart from the row we discussed: "replaces the docs when the published manifest names a remote" now asserts the refusal and that the curated doc survives.

@github-actions

Copy link
Copy Markdown

No new findings in the specified PR changes.

  • Resolved: The earlier docPath issue is fixed: retained AI components use the staged docPath and entrypoints, with a slug-migration regression test.
  • Testing: The description documents a build and representative real-CLI verification, including runs on cad9e39b. Deferred provider/agent coverage is stated; no testing-record finding.

The shallow checkout prevented the three-dot command, so I recovered the shared parent from commit metadata and excluded unrelated base changes. No PR code was run, built, or installed.

@SaulMoro SaulMoro left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was generated by AI during triage.

@hz89Dev Thanks, and for porting #1427's root hash. Checked with the built CLI at cad9e39 against main (37f6139):

  • /a/svc then /b/svc with different origins now give github__acme__svc/ and github__other__svc/, and A's curated doc stays. main keeps one svc/ and loses it.
  • svc/ published by main from the same repo moves once to github__acme__svc/, with docs and _domains.json, and no file in the teamwiki still names evidence/code/svc/. The next import runs incrementally, and the kept AI components point at the new docPath.
  • An origin-less checkout over remote-published evidence, and a checkout whose origin was removed, exit 1 with the refusal and leave svc/ unchanged.
  • --from-repo, an HTTPS clone and an SSH clone of the same repo land in one directory. Origin-less and subdirectory imports keep their old names.
  • --skip-enrich keeps the AI components (auth stays INFERRED with its responsibilities) and still prunes a deleted one. main drops them to the fallback.
  • The #1427 port holds: an origin-less checkout re-imported after a change runs incrementally and keeps its curated doc and localRootHash.

One change before this is approved:

  • [P1 blocking] Old directory-named evidence with no readable source identity is left beside the new slug with no message. src/import-codebase.ts:246-258 moves evidence/code/<legacySlug>/ only when publishedSource returns same. For unknown it says nothing. Two cases reach that branch:

    • A team upgrading from 0.27.0. 0.27.0 publishes evidence/code/svc/ with no per-codebase source-manifest.json, and its root manifest has no project or repoUrl. Importing the same checkout with an origin on this head gives evidence/code: github__acme__svc svc. The curated docs stay only in svc/, and router.md and index.md list both codebases. svc/ is never updated again, so recall keeps serving its stale facts next to the current ones.
    • An origin-less import whose checkout later gets an origin. The outcome is the same, and svc/ carries a localRootHash sidecar.

    unknown can't prove it's the same repo, so moving it would be wrong. A warning is enough:

    } else if (legacy?.kind === 'unknown') {
      log.warn(`teamwiki/evidence/code/${legacySlug}/ names no repository, so it was left in place. `
        + `If it is an older copy of teamwiki/evidence/code/${slug}/, move any curated docs you want to keep from it, then remove it.`);
    }

Non-blocking:

  • [P2 non-blocking] warnStaleWikiScopes was inserted between the /** Parse --max-files: … */ comment and parseMaxFiles (src/import.ts:83-84), so that comment now sits on the new function. Moving the new function above the comment fixes it.
  • [P3 nit] docs/guide/distribution.md:186 and its Chinese page still say wiki: [checkout] # codebase slug, evidence/code/checkout/. A checkout with an origin is now github__<owner>__checkout, and the new warning tells teams to change exactly that entry. "the slug teamai import printed as project:" would match skill-data/wiki/SKILL.md:306.
  • [P3 nit] For a checkout whose origin was removed, the "no origin remote" refusal suggests renaming the directory or removing the evidence. Adding the origin back keeps the curated docs, since the import then moves svc/, so it could come first.
  • [P3 nit] The body marks this breaking, but CHANGELOG.md has no [Unreleased] line for the slug change.
  • Two origin-less checkouts with the same name and different roots still replace each other, as the body says. That is the #1422 gap 2 repro when neither checkout has a remote, so the issue stays open for it.

CI review: we agree the docPath issue it raised is fixed and the testing record is sufficient. It did not consider the unidentified old evidence above.

Verified at cad9e39: build, tsc and lint pass; the 11 related unit files (169 tests, the 4 changed files also under a symlinked TMPDIR) and the 7 e2e files that call the changed functions (114 tests) pass; 11 of the changed tests fail on main. Not run here: the resources.wiki warning with a real team repo (the unit test covers it, and so does the body), ci extract-mr with the real CLI (it shares the slug function), and hosts other than github.com.

@jeff-r2026 This changes the team repo layout (evidence/code/<name>/ becomes <provider>__<owner>__<repo>/, with a move), so it is yours to approve once the item above is fixed.

Two --dir checkouts with the same basename shared one evidence directory,
so importing /b/svc replaced the evidence of /a/svc. A checkout root whose
origin names a repo now takes the slug --from-repo, --from-mr and
ci extract-mr give that repo.
A checkout named after its directory (no origin, or a subdirectory) can
still share a slug with another codebase. When the published
source-manifest.json names a repository and the checkout being imported
is a different one, or has no remote, stop with an actionable error
instead of replacing that codebase's evidence.
…mote slug

Before this change, --dir published a checkout with an origin under its
directory name. Now that it takes the remote's slug, its next import moves
that evidence, curated docs included, to the new slug instead of leaving a
duplicate codebase in the teamwiki. When the remote slug already has
evidence, the old copy is kept and a warning names it.
A daily static sync (import --skip-enrich --incremental) replaced the
published AI _manifest.json with the static fallback, losing each
component's category and responsibilities and the edges between them.
For the same repository, keep those entries for every component still
in the code, refresh their entrypoints, and give new components the
static entry.
They now share one slug, so comparing scheme-sensitive repo identities
refused an import that CI over HTTPS and a member's SSH clone both make.
…repo

--dir repo/apps/api and --dir repo/libs/api both take the slug api. Compare
the extracted directory as well as the repository before treating the
published evidence as this checkout's.
A role or project that lists a checkout's directory-named slug under
resources.wiki no longer matches once the evidence takes the remote's
slug, and recall then shares that codebase with everyone. Warn with the
entries to change.
A skipped-enrichment import that moves directory-named evidence to the
remote slug kept each component's old docPath, so the manifest pointed
at pages under the removed directory. Take the doc path from the fresh
extraction, as the entrypoints already are.
…n guarding its evidence

Tencent#1427 compares localRootHash in sameRepository, which this branch replaced
with publishedSource. Parse the published and staged sources as a remote
or a local root, and treat two origin-less checkouts as the same source
when the hash and the directory match. A differing hash stays unknown, so
a checkout moved to another path re-imports instead of being refused.

An origin-less checkout over evidence a remote repo published is now
refused rather than replacing it, as the other refusals are.
…tory

Evidence published before the per-codebase source manifest, or before the
checkout had an origin, cannot be shown to be this repo's, so it is not
moved to the remote slug. It used to stay beside the new slug with no
message, and recall kept serving its stale facts. Name it, so the team
can move the curated docs and remove it.
… checkout

A checkout whose origin was removed is refused over its own evidence.
Adding the origin back keeps the curated docs, since the import then
moves the directory-named evidence to the remote slug.
@hz89Dev
hz89Dev force-pushed the fix/import-dir-slug-collision branch from cad9e39 to ad4842e Compare October 27, 2026 09:30
@hz89Dev

hz89Dev commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

All five are addressed in ad4842e, rebased on current main.

  • [P1] Unidentified directory-named evidence: fixed in 6ae2b82. The legacy branch is now a switch over publishedSource. unknown leaves evidence/code/<legacySlug>/ in place and warns with your wording, same moves as before, and different stays silent because it is another repo's evidence. A test covers both cases: a manifest removed as 0.27.0 publishes it, and an origin added after an origin-less import. On the built CLI both print teamwiki/evidence/code/svc/ names no repository, so it was left in place. If it is an older copy of teamwiki/evidence/code/github__acme__svc/, … and keep svc/.
  • [P2] --max-files comment: moved back onto parseMaxFiles (e4cd32e).
  • [P3] distribution.md: both pages now show wiki: [github__acme__checkout] # the slug teamai import printed as project: (fb8b841).
  • [P3] Refusal for a removed origin: it now starts with "If this checkout is that repository, add its origin remote back and import it again; otherwise …" (09b023b). On the built CLI, adding the origin back then moves svc/ to github__acme__svc/.
  • [P3] CHANGELOG: added a Breaking Changes entry under [Unreleased] (ad4842e).

Agreed on the last point: this PR only refs #1422, so the issue stays open for two origin-less checkouts with the same name and different roots. The new runs are in the PR body table.

@github-actions

Copy link
Copy Markdown
  • [P2 non-blocking] Check legacy ownership before recommending a scope replacement — src/import.ts:469. If svc/ belongs to acme/svc and a role scopes it with resources.wiki: [svc], importing another checkout named svc from other/svc correctly leaves that evidence untouched, but this unconditional warning tells the role to replace its scope with github__other__svc. That points it at the wrong repository. Only recommend replacement when the legacy evidence belongs to the imported source.

Resolved: The earlier docPath finding is fixed; retained components use staged paths, with a slug-migration regression test.

Testing: The description documents sufficient build and representative real-CLI verification, including the current head. No testing-record finding.

No PR code was run, built, or installed.

… evidence

A checkout of other/svc next to evidence/code/svc/ from acme/svc leaves
that evidence in place, and a role scoping svc still means it. Telling
the role to switch to github__other__svc pointed it at the wrong repo.
@hz89Dev

hz89Dev commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Fixed in d9eea78. import --dir now names stale resources.wiki entries only once evidence/code/<old name>/ holds no evidence, meaning it moved to the new slug or never existed. While it is still there (another repo's, or an unidentified or older copy the import already warned about), a scope on the old name still points at that evidence, so no change is suggested. Once the team removes an older copy, the next import names the entries. A new import-dir test covers the case you describe: my-project/ from acme/my-project, a role scoping my-project, and an import from other/my-project. It failed before the change.

@github-actions

Copy link
Copy Markdown

No new findings in the specified PR diff.

  • Resolved: The earlier docPath issue is fixed: retained components use staged docPath and entrypoints, with a slug-migration regression test.
  • Resolved: The stale-scope warning now runs only when the legacy evidence directory is absent, avoiding the incorrect replacement advice when it still holds another repository’s evidence.
  • Testing: The description documents a build and representative real-CLI verification. Deferred provider/agent coverage is stated; no testing-record finding.

No PR code was run, built, or installed during this review.

@SaulMoro SaulMoro left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was generated by AI during triage.

@hz89Dev Thanks, everything from our last comment is resolved. With the built CLI at d9eea78:

  • Evidence published by 0.27.0, and an origin-less import whose checkout later got an origin, are now left in place with a warning, and the curated doc stays in svc/.
  • For a checkout whose origin was removed, the refusal suggests adding it back first. Doing that and importing again moves svc/ to github__acme__svc/ and keeps the curated doc.
  • In team mode, with a role scoping wiki: [svc]: importing another repo named svc gives github__other__svc/ and no resources.wiki warning, which was the CI review's point on ad4842e. Importing the same repo moves svc/ and tells the role to change its entry to github__acme__svc.
  • The --max-files comment is back on parseMaxFiles, both distribution.md pages show the new slug, and CHANGELOG.md has the breaking change.

No new findings. Two origin-less checkouts with the same name and different roots still replace each other, as the body says; #1422 stays open for that.

CI review: we agree its scope-warning point on ad4842e was real and is fixed here (the new test fails on the previous source), and that the current head has no new findings.

Verified at d9eea78: build, tsc and lint pass; the 11 related unit files (172 tests, the 4 changed files also under a symlinked TMPDIR) and 7 related e2e files (114 tests) pass. Some CI jobs were still running at the time of this comment. Not run here: hosts other than github.com and providers other than git.

@jeff-r2026 This is ready for your decision on the layout change, together with #1448.

@SaulMoro SaulMoro left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was generated by AI during triage.

@hz89Dev Approving, on top of our comment at this head. We also ran one team through a release change, on one shared wiki: 0.28.0, this PR, 0.28.0, this PR, 0.28.0. No curated content is lost: docs/curated.md moves to github__acme__svc/ once and stays there. When a teammate still on 0.28.0 imports after the move, svc/ comes back and the codebase is listed twice in router.md and index.md until everyone upgrades. This PR's build reports that on every run, and it never moves or deletes anything again. Two runs of this PR's build in a row on a fresh wiki leave the same directories.

[P3 nit] While a teammate is still on an older release, the svc/ the warning calls "an older copy" is the one they keep rewriting, so removing it lasts only until their next import. The warning, or the CHANGELOG line, could say that teammates on an older release recreate it until they upgrade.

@jeff-r2026 Breaking for the next tag: import --dir publishes a checkout with an origin under <provider>__<owner>__<repo> and moves its old directory-named evidence there; see CHANGELOG.

@SaulMoro
SaulMoro merged commit f43e671 into Tencent:main Oct 27, 2026
18 checks passed
Walternater pushed a commit to Walternater/teamai-cli that referenced this pull request Nov 2, 2026
…and keep AI components on --skip-enrich (Tencent#1440)

* fix(import): name a --dir checkout with an origin after its remote repo

Two --dir checkouts with the same basename shared one evidence directory,
so importing /b/svc replaced the evidence of /a/svc. A checkout root whose
origin names a repo now takes the slug --from-repo, --from-mr and
ci extract-mr give that repo.

* fix(import): refuse to replace evidence published from a different repo

A checkout named after its directory (no origin, or a subdirectory) can
still share a slug with another codebase. When the published
source-manifest.json names a repository and the checkout being imported
is a different one, or has no remote, stop with an actionable error
instead of replacing that codebase's evidence.

* fix(import): move a checkout's old directory-named evidence to its remote slug

Before this change, --dir published a checkout with an origin under its
directory name. Now that it takes the remote's slug, its next import moves
that evidence, curated docs included, to the new slug instead of leaving a
duplicate codebase in the teamwiki. When the remote slug already has
evidence, the old copy is kept and a warning names it.

* fix(import): keep AI-written components on a --skip-enrich import

A daily static sync (import --skip-enrich --incremental) replaced the
published AI _manifest.json with the static fallback, losing each
component's category and responsibilities and the edges between them.
For the same repository, keep those entries for every component still
in the code, refresh their entrypoints, and give new components the
static entry.

* docs(knowledge): say which slug import --dir uses

* fix(import): treat SSH and HTTPS clones of one repo as the same source

They now share one slug, so comparing scheme-sensitive repo identities
refused an import that CI over HTTPS and a member's SSH clone both make.

* fix(import): refuse to replace a same-named subdirectory of the same repo

--dir repo/apps/api and --dir repo/libs/api both take the slug api. Compare
the extracted directory as well as the repository before treating the
published evidence as this checkout's.

* refactor(codebase): resolve a checkout's anchors once per slug lookup

* fix(import): name roles and projects still scoping the old --dir slug

A role or project that lists a checkout's directory-named slug under
resources.wiki no longer matches once the evidence takes the remote's
slug, and recall then shares that codebase with everyone. Warn with the
entries to change.

* docs: describe the --dir slug and refusal precisely, in the wiki skill too

* fix(import): point kept AI components at the evidence they now live in

A skipped-enrichment import that moves directory-named evidence to the
remote slug kept each component's old docPath, so the manifest pointed
at pages under the removed directory. Take the doc path from the fresh
extraction, as the entrypoints already are.

* fix(import): recognise an origin-less checkout by its hashed root when guarding its evidence

Tencent#1427 compares localRootHash in sameRepository, which this branch replaced
with publishedSource. Parse the published and staged sources as a remote
or a local root, and treat two origin-less checkouts as the same source
when the hash and the directory match. A differing hash stays unknown, so
a checkout moved to another path re-imports instead of being refused.

An origin-less checkout over evidence a remote repo published is now
refused rather than replacing it, as the other refusals are.

* fix(import): warn about directory-named evidence that names no repository

Evidence published before the per-codebase source manifest, or before the
checkout had an origin, cannot be shown to be this repo's, so it is not
moved to the remote slug. It used to stay beside the new slug with no
message, and recall kept serving its stale facts. Name it, so the team
can move the curated docs and remove it.

* style(import): keep the --max-files comment on parseMaxFiles

* docs: scope a wiki entry by the slug teamai import prints

* fix(import): suggest adding the origin back before renaming a refused checkout

A checkout whose origin was removed is refused over its own evidence.
Adding the origin back keeps the curated docs, since the import then
moves the directory-named evidence to the remote slug.

* docs(changelog): note the --dir slug change for Tencent#1422

* fix(import): suggest a new wiki scope only when the old name holds no evidence

A checkout of other/svc next to evidence/code/svc/ from acme/svc leaves
that evidence in place, and a role scoping svc still means it. Telling
the role to switch to github__other__svc pointed it at the wrong repo.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants