@aventara/client 0.0.0-stage → 0.1.0-pilot.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/LICENSE +91 -0
  2. package/LICENSE-ADDITIONAL-PERMISSION.md +9 -0
  3. package/README.md +268 -2
  4. package/dist/avclient.bin.d.ts +2 -0
  5. package/dist/avclient.bin.js +15 -0
  6. package/dist/cli/command.parser.d.ts +30 -0
  7. package/dist/cli/command.parser.js +132 -0
  8. package/dist/cli/generate.command.d.ts +24 -0
  9. package/dist/cli/generate.command.js +41 -0
  10. package/dist/cli/generation-failure.renderer.d.ts +6 -0
  11. package/dist/cli/generation-failure.renderer.js +54 -0
  12. package/dist/cli/generation-success.renderer.d.ts +32 -0
  13. package/dist/cli/generation-success.renderer.js +47 -0
  14. package/dist/cli/terminal.prompter.d.ts +13 -0
  15. package/dist/cli/terminal.prompter.js +53 -0
  16. package/dist/cli/warning.renderer.d.ts +10 -0
  17. package/dist/cli/warning.renderer.js +14 -0
  18. package/dist/cli.d.ts +29 -0
  19. package/dist/cli.js +71 -0
  20. package/dist/config/client-config.interface.d.ts +62 -0
  21. package/dist/config/client-config.interface.js +14 -0
  22. package/dist/config/config.loader.d.ts +33 -0
  23. package/dist/config/config.loader.js +80 -0
  24. package/dist/config/config.resolver.d.ts +50 -0
  25. package/dist/config/config.resolver.js +126 -0
  26. package/dist/config/env.cascade.d.ts +84 -0
  27. package/dist/config/env.cascade.js +126 -0
  28. package/dist/contract/contract.acceptance.d.ts +77 -0
  29. package/dist/contract/contract.acceptance.js +124 -0
  30. package/dist/contract/contract.fetcher.d.ts +64 -0
  31. package/dist/contract/contract.fetcher.js +85 -0
  32. package/dist/contract/contract.loader.d.ts +32 -0
  33. package/dist/contract/contract.loader.js +32 -0
  34. package/dist/emit/banner.emitter.d.ts +31 -0
  35. package/dist/emit/banner.emitter.js +42 -0
  36. package/dist/emit/client-surface.emitter.d.ts +32 -0
  37. package/dist/emit/client-surface.emitter.js +236 -0
  38. package/dist/emit/client-tree.emitter.d.ts +37 -0
  39. package/dist/emit/client-tree.emitter.js +103 -0
  40. package/dist/emit/contract-carrier.emitter.d.ts +13 -0
  41. package/dist/emit/contract-carrier.emitter.js +60 -0
  42. package/dist/emit/derivation.emitter.d.ts +45 -0
  43. package/dist/emit/derivation.emitter.js +233 -0
  44. package/dist/emit/descriptor.emitter.d.ts +4 -0
  45. package/dist/emit/descriptor.emitter.js +97 -0
  46. package/dist/emit/emitted-tree.interface.d.ts +61 -0
  47. package/dist/emit/emitted-tree.interface.js +18 -0
  48. package/dist/emit/enum.emitter.d.ts +24 -0
  49. package/dist/emit/enum.emitter.js +42 -0
  50. package/dist/emit/name.deriver.d.ts +153 -0
  51. package/dist/emit/name.deriver.js +411 -0
  52. package/dist/emit/named-type.emitter.d.ts +32 -0
  53. package/dist/emit/named-type.emitter.js +50 -0
  54. package/dist/emit/runtime.emitter.d.ts +87 -0
  55. package/dist/emit/runtime.emitter.js +707 -0
  56. package/dist/emit/scalar.codec.d.ts +63 -0
  57. package/dist/emit/scalar.codec.js +498 -0
  58. package/dist/emit/transaction.emitter.d.ts +17 -0
  59. package/dist/emit/transaction.emitter.js +438 -0
  60. package/dist/generate.d.ts +123 -0
  61. package/dist/generate.js +98 -0
  62. package/dist/index.d.ts +8 -0
  63. package/dist/index.js +8 -0
  64. package/dist/init/client-config.template.d.ts +6 -0
  65. package/dist/init/client-config.template.js +22 -0
  66. package/dist/init/client-init.errors.d.ts +9 -0
  67. package/dist/init/client-init.errors.js +9 -0
  68. package/dist/init/client-init.orchestrator.d.ts +3 -0
  69. package/dist/init/client-init.orchestrator.js +82 -0
  70. package/dist/init/client-init.planner.d.ts +26 -0
  71. package/dist/init/client-init.planner.js +88 -0
  72. package/dist/init/client-init.questions.d.ts +52 -0
  73. package/dist/init/client-init.questions.js +124 -0
  74. package/dist/init/client-project.inspector.d.ts +15 -0
  75. package/dist/init/client-project.inspector.js +32 -0
  76. package/dist/init/command.runner.d.ts +8 -0
  77. package/dist/init/command.runner.js +17 -0
  78. package/dist/node-version.guard.d.ts +8 -0
  79. package/dist/node-version.guard.js +59 -0
  80. package/dist/output/output.validator.d.ts +76 -0
  81. package/dist/output/output.validator.js +254 -0
  82. package/dist/output/output.writer.d.ts +162 -0
  83. package/dist/output/output.writer.js +499 -0
  84. package/package.json +47 -3
@@ -0,0 +1,254 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { readdir, readFile } from "node:fs/promises";
3
+ import * as nodeModule from "node:module";
4
+ import { createRequire } from "node:module";
5
+ import path from "node:path";
6
+ import { pathToFileURL } from "node:url";
7
+ import { carriesGeneratedOwnership } from "../emit/banner.emitter.js";
8
+ /**
9
+ * A resolver for `specifier`, looked up from this package the way Node would look
10
+ * it up: an optional peer is linked beside the package that declares it.
11
+ */
12
+ export function typeScriptResolverFor(specifier) {
13
+ return async () => {
14
+ let resolved;
15
+ try {
16
+ resolved = createRequire(import.meta.url).resolve(specifier);
17
+ }
18
+ catch (error) {
19
+ if (isErrorWithCode(error, "MODULE_NOT_FOUND")) {
20
+ return undefined;
21
+ }
22
+ throw error;
23
+ }
24
+ const loaded = (await import(pathToFileURL(resolved).href));
25
+ return loaded.default ?? loaded;
26
+ };
27
+ }
28
+ /** The `typescript` this package's optional peer names (Q5). */
29
+ export const resolveInstalledTypeScript = typeScriptResolverFor("typescript");
30
+ /**
31
+ * D4 — the resolved `typescript` has no classic compiler API. TypeScript 7 is
32
+ * such a package (plan B3: it exports its version and nothing else), and before
33
+ * this refusal the type check died on it with a `TypeError` stack. A refusal,
34
+ * not a degraded check: a compiler the developer installed is not "absent", and
35
+ * passing it over silently would weaken the check they chose.
36
+ */
37
+ export class UnsupportedTypeScriptError extends Error {
38
+ name = "UnsupportedTypeScriptError";
39
+ }
40
+ /** This package's own manifest: the one statement of the `typescript` range it supports. */
41
+ const MANIFEST = new URL("../../package.json", import.meta.url);
42
+ function declaredTypeScriptRange() {
43
+ const manifest = JSON.parse(readFileSync(MANIFEST, "utf8"));
44
+ return manifest.peerDependencies?.typescript ?? "(undeclared)";
45
+ }
46
+ /** Refuses a compiler without the API {@link typeFindingsOf} uses. */
47
+ function assertClassicCompilerApi(compiler) {
48
+ // Two members, not `Partial<typeof ts>`: a mapped type over the whole
49
+ // compiler namespace costs thousands of instantiations to ask two questions.
50
+ const api = compiler;
51
+ if (typeof api.createProgram === "function") {
52
+ return;
53
+ }
54
+ throw new UnsupportedTypeScriptError(`the installed \`typescript\` ${api.version ?? "(no version)"} has no classic compiler API (\`createProgram\`) to type-check the generated output with; install \`typescript\` in the range @aventara/client supports, ${declaredTypeScriptRange()}.`);
55
+ }
56
+ /**
57
+ * The warning a run without `typescript` carries. Loud on purpose, in its words:
58
+ * the prefix is the CLI's one `warning:`, so the text carries none of its own.
59
+ */
60
+ export const TYPESCRIPT_UNRESOLVED_WARNING = "`typescript` could not be resolved, so the generated output was NOT type-checked — " +
61
+ "only its shape and syntax were. Install `typescript` (an optional peer of @aventara/client) " +
62
+ "in this project to restore the type check.";
63
+ /** The warning a run with neither `typescript` nor Node's parser carries. */
64
+ export const SYNTAX_CHECK_UNAVAILABLE_WARNING = "neither `typescript` nor Node's TypeScript parser is available, so the generated " +
65
+ "output was NOT type-checked or parsed — only its shape was. Install `typescript` (an " +
66
+ "optional peer of @aventara/client) in this project to restore the type check.";
67
+ /**
68
+ * Judges `directory`, which the caller has just filled with `tree`.
69
+ */
70
+ export async function validateOutputTree(directory, tree, resolveTypeScript = resolveInstalledTypeScript) {
71
+ const shapeFindings = await shapeFindingsOf(directory, tree);
72
+ if (shapeFindings.length > 0) {
73
+ return { accepted: false, checked: "shape", findings: shapeFindings };
74
+ }
75
+ const compiler = await resolveTypeScript();
76
+ if (compiler !== undefined) {
77
+ assertClassicCompilerApi(compiler);
78
+ const findings = typeFindingsOf(compiler, directory, tree);
79
+ return findings.length > 0
80
+ ? { accepted: false, checked: "types", findings }
81
+ : { accepted: true, checked: "types", warnings: [] };
82
+ }
83
+ const strip = nodeTypeScriptParser();
84
+ if (strip === undefined) {
85
+ return {
86
+ accepted: true,
87
+ checked: "shape",
88
+ warnings: [SYNTAX_CHECK_UNAVAILABLE_WARNING],
89
+ };
90
+ }
91
+ const findings = syntaxFindingsOf(strip, tree);
92
+ return findings.length > 0
93
+ ? { accepted: false, checked: "syntax", findings }
94
+ : {
95
+ accepted: true,
96
+ checked: "syntax",
97
+ warnings: [TYPESCRIPT_UNRESOLVED_WARNING],
98
+ };
99
+ }
100
+ /* ------------------------------------------------------------------ *
101
+ * Shape *
102
+ * ------------------------------------------------------------------ */
103
+ async function shapeFindingsOf(directory, tree) {
104
+ const findings = [];
105
+ const expected = new Map(tree.map((file) => [file.path, file.bytes]));
106
+ const entries = await readdir(directory, {
107
+ recursive: true,
108
+ withFileTypes: true,
109
+ });
110
+ const present = new Set();
111
+ for (const entry of entries) {
112
+ if (entry.isDirectory()) {
113
+ continue;
114
+ }
115
+ const relative = path
116
+ .relative(directory, path.join(entry.parentPath, entry.name))
117
+ .split(path.sep)
118
+ .join("/");
119
+ present.add(relative);
120
+ if (!entry.isFile() || !expected.has(relative)) {
121
+ findings.push(`${relative}: not part of the emitted tree`);
122
+ }
123
+ }
124
+ const decoder = new TextDecoder("utf-8", { fatal: true });
125
+ for (const [relative, bytes] of expected) {
126
+ if (!present.has(relative)) {
127
+ findings.push(`${relative}: missing`);
128
+ continue;
129
+ }
130
+ const written = await readFile(path.join(directory, relative));
131
+ if (!Buffer.from(bytes).equals(written)) {
132
+ findings.push(`${relative}: not the bytes that were emitted`);
133
+ continue;
134
+ }
135
+ let text;
136
+ try {
137
+ text = decoder.decode(written);
138
+ }
139
+ catch {
140
+ findings.push(`${relative}: not UTF-8`);
141
+ continue;
142
+ }
143
+ if (!carriesGeneratedOwnership(text)) {
144
+ findings.push(`${relative}: does not carry the generated banner`);
145
+ }
146
+ }
147
+ return findings.sort();
148
+ }
149
+ /* ------------------------------------------------------------------ *
150
+ * Types: a real program over the tree, and nothing outside it *
151
+ * ------------------------------------------------------------------ */
152
+ /**
153
+ * The emitted-tree fixture's settings (`__fixtures__/emitted-tree.fixture.ts`),
154
+ * restated here because production code does not import a test fixture: a
155
+ * consumer's strictest plausible configuration, with no DOM and no Node (§15.7:
156
+ * both are first-class targets, so the tree may assume neither).
157
+ */
158
+ function compilerOptionsFor(compiler) {
159
+ return {
160
+ target: compiler.ScriptTarget.ES2022,
161
+ module: compiler.ModuleKind.NodeNext,
162
+ moduleResolution: compiler.ModuleResolutionKind.NodeNext,
163
+ lib: ["lib.es2022.d.ts"],
164
+ types: [],
165
+ strict: true,
166
+ noUncheckedIndexedAccess: true,
167
+ exactOptionalPropertyTypes: true,
168
+ verbatimModuleSyntax: true,
169
+ isolatedModules: true,
170
+ erasableSyntaxOnly: true,
171
+ noUnusedLocals: true,
172
+ noUnusedParameters: true,
173
+ noImplicitReturns: true,
174
+ noImplicitOverride: true,
175
+ noFallthroughCasesInSwitch: true,
176
+ noPropertyAccessFromIndexSignature: true,
177
+ skipDefaultLibCheck: true,
178
+ noEmit: true,
179
+ };
180
+ }
181
+ /**
182
+ * The tree is served as an ES module package of its own: NodeNext reads the
183
+ * nearest `package.json` to decide a `.ts` file's module format, and the one a
184
+ * consumer's project happens to have must not decide this verdict.
185
+ */
186
+ const VIRTUAL_PACKAGE_JSON = '{ "type": "module" }';
187
+ function typeFindingsOf(compiler, directory, tree) {
188
+ const root = path.resolve(directory);
189
+ const options = compilerOptionsFor(compiler);
190
+ const libraryDirectory = path.dirname(compiler.getDefaultLibFilePath(options));
191
+ const virtualPackageJson = path.join(root, "package.json");
192
+ const servable = (file) => isInside(root, file) || isInside(libraryDirectory, file);
193
+ const host = compiler.createCompilerHost(options, true);
194
+ const baseFileExists = host.fileExists.bind(host);
195
+ const baseReadFile = host.readFile.bind(host);
196
+ const baseDirectoryExists = host.directoryExists?.bind(host);
197
+ host.fileExists = (file) => path.resolve(file) === virtualPackageJson ||
198
+ (servable(path.resolve(file)) && baseFileExists(file));
199
+ host.readFile = (file) => path.resolve(file) === virtualPackageJson
200
+ ? VIRTUAL_PACKAGE_JSON
201
+ : servable(path.resolve(file))
202
+ ? baseReadFile(file)
203
+ : undefined;
204
+ host.directoryExists = (candidate) => {
205
+ const resolved = path.resolve(candidate);
206
+ return ((servable(resolved) || isInside(resolved, root)) &&
207
+ (baseDirectoryExists?.(candidate) ?? true));
208
+ };
209
+ host.getCurrentDirectory = () => root;
210
+ const program = compiler.createProgram({
211
+ rootNames: tree.map((file) => path.join(root, file.path)),
212
+ options,
213
+ host,
214
+ });
215
+ const diagnostics = compiler.getPreEmitDiagnostics(program);
216
+ return diagnostics.map((diagnostic) => compiler
217
+ .formatDiagnostic(diagnostic, {
218
+ getCanonicalFileName: (file) => file,
219
+ getCurrentDirectory: () => root,
220
+ getNewLine: () => "\n",
221
+ })
222
+ .trimEnd());
223
+ }
224
+ /** Whether `file` is `directory` or lies beneath it. */
225
+ function isInside(directory, file) {
226
+ const relative = path.relative(directory, file);
227
+ return (relative === "" ||
228
+ (!relative.startsWith("..") && !path.isAbsolute(relative)));
229
+ }
230
+ /** Node's TypeScript parser, where this Node has one. */
231
+ function nodeTypeScriptParser() {
232
+ const candidate = nodeModule.stripTypeScriptTypes;
233
+ return typeof candidate === "function" ? candidate : undefined;
234
+ }
235
+ function syntaxFindingsOf(strip, tree) {
236
+ const decoder = new TextDecoder("utf-8");
237
+ const findings = [];
238
+ for (const file of tree) {
239
+ try {
240
+ strip(decoder.decode(file.bytes));
241
+ }
242
+ catch (error) {
243
+ if (isErrorWithCode(error, "ERR_INVALID_TYPESCRIPT_SYNTAX")) {
244
+ findings.push(`${file.path}: ${error.message}`);
245
+ continue;
246
+ }
247
+ throw error;
248
+ }
249
+ }
250
+ return findings;
251
+ }
252
+ function isErrorWithCode(error, code) {
253
+ return error instanceof Error && error.code === code;
254
+ }
@@ -0,0 +1,162 @@
1
+ import * as fsPromises from "node:fs/promises";
2
+ import type { AbsolutePath } from "../config/client-config.interface.js";
3
+ import { type ClientEmission, type EmittedTree } from "../emit/emitted-tree.interface.js";
4
+ import { type OutputCheck, type TypeScriptResolver } from "./output.validator.js";
5
+ /**
6
+ * §15.3's last two steps as one call — emit into a temporary directory, validate
7
+ * it, atomically replace the generated output — and the property that makes them
8
+ * one: **a failed generation leaves the previous valid output intact**, byte for
9
+ * byte.
10
+ *
11
+ * # What it owns (architect, 2026-10-04)
12
+ *
13
+ * The output is written into `generateAt`, a directory the developer SHARES: it
14
+ * may hold their own files. The generator owns exactly two entries in it —
15
+ * `AvClient.ts` and `generated/` — and reads, refuses on, moves or deletes
16
+ * nothing else there, apart from the transient entries it names itself
17
+ * (`.aventara-next-*`, `.aventara-ready-*`, `.generated.aventara-previous`).
18
+ *
19
+ * - `generated/` is replaced WHOLE, never merged: a file the previous run emitted
20
+ * and this one does not is gone. It is replaced only when every file in it
21
+ * carries the generated banner's ownership line (an empty directory owns
22
+ * nothing and is fine); otherwise the run is refused, naming the foreign files.
23
+ * - `AvClient.ts` is replaced only when it is absent or carries the ownership
24
+ * line; otherwise the run is refused, naming it.
25
+ *
26
+ * # The order, and its one window
27
+ *
28
+ * The whole tree — `AvClient.ts` and `generated/` together — is written into a
29
+ * staging directory inside `generateAt` (same filesystem, so every move is one
30
+ * atomic `rename`) and validated there as one program. Then:
31
+ *
32
+ * 1. the staging directory is renamed `.aventara-ready-*`: from here it is known
33
+ * to be validated;
34
+ * 2. the previous `generated/` → `.generated.aventara-previous`;
35
+ * 3. the staged `generated/` → `generated/`;
36
+ * 4. the staged `AvClient.ts` → `AvClient.ts`, one rename over the old file;
37
+ * 5. the previous `generated/` and the staging directory are removed.
38
+ *
39
+ * A failed step 3 renames the previous `generated/` straight back; a failed step
40
+ * 4 moves the new `generated/` out again and the previous back, so a failure at
41
+ * any step leaves the previous pair. Node has no atomic exchange of two entries,
42
+ * so two windows remain for a process KILLED mid-replace, and the next run's
43
+ * recovery closes each before it does anything else:
44
+ *
45
+ * - between 2 and 3, `generated/` does not exist: the previous one is put back;
46
+ * - between 3 and 4, the new `generated/` sits beside the old `AvClient.ts`: the
47
+ * validated `AvClient.ts` is still staged in `.aventara-ready-*`, so it is
48
+ * moved in, completing the new pair rather than leaving a mismatched one.
49
+ *
50
+ * Staging directories that never reached step 1 were not validated and are
51
+ * removed.
52
+ *
53
+ * # Failure is a sentence
54
+ *
55
+ * A refusal, a rejected tree or a filesystem error (an `Error` with a `code`) is
56
+ * an `OutputWriteError` naming its stage — the person running the generator can
57
+ * act on it (M3). Any other error is a defect and propagates with its stack —
58
+ * the very error thrown, not a wrapper — and what the run said before it is
59
+ * kept beside it, read by `warningsRaisedBeforeDefect`.
60
+ *
61
+ * A refusal carries every warning the run raised before it stopped, in the order
62
+ * a success would have returned them: a crash recovered in `prepare` changed the
63
+ * disk, and the person has to hear that even when the run then fails.
64
+ *
65
+ * A failed run removes the directories it created to reach `generateAt` — and
66
+ * only those, and only while empty — so a first run that fails leaves no empty
67
+ * `generateAt` behind.
68
+ */
69
+ /** The four stages a write passes through, in order. */
70
+ export type OutputWriteStage = "prepare" | "temp" | "validate" | "replace";
71
+ /** A write that stopped; the previous output is intact unless it says otherwise. */
72
+ export declare class OutputWriteError extends Error {
73
+ readonly stage: OutputWriteStage;
74
+ readonly name = "OutputWriteError";
75
+ /**
76
+ * What the run said before it stopped, in the order a success returns its
77
+ * warnings: the emission's, the validator's, the writer's. Printed before the
78
+ * refusal's sentence.
79
+ */
80
+ readonly warnings: readonly string[];
81
+ constructor(stage: OutputWriteStage, message: string, options?: {
82
+ readonly cause?: unknown;
83
+ readonly warnings?: readonly string[];
84
+ });
85
+ /** The same refusal, with `earlier` said before the warnings it already carries. */
86
+ precededBy(earlier: readonly string[]): OutputWriteError;
87
+ }
88
+ /**
89
+ * Records `earlier` as said before `defect`, ahead of anything already recorded,
90
+ * and returns `defect` unchanged so the caller rethrows the same error.
91
+ */
92
+ export declare function precedeDefect(defect: unknown, earlier: readonly string[]): unknown;
93
+ /**
94
+ * What the run said before `defect` stopped it, in the order a success returns
95
+ * its warnings; empty when nothing was, or for an error no run threw.
96
+ */
97
+ export declare function warningsRaisedBeforeDefect(defect: unknown): readonly string[];
98
+ /**
99
+ * The filesystem calls the writer makes, `node:fs/promises` by default. A seam
100
+ * because the filesystem is the writer's one external collaborator, and the only
101
+ * way to prove a failure at the replace stage leaves the previous output intact
102
+ * is to make a real rename fail.
103
+ */
104
+ export type OutputFileSystem = Pick<typeof fsPromises, "lstat" | "mkdir" | "open" | "readdir" | "rename" | "rm" | "rmdir" | "writeFile">;
105
+ export interface ClientOutputWriteInput {
106
+ /** What `emitClientTree` returned: the tree, and the warnings it raised. */
107
+ readonly emission: ClientEmission;
108
+ /** The resolved `generateAt` (§15.2, architect 2026-10-04). Created when missing. */
109
+ readonly generateAt: AbsolutePath;
110
+ /**
111
+ * Content in `AvClient.ts` or `generated/` the generator did not produce, which
112
+ * the person running it confirmed may be overwritten or removed (architect,
113
+ * 2026-10-04) — the paths `findForeignOutputContent` listed. Anything foreign
114
+ * that is not listed here is refused.
115
+ */
116
+ readonly overrideForeign?: readonly string[];
117
+ /** The `typescript` optional peer; the installed one by default (Q6). */
118
+ readonly resolveTypeScript?: TypeScriptResolver;
119
+ readonly fileSystem?: OutputFileSystem;
120
+ }
121
+ export interface ClientOutputWritten {
122
+ readonly generateAt: AbsolutePath;
123
+ /** How deeply the tree was validated before it replaced the output (Q6). */
124
+ readonly checked: OutputCheck;
125
+ /**
126
+ * Everything the run has to say without failing, in order: the emission's own
127
+ * warnings (the rename ladder's), then the validator's (a degraded check), then
128
+ * the writer's (a recovered crash, a leftover it could not remove). Returned,
129
+ * never printed: the CLI owns the terminal.
130
+ */
131
+ readonly warnings: readonly string[];
132
+ }
133
+ /**
134
+ * Writes `emission.tree` as `<generateAt>/AvClient.ts` and `<generateAt>/generated/`.
135
+ *
136
+ * @throws OutputWriteError when the write stops; the previous output is intact.
137
+ */
138
+ export declare function writeClientOutput(input: ClientOutputWriteInput): Promise<ClientOutputWritten>;
139
+ /**
140
+ * Lists what in `<generateAt>/AvClient.ts` and `<generateAt>/generated/` the
141
+ * generator did not produce — a file without the ownership line, a symbolic link,
142
+ * anything that is not a regular file — so the person running it can be asked
143
+ * before a generation overwrites or removes it (architect, 2026-10-04). Reads
144
+ * only; looks at nothing else in `generateAt`. Empty when `generateAt` does not
145
+ * exist yet.
146
+ *
147
+ * It sees what the write will see once it has recovered a killed run, without
148
+ * recovering it: a `generated/` the killed run moved aside with nothing in its
149
+ * place is read where it lies and its files named where recovery puts them back
150
+ * (`generated/…`). So the question asked, and `--yes`, cover them too — and a
151
+ * run that stops here has still touched nothing.
152
+ *
153
+ * @throws OutputWriteError when `generateAt` is not a directory, `generated` is
154
+ * not a directory or `AvClient.ts` is not a file: no confirmation repairs that.
155
+ */
156
+ export declare function findForeignOutputContent(generateAt: AbsolutePath, fileSystem?: OutputFileSystem): Promise<readonly string[]>;
157
+ /**
158
+ * Whether `AvClient.ts` and `generated/` in `generateAt` hold exactly `tree` —
159
+ * every file byte for byte, no other file, and nothing a killed run moved aside —
160
+ * so that writing `tree` would change nothing (Phase 12-rest Q6: "up to date").
161
+ */
162
+ export declare function ownedOutputMatches(generateAt: AbsolutePath, tree: EmittedTree): Promise<boolean>;