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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Version>10.0.5</Version>
<Version>10.0.6</Version>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<SatelliteResourceLanguages>false</SatelliteResourceLanguages>
Expand Down
2 changes: 1 addition & 1 deletion tools/BootstrapBlazor.LLMsDocs.Cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ bb-llms get Button --base-url ./wwwroot/llms

> 含义:官网发版后,最长 24 小时本地才会感知到文档更新(用 `--refresh` 可立即刷新)。

索引同样缓存。新增组件或扩展服务已发布但提示索引中不存在时,使用 `bb-llms get SelectCity --refresh` 同时刷新索引及目标文档。
索引同样缓存。`get` 在缓存索引中找不到名称时,会自动回源刷新一次索引后重试,便于发现新增组件或扩展服务;刷新后仍不存在才报错。索引已回源、指定了 `--refresh` / `--no-cache` 或使用本地目录时,不会重复刷新。已有文档内容仍遵循 24 小时缓存,可使用 `bb-llms get SelectCity --refresh` 立即刷新索引及目标文档。此自动刷新行为需要更新到包含该功能的 CLI 版本。

---

Expand Down
18 changes: 15 additions & 3 deletions tools/BootstrapBlazor.LLMsDocs.Cli/Services/LlmsDocsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,20 @@ public async Task<string> GetComponentAsync(string name, CancellationToken ct =
throw new ArgumentException("Component name is required.", nameof(name));
}

var entry = ParseIndex(await GetIndexAsync(ct))
var index = "";
var usedCachedIndex = _useCache && !_refresh && TryReadFreshCache("llms.txt", out index);
if (!usedCachedIndex)
{
index = await GetIndexAsync(ct);
Comment on lines +74 to +77

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Concurrent cache update skips retry

When another process writes a fresh index containing no requested component between the initial cache check and GetIndexAsync's cache check, GetIndexAsync can read a newly written cache after the initial check misses, while usedCachedIndex remains false; if that index lacks the requested name, GetComponentAsync skips the automatic refresh and reports the component as not found.

Use the cache result and its provenance from a single lookup, or ensure that a miss on the initial lookup forces the subsequent index fetch to bypass cache.

Prompt for AI agents
In `tools/BootstrapBlazor.LLMsDocs.Cli/Services/LlmsDocsClient.cs` at lines 74-77:

**Concurrent cache update skips retry**

When another process writes a fresh index containing no requested component between the initial cache check and `GetIndexAsync`'s cache check, `GetIndexAsync` can read a newly written cache after the initial check misses, while `usedCachedIndex` remains false; if that index lacks the requested name, `GetComponentAsync` skips the automatic refresh and reports the component as not found.

Use the cache result and its provenance from a single lookup, or ensure that a miss on the initial lookup forces the subsequent index fetch to bypass cache.

}

var entry = ParseIndex(index)
.FirstOrDefault(e => string.Equals(e.Name, name, StringComparison.OrdinalIgnoreCase));
if (entry is null && usedCachedIndex)
{
entry = ParseIndex(await FetchAsync("llms.txt", ct, refresh: true))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Stale indexes stay fresh

When concurrent refreshes fetch different index revisions, WriteContent can overwrite a newer llms.txt body or pair an older body with newer metadata; a later 304 then treats that stale body as fresh for 24 hours, so get misses newly published components.

Serialize refreshes for each shared index and commit its body and metadata together, so concurrent responses cannot overwrite or mix revisions.

Prompt for AI agents
In `tools/BootstrapBlazor.LLMsDocs.Cli/Services/LlmsDocsClient.cs` at line 84:

**Stale indexes stay fresh**

When concurrent refreshes fetch different index revisions, `WriteContent` can overwrite a newer `llms.txt` body or pair an older body with newer metadata; a later 304 then treats that stale body as fresh for 24 hours, so `get` misses newly published components.

Serialize refreshes for each shared index and commit its body and metadata together, so concurrent responses cannot overwrite or mix revisions.

.FirstOrDefault(e => string.Equals(e.Name, name, StringComparison.OrdinalIgnoreCase));
}
if (entry is null)
{
throw new FileNotFoundException($"Component not found in index: {name}");
Expand Down Expand Up @@ -112,7 +124,7 @@ public async Task<IReadOnlyList<ComponentEntry>> SearchAsync(string query, int l

// ---- core fetch -------------------------------------------------------

private async Task<string> FetchAsync(string relativePath, CancellationToken ct)
private async Task<string> FetchAsync(string relativePath, CancellationToken ct, bool refresh = false)
{
if (!_isRemote)
{
Expand All @@ -127,7 +139,7 @@ private async Task<string> FetchAsync(string relativePath, CancellationToken ct)

var url = new Uri(_baseUri!, relativePath);

if (_useCache && !_refresh && TryReadFreshCache(relativePath, out var fresh))
if (_useCache && !_refresh && !refresh && TryReadFreshCache(relativePath, out var fresh))
{
return fresh;
}
Expand Down
Loading