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
33 changes: 12 additions & 21 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,18 @@
`cjson` (v1.7.18), `nanopb` (v0.4.9.1), `lvgl` (v9.2.2), `tinyusb` (v0.18.0), and `unity` (v2.6.1).

### Fixed
- **`ebuild.lock` is read, not only written.** `_install_packages` created the `Lockfile` after resolution and only ever saved it, and nothing called `Lockfile.load()`, so the lock pinned nothing: an unpinned package resolved to the newest recipe on every machine and every run, and a stale `ebuild.lock` sat beside it claiming otherwise. The lock is now loaded before resolving and handed to `PackageResolver.resolve(requested, lockfile=...)`, which uses it as a pin for every package the request leaves open. Precedence is request, then lock, then newest. A locked version no recipe provides is an error (`ebuild.lock pins 'zlib' at v9.9.9 ...`), not a silent fallback, and a locked entry whose recorded URL or checksum no longer matches the recipe of that version is refused -- the version reproduces the name, the lock exists to reproduce the bytes. An explicit request outranks the lock and the lock is rewritten from the result, as before. `Lockfile.load()` drops malformed entries instead of failing later. A lock that is not valid YAML, or cannot be opened, is reported as a resolution error (`[error] ... ebuild.lock is not valid YAML: ...`) instead of a `yaml.scanner.ScannerError` traceback; the recorded `build` system is compared on read-back like `url` and `checksum` (`Lockfile.CHECKED_FIELDS` is the one list both sides use); and `docs/dependency-management.md` documents the file, its precedence, and how to upgrade. (`ebuild/packages/resolver.py`, `ebuild/packages/lockfile.py`, `ebuild/cli/commands.py`, `docs/dependency-management.md`)
- **CI on master runs to completion again.** ruff stopped the pipeline at its
first step on findings the merges had introduced (an F811 duplicate import,
W292, E402). `PackageRecipe.to_dict()` -- defined by #111, deleted by #112's
replay of the same file -- is restored; nine `test_index_sync` cases and mypy
had failed without it. It now emits `install_args`, which the original never
did, and hands back copies of its list fields rather than the live lists;
the index sync mapping carries `install_args` through to the cached recipe.
The vendored `core/eos/docs/three-way-alignment.md` is reverted to its pin
(the correction #109 made there is filed upstream as embeddedos-org/eos#149).
The OSSF Scorecard action moved to the ghcr.io-hosted release and is pinned
by commit. yamllint on the Windows legs: YAML is pinned to LF in
`.gitattributes`, so an existing Windows clone needs its files checked out
again once (`git rm --cached -r . && git reset --hard HEAD`, or a re-clone;
see CONTRIBUTING.md). On Python 3.10 and 3.11 `ebuild/plugins/__init__.py`
now type-checks: the `entry_points()` fallback is spelled out with a cast
instead of a `# type: ignore` naming the wrong error code.
(`ebuild/packages/recipe.py`, `ebuild/packages/index_sync.py`,
`ebuild/plugins/__init__.py`, `core/eos/docs/three-way-alignment.md`,
`.github/workflows/scorecard.yml`, `.gitattributes`, `.yamllint.yml`, and
the three lint-fixed test files.)
- **`ebuild analyze --llm` no longer reports success when the model call failed.**
`LLMClient` always probed `http://localhost:11434` for Ollama availability,
even when constructed with a different `base_url`; OpenAI-compatible URLs
that already ended in `/v1` were joined to `/v1/v1/chat/completions`;
`file://` (and other non-HTTP schemes) could reach `urllib.request.urlopen`;
an empty or failed response still made `ebuild analyze --llm` print
"LLM analysis complete". Availability now probes the configured host,
`/v1` is joined once, non-HTTP(S) endpoints are rejected, responses are
capped at 10 MB, error text redacts bearer tokens, and a failed call
records `llm_failed:<provider>` so the CLI warns and continues with the
rule-engine profile (`ebuild/eos_ai/llm_integration.py`,
`ebuild/eos_ai/eos_hw_analyzer.py`, `ebuild/cli/commands.py`).
- **`ebuild test` now finds Windows test binaries.** The Ninja edge for a
native `type: test` target already carried the platform suffix
(`_exe_suffix()` names it `<name>.exe` on Windows), but `ebuild test`
Expand Down
2 changes: 1 addition & 1 deletion MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ notices the obvious-looking alternative.

| Date | Decision | Reason | Rejected alternative |
|------|----------|--------|----------------------|
| — | None recorded yet. | — | — |
| 2026-09-13 | LLM endpoints accept `http` and `https`; package-index fetch stays HTTPS-only | Ollama's default listener is `http://localhost:11434`. Requiring HTTPS would break the documented local path. `file://` and other urllib schemes are rejected. | Reuse the index-sync `https://` allowlist for LLM URLs — rejected because it would disable stock Ollama. |

<!-- Example of the level of detail worth recording:
| 2026-03-14 | Queue writes in-process rather than via Redis | Deploy target has no
Expand Down
34 changes: 16 additions & 18 deletions TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ Status is one of: `todo`, `in-progress`, `blocked`, `review`, `done`.
| T-002 | Fix Windows Ninja test-target path parsing | backend | Maintenance | review | none |
| T-003 | `ebuild package` looks for the unsuffixed binary on Windows (`_build/app` rather than `_build/app.exe`) | backend | Maintenance | review | none |
| T-004 | `_report_footprint` (the flash/RAM report `ebuild build` prints) looks for the unsuffixed binary on Windows, and fails silently rather than logging why | backend | Maintenance | review | none |
| T-005 | Move `executable_output_path()` out of the Ninja-specific backend into a backend-neutral module (`ebuild/build/layout.py`), re-exported from `ninja_backend` for compatibility | backend | Maintenance | review | none |
| T-005 | Move `executable_output_path()` out of the Ninja-specific backend into a backend-neutral module (`ebuild/build/layout.py`), re-exported from `ninja_backend` for compatibility | backend | Maintenance | todo | none |
| T-006 | Make optional LLM analysis honest: probe configured Ollama URL, join OpenAI `/v1` once, reject non-HTTP(S), do not report `--llm` success on a failed call | backend | Maintenance | review | none |
| T-007 | Flash/RAM size regex requires `[mk]b` immediately before `flash`/`ram`, so `"2MB SPI flash"` yields `flash_size=0` and `generate_boot_yaml` silently defaults to 1 MB | backend | Maintenance | todo | none |
| T-008 | `IndexSyncManager.sync` calls `PackageRecipe.to_dict()`, which does not exist; `tests/unit/test_index_sync.py` currently fails 9 tests on that AttributeError. Unrelated to T-006. | backend | Maintenance | todo | none |

### Evidence (self-reported by implementer; pending independent review per `.ai/reviewer.md` — "if you implemented it, you do not approve it")

Expand Down Expand Up @@ -46,23 +49,18 @@ Status is one of: `todo`, `in-progress`, `blocked`, `review`, `done`.
process cwd's own `eos.yaml`/`board.yaml`, if any, cannot change what it
measures; confirmed to fail against the pre-fix lookup (no report
emitted) and pass against the fix.
- **T-005**: `executable_output_path()` now lives in
`ebuild/build/layout.py`; `ninja_backend` re-exports the same function for
compatibility, and CLI consumers/tests use the backend-neutral owner. The
focused suite (`tests/unit/test_package_efw.py`,
`tests/unit/test_golden_path_commands.py`, `tests/unit/test_footprint.py`,
and `tests/unit/test_ninja_backend.py`) reports **119 passed, 1 skipped**;
the compatibility test asserts both import paths are identical and the
Windows suffix path is exercised.
- **Suite result**: the earlier **560 passed, 6 skipped, exit code 0** record
predates the current branch and must not be used as its validation result.
On the current PR head, the full suite on Windows CPython 3.14 reports
**669 passed, 6 skipped, 9 failed**; all nine failures are in
`tests/unit/test_index_sync.py` because `PackageRecipe.to_dict` is missing.
The independent Linux review run selected a different platform-sensitive
set (**670 passed, 4 skipped, 10 failed**), including one build-directory
test. These failures reproduce outside T-005 and are not hidden by this
change.
- **T-006**: `LLMClient.is_available()` probes `self.base_url` rather than
hardcoded localhost; `_openai_chat_url()` joins `/v1` once; non-HTTP(S)
schemes never reach `urlopen`; a failed `analyze_with_llm` appends
`llm_failed:<provider>` and `ebuild analyze --llm` warns instead of
printing success. Covered by `tests/ebuild/test_llm_integration.py`
(**33 passed**). The `/v1` join test was confirmed to fail against the
pre-fix `f"{base}/v1/chat/completions"` (3 parametrized cases produced
`/v1/v1/chat/completions`) and pass against the fix. `llm_integration.py`
coverage **99.46%** on that file. Full `tests/ebuild` + `tests/unit`:
**699 passed, 3 skipped, 9 failed** — the 9 are pre-existing
`PackageRecipe.to_dict` errors in `index_sync.py`, recorded as T-008.


## Completed

Expand Down
21 changes: 15 additions & 6 deletions docs/ai-input-formats.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,17 +95,23 @@ ebuild analyze notes.txt # → Text + component DB
# Auto-detects Ollama (local) or uses OPENAI_API_KEY from environment
ebuild analyze "nRF52840 BLE sensor" --llm

# Explicit provider
ebuild analyze design.kicad_sch --llm-provider ollama --llm-model llama3
ebuild analyze design.kicad_sch --llm-provider openai --llm-model gpt-4o
# File input still needs --file; the first argument is a text description.
ebuild analyze --file design.kicad_sch --llm
```

`--llm-provider` and `--llm-model` are not CLI flags. Provider selection is
environment-based (and Ollama auto-detects on localhost):

**LLM Provider Auto-Detection:**
1. **Ollama** (local, free) — checks `http://localhost:11434`
1. **Ollama** (local, free) — probes the client's configured URL, default `http://localhost:11434`
2. **OpenAI** — uses `OPENAI_API_KEY` env var
3. **Custom** — uses `EOS_LLM_API_KEY` + `EOS_LLM_URL` + `EOS_LLM_MODEL` env vars
3. **Custom** — uses `EOS_LLM_API_KEY` + `EOS_LLM_URL` + `EOS_LLM_MODEL` env vars. `EOS_LLM_URL` may be the origin (`https://api.example.com`) or the v1 root (`https://api.example.com/v1`); both resolve to `/v1/chat/completions`. Only `http://` and `https://` URLs are accepted.
4. **None** — works without LLM (rule engine only)

A failed LLM call does **not** fail the command. The rule-engine profile is
kept, and the CLI warns that LLM analysis did not complete rather than
printing success.

---

## NOT Supported (Today)
Expand Down Expand Up @@ -212,7 +218,10 @@ The generated prompt asks the LLM for:
4. Pin assignments for detected peripherals
5. Recommended RTOS and rationale

**This is manual** — you paste the prompt into ChatGPT, Claude, or a local LLM. The LLM response is not automatically consumed by ebuild.
`ebuild analyze --llm` sends this prompt to the configured provider and
merges extra peripherals back into the profile. The same `llm_prompt.txt`
is still written under `--output-dir` so you can paste it into a model
yourself when `--llm` is off, or when the provider call fails.

---

Expand Down
6 changes: 6 additions & 0 deletions docs/eos_ai_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,3 +328,9 @@ for deeper analysis:
Supported LLMs (all optional):
- Local: Ollama, llama.cpp, LM Studio
- Cloud: OpenAI, Anthropic, Grok, Gemini

`ebuild analyze --llm` calls `LLMClient.auto()` (Ollama on localhost, then
`OPENAI_API_KEY`, then `EOS_LLM_API_KEY` + `EOS_LLM_URL`). A failed call
leaves the rule-engine profile in place and prints a warning; it does not
report LLM analysis as complete. The generated `llm_prompt.txt` remains
available for a manual paste.
Loading
Loading