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
15 changes: 15 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,18 @@ Usage notes:
- A feature should satisfy the current design when first introduced. Amend later corrections into that original commit instead of preserving `fix`, `fixup!`, cleanup, or compatibility commits. A deliberate cross-cutting refactor may remain separate when that history is meaningful.
- Keep packaging and deployment commits after the application behavior they package.
- During a history rewrite, validate the repository at each meaningful snapshot and remove temporary branches, handoff files, and TODO files when finished.
- For stacked pull requests, do not repeatedly rebase and push downstream branches while an upstream pull request is still under review. Use the wait time to inspect and fix downstream feedback locally without pushing. After the upstream pull request is merged, rebase the remaining stack in dependency order, validate every layer, then push each rewritten branch once.
- Do not push, force-push, or open a pull request without explicit user approval.

## Pull request review workflow

- Run proportional local verification, then manually review the complete diff before creating or updating a pull request. Fix every known issue and repeat both steps until they are clean.
- Treat a successful reviewer check as evidence that a bot finished, not evidence that its findings are clean. Before declaring a pull request review-complete, ready to merge, or merged safely:
- record the current `headRefOid` and confirm every requested review was generated for that exact commit;
- query GitHub review threads with thread-aware GraphQL data and inspect `isResolved` and `isOutdated`; flat comments and aggregate badges are insufficient;
- read the latest-head section of edited or cumulative bot summaries instead of relying on their top-level bug count, crossed-out history, or an earlier review result;
- fix every valid finding and request a new review for the new head, or record a concrete technical rationale for rejecting the finding and close the thread before merging.
- Do not merge with an unresolved actionable review thread or an unadjudicated latest-head finding, including an item labelled optional or informational. When a finding is intentionally rejected, make the capability or behavior contract explicit in code or the owning architecture documentation when ambiguity caused the finding.
- Open the pull request as a draft and request a GitHub Copilot review. Address valid feedback, then repeat local verification, manual review, and Copilot review until no known issue remains.
- Only after that loop is clean, mark the pull request ready for review. Treat the automatic CodeRabbit run triggered by the ready pull request as the code-review stage; do not start a duplicate CodeRabbit CLI review or manually request another CodeRabbit run.
- After each ready-state update, wait for both CodeRabbit and GitHub Copilot to finish and make all feedback available before fixing or pushing anything. Evaluate their findings together, fix every valid issue in one batch, revalidate, push once, and repeat the two-reviewer wait. Run the `autofix` skill only after both reviews have completed.
Expand All @@ -108,6 +115,8 @@ Usage notes:

- Make local verification proportional to the change. Start with the narrowest useful test and add direct probes for the behavior or boundary that changed.
- Local tests should catch likely mistakes quickly. Avoid expensive multi-platform builds, broad network calls, or full external end-to-end runs unless the risk justifies them or the user asks; exhaustive matrices belong in CI.
- On macOS, run Python tests and coverage in the configured native project environment. Do not use Docker as a substitute for native tests; use containers only when the changed behavior is container-specific or a configured hook owns that check.
- After a rebase or manual conflict resolution, run Ruff formatting before the full test pass, then rerun all repository hooks. Conflict markers can leave syntactically valid but unformatted combinations that should be caught locally rather than by CI or a reviewer.
- For container and workflow changes, verify observable behavior rather than relying on configuration inspection alone. Useful probes include the final process tree, runtime user, argument override behavior, filesystem contents, or an actual workflow log.
- Use mocks and dummy configuration for routine tests. Use real services only for an explicitly requested end-to-end test, and never expose private inputs in output.
- If a check cannot run because of the local environment, report the exact limitation and what remains unverified instead of installing an alternative stack.
Expand All @@ -116,6 +125,12 @@ Usage notes:

Before every commit, run the following checks. Do not commit if any check fails.

Run the repository-wide hooks first; they include Ruff check and Ruff format validation as well as the remaining static checks:

```bash
prek run --all-files
```

### Coverage

```bash
Expand Down
2 changes: 1 addition & 1 deletion docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ service 将最终解析得到的完整地点名作为 `location_scope.full_name`

`WeatherContextProvider` 以关注地区的经纬度为输入,返回统一的天气上下文快照,核心编排不依赖具体厂商响应结构。快照包含天气预报、可选生活指数、可选空气质量和可选花粉过敏原;独立 `AirQualityProvider` 只承担缺失空气质量时的补充。`AirQualitySnapshot` 用统一生效时间配合 observation/forecast 时间类型表达资料语义,渲染层据此分别标注“观测时间”或“预报时段”,避免把 QWeather 或 Open-Meteo 的目标日期预报描述成既成观测。

能力组合边界由 `capabilities.py` 的 `CapabilityProviderSet` 承担。天气、空气质量、过敏原、生活指数、预警和短时预报属于可独立声明的 capability;现有 QWeather/Open-Meteo 完整上下文 adapter 暂时挂在天气槽位,AQICN 挂在空气质量槽位。这样本地气象机构可以只实现预警或 nowcast,而不必伪装为完整天气 provider;后续能力 provider 不应为填充无关字段而发起额外请求。
能力组合边界由 `capabilities.py` 的 `CapabilityProviderSet` 承担。天气、空气质量、结构化过敏原、生活指数、预警和短时预报属于可独立声明的 capability;现有 QWeather/Open-Meteo 完整上下文 adapter 暂时挂在天气槽位,AQICN 挂在空气质量槽位。`ALLERGEN` 表示 adapter 能提供独立 `AllergenSnapshot`,不表示任意与过敏有关的文本;QWeather 类型 7 综合过敏指数仍属于 `LIFESTYLE`,即使它会标记文档包含过敏建议。这样本地气象机构可以只实现预警或 nowcast,而不必伪装为完整天气 provider;后续能力 provider 不应为填充无关字段而发起额外请求。
Comment thread
qodo-code-review[bot] marked this conversation as resolved.

`QWeatherProvider` 的常规预报读取实时空气质量、今明两日天气和当日生活指数;显式目标日期查询改用 3 日生活指数及 3 日空气质量预报,并按天气预报中的目标日期选择同一天的数据。它提供目标日期天气、温度、风、湿度、预期降水及运动、穿衣、旅游、舒适度和交通指数。空气质量请求失败不会丢弃已经有效的天气结果;常规预报把空气质量留空交给补充层,目标日期查询则保留缺失而不使用当前 AQICN 观测冒充预报。

Expand Down
2 changes: 2 additions & 0 deletions docs/notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ DeepSeek 是唯一保留旧环境变量别名的 LLM provider:`DEEPSEEK_MODEL`

天气能力先按可独立替换的领域能力建模,但现有 QWeather 和 Open-Meteo adapter 仍各用一次完整请求流程返回天气、空气质量、生活指数或过敏原的组合快照。`CapabilityProviderSet` 因此把完整 adapter 放在天气槽位,只把已有独立 adapter 的 AQICN 放在空气质量槽位;为了拆分类型而重复请求同一厂商会增加延迟、配额消耗和部分失败状态。fallback 的 `weather_metadata` 只声明所有候选 adapter 都支持的能力,避免当前实际选中某一路时 `supports()` 返回无法兑现的结果。若厂商提供可独立调用且有实际替换需求的预警、nowcast 或过敏原接口,应新增对应槽位和薄 adapter;若未来路由需要表达“至少一个候选支持”而不是共同保证,则应另建聚合元数据,不能改变当前 `supports()` 的语义。

`ALLERGEN` capability 只声明 adapter 能提供独立、结构化的 `AllergenSnapshot`,而不是泛指任何与过敏有关的用户文本。路由元数据需要表达可独立替换的数据契约,因此 QWeather 类型 7 综合过敏指数仍归入 `LIFESTYLE`;面向简报完整性的 `has_allergen_information` 可以同时为真,用来要求模型给出过敏建议。这一选择接受了“QWeather 有过敏建议但 `supports(ALLERGEN)` 为假”的表面差异,以避免把综合指数误当成具体花粉浓度或可替换的过敏原数据源。若未来路由需要查找“任意过敏建议”,应新增不同 capability,而不能扩宽 `ALLERGEN`;若 QWeather 提供结构化花粉种类和浓度,则应重新声明其支持 `ALLERGEN`。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. allergen contract duplicated in docs 📘 Rule violation ⚙ Maintainability

The ALLERGEN capability contract is described in both docs/design.md and docs/notes.md with
overlapping multi-sentence text, but docs/notes.md does not point to a canonical source. This
duplication can drift over time and violates the requirement to link to a single authoritative
document instead of repeating detailed contracts.
Agent Prompt
## Issue description
`docs/notes.md` repeats the `ALLERGEN` capability contract that is also documented in `docs/design.md`, without linking to a single canonical source.

## Issue Context
The repo now has two separate documents explaining the same contract details; this increases the likelihood of future divergence.

## Fix Focus Areas
- docs/notes.md[40-40]
- docs/design.md[51-51]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


`Article.id` 是带 Feed 身份的文章级稳定 ID,用于去重和模型引用;`Article.source_id` 是 Feed 配置 ID。同一内容出现在不同 Feed 时保留不同文章 ID,以维持来源隔离和可追溯性。只有在产品明确引入跨来源 canonical identity、并定义转载和更新版本的合并规则后,才应重新评估这一选择。

LLM schema 到 `BriefingResult` 的转换保持为领域模型之外的 `parse_result()`。领域 dataclass 不依赖 Pydantic 或 any-llm,provider 也不拥有来源 ID 归属规则。将转换放入任一对象都会让平台 SDK 或不可信输入校验进入错误的层级。
Expand Down
15 changes: 4 additions & 11 deletions weather_briefing/capabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,7 @@ async def _fetch_context(
longitude: float,
forecast_date: pendulum.Date | None,
) -> WeatherContextSnapshot:
"""Call providers that support either current or dated context."""
if forecast_date is None:
return await provider.fetch(latitude, longitude)
from .weather_context import DatedWeatherContextProvider, WeatherContextError

if not isinstance(provider, DatedWeatherContextProvider):
raise WeatherContextError(f"{type(provider).__name__} does not support target forecast dates")
fetch_for_date = provider.fetch_for_date
if not callable(fetch_for_date):
raise WeatherContextError(f"{type(provider).__name__} does not support target forecast dates")
return await fetch_for_date(latitude, longitude, forecast_date)
"""Route current or dated context through the shared provider boundary."""
from .weather_context import fetch_weather_context

return await fetch_weather_context(provider, latitude, longitude, forecast_date)
5 changes: 4 additions & 1 deletion weather_briefing/weather_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,10 @@ async def fetch_weather_context(
return await provider.fetch(latitude, longitude)
if not isinstance(provider, DatedWeatherContextProvider):
raise WeatherContextError(f"{type(provider).__name__} does not support target forecast dates")
return await provider.fetch_for_date(latitude, longitude, forecast_date)
fetch_for_date = provider.fetch_for_date
if not callable(fetch_for_date):
raise WeatherContextError(f"{type(provider).__name__} does not support target forecast dates")
return await fetch_for_date(latitude, longitude, forecast_date)


def _elapsed_milliseconds(started_at: float) -> int:
Expand Down