diff --git a/.claude/rules/server-middleware.md b/.agents/rules/server-middleware.md similarity index 100% rename from .claude/rules/server-middleware.md rename to .agents/rules/server-middleware.md diff --git a/.claude/skills/incremental-build/SKILL.md b/.agents/skills/incremental-build/SKILL.md similarity index 100% rename from .claude/skills/incremental-build/SKILL.md rename to .agents/skills/incremental-build/SKILL.md diff --git a/.claude/skills/incremental-build/architecture.md b/.agents/skills/incremental-build/architecture.md similarity index 97% rename from .claude/skills/incremental-build/architecture.md rename to .agents/skills/incremental-build/architecture.md index 35837b61834..cc8305d9ef3 100644 --- a/.claude/skills/incremental-build/architecture.md +++ b/.agents/skills/incremental-build/architecture.md @@ -59,7 +59,7 @@ Use this table to locate source files. ALWAYS read the relevant source file befo | `ResourceRequestGraph` | `lib/build/cache/ResourceRequestGraph.js` | DAG of request sets with delta encoding and best-parent optimization | | `ResourceIndex` | `lib/build/cache/index/ResourceIndex.js` | Wrapper around hash trees with delta detection | | `HashTree` | `lib/build/cache/index/HashTree.js` | Directory-based Merkle tree for resource hashing | -| `TaskInputSet` | `lib/build/cache/index/TaskInputSet.js` | Flat set of a task's non-resource inputs (env vars, tracked TaskUtil reads), hashed into one signature folded into the task's project-component signature. A sibling of `HashTree` (shares the `version`/`toCacheObject`/`fromCache` conventions) but deliberately not a Merkle tree: inputs are few, unordered, and non-hierarchical. Persists only entry type/name; values are re-read on lookup. Exports `normalizeInputValue`, the shared record/lookup value normalizer | +| `TaskInputSet` | `lib/build/cache/index/TaskInputSet.js` | Flat set of a task's non-resource inputs (env vars, tracked TaskUtil reads), hashed into the input component of the stage signature. A sibling of `HashTree` (shares the `version`/`toCacheObject`/`fromCache` conventions) but deliberately not a Merkle tree: inputs are few, unordered, and non-hierarchical. Persists only entry type/name; values are re-read on lookup. Exports `normalizeInputValue`, the shared record/lookup value normalizer | | `SharedHashTree` | `lib/build/cache/index/SharedHashTree.js` | HashTree with structural sharing via TreeRegistry | | `TreeRegistry` | `lib/build/cache/index/TreeRegistry.js` | Batch update coordinator for shared trees | | `TreeNode` | `lib/build/cache/index/TreeNode.js` | Merkle tree node (resource or directory) | @@ -273,14 +273,24 @@ The cache uses content-based signatures at multiple levels: | **Stage signature** | An explicit four-component tuple `projectIndexSignature-dependencyIndexSignature-inputSignature-rootSignature`, joined by `-` (each component a SHA-256 hex digest, so the separator never occurs inside a component and the split is lossless). The four dimensions (project resources, dependency resources, non-resource **input signature**, root resources) are independent slots rather than folded into two, so a delta pairs a changed project or dependency signature with the current input and root signatures with no reverse mapping. The join/split primitives live in `cache/stageSignature.js`; `BuildStageCache.getStageSignatures()` composes the exact-match candidates. One stage per step for a step-based task, one per legacy task | `ProjectBuildCache.prepareStageExecutionAndValidateCache()`, `BuildStageCache.getStageSignatures()` | | **Result signature** | A four-component tuple `sourceSignature-combinedDependencySignature-aggregatedInputSignature-aggregatedRootSignature`. The dependency component is a cartesian product over per-stage candidate dependency signatures; the aggregated input and root signatures are single current values (constants across the product). Both the candidate list and the stored signature derive the per-stage dependency list from the single stage order (`#stageOrder`), so the store and lookup sides cover the same stages in the same order (a declared stage missing a signature throws rather than silently never matching). Grows by one factor per stage, bounded because only dependency-reading stages with a delta contribute a factor >1 | `ProjectBuildCache.#getResultStageSignature()` / `#getPossibleResultStageSignatures()` | +### Cache Validation + +Persistent-cache initialization restores the stage order and every stage's project, dependency, input, and root request metadata before result validation starts. The first validation therefore computes result-signature candidates from the current source and dependency indices, freshly resolved non-resource input values, and refreshed root indices. A matching result can be imported without executing its stages. A miss continues to per-stage lookup, where the same four stage-signature dimensions decide which stages can be restored. + +`ProjectBuildContext` and its `ProjectBuildCache` are reused across sequential runs. The cache must compare the current aggregated input signature with the aggregate used by the last successful validation or build before a fresh in-memory result bypasses `#findResultCache()`. The comparison must participate in the early `possiblyRequiresBuild()` decision as well as `ProjectBuildCache.validateCache()`, because `ProjectBuilder` can otherwise skip validation for a cache already marked fresh. + +### Cache Modes + +`Default` reads valid cache entries and rebuilds missing or invalid results. `ReadOnly` uses valid entries but does not persist new results. `Off` bypasses cache lookup and persistence. `Force` permits only a valid cached result and must fail before task execution whenever any result-signature dimension has no match. The Force check belongs at the common result-validation boundary so source, dependency, non-resource input, root-resource, and metadata failures follow the same rule. + ### Non-Resource Task Inputs A task's output can depend on inputs that are not resources: an environment variable, or a value read through the `TaskUtil` interface (`isRootProject()`, `getDependencies()`, a dependency's version via `getProject(name).getVersion()`, `getCustomConfiguration()`, framework getters). None of these feed the resource indices or the build signature, so without tracking, changing one between builds leaves a stale cached result being served. The canonical example: `generateLibraryManifest` embeds a dependency's version as the manifest `minVersion` via `getProject(depName).getVersion()`; removing or bumping that dependency must re-run the task even though no source resource changed. Tracking has a record side and a lookup side, mirroring the resource-request flow: -- **Record** (task executes): the TaskRunner hands the task a `MonitoredTaskUtil` instead of the raw `TaskUtil`. It is a Proxy that preserves the wrapped shape (a custom task's limited interface stays limited) and records every tracked read as `{type, name, value}`, normalizing the value via `normalizeInputValue`. `getProject(name)` returns a wrapped project whose tracked accessors record under the project's name; its `getReader()` result is wrapped in a `MonitoredReader` so the resources the task reads through it are recorded as resource requests, and, for the project being built, its `getRootReader()` result is wrapped too so reads of files outside the UI5 resource model are recorded as root requests (see the reader-monitoring note below), while untracked members (`getRootPath`, `getSpecVersion`, ...) pass straight through unrecorded. After the task, the TaskRunner drains `getInputRecording()` into `ProjectBuildCache.recordStageResult`, which builds a `TaskInputSet` and folds its signature into the task's project-component signature (`combineProjectAndInputSignature`). Only entry type/name are persisted (`stage_request_metadata` type `"input"`), never values. -- **Lookup** (later build): `BuildStageCache.getInputSignature(resolveValue)` recomputes the input signature, re-reading each recorded input's *current* value through `ProjectBuildContext.resolveInputValue(type, name)` (which reaches `process.env` and the current project graph). A value that differs from the one baked into the cached stage signature misses the cache and re-runs the task. Record and lookup normalize through the same `normalizeInputValue`, so equal values compare equal. +- **Record** (task executes): the TaskRunner hands the task a `MonitoredTaskUtil` instead of the raw `TaskUtil`. It is a Proxy that preserves the wrapped shape (a custom task's limited interface stays limited) and records every tracked read as `{type, name, value}`, normalizing the value via `normalizeInputValue`. `getProject(name)` returns a wrapped project whose tracked accessors record under the project's name; its `getReader()` result is wrapped in a `MonitoredReader` so the resources the task reads through it are recorded as resource requests, and, for the project being built, its `getRootReader()` result is wrapped too so reads of files outside the UI5 resource model are recorded as root requests (see the reader-monitoring note below), while untracked members (`getRootPath`, `getSpecVersion`, ...) pass straight through unrecorded. After the task, the TaskRunner drains `getInputRecording()` into `ProjectBuildCache.recordStageResult`. `BuildStageCache.recordRequests()` builds a `TaskInputSet` and stores its signature in the non-resource input slot of the explicit `[project, dependency, input, root]` stage-signature tuple. Only entry type/name are persisted (`stage_request_metadata` type `"input"`), never values. +- **Lookup** (later build): `BuildStageCache.getInputSignature(resolveValue)` recomputes the input signature, re-reading each recorded input's *current* value through `ProjectBuildContext.resolveInputValue(type, name)` (which reaches `process.env` and the current project graph). Project-level result lookup hashes the current per-stage input signatures into `aggregatedInputSignature`, while stage lookup uses each current input signature directly. A value that differs from the one baked into a cached signature misses that result or stage once validation reaches the corresponding lookup. Record and lookup normalize through the same `normalizeInputValue`, so equal values compare equal. Excluded from input-value tracking: mutations (`setTag`/`clearTag`, already captured as tag operations in the hash trees), constructors (`resourceFactory`), readers (`getReader`/`getRootReader`), side effects (`registerCleanupTask`), and the FS-path accessors (`getRootPath`/`getSourcePath`) whose absolute, machine-specific values would make cache entries non-portable. diff --git a/.claude/skills/incremental-build/performance-investigation.md b/.agents/skills/incremental-build/performance-investigation.md similarity index 100% rename from .claude/skills/incremental-build/performance-investigation.md rename to .agents/skills/incremental-build/performance-investigation.md diff --git a/.claude/skills/ui5-fs/SKILL.md b/.agents/skills/ui5-fs/SKILL.md similarity index 100% rename from .claude/skills/ui5-fs/SKILL.md rename to .agents/skills/ui5-fs/SKILL.md diff --git a/.claude/skills/ui5-fs/architecture.md b/.agents/skills/ui5-fs/architecture.md similarity index 100% rename from .claude/skills/ui5-fs/architecture.md rename to .agents/skills/ui5-fs/architecture.md diff --git a/.claude/skills b/.claude/skills new file mode 120000 index 00000000000..2b7a412b8fa --- /dev/null +++ b/.claude/skills @@ -0,0 +1 @@ +../.agents/skills \ No newline at end of file