Problem
The knowledge generator exposes deprecated public members as if they were normal
supported API.
TextOrnaments.spacedUpper(String) is deprecated in 2.4.0, but
knowledge/api/templates.md renders:
### TextOrnaments (class)
- `String upper(String value)`
- `String spacedUpper(String value)`
- `String joinPipe(String... parts)`
Nothing says which of the first two should not be used. api-query is no better:
$ node knowledge/tools/api-query/api-query.mjs --type TextOrnaments --surface templates
class com.demcha.compose.document.templates.core.text.TextOrnaments
methods (3):
String upper(String value)
String spacedUpper(String value)
String joinPipe(String... parts)
The pack exists so an agent picks the right API instead of inventing one. Here it
does the opposite of its job: the Java API marks the method correctly, and the
generated surface launders that signal away, so an agent querying knowledge/api
can actively choose the deprecated call.
This is not limited to spacedUpper — every @Deprecated public member across
the published modules is currently indistinguishable from supported API.
Desired behavior
- detect
java.lang.Deprecated from RuntimeVisibleAnnotations;
- carry deprecated metadata into the generated API schema;
- preserve
since / forRemoval where available if practical;
- render deprecated members clearly in
knowledge/api/*.md;
- expose the state through
api-query;
- add fixtures/tests proving deprecated methods cannot appear as ordinary API;
- keep
extract-api --check enforcing the generated result.
Acceptance example:
TextOrnaments.spacedUpper(String)
deprecated since 2.4.0
replacement: upper(...) + SPACED_CAPS / DocumentLetterSpacing
Do not infer replacements automatically unless they are explicitly available from
source metadata/documentation.
What the current code already gives you, and what it does not
Three findings from reading the toolchain, so the work is not re-discovered:
1. Detection is nearly free. readAnnotations already collects every
RuntimeVisibleAnnotations entry for the class and for each method — there is no
allow-list of recognised annotations. java.lang.Deprecated has RUNTIME
retention, so it is already being parsed and then discarded. The per-method map is
keyed through memberKeyForMember, which the member mapping in extract-api.mjs
already consults.
2. since / forRemoval need a parser change, small but real.
readAnnotation in lib/annotations.mjs deliberately walks past the element pairs:
/** Read one annotation's type descriptor and step over its element pairs. */
function readAnnotation(r, utf8) {
const typeIndex = r.u2();
const pairs = r.u2();
for (let i = 0; i < pairs; i += 1) {
r.skip(2); // element_name_index
skipElementValue(r);
}
return utf8.get(typeIndex) ?? null;
}
Only the descriptor survives. Both @Deprecated elements are constant-valued
(String since, boolean forRemoval), so reading them means turning
skipElementValue into a read for the constant tags and leaving the rest skipped.
Worth keeping that narrow — the file's own stance is that a parse it cannot do
must fail loudly rather than degrade to "unannotated".
3. The replacement text is NOT reachable today, which is what makes the
"do not infer" constraint the right call. It lives only in the @deprecated
Javadoc tag. The extractor's one source-reading pass, lib/source-names.mjs,
reads the sources jar for parameter names alone, and its first act is
function stripComments(source) {
return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "");
}
— so the tag is destroyed before anything looks at it. Its header also states the
invariant that nothing in that pass may add a member. Surfacing the replacement
therefore means a deliberate new seam, not a tweak, and until one exists the
acceptance example's replacement: line can only be populated for members whose
Javadoc is actually read. Emitting deprecated since 2.4.0 with no replacement
line is the correct output in the meantime; guessing one would be worse than
omitting it, since the whole value of this file is that what it says can be
trusted.
Notes on scope
- Schema is
schemaVersion: 2. Adding an optional member/type key is additive,
but every consumer that pretty-prints members needs updating in the same change
or the data lands somewhere nothing displays: lib/render-markdown.mjs and
knowledge/tools/api-query/api-query.mjs at minimum.
knowledge/manifest.json carries no hashes or counts, only a file index, so it
does not need regenerating for this.
- The fixture step in
.github/workflows/ci.yml compares the set of
*.test.mjs suites against a declared list and goes red when they differ. A new
fixture suite must be added to that list in the same commit — by design.
- Repo-wide blast radius is small: 6
@Deprecated public members in main sources
today, so the regenerated surfaces will not churn much.
Related
Problem
The knowledge generator exposes deprecated public members as if they were normal
supported API.
TextOrnaments.spacedUpper(String)is deprecated in 2.4.0, butknowledge/api/templates.mdrenders:Nothing says which of the first two should not be used.
api-queryis no better:The pack exists so an agent picks the right API instead of inventing one. Here it
does the opposite of its job: the Java API marks the method correctly, and the
generated surface launders that signal away, so an agent querying
knowledge/apican actively choose the deprecated call.
This is not limited to
spacedUpper— every@Deprecatedpublic member acrossthe published modules is currently indistinguishable from supported API.
Desired behavior
java.lang.DeprecatedfromRuntimeVisibleAnnotations;since/forRemovalwhere available if practical;knowledge/api/*.md;api-query;extract-api --checkenforcing the generated result.Acceptance example:
Do not infer replacements automatically unless they are explicitly available from
source metadata/documentation.
What the current code already gives you, and what it does not
Three findings from reading the toolchain, so the work is not re-discovered:
1. Detection is nearly free.
readAnnotationsalready collects everyRuntimeVisibleAnnotationsentry for the class and for each method — there is noallow-list of recognised annotations.
java.lang.DeprecatedhasRUNTIMEretention, so it is already being parsed and then discarded. The per-method map is
keyed through
memberKeyForMember, which the member mapping inextract-api.mjsalready consults.
2.
since/forRemovalneed a parser change, small but real.readAnnotationinlib/annotations.mjsdeliberately walks past the element pairs:Only the descriptor survives. Both
@Deprecatedelements are constant-valued(
String since,boolean forRemoval), so reading them means turningskipElementValueinto a read for the constant tags and leaving the rest skipped.Worth keeping that narrow — the file's own stance is that a parse it cannot do
must fail loudly rather than degrade to "unannotated".
3. The replacement text is NOT reachable today, which is what makes the
"do not infer" constraint the right call. It lives only in the
@deprecatedJavadoc tag. The extractor's one source-reading pass,
lib/source-names.mjs,reads the sources jar for parameter names alone, and its first act is
— so the tag is destroyed before anything looks at it. Its header also states the
invariant that nothing in that pass may add a member. Surfacing the replacement
therefore means a deliberate new seam, not a tweak, and until one exists the
acceptance example's
replacement:line can only be populated for members whoseJavadoc is actually read. Emitting
deprecated since 2.4.0with no replacementline is the correct output in the meantime; guessing one would be worse than
omitting it, since the whole value of this file is that what it says can be
trusted.
Notes on scope
schemaVersion: 2. Adding an optional member/type key is additive,but every consumer that pretty-prints members needs updating in the same change
or the data lands somewhere nothing displays:
lib/render-markdown.mjsandknowledge/tools/api-query/api-query.mjsat minimum.knowledge/manifest.jsoncarries no hashes or counts, only a file index, so itdoes not need regenerating for this.
.github/workflows/ci.ymlcompares the set of*.test.mjssuites against a declared list and goes red when they differ. A newfixture suite must be added to that list in the same commit — by design.
@Deprecatedpublic members in main sourcestoday, so the regenerated surfaces will not churn much.
Related
graph-compose-templates. Same rootcause in a different gate: a published Stable surface whose guarantees nothing
mechanical is checking.