TYPO3 Extension Upgrade
Skill: typo3-extension-upgrade
Keep a TYPO3 extension, sitepackage or project package working on the TYPO3 and PHP versions it declares, or carry it to another set: code broken by what a supported major deprecated or removed, adding a new major, dropping one, and proving every version it claims.
Markdown source#
---
name: typo3-extension-upgrade
description: 'Keep a TYPO3 extension, sitepackage or project package working on the TYPO3 and PHP versions it declares, or carry it to another set: code broken by what a supported major deprecated or removed, adding a new major, dropping one, and proving every version it claims.'
compatibility: Needs the typo3-dev-companion MCP server, which owns every lookup this workflow routes to and publishes this skill together with the references/base.md it opens on. Install it from github.com/TYPO3/dev-companion and run typo3-dev-companion install in the project. A copy taken out of that repository's skills directory alone has neither the tools nor that base file.
---
# TYPO3 Extension Upgrade
A package is broken by what a major it already supports removed. Or it has to
support a range it does not declare yet. Both run in the same order, where each
step decides what the next one is worth. Keep this skill as routing and
workflow. Never keep version-specific APIs, constraints, replacements, or the
contents of a changelog. Every one of those is a property of the installation
you read and of the target you aim at.
## The order
1. Work through [references/base.md](references/base.md). It fixes what this
package is and what it ships. Its last step before the checkout is the sweep
of the installed core's deprecations over that surface. This workflow starts
from the result of that sweep rather than restating it.
2. Widen the sweep, below, into the work list.
3. Settle the range the package has to serve, below. Not before: what breaks
decides whether you can reach a range at all. Where you cross nothing, that
range is the declared one. This step reads it rather than resolves it. It
still decides what the two steps after it may do.
4. Change what the list justifies, and nothing else.
5. Prove it against every combination the package declares.
## Widen the sweep into a work list
The base sweeps one source. This workflow needs three, because each reaches call
sites the others cannot:
- **The changelog**, as the base sweeps it, and `typo3_changelog_lookup` again
with `type: breaking`. Same majors, still no query and no tag. A review asks
what will stop working. An upgrade also asks what already has.
- **The Extension Scanner**, in the installation's own Upgrade module. It needs
a reachable backend and an administrator, and it reads the extension's
installed files. It finds the call sites of what its matchers cover. The
`FullyScanned` / `PartiallyScanned` tag the base carries out of the changelog
says whether its silence means anything. A clean scan for a partially scanned
entry is not a result. You find those call sites.
- **The deprecation annotations on what this package actually calls**, in the
installed core and in the packages it depends on. A changelog entry is per
release, and the core writes one. An annotation sits on the class, method or
property itself. So only this source reaches a symbol deprecated outside the
majors the sweep covered. Only it reaches one in a package that publishes no
changelog at all. A class deprecated as a whole takes every call site of it
with it.
The **target** is the major this work has to reach. That is the one you add, or,
where you add nothing, the declared one the code fails on.
Both the changelog and the scanner answer from the **installed core**. This
whole order rests on that boundary. They say what this package owes the majors
it already runs on. They do not know what the target major changed until the
installation is on it. Until then the target's changes come from official
documentation for that version, never from memory. A list of "what the new major
changed" written from recall reads exactly like one you looked up.
Once the installation is on the target, run the sweep again there. That second
pass says whether the work is complete.
Write the result down before you change a file. Write one entry per call site,
with the identifier and the path and line in this package. Add which declared
major deprecates or removes it, and which of the three established it.
That list is the work, and the result closes on it. Include the entries that
came back empty, with the majors they covered.
## Settle the range, rather than assert it
Where the work crosses no range, the first two entries are the whole of this
step. The declared range is what the fix has to hold on. The three below them
decide a constraint that does not move.
- The declared range is in the Composer manifest and in `ext_emconf.php`. The
two either say the same thing, or the difference is itself a finding. For a
non-Composer installation `ext_emconf.php` is the only constraint that
governs.
- The PHP range is the intersection of what every declared TYPO3 major supports.
It is never the PHP the current machine happens to run.
- Where the package requires a system extension, establish that the target still
ships it. `typo3_system_extension_lookup` answers by key and package name and
does not need it installed. One that stopped being part of the core is a
requirement that cannot resolve. The replacement is a decision, not a rename.
- Let the dependency solver answer, and quote what it printed. A constraint that
should work and one the solver accepts are different claims. The solver
reports a third-party dependency without a release for the target as a
conflict rather than as advice. That dependency then decides the schedule.
- To drop a major is the user's decision, never one you take to make the code
simpler. When you widen to a new one, keep every version the package declares
today unless the request says otherwise.
## The boundary of what may change
The **lowest declared major decides every shape in the package**. A registration
form, attribute or API introduced later cannot replace one that still has to
work there. So a runtime branch on the major and a registration written the
older way are what the declared range requires. They are not debt to clean up.
Say so where the code already does it. The alternative is an upgrade that breaks
the version it was told to keep.
Where a replacement belongs to a subsystem outside the base's scope, ask its
conventions before you write it. That is `typo3_hint_lookup` with the concrete
paths, and `typo3_documentation_lookup` with the target version where the
official API decides the shape. Where nothing in the declared range replaces a
removal, that is the answer. One package version cannot serve both. A silent
choice is how a supported version stops working without anyone's notice.
Change what the work list justifies. An upgrade is not a modernization, a
cleanup or a rewrite.
**Where the change in front of you is not on the list, invoke the workflow that
owns it.** Do not make it here. That is `typo3-extension-health` for what else
is wrong with the package. It is `typo3-extension-testing` for coverage the
upgrade wants but does not have. It is `typo3-extension-documentation` for the
manual that now describes a different range.
That is a step at the moment the reading turns it up, not a note about
ownership. Load the skill by name and work from it.
## Prove it on every version it claims
1. Build the matrix from the declaration, not from convenience. Take every TYPO3
major the package declares against the PHP versions that major supports.
2. Resolve each cell before you run it. Treat a cell that will not resolve as a
result — it is the finding. A skip there is what lets a package declare a
version nobody has ever installed it on.
3. Run the repository's own commands per cell, the checks first. A step that
runs in only one cell leaves the others unproven. That includes the ones the
repository's CI declares. The installation supplies one cell. For every other
one, ask `typo3_rule_lookup` with
`documentId="extension/compatibility/running-on-a-declared-major-that-is-not-installed"`.
It says how you make that cell exist beside it and what it costs the
installation.
It says how you tell a cell that could have failed from one that could not.
4. Report the work list with every entry closed or explicitly left open. Report
the resolutions with what the solver printed. Report what changed and what
did not on purpose, and the matrix cell by cell. Name a cell nobody ran as
unrun rather than leave it out. The matrix is the claim the package makes
about itself. An unrun cell is the part of that claim nothing stands behind.
5. Draft the message with `typo3_commit_message_guide` and `workflow="project"`.
The crossing lands in the package's own repository, and the range it now
declares is what the message is about.
This skill owns what a package owes the TYPO3 majors it declares and the ones it
is meant to declare. That is the sweep that says what breaks and the constraints
that say what it may declare. It is the changes those two justify, and the proof
per declared combination.
It does not own the decision whether the package is otherwise sound. It owns
neither a missing harness nor a rewrite of the documentation. The sections above
name each of those with the workflow it belongs to, and the crossing to it is
explicit. State the verified point the upgrade reached. Stop before you edit
that owner's files. Carry across the range and the call sites you already
established.
markdown
---
name: typo3-extension-upgrade
description: 'Keep a TYPO3 extension, sitepackage or project package working on the TYPO3 and PHP versions it declares, or carry it to another set: code broken by what a supported major deprecated or removed, adding a new major, dropping one, and proving every version it claims.'
compatibility: Needs the typo3-dev-companion MCP server, which owns every lookup this workflow routes to and publishes this skill together with the references/base.md it opens on. Install it from github.com/TYPO3/dev-companion and run typo3-dev-companion install in the project. A copy taken out of that repository's skills directory alone has neither the tools nor that base file.
---
# TYPO3 Extension Upgrade
A package is broken by what a major it already supports removed. Or it has to
support a range it does not declare yet. Both run in the same order, where each
step decides what the next one is worth. Keep this skill as routing and
workflow. Never keep version-specific APIs, constraints, replacements, or the
contents of a changelog. Every one of those is a property of the installation
you read and of the target you aim at.
## The order
1. Work through [references/base.md](references/base.md). It fixes what this
package is and what it ships. Its last step before the checkout is the sweep
of the installed core's deprecations over that surface. This workflow starts
from the result of that sweep rather than restating it.
2. Widen the sweep, below, into the work list.
3. Settle the range the package has to serve, below. Not before: what breaks
decides whether you can reach a range at all. Where you cross nothing, that
range is the declared one. This step reads it rather than resolves it. It
still decides what the two steps after it may do.
4. Change what the list justifies, and nothing else.
5. Prove it against every combination the package declares.
## Widen the sweep into a work list
The base sweeps one source. This workflow needs three, because each reaches call
sites the others cannot:
- **The changelog**, as the base sweeps it, and `typo3_changelog_lookup` again
with `type: breaking`. Same majors, still no query and no tag. A review asks
what will stop working. An upgrade also asks what already has.
- **The Extension Scanner**, in the installation's own Upgrade module. It needs
a reachable backend and an administrator, and it reads the extension's
installed files. It finds the call sites of what its matchers cover. The
`FullyScanned` / `PartiallyScanned` tag the base carries out of the changelog
says whether its silence means anything. A clean scan for a partially scanned
entry is not a result. You find those call sites.
- **The deprecation annotations on what this package actually calls**, in the
installed core and in the packages it depends on. A changelog entry is per
release, and the core writes one. An annotation sits on the class, method or
property itself. So only this source reaches a symbol deprecated outside the
majors the sweep covered. Only it reaches one in a package that publishes no
changelog at all. A class deprecated as a whole takes every call site of it
with it.
The **target** is the major this work has to reach. That is the one you add, or,
where you add nothing, the declared one the code fails on.
Both the changelog and the scanner answer from the **installed core**. This
whole order rests on that boundary. They say what this package owes the majors
it already runs on. They do not know what the target major changed until the
installation is on it. Until then the target's changes come from official
documentation for that version, never from memory. A list of "what the new major
changed" written from recall reads exactly like one you looked up.
Once the installation is on the target, run the sweep again there. That second
pass says whether the work is complete.
Write the result down before you change a file. Write one entry per call site,
with the identifier and the path and line in this package. Add which declared
major deprecates or removes it, and which of the three established it.
That list is the work, and the result closes on it. Include the entries that
came back empty, with the majors they covered.
## Settle the range, rather than assert it
Where the work crosses no range, the first two entries are the whole of this
step. The declared range is what the fix has to hold on. The three below them
decide a constraint that does not move.
- The declared range is in the Composer manifest and in `ext_emconf.php`. The
two either say the same thing, or the difference is itself a finding. For a
non-Composer installation `ext_emconf.php` is the only constraint that
governs.
- The PHP range is the intersection of what every declared TYPO3 major supports.
It is never the PHP the current machine happens to run.
- Where the package requires a system extension, establish that the target still
ships it. `typo3_system_extension_lookup` answers by key and package name and
does not need it installed. One that stopped being part of the core is a
requirement that cannot resolve. The replacement is a decision, not a rename.
- Let the dependency solver answer, and quote what it printed. A constraint that
should work and one the solver accepts are different claims. The solver
reports a third-party dependency without a release for the target as a
conflict rather than as advice. That dependency then decides the schedule.
- To drop a major is the user's decision, never one you take to make the code
simpler. When you widen to a new one, keep every version the package declares
today unless the request says otherwise.
## The boundary of what may change
The **lowest declared major decides every shape in the package**. A registration
form, attribute or API introduced later cannot replace one that still has to
work there. So a runtime branch on the major and a registration written the
older way are what the declared range requires. They are not debt to clean up.
Say so where the code already does it. The alternative is an upgrade that breaks
the version it was told to keep.
Where a replacement belongs to a subsystem outside the base's scope, ask its
conventions before you write it. That is `typo3_hint_lookup` with the concrete
paths, and `typo3_documentation_lookup` with the target version where the
official API decides the shape. Where nothing in the declared range replaces a
removal, that is the answer. One package version cannot serve both. A silent
choice is how a supported version stops working without anyone's notice.
Change what the work list justifies. An upgrade is not a modernization, a
cleanup or a rewrite.
**Where the change in front of you is not on the list, invoke the workflow that
owns it.** Do not make it here. That is `typo3-extension-health` for what else
is wrong with the package. It is `typo3-extension-testing` for coverage the
upgrade wants but does not have. It is `typo3-extension-documentation` for the
manual that now describes a different range.
That is a step at the moment the reading turns it up, not a note about
ownership. Load the skill by name and work from it.
## Prove it on every version it claims
1. Build the matrix from the declaration, not from convenience. Take every TYPO3
major the package declares against the PHP versions that major supports.
2. Resolve each cell before you run it. Treat a cell that will not resolve as a
result — it is the finding. A skip there is what lets a package declare a
version nobody has ever installed it on.
3. Run the repository's own commands per cell, the checks first. A step that
runs in only one cell leaves the others unproven. That includes the ones the
repository's CI declares. The installation supplies one cell. For every other
one, ask `typo3_rule_lookup` with
`documentId="extension/compatibility/running-on-a-declared-major-that-is-not-installed"`.
It says how you make that cell exist beside it and what it costs the
installation.
It says how you tell a cell that could have failed from one that could not.
4. Report the work list with every entry closed or explicitly left open. Report
the resolutions with what the solver printed. Report what changed and what
did not on purpose, and the matrix cell by cell. Name a cell nobody ran as
unrun rather than leave it out. The matrix is the claim the package makes
about itself. An unrun cell is the part of that claim nothing stands behind.
5. Draft the message with `typo3_commit_message_guide` and `workflow="project"`.
The crossing lands in the package's own repository, and the range it now
declares is what the message is about.
This skill owns what a package owes the TYPO3 majors it declares and the ones it
is meant to declare. That is the sweep that says what breaks and the constraints
that say what it may declare. It is the changes those two justify, and the proof
per declared combination.
It does not own the decision whether the package is otherwise sound. It owns
neither a missing harness nor a rewrite of the documentation. The sections above
name each of those with the workflow it belongs to, and the crossing to it is
explicit. State the verified point the upgrade reached. Stop before you edit
that owner's files. Carry across the range and the call sites you already
established.
References#
Where every task starts#
# Where every task starts
## Nothing starts until the server answers
A skill is a file the installer left behind. It loads and reads the same whether
the tools behind it are there or not, and neither side notices. So the first
call below is also the check.
- A client may carry this server's name in each tool's name:
`mcp__<server>__typo3_project_describe`. So a search for the bare name comes
back empty where the server is there. A search for a tool's schema needs the
same form. A `select:` on the bare names returns nothing where the tools are
there. Look for the qualified form before you read an empty result as an
answer about the server.
- No `typo3_` tool in this session, or a first call that errors: stop. Say that
this workflow needs the server and it is not there, and name what came back.
- Do not fall back to general TYPO3 knowledge, and do not start to read the
checkout. That answer carries this workflow's order and confidence and none of
its evidence. Nothing in it says which of the two it is.
- Continue only when the user asks you to after you said so. Repeat it in the
answer and in every finding a lookup would have carried.
## The order
This is an order rather than a list. Each step decides what the next one is
worth. Where a step below carries a condition to skip it, that condition is
narrow on purpose. A skipped prescription teaches the next reader to skip the
ones that matter too.
1. **`typo3_project_describe`** — the repository and whether it holds an
installation yet. It reports the TYPO3 and PHP version, the project's own
extensions, its sites, and the commands this repository declares. That
version filters every later answer. A check the repository does not declare
is a wrong answer however sensible it sounds.
The answer ends with the whole procedures this server carries, as ids. That
list is the only place a client that renders no resource list sees their
names. Each one is a `typo3_rule_lookup` with that `documentId` rather than a
search.
2. **`typo3_extension_describe`** for each extension in scope. It says what the
extension registers, and what it ships beside that. That is its manual, its
README, its test layers, and its XLF files with the source language each one
declares. It also says what the extension does *not* ship, and that is the
half no file listing gives you.
Where step 1 reported no extension, that answer is this step, and there is
nothing to call. Say so. A core checkout is that case, because step 1 names
the project's own extensions and not TYPO3's.
3. **`typo3_task_guide`** with a short English task, the paths it touches, the
target version and the change type. It answers the workflow this task belongs
to and the checks that come with it.
Run it in every session, this skill's own tasks included. The guide builds
the brief from the paths as well as the task text. No skill knows which paths
the caller holds.
A skill that covers the task is not that brief. A skipped step costs the
hints and the core checks those paths match. Where the guide's own answer
named this skill, this is one call for an answer already in the session. The
price of a step there is nothing to decide about.
4. **`typo3_hint_lookup`** for each subsystem in scope, with its concrete paths.
One query per subsystem. A single broad query is not subsystem evidence.
Where step 3 ran with those paths, its answer says whether you still owe this
step. A brief that carried everything the lookup matched says so: "these are
everything typo3_hint_lookup matches for these paths". There the guide made
the call, and the same query returns the same hints. A brief that stopped
short says that instead and names the ids it left. You owe those: fetch them
by id rather than repeat the query.
Read the sentence rather than the populated `hints` key. That key is present
either way and does not tell the two apart. `omittedHints` is that sentence
as data. It is empty where the brief carried everything, and it holds the ids
the brief left where it stopped short.
5. **`typo3_changelog_lookup` with `type: deprecation`**, at each major the
package declares. Omit the query and raise `limit` to carry that major whole.
Those two are the changelog's own axes, and the extension's vocabulary is not
among them. An entry carries a query only when its title carries every word
of it at once. The core titled those entries about its own code.
That is one call per declared major, and what comes back is the major. Every
entry carries its own index tags. `ext:core`, `ext:frontend`, `ext:form` and
the rest name the system extension a change is **in**. `TCA`, `TypoScript`,
`Fluid`, `YAML`, `Backend`, `Frontend` name the surface.
Step 2 picks the package's entries out of that answer by those tags. The tags
are the system extensions it requires, renders through or registers into, and
the kinds of file it ships. That costs no further call. An extension key of
your own is not among them and matches nothing. `tag` narrows one question
inside a major rather than composes the sweep out of eleven.
You check the answers against step 2, which is the other half the words did.
Verify each identifier that comes back in the checkout. A deprecation nothing
here calls is not a finding.
Carry the `FullyScanned` / `PartiallyScanned` tag into the answer. It says
whether the Extension Scanner can find the remaining call sites or whether
that reading is yours. Bounded this way, you can write the sweep before you
open a file. That is why it is a step of the order rather than something the
reading stumbles into.
**What its silence is worth.** A changelog records change events. So a
pattern nothing has touched for ten majors has no entry at all. An empty
sweep is therefore not an answer about what still works. "Does this still
work in version N" goes to `typo3_documentation_lookup` at that version. Ask
it here, and whenever the reading raises it again.
That is a question for a documented surface: a ViewHelper, a TCA type, a
TypoScript setting. The manual matches page titles, section paths and what
each manual declares by name, never the text of a page. Declared is a
property, a class or method the manual documents, a console command. You
reach one by its own name where the query writes that name the way code
does. You also reach it where the query is nothing but the name. A PHP
identifier the manual does not declare has no page named after it.
An identifier goes to `typo3_changelog_lookup` under its own name. That
reaches the entries that write it, however the core titled the change. Then
it goes to the class below. Where the manual has no page for a surface
either, that is a result and not an answer. Undocumented is not unsupported.
**A second declared major.** A package that declares more than one asks a
second question of every deprecation the sweep returns. Is the replacement on
the lower one? The entry's `issue` is a query of its own, and it reaches
every entry filed under that number. The Feature the core announced the
replacement in is among them.
The version the core released it in settles that question. Where the number
reaches no sibling, nobody wrote an entry for the replacement.
`typo3_rule_lookup` with
`documentId="extension/compatibility/a-declared-major-that-is-not-installed"`
is the reading that closes it.
**Where you do not owe the sweep.** A task that produces no change does not
reach this step at all. The property is what the task produces. A triage, a
reproduction and a review illustrate it; they are not the list you read it
off. The sweep asks what a package will have to stop calling. A task that
writes nothing is not going to call anything.
The exemption ends where the workflow produces a change. A review asked to
make the change is that other workflow. It starts this order again with the
files it is about to write. To carry somebody else's patch onto current code
is on the same side. It writes commits. The sweep says whether the code that
moved under the patch deprecated something the patch calls.
Skip the sweep only where the change touches no TYPO3 API: a code style
fixer, a CI file, an `.editorconfig`. A deprecation is a statement about API
the package calls. So a change that calls none has nothing for the sweep to
land on. The sweep is empty before it runs.
That condition is worth a statement, because this step is the largest answer
the order asks for. It is one call per declared major, with that major's
deprecations whole. You read which side a change falls on off the files it
touches, never off the task it started as. One PHP file edited along the way
puts it back among the ordinary ones.
A skip there costs the deprecation no finding would have walked into. How
small the change is decides nothing either. Three statements can call a
deprecated API as easily as three hundred.
A test file is one of those wherever it sits. It calls the API it exercises
and the framework around it, and both deprecate. A fixture is exempt where it
is data the suite reads, and not where it is a class.
**Before the reading**, write down what the order established. That is the
version that filters every later answer, the packages in scope, and the commands
this repository declares. Write down which steps what discharged. Those are
answers already in the session rather than a second reading. What the files show
belongs to the report at the other end. A caller who cannot see what an answer
rests on cannot tell it from one that rests on nothing.
**Then** read the checkout. Not before. A file list first makes everything after
the list look optional. The conventions then arrive as a footnote to a verdict
that has already formed.
**Before the first edit**, name the files this change will create, change or
delete. A deletion is the caller's to ask for, and this is somebody else's
checkout. It is the one act nothing here can put back.
**Last**, the report names every step of this order it did not reach, and what
stood in for it. That is an answer already in the session, a condition that made
the step empty, or an exemption. A reader cannot tell a step passed over in
silence from one somebody dropped.
## When the lookups run out
A behaviour question that survives the lookups above is one you read out of the
installed source. Do not guess at it. The class that implements the behaviour
and the one it inherits from answer it. That reading is the step after the
lookups. It replaces a change to the code until it works.
A first change that did not work is evidence about the reading. So the second
attempt at one failure reads the source rather than changes the code again.
What it settles is what this installation does and never what TYPO3 supports. So
a finding says that you could not settle the question beyond the version
installed. An answer built on the reading names the version it holds for.
## What each runtime lookup adds after the extension answer
`typo3_extension_describe` in step 2 says what one package registers. The
lookups below say what the installation resolved. That is a different fact even
where the words are the same. So step 2 has made none of these calls:
- `typo3_backend_module_lookup` — the tree position, the labels, the access
level, the routes and the navigation component the parent module supplies.
Step 2 lists the modules the package declares. A declaration cannot show that
inheritance.
- `typo3_icon_lookup` — whether any installed package registers an identifier.
That validates the ones a template uses. Step 2 lists the identifiers this
package contributes.
- `typo3_label_lookup` — the labels as the installation resolves them, with its
overrides applied. Step 2 lists the package's XLF files and the source
language each declares, never what a unit says here.
- `typo3_fluid_namespace_list` — the prefixes any template may use without a
declaration, from every package at once. Step 2 lists the package's own
declarations. So an empty list there is no evidence that no package registers
a prefix globally.
- `typo3_configuration_lookup` — the resolved configuration value, after every
extension has had its say. For a form data group it gives the order the
providers really run in. Step 2 answers nothing about that surface at all.
What a registration declares is not what the installation resolves.
- `typo3_service_lookup` — the class the container really injects for a service
id, an interface or a tag. Decorations and overrides count. Step 2 lists what
the package's own `Services.yaml` declares, never what won.
- `typo3_schema_lookup` — the columns TYPO3 derives for a table from its TCA. It
gives the type, the nullability and the default each one gets. Step 2 lists
the tables the package registers and nothing about their shape.
- `typo3_flexform_lookup` — the data structure the installation resolves a
`type=flex` field to, sheet by sheet, with listeners and migrations applied.
Step 2 lists the content elements a package registers, never the structure
each one's form builds.
- `typo3_record_lookup` — the rows of any table the installation has TCA for. It
says how many there are and where they sit. It says what one column holds
across them and which rows depart from its default. Step 2 has no row in its
answer at all.
None of these says whether what it reports is right. `typo3_hint_lookup` and
`typo3_documentation_lookup` do. A subsystem its own runtime lookup confirmed
can still break every rule that governs it. So it is not established until you
asked both.
## A rule reads in both directions
It says what new code should do, and it says what this checkout already does
wrong. A file that settled into the opposite of a rule is a finding, not a local
style to preserve. Consistency with a project's own habit establishes nothing
about whether the habit is right.
## What the code is for is evidence, and the repository states it
A mechanism that costs something is not a defect because it costs. Before you
report one, find what it is there for and say so. That is the manual, the
README, the changelog, the setting that drives it, or the declared versions.
Where the documentation states a purpose, what you have is a trade-off, not a
defect. Name it with its cost and its alternative. Where you cannot find one,
the finding says that you could not establish one, not that none exists. If you
skip this, your review is a list of everything the author did on purpose.
## What a finding rests on is part of the finding
Three things carry one. A file you read, at its path and its line. A command you
ran, with what it printed. A mechanism you traced into an installed package. Say
which of the three it is. If you leave it unsaid, a finding from a CI file
weighs as much as one with a verified line.
Where one of the project's own commands would settle it, run it.
`typo3_project_describe` marks each command it lists **check**, **change** or
**unknown**, read off the declared body. A check reports and hands the code back
as it was. So even a task told not to change files runs it. The linter the
repository already declares is the cheapest evidence in it.
Do not run a change under that instruction. Name an unknown in the answer as
evidence that is available, and do not run it unasked. An unknown is a test
suite, a shell pipeline, a console command.
What a check prints is not the finding. The configuration that makes it fail is
still what the finding is about. The run takes that finding from derived to
established.
## What this server does not know
It does not read your working tree. You establish which files changed, which
branch you are on, and whether a path or an identifier still exists there. Then
pass the concrete paths back, because that turns a general convention into an
answer about this code.
## Query it in English
The knowledge is English and the match is lexical. So a query in another
language reaches the loanwords the two happen to share and nothing else.
Translate the subject before the call and the answer back afterwards, whatever
language you speak with the user.
markdown
# Where every task starts
## Nothing starts until the server answers
A skill is a file the installer left behind. It loads and reads the same whether
the tools behind it are there or not, and neither side notices. So the first
call below is also the check.
- A client may carry this server's name in each tool's name:
`mcp__<server>__typo3_project_describe`. So a search for the bare name comes
back empty where the server is there. A search for a tool's schema needs the
same form. A `select:` on the bare names returns nothing where the tools are
there. Look for the qualified form before you read an empty result as an
answer about the server.
- No `typo3_` tool in this session, or a first call that errors: stop. Say that
this workflow needs the server and it is not there, and name what came back.
- Do not fall back to general TYPO3 knowledge, and do not start to read the
checkout. That answer carries this workflow's order and confidence and none of
its evidence. Nothing in it says which of the two it is.
- Continue only when the user asks you to after you said so. Repeat it in the
answer and in every finding a lookup would have carried.
## The order
This is an order rather than a list. Each step decides what the next one is
worth. Where a step below carries a condition to skip it, that condition is
narrow on purpose. A skipped prescription teaches the next reader to skip the
ones that matter too.
1. **`typo3_project_describe`** — the repository and whether it holds an
installation yet. It reports the TYPO3 and PHP version, the project's own
extensions, its sites, and the commands this repository declares. That
version filters every later answer. A check the repository does not declare
is a wrong answer however sensible it sounds.
The answer ends with the whole procedures this server carries, as ids. That
list is the only place a client that renders no resource list sees their
names. Each one is a `typo3_rule_lookup` with that `documentId` rather than a
search.
2. **`typo3_extension_describe`** for each extension in scope. It says what the
extension registers, and what it ships beside that. That is its manual, its
README, its test layers, and its XLF files with the source language each one
declares. It also says what the extension does *not* ship, and that is the
half no file listing gives you.
Where step 1 reported no extension, that answer is this step, and there is
nothing to call. Say so. A core checkout is that case, because step 1 names
the project's own extensions and not TYPO3's.
3. **`typo3_task_guide`** with a short English task, the paths it touches, the
target version and the change type. It answers the workflow this task belongs
to and the checks that come with it.
Run it in every session, this skill's own tasks included. The guide builds
the brief from the paths as well as the task text. No skill knows which paths
the caller holds.
A skill that covers the task is not that brief. A skipped step costs the
hints and the core checks those paths match. Where the guide's own answer
named this skill, this is one call for an answer already in the session. The
price of a step there is nothing to decide about.
4. **`typo3_hint_lookup`** for each subsystem in scope, with its concrete paths.
One query per subsystem. A single broad query is not subsystem evidence.
Where step 3 ran with those paths, its answer says whether you still owe this
step. A brief that carried everything the lookup matched says so: "these are
everything typo3_hint_lookup matches for these paths". There the guide made
the call, and the same query returns the same hints. A brief that stopped
short says that instead and names the ids it left. You owe those: fetch them
by id rather than repeat the query.
Read the sentence rather than the populated `hints` key. That key is present
either way and does not tell the two apart. `omittedHints` is that sentence
as data. It is empty where the brief carried everything, and it holds the ids
the brief left where it stopped short.
5. **`typo3_changelog_lookup` with `type: deprecation`**, at each major the
package declares. Omit the query and raise `limit` to carry that major whole.
Those two are the changelog's own axes, and the extension's vocabulary is not
among them. An entry carries a query only when its title carries every word
of it at once. The core titled those entries about its own code.
That is one call per declared major, and what comes back is the major. Every
entry carries its own index tags. `ext:core`, `ext:frontend`, `ext:form` and
the rest name the system extension a change is **in**. `TCA`, `TypoScript`,
`Fluid`, `YAML`, `Backend`, `Frontend` name the surface.
Step 2 picks the package's entries out of that answer by those tags. The tags
are the system extensions it requires, renders through or registers into, and
the kinds of file it ships. That costs no further call. An extension key of
your own is not among them and matches nothing. `tag` narrows one question
inside a major rather than composes the sweep out of eleven.
You check the answers against step 2, which is the other half the words did.
Verify each identifier that comes back in the checkout. A deprecation nothing
here calls is not a finding.
Carry the `FullyScanned` / `PartiallyScanned` tag into the answer. It says
whether the Extension Scanner can find the remaining call sites or whether
that reading is yours. Bounded this way, you can write the sweep before you
open a file. That is why it is a step of the order rather than something the
reading stumbles into.
**What its silence is worth.** A changelog records change events. So a
pattern nothing has touched for ten majors has no entry at all. An empty
sweep is therefore not an answer about what still works. "Does this still
work in version N" goes to `typo3_documentation_lookup` at that version. Ask
it here, and whenever the reading raises it again.
That is a question for a documented surface: a ViewHelper, a TCA type, a
TypoScript setting. The manual matches page titles, section paths and what
each manual declares by name, never the text of a page. Declared is a
property, a class or method the manual documents, a console command. You
reach one by its own name where the query writes that name the way code
does. You also reach it where the query is nothing but the name. A PHP
identifier the manual does not declare has no page named after it.
An identifier goes to `typo3_changelog_lookup` under its own name. That
reaches the entries that write it, however the core titled the change. Then
it goes to the class below. Where the manual has no page for a surface
either, that is a result and not an answer. Undocumented is not unsupported.
**A second declared major.** A package that declares more than one asks a
second question of every deprecation the sweep returns. Is the replacement on
the lower one? The entry's `issue` is a query of its own, and it reaches
every entry filed under that number. The Feature the core announced the
replacement in is among them.
The version the core released it in settles that question. Where the number
reaches no sibling, nobody wrote an entry for the replacement.
`typo3_rule_lookup` with
`documentId="extension/compatibility/a-declared-major-that-is-not-installed"`
is the reading that closes it.
**Where you do not owe the sweep.** A task that produces no change does not
reach this step at all. The property is what the task produces. A triage, a
reproduction and a review illustrate it; they are not the list you read it
off. The sweep asks what a package will have to stop calling. A task that
writes nothing is not going to call anything.
The exemption ends where the workflow produces a change. A review asked to
make the change is that other workflow. It starts this order again with the
files it is about to write. To carry somebody else's patch onto current code
is on the same side. It writes commits. The sweep says whether the code that
moved under the patch deprecated something the patch calls.
Skip the sweep only where the change touches no TYPO3 API: a code style
fixer, a CI file, an `.editorconfig`. A deprecation is a statement about API
the package calls. So a change that calls none has nothing for the sweep to
land on. The sweep is empty before it runs.
That condition is worth a statement, because this step is the largest answer
the order asks for. It is one call per declared major, with that major's
deprecations whole. You read which side a change falls on off the files it
touches, never off the task it started as. One PHP file edited along the way
puts it back among the ordinary ones.
A skip there costs the deprecation no finding would have walked into. How
small the change is decides nothing either. Three statements can call a
deprecated API as easily as three hundred.
A test file is one of those wherever it sits. It calls the API it exercises
and the framework around it, and both deprecate. A fixture is exempt where it
is data the suite reads, and not where it is a class.
**Before the reading**, write down what the order established. That is the
version that filters every later answer, the packages in scope, and the commands
this repository declares. Write down which steps what discharged. Those are
answers already in the session rather than a second reading. What the files show
belongs to the report at the other end. A caller who cannot see what an answer
rests on cannot tell it from one that rests on nothing.
**Then** read the checkout. Not before. A file list first makes everything after
the list look optional. The conventions then arrive as a footnote to a verdict
that has already formed.
**Before the first edit**, name the files this change will create, change or
delete. A deletion is the caller's to ask for, and this is somebody else's
checkout. It is the one act nothing here can put back.
**Last**, the report names every step of this order it did not reach, and what
stood in for it. That is an answer already in the session, a condition that made
the step empty, or an exemption. A reader cannot tell a step passed over in
silence from one somebody dropped.
## When the lookups run out
A behaviour question that survives the lookups above is one you read out of the
installed source. Do not guess at it. The class that implements the behaviour
and the one it inherits from answer it. That reading is the step after the
lookups. It replaces a change to the code until it works.
A first change that did not work is evidence about the reading. So the second
attempt at one failure reads the source rather than changes the code again.
What it settles is what this installation does and never what TYPO3 supports. So
a finding says that you could not settle the question beyond the version
installed. An answer built on the reading names the version it holds for.
## What each runtime lookup adds after the extension answer
`typo3_extension_describe` in step 2 says what one package registers. The
lookups below say what the installation resolved. That is a different fact even
where the words are the same. So step 2 has made none of these calls:
- `typo3_backend_module_lookup` — the tree position, the labels, the access
level, the routes and the navigation component the parent module supplies.
Step 2 lists the modules the package declares. A declaration cannot show that
inheritance.
- `typo3_icon_lookup` — whether any installed package registers an identifier.
That validates the ones a template uses. Step 2 lists the identifiers this
package contributes.
- `typo3_label_lookup` — the labels as the installation resolves them, with its
overrides applied. Step 2 lists the package's XLF files and the source
language each declares, never what a unit says here.
- `typo3_fluid_namespace_list` — the prefixes any template may use without a
declaration, from every package at once. Step 2 lists the package's own
declarations. So an empty list there is no evidence that no package registers
a prefix globally.
- `typo3_configuration_lookup` — the resolved configuration value, after every
extension has had its say. For a form data group it gives the order the
providers really run in. Step 2 answers nothing about that surface at all.
What a registration declares is not what the installation resolves.
- `typo3_service_lookup` — the class the container really injects for a service
id, an interface or a tag. Decorations and overrides count. Step 2 lists what
the package's own `Services.yaml` declares, never what won.
- `typo3_schema_lookup` — the columns TYPO3 derives for a table from its TCA. It
gives the type, the nullability and the default each one gets. Step 2 lists
the tables the package registers and nothing about their shape.
- `typo3_flexform_lookup` — the data structure the installation resolves a
`type=flex` field to, sheet by sheet, with listeners and migrations applied.
Step 2 lists the content elements a package registers, never the structure
each one's form builds.
- `typo3_record_lookup` — the rows of any table the installation has TCA for. It
says how many there are and where they sit. It says what one column holds
across them and which rows depart from its default. Step 2 has no row in its
answer at all.
None of these says whether what it reports is right. `typo3_hint_lookup` and
`typo3_documentation_lookup` do. A subsystem its own runtime lookup confirmed
can still break every rule that governs it. So it is not established until you
asked both.
## A rule reads in both directions
It says what new code should do, and it says what this checkout already does
wrong. A file that settled into the opposite of a rule is a finding, not a local
style to preserve. Consistency with a project's own habit establishes nothing
about whether the habit is right.
## What the code is for is evidence, and the repository states it
A mechanism that costs something is not a defect because it costs. Before you
report one, find what it is there for and say so. That is the manual, the
README, the changelog, the setting that drives it, or the declared versions.
Where the documentation states a purpose, what you have is a trade-off, not a
defect. Name it with its cost and its alternative. Where you cannot find one,
the finding says that you could not establish one, not that none exists. If you
skip this, your review is a list of everything the author did on purpose.
## What a finding rests on is part of the finding
Three things carry one. A file you read, at its path and its line. A command you
ran, with what it printed. A mechanism you traced into an installed package. Say
which of the three it is. If you leave it unsaid, a finding from a CI file
weighs as much as one with a verified line.
Where one of the project's own commands would settle it, run it.
`typo3_project_describe` marks each command it lists **check**, **change** or
**unknown**, read off the declared body. A check reports and hands the code back
as it was. So even a task told not to change files runs it. The linter the
repository already declares is the cheapest evidence in it.
Do not run a change under that instruction. Name an unknown in the answer as
evidence that is available, and do not run it unasked. An unknown is a test
suite, a shell pipeline, a console command.
What a check prints is not the finding. The configuration that makes it fail is
still what the finding is about. The run takes that finding from derived to
established.
## What this server does not know
It does not read your working tree. You establish which files changed, which
branch you are on, and whether a path or an identifier still exists there. Then
pass the concrete paths back, because that turns a general convention into an
answer about this code.
## Query it in English
The knowledge is English and the match is lexical. So a query in another
language reaches the loanwords the two happen to share and nothing else.
Translate the subject before the call and the answer back afterwards, whatever
language you speak with the user.