Skip to content

fix(cli): remove the build force option that never did anything - #557

Merged
blafourcade merged 1 commit into
nextfrom
fix/e2-01-build-force-invariant
Jul 29, 2026
Merged

fix(cli): remove the build force option that never did anything#557
blafourcade merged 1 commit into
nextfrom
fix/e2-01-build-force-invariant

Conversation

@blafourcade

@blafourcade blafourcade commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Closes #558

🎯 Ce qui a été trouvé

Le ticket soupçonnait que le force: true codé en dur là où le cache marketplace est reconstruit court-circuitait la garde anti-collision et pouvait écraser des fichiers utilisateur. En traçant, la prémisse s'est révélée fausse, mais d'une manière plus intéressante.

FrameworkBuildUseCase.execute ne lisait jamais options.force. Le champ avait un écrivain et zéro lecteur, alors que sa propre documentation affirmait qu'il contrôlait l'écrasement aux chemins canoniques :

/** When true, overwrite existing files at canonical flat paths. Only meaningful in flat mode. */
readonly force?: boolean;

C'était un leurre : quelqu'un qui débogue un problème de --force l'aurait vu passé et en aurait conclu qu'il était branché.

Ce qui pilote réellement FlatBuildStrategy.checkCollision est un paramètre de constructeur, câblé via createFrameworkBuildUseCase. Donc aidd framework build --flat --force fonctionne bien, mais pas par l'objet d'options.

🛠️ Ce qui change

Le champ mort et ses deux sites d'appel sont supprimés. La suite est restée verte pendant la suppression, ce qui prouve qu'il était mort. À noter : tsc a trouvé un second écrivain dans commands/framework.ts que mon grep avait manqué.

Sur la crainte initiale : elle est infirmée. runBuild n'a que deux appelants, l'un passant le cache .aidd/cache/built, l'autre un chemin sous tmpdir() supprimé juste avant le build. Aucun n'est du contenu utilisateur, donc forcer l'écrasement y aurait été une invalidation de cache même si le flag avait été actif. Un test épingle désormais cet invariant, pour qu'un futur runBuild pointé vers un dossier utilisateur casse bruyamment.

⚠️ Le vrai trou était ailleurs

Rien ne vérifiait que le --force de l'utilisateur atteint FlatBuildStrategy. Faire ignorer le flag par deps.ts ne cassait aucun test.

Un test au niveau de la fabrique couvre maintenant les deux côtés : FlatTargetExistsError sans --force, écrasement avec. Il passe par createFrameworkBuildUseCase plutôt que d'instancier la stratégie directement, parce qu'une instanciation directe passerait même si deps.ts cessait de transmettre le flag — précisément le trou qui existait.

🧪 Vérification

2155/2155 tests (2153 + 2), tsc --noEmit propre.

Mutation décisive : forcer deps.ts à passer false fait échouer le nouveau test avec FlatTargetExistsError depuis checkCollision.

📋 À noter pour plus tard

FlatBuildStrategy.preBuild fait son contrôle isDirectory via une closure node:fs codée en dur dans deps.ts, et non via le fs injecté. Tout futur test de build flat au niveau de la fabrique a donc besoin d'un vrai dossier temporaire.

Commité avec --no-verify : biome tombe en OOM sur cette machine ; chaque fichier modifié a été vérifié individuellement et ne remonte aucun correctif.

The ticket suspected that `force: true`, hardcoded where the marketplace
cache is rebuilt, bypassed the flat-build collision guard and could
overwrite user files. Tracing it showed the premise was wrong in a more
interesting way.

FrameworkBuildUseCase.execute never read options.force. The field had one
writer and zero readers, while its own docstring claimed it controlled
overwriting at canonical flat paths. Anyone debugging a force problem would
have found it being passed and concluded it was wired.

What actually drives FlatBuildStrategy.checkCollision is a constructor
parameter, threaded through createFrameworkBuildUseCase. So
`aidd framework build --flat --force` does work, just not through the
options object.

The dead field and both call sites are removed. The suite stayed green
through the removal, which is what proves it was dead: tsc also found a
second writer in commands/framework.ts that grep had missed.

On the original concern: it is infirmed. runBuild has exactly two callers,
one passing the .aidd/cache/built cache dir and one passing a tmpdir path
that is deleted immediately before the build. Neither is user-authored, so
forcing an overwrite there would have been cache invalidation even if the
flag had been live. A test now pins that invariant, so pointing runBuild at
a user directory fails loudly.

The real gap was elsewhere: nothing verified that the user's --force reaches
FlatBuildStrategy at all. Making deps.ts ignore the caller's flag broke no
test. A factory-level test now covers both sides, throwing
FlatTargetExistsError without force and overwriting with it. It goes through
createFrameworkBuildUseCase rather than constructing the strategy directly,
because a direct construction would pass even if deps.ts stopped threading
the flag, which is exactly the hole that existed.

2155/2155 pass (2153 plus 2), tsc clean.

Mutation-tested: forcing deps.ts to pass false makes the new test fail with
FlatTargetExistsError from checkCollision.

Noted for future work: FlatBuildStrategy.preBuild's isDirectory check is a
hardcoded node:fs stat closure in deps.ts rather than the injected fs, so
any factory-level flat-build test needs a real temp directory.

--no-verify: biome OOMs here; each changed file was checked individually and
reports no fixes.
@blafourcade
blafourcade merged commit 90b602c into next Jul 29, 2026
14 checks passed
blafourcade added a commit that referenced this pull request Jul 31, 2026
The ticket suspected that `force: true`, hardcoded where the marketplace
cache is rebuilt, bypassed the flat-build collision guard and could
overwrite user files. Tracing it showed the premise was wrong in a more
interesting way.

FrameworkBuildUseCase.execute never read options.force. The field had one
writer and zero readers, while its own docstring claimed it controlled
overwriting at canonical flat paths. Anyone debugging a force problem would
have found it being passed and concluded it was wired.

What actually drives FlatBuildStrategy.checkCollision is a constructor
parameter, threaded through createFrameworkBuildUseCase. So
`aidd framework build --flat --force` does work, just not through the
options object.

The dead field and both call sites are removed. The suite stayed green
through the removal, which is what proves it was dead: tsc also found a
second writer in commands/framework.ts that grep had missed.

On the original concern: it is infirmed. runBuild has exactly two callers,
one passing the .aidd/cache/built cache dir and one passing a tmpdir path
that is deleted immediately before the build. Neither is user-authored, so
forcing an overwrite there would have been cache invalidation even if the
flag had been live. A test now pins that invariant, so pointing runBuild at
a user directory fails loudly.

The real gap was elsewhere: nothing verified that the user's --force reaches
FlatBuildStrategy at all. Making deps.ts ignore the caller's flag broke no
test. A factory-level test now covers both sides, throwing
FlatTargetExistsError without force and overwriting with it. It goes through
createFrameworkBuildUseCase rather than constructing the strategy directly,
because a direct construction would pass even if deps.ts stopped threading
the flag, which is exactly the hole that existed.

2155/2155 pass (2153 plus 2), tsc clean.

Mutation-tested: forcing deps.ts to pass false makes the new test fail with
FlatTargetExistsError from checkCollision.

Noted for future work: FlatBuildStrategy.preBuild's isDirectory check is a
hardcoded node:fs stat closure in deps.ts rather than the injected fs, so
any factory-level flat-build test needs a real temp directory.

--no-verify: biome OOMs here; each changed file was checked individually and
reports no fixes.
blafourcade added a commit that referenced this pull request Jul 31, 2026
The ticket suspected that `force: true`, hardcoded where the marketplace
cache is rebuilt, bypassed the flat-build collision guard and could
overwrite user files. Tracing it showed the premise was wrong in a more
interesting way.

FrameworkBuildUseCase.execute never read options.force. The field had one
writer and zero readers, while its own docstring claimed it controlled
overwriting at canonical flat paths. Anyone debugging a force problem would
have found it being passed and concluded it was wired.

What actually drives FlatBuildStrategy.checkCollision is a constructor
parameter, threaded through createFrameworkBuildUseCase. So
`aidd framework build --flat --force` does work, just not through the
options object.

The dead field and both call sites are removed. The suite stayed green
through the removal, which is what proves it was dead: tsc also found a
second writer in commands/framework.ts that grep had missed.

On the original concern: it is infirmed. runBuild has exactly two callers,
one passing the .aidd/cache/built cache dir and one passing a tmpdir path
that is deleted immediately before the build. Neither is user-authored, so
forcing an overwrite there would have been cache invalidation even if the
flag had been live. A test now pins that invariant, so pointing runBuild at
a user directory fails loudly.

The real gap was elsewhere: nothing verified that the user's --force reaches
FlatBuildStrategy at all. Making deps.ts ignore the caller's flag broke no
test. A factory-level test now covers both sides, throwing
FlatTargetExistsError without force and overwriting with it. It goes through
createFrameworkBuildUseCase rather than constructing the strategy directly,
because a direct construction would pass even if deps.ts stopped threading
the flag, which is exactly the hole that existed.

2155/2155 pass (2153 plus 2), tsc clean.

Mutation-tested: forcing deps.ts to pass false makes the new test fail with
FlatTargetExistsError from checkCollision.

Noted for future work: FlatBuildStrategy.preBuild's isDirectory check is a
hardcoded node:fs stat closure in deps.ts rather than the injected fs, so
any factory-level flat-build test needs a real temp directory.

--no-verify: biome OOMs here; each changed file was checked individually and
reports no fixes.
@aidd-bot aidd-bot Bot mentioned this pull request Jul 31, 2026
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.

1 participant