@jhlagado/azm 0.2.12 → 0.2.13

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 (44) hide show
  1. package/dist/src/api-compile.d.ts +7 -1
  2. package/dist/src/api-compile.js +17 -5
  3. package/dist/src/api-register-contracts.js +69 -2
  4. package/dist/src/api-tooling.d.ts +1 -1
  5. package/dist/src/cli/artifact-files.d.ts +1 -0
  6. package/dist/src/cli/artifact-files.js +5 -0
  7. package/dist/src/cli/parse-args.d.ts +6 -1
  8. package/dist/src/cli/parse-args.js +59 -0
  9. package/dist/src/cli/run.js +2 -2
  10. package/dist/src/cli/usage.js +5 -0
  11. package/dist/src/cli/write-artifacts.d.ts +1 -1
  12. package/dist/src/cli/write-artifacts.js +15 -2
  13. package/dist/src/expansion/op-expansion.js +12 -1
  14. package/dist/src/index.d.ts +1 -1
  15. package/dist/src/outputs/types.d.ts +13 -1
  16. package/dist/src/register-contracts/analyze-helpers.d.ts +6 -1
  17. package/dist/src/register-contracts/analyze-helpers.js +67 -0
  18. package/dist/src/register-contracts/analyze.d.ts +8 -1
  19. package/dist/src/register-contracts/analyze.js +353 -16
  20. package/dist/src/register-contracts/interfaceContracts.js +45 -0
  21. package/dist/src/register-contracts/liveness.js +23 -0
  22. package/dist/src/register-contracts/policy.d.ts +2 -0
  23. package/dist/src/register-contracts/policy.js +54 -0
  24. package/dist/src/register-contracts/programModel-boundaries.d.ts +5 -1
  25. package/dist/src/register-contracts/programModel-boundaries.js +20 -5
  26. package/dist/src/register-contracts/programModel-routines.js +37 -6
  27. package/dist/src/register-contracts/ratchet.d.ts +3 -0
  28. package/dist/src/register-contracts/ratchet.js +88 -0
  29. package/dist/src/register-contracts/report.d.ts +8 -1
  30. package/dist/src/register-contracts/report.js +174 -0
  31. package/dist/src/register-contracts/smartCommentParsing.js +22 -0
  32. package/dist/src/register-contracts/summary-boundary.js +9 -2
  33. package/dist/src/register-contracts/tooling.d.ts +2 -1
  34. package/dist/src/register-contracts/tooling.js +2 -0
  35. package/dist/src/register-contracts/types.d.ts +157 -0
  36. package/dist/src/syntax/parse-line.js +3 -0
  37. package/docs/codebase/02-source-loading-and-parsing.md +10 -6
  38. package/docs/codebase/04-ops-and-register-contracts.md +49 -4
  39. package/docs/codebase/05-interfaces-and-output-artifacts.md +56 -6
  40. package/docs/codebase/06-verification-and-maintenance.md +10 -2
  41. package/docs/codebase/appendices/a-directory-file-reference.md +3 -1
  42. package/docs/codebase/appendices/b-compile-flow-reference.md +7 -5
  43. package/docs/codebase/appendices/c-public-surface-reference.md +19 -5
  44. package/package.json +1 -1
@@ -3,7 +3,7 @@ import { writeHex } from './outputs/write-hex.js';
3
3
  import type { AddressRange, Artifact, D8mArtifact, D8mFileEntry, D8mFileSymbol, D8mGenerator, D8mJson, D8mSegment, D8mSymbol, EmittedByteMap, FormatWriters, SymbolEntry, WriteD8mOptions } from './outputs/types.js';
4
4
  import type { Diagnostic } from './model/diagnostic.js';
5
5
  import type { CaseStyleMode } from './tooling/case-style.js';
6
- import type { RegisterContractsMode } from './register-contracts/types.js';
6
+ import type { RegisterContractsMode, RegisterContractsPolicy, RegisterContractsReportFormat } from './register-contracts/types.js';
7
7
  export { writeHex, defaultFormatWriters };
8
8
  export type { AddressRange, Artifact, D8mArtifact, D8mFileEntry, D8mFileSymbol, D8mGenerator, D8mJson, D8mSegment, D8mSymbol, EmittedByteMap, FormatWriters, SymbolEntry, WriteD8mOptions, };
9
9
  export type CompileDependencies = CompileNextDependencies;
@@ -30,8 +30,14 @@ export interface CompileNextFunctionOptions {
30
30
  readonly registerContracts?: RegisterContractsMode;
31
31
  /** @deprecated Use registerContracts. */
32
32
  readonly registerCare?: RegisterContractsMode;
33
+ readonly registerContractsPolicy?: RegisterContractsPolicy;
33
34
  readonly emitRegisterReport?: boolean;
35
+ readonly registerContractsReportFormat?: RegisterContractsReportFormat;
36
+ readonly registerContractsBaseline?: string;
37
+ readonly registerContractsRatchet?: boolean;
34
38
  readonly emitRegisterInterface?: boolean;
39
+ readonly emitRegisterInference?: boolean;
40
+ readonly registerContractsInferenceFormat?: 'json' | 'markdown';
35
41
  readonly emitRegisterAnnotations?: boolean;
36
42
  readonly fixRegisterContracts?: boolean;
37
43
  readonly acceptRegisterOutputCandidates?: string[];
@@ -5,12 +5,13 @@ import { runRegisterContracts, shouldAnalyzeRegisterContracts } from './api-regi
5
5
  import { analyzeProgramNext, loadProgramNext } from './tooling/api.js';
6
6
  import { defaultFormatWriters } from './outputs/index.js';
7
7
  import { writeHex } from './outputs/write-hex.js';
8
+ import { registerContractsPolicyModeForFile } from './register-contracts/policy.js';
8
9
  import { buildRegisterContractsProgramModel } from './register-contracts/programModel.js';
9
10
  function parseUnresolvedSymbolName(message) {
10
11
  const match = /^Unresolved symbol "([^"]+)"/.exec(message);
11
12
  return match?.[1];
12
13
  }
13
- function isSuppressedUnknownSymbolInRegisterContractsMode(diagnostic, directCalls) {
14
+ function isSuppressedUnknownSymbolInRegisterContractsMode(diagnostic, directCalls, policy, fallbackMode) {
14
15
  if (directCalls === undefined || directCalls.length === 0) {
15
16
  return false;
16
17
  }
@@ -27,7 +28,11 @@ function isSuppressedUnknownSymbolInRegisterContractsMode(diagnostic, directCall
27
28
  return directCalls.some((call) => call.target === symbol &&
28
29
  call.file === diagnostic.sourceName &&
29
30
  call.line === diagnostic.line &&
30
- call.column === diagnostic.column);
31
+ call.column === diagnostic.column &&
32
+ !isRegisterContractsPolicyOffForFile(call.file, policy, fallbackMode));
33
+ }
34
+ function isRegisterContractsPolicyOffForFile(file, policy, fallbackMode) {
35
+ return policy !== undefined && registerContractsPolicyModeForFile(file, policy, fallbackMode) === 'off';
31
36
  }
32
37
  export { writeHex, defaultFormatWriters };
33
38
  /**
@@ -56,10 +61,14 @@ export async function compile(entryFile, options = {}, deps = { formats: default
56
61
  .directCalls
57
62
  : undefined;
58
63
  diagnostics.push(...analysis.diagnostics.filter((diagnostic) => analyzeRegisterContractsNow
59
- ? !isSuppressedUnknownSymbolInRegisterContractsMode(diagnostic, directCalls)
64
+ ? !isSuppressedUnknownSymbolInRegisterContractsMode(diagnostic, directCalls, options.registerContractsPolicy, options.registerContracts ?? options.registerCare)
60
65
  : true));
61
66
  const artifacts = [];
62
67
  if (hasErrors(diagnostics)) {
68
+ if (analyzeRegisterContractsNow && options.emitRegisterReport === true) {
69
+ const registerContracts = await runRegisterContracts(loaded.loadedProgram, options);
70
+ artifacts.push(...registerContractsReportArtifacts(registerContracts.artifacts));
71
+ }
63
72
  sortDiagnosticsInPlace(diagnostics);
64
73
  return { diagnostics, artifacts };
65
74
  }
@@ -76,11 +85,11 @@ export async function compile(entryFile, options = {}, deps = { formats: default
76
85
  const program = loaded.loadedProgram.program.files[0]?.items ?? [];
77
86
  const assembled = assembleProgram(program);
78
87
  diagnostics.push(...assembled.diagnostics.filter((diagnostic) => analyzeRegisterContractsNow
79
- ? !isSuppressedUnknownSymbolInRegisterContractsMode(diagnostic, directCalls)
88
+ ? !isSuppressedUnknownSymbolInRegisterContractsMode(diagnostic, directCalls, options.registerContractsPolicy, options.registerContracts ?? options.registerCare)
80
89
  : true));
81
90
  sortDiagnosticsInPlace(diagnostics);
82
91
  if (hasErrors(diagnostics)) {
83
- return { diagnostics, artifacts: [] };
92
+ return { diagnostics, artifacts: registerContractsReportArtifacts(artifacts) };
84
93
  }
85
94
  const emittedArtifacts = await emitAssemblyArtifacts({
86
95
  entryFile: normalizedEntry,
@@ -100,6 +109,9 @@ export async function compile(entryFile, options = {}, deps = { formats: default
100
109
  function hasErrors(diagnostics) {
101
110
  return diagnostics.some((diagnostic) => diagnostic.severity === 'error');
102
111
  }
112
+ function registerContractsReportArtifacts(artifacts) {
113
+ return artifacts.filter((artifact) => artifact.kind === 'register-contracts-report');
114
+ }
103
115
  function sortDiagnosticsInPlace(diagnostics) {
104
116
  dedupeDiagnosticsInPlace(diagnostics);
105
117
  diagnostics.sort((left, right) => {
@@ -8,10 +8,13 @@ export function shouldAnalyzeRegisterContracts(options) {
8
8
  return (registerContractsMode !== 'off' ||
9
9
  options.emitRegisterReport === true ||
10
10
  options.emitRegisterInterface === true ||
11
+ options.emitRegisterInference === true ||
11
12
  options.emitRegisterAnnotations === true ||
12
13
  options.fixRegisterContracts === true ||
14
+ options.registerContractsPolicy !== undefined ||
13
15
  (options.acceptRegisterOutputCandidates?.length ?? 0) > 0 ||
14
- (options.registerContractsInterfaces?.length ?? options.registerCareInterfaces?.length ?? 0) > 0);
16
+ (options.registerContractsInterfaces?.length ?? options.registerCareInterfaces?.length ?? 0) > 0 ||
17
+ options.registerContractsBaseline !== undefined);
15
18
  }
16
19
  export async function runRegisterContracts(loadedProgram, options) {
17
20
  const diagnostics = [];
@@ -20,10 +23,24 @@ export async function runRegisterContracts(loadedProgram, options) {
20
23
  if (hasErrors(diagnostics)) {
21
24
  return { diagnostics, artifacts };
22
25
  }
26
+ const baselineReport = await loadBaselineReport(options.registerContractsBaseline, diagnostics);
27
+ if (hasErrors(diagnostics)) {
28
+ return { diagnostics, artifacts };
29
+ }
23
30
  const registerContracts = analyzeRegisterContracts(loadedProgram, {
24
31
  mode: options.registerContracts ?? options.registerCare ?? 'off',
32
+ ...(options.registerContractsPolicy !== undefined
33
+ ? { policy: options.registerContractsPolicy }
34
+ : {}),
25
35
  emitReport: options.emitRegisterReport === true,
36
+ ...(options.registerContractsReportFormat !== undefined
37
+ ? { reportFormat: options.registerContractsReportFormat }
38
+ : {}),
26
39
  emitInterface: options.emitRegisterInterface === true,
40
+ emitInference: options.emitRegisterInference === true,
41
+ ...(options.registerContractsInferenceFormat !== undefined
42
+ ? { inferenceFormat: options.registerContractsInferenceFormat }
43
+ : {}),
27
44
  emitAnnotations: options.emitRegisterAnnotations === true || options.fixRegisterContracts === true,
28
45
  fixRegisterContracts: options.fixRegisterContracts === true,
29
46
  acceptedOutputCandidates: parseAcceptedOutputCandidates(options.acceptRegisterOutputCandidates ?? []),
@@ -33,13 +50,36 @@ export async function runRegisterContracts(loadedProgram, options) {
33
50
  }
34
51
  : {}),
35
52
  ...(interfaceContracts.length > 0 ? { interfaceContracts } : {}),
53
+ ...(baselineReport !== undefined ? { baselineReport } : {}),
54
+ ...(options.registerContractsBaseline !== undefined
55
+ ? { baselineFile: normalize(options.registerContractsBaseline) }
56
+ : {}),
57
+ ratchet: options.registerContractsRatchet === true,
36
58
  });
37
59
  if (registerContracts.reportText !== undefined) {
38
- artifacts.push({ kind: 'register-contracts-report', text: registerContracts.reportText });
60
+ artifacts.push({
61
+ kind: 'register-contracts-report',
62
+ ...(registerContracts.reportFormat !== undefined
63
+ ? { format: registerContracts.reportFormat }
64
+ : {}),
65
+ text: registerContracts.reportText,
66
+ ...(registerContracts.reportJson !== undefined ? { json: registerContracts.reportJson } : {}),
67
+ ...(registerContracts.findings !== undefined ? { findings: registerContracts.findings } : {}),
68
+ });
39
69
  }
40
70
  if (registerContracts.interfaceText !== undefined) {
41
71
  artifacts.push({ kind: 'register-contracts-interface', text: registerContracts.interfaceText });
42
72
  }
73
+ if (registerContracts.inferenceText !== undefined) {
74
+ artifacts.push({
75
+ kind: 'register-contracts-inference',
76
+ format: registerContracts.inferenceFormat ?? 'json',
77
+ text: registerContracts.inferenceText,
78
+ ...(registerContracts.inferenceJson !== undefined
79
+ ? { json: registerContracts.inferenceJson }
80
+ : {}),
81
+ });
82
+ }
43
83
  if (registerContracts.annotations !== undefined && registerContracts.annotations.length > 0) {
44
84
  artifacts.push({
45
85
  kind: 'register-contracts-annotations',
@@ -52,6 +92,33 @@ export async function runRegisterContracts(loadedProgram, options) {
52
92
  diagnostics.push(...registerContracts.diagnostics);
53
93
  return { artifacts, diagnostics };
54
94
  }
95
+ async function loadBaselineReport(rawPath, diagnostics) {
96
+ if (rawPath === undefined)
97
+ return undefined;
98
+ const baselinePath = normalize(rawPath);
99
+ try {
100
+ const parsed = JSON.parse(await readFile(baselinePath, 'utf8'));
101
+ if (parsed.format !== 'azm-register-contracts-report' || !Array.isArray(parsed.findings)) {
102
+ diagnostics.push({
103
+ severity: 'error',
104
+ code: 'AZMN_REGISTER_CONTRACTS',
105
+ sourceName: baselinePath,
106
+ message: 'Register contracts baseline must be a JSON register-contracts report',
107
+ });
108
+ return undefined;
109
+ }
110
+ return parsed;
111
+ }
112
+ catch (error) {
113
+ diagnostics.push({
114
+ severity: 'error',
115
+ code: 'AZMN_REGISTER_CONTRACTS',
116
+ sourceName: baselinePath,
117
+ message: `Unable to read register contracts baseline: ${String(error)}`,
118
+ });
119
+ return undefined;
120
+ }
121
+ }
55
122
  async function loadInterfaceContracts(interfaces, diagnostics) {
56
123
  const interfaceContracts = [];
57
124
  for (const rawInterface of interfaces) {
@@ -4,4 +4,4 @@ export { DiagnosticIds } from './model/diagnostic.js';
4
4
  export type { AnalyzeProgramOptions, AnalyzeProgramResult, LoadedProgram, LoadProgramOptions, LoadProgramResult, AnalyzeProgramNextOptions, AnalyzeProgramNextResult, LoadedProgramNext, LoadProgramNextOptions, LoadProgramNextResult, } from './tooling/api.js';
5
5
  export type { CaseStyleMode } from './tooling/case-style.js';
6
6
  export type { Diagnostic, DiagnosticId, DiagnosticSeverity } from './model/diagnostic.js';
7
- export type { RegisterCareMode, RegisterCareOutputCandidate, RegisterCareUnit, RegisterContractsMode, RegisterContractsOutputCandidate, RegisterContractsUnit, } from './register-contracts/types.js';
7
+ export type { RegisterCareMode, RegisterCareOutputCandidate, RegisterCareUnit, RegisterContractsMode, RegisterContractsFinding, RegisterContractsFindingKind, RegisterContractsOutputCandidate, RegisterContractsUnit, } from './register-contracts/types.js';
@@ -6,6 +6,7 @@ interface ArtifactPaths {
6
6
  readonly asm80: string;
7
7
  readonly registerContractsReport: string;
8
8
  readonly registerContractsInterface: string;
9
+ readonly registerContractsInference: string;
9
10
  }
10
11
  interface ArtifactWriteResult {
11
12
  readonly primaryPath?: string;
@@ -57,6 +57,11 @@ function queueRegisterContractsArtifacts(writes, byKind, paths) {
57
57
  writes.push(writeTextArtifact(paths.registerContractsInterface, iface.text));
58
58
  registerContractsPath ??= paths.registerContractsInterface;
59
59
  }
60
+ const inference = byKind.get('register-contracts-inference');
61
+ if (inference?.kind === 'register-contracts-inference') {
62
+ writes.push(writeTextArtifact(paths.registerContractsInference, inference.text));
63
+ registerContractsPath ??= paths.registerContractsInference;
64
+ }
60
65
  return registerContractsPath;
61
66
  }
62
67
  function queueRegisterContractsAnnotationArtifacts(writes, byKind) {
@@ -1,4 +1,4 @@
1
- import type { RegisterContractsMode } from '../register-contracts/types.js';
1
+ import type { RegisterContractsInferenceFormat, RegisterContractsMode, RegisterContractsReportFormat } from '../register-contracts/types.js';
2
2
  import type { CaseStyleMode } from '../tooling/case-style.js';
3
3
  import { cliUsage } from './usage.js';
4
4
  export type CliExit = {
@@ -16,7 +16,12 @@ export type CliOptions = {
16
16
  caseStyle: CaseStyleMode;
17
17
  registerContracts: RegisterContractsMode;
18
18
  emitRegisterReport: boolean;
19
+ registerContractsReportFormat: RegisterContractsReportFormat;
20
+ registerContractsBaseline: string | undefined;
21
+ registerContractsRatchet: boolean;
19
22
  emitRegisterInterface: boolean;
23
+ emitRegisterInference: boolean;
24
+ registerContractsInferenceFormat: RegisterContractsInferenceFormat;
20
25
  emitRegisterAnnotations: boolean;
21
26
  fixRegisterContracts: boolean;
22
27
  acceptRegisterOutputCandidates: string[];
@@ -38,6 +38,20 @@ const BOOLEAN_FLAG_ACTIONS = [
38
38
  state.emitRegisterInterface = true;
39
39
  },
40
40
  },
41
+ {
42
+ flags: ['--reg-infer'],
43
+ apply: (state) => {
44
+ state.emitRegisterInference = true;
45
+ },
46
+ },
47
+ {
48
+ flags: ['--reg-ratchet'],
49
+ apply: (state) => {
50
+ state.registerContractsRatchet = true;
51
+ state.emitRegisterReport = true;
52
+ state.registerContractsReportFormat = 'json';
53
+ },
54
+ },
41
55
  {
42
56
  flags: ['--fix'],
43
57
  apply: (state) => {
@@ -67,7 +81,12 @@ function createDefaultCliState() {
67
81
  caseStyle: 'off',
68
82
  registerContracts: 'off',
69
83
  emitRegisterReport: false,
84
+ registerContractsReportFormat: 'text',
85
+ registerContractsBaseline: undefined,
86
+ registerContractsRatchet: false,
70
87
  emitRegisterInterface: false,
88
+ emitRegisterInference: false,
89
+ registerContractsInferenceFormat: 'json',
71
90
  emitRegisterAnnotations: false,
72
91
  fixRegisterContracts: false,
73
92
  acceptRegisterOutputCandidates: [],
@@ -199,6 +218,37 @@ function parseRegisterProfileArg(arg, argv, indexRef, state) {
199
218
  state.registerContractsProfile = parsed.value;
200
219
  return true;
201
220
  }
221
+ function parseRegisterReportFormatArg(arg, argv, indexRef, state) {
222
+ const parsed = readMatchedFlagValue(arg, argv, indexRef, ['--reg-report-format']);
223
+ if (!parsed)
224
+ return false;
225
+ if (parsed.value !== 'text' && parsed.value !== 'json') {
226
+ fail(`Unsupported ${parsed.flag} "${parsed.value}" (expected text|json)`);
227
+ }
228
+ state.emitRegisterReport = true;
229
+ state.registerContractsReportFormat = parsed.value;
230
+ return true;
231
+ }
232
+ function parseRegisterInferenceFormatArg(arg, argv, indexRef, state) {
233
+ const parsed = readMatchedFlagValue(arg, argv, indexRef, ['--reg-infer-format']);
234
+ if (!parsed)
235
+ return false;
236
+ if (parsed.value !== 'json' && parsed.value !== 'markdown') {
237
+ fail(`Unsupported ${parsed.flag} "${parsed.value}" (expected json|markdown)`);
238
+ }
239
+ state.emitRegisterInference = true;
240
+ state.registerContractsInferenceFormat = parsed.value;
241
+ return true;
242
+ }
243
+ function parseRegisterBaselineArg(arg, argv, indexRef, state) {
244
+ const parsed = readMatchedFlagValue(arg, argv, indexRef, ['--reg-baseline']);
245
+ if (!parsed)
246
+ return false;
247
+ state.registerContractsBaseline = parsed.value;
248
+ state.emitRegisterReport = true;
249
+ state.registerContractsReportFormat = 'json';
250
+ return true;
251
+ }
202
252
  function parseRegisterInterfaceArg(arg, argv, indexRef, state) {
203
253
  if (arg !== '--interface' && !arg.startsWith('--interface='))
204
254
  return false;
@@ -264,7 +314,12 @@ function finalizeCliOptions(state) {
264
314
  caseStyle: state.caseStyle,
265
315
  registerContracts: state.registerContracts,
266
316
  emitRegisterReport: state.emitRegisterReport,
317
+ registerContractsReportFormat: state.registerContractsBaseline !== undefined ? 'json' : state.registerContractsReportFormat,
318
+ registerContractsBaseline: state.registerContractsBaseline,
319
+ registerContractsRatchet: state.registerContractsRatchet,
267
320
  emitRegisterInterface: state.emitRegisterInterface,
321
+ emitRegisterInference: state.emitRegisterInference,
322
+ registerContractsInferenceFormat: state.registerContractsInferenceFormat,
268
323
  emitRegisterAnnotations: state.emitRegisterAnnotations,
269
324
  fixRegisterContracts: state.fixRegisterContracts,
270
325
  acceptRegisterOutputCandidates: state.acceptRegisterOutputCandidates,
@@ -299,6 +354,7 @@ function emitsRegisterContractsArtifact(state) {
299
354
  state.registerContracts !== 'off',
300
355
  state.emitRegisterReport,
301
356
  state.emitRegisterInterface,
357
+ state.emitRegisterInference,
302
358
  state.emitRegisterAnnotations,
303
359
  state.fixRegisterContracts,
304
360
  state.acceptRegisterOutputCandidates.length > 0,
@@ -324,6 +380,9 @@ const VALUE_ARG_PARSERS = [
324
380
  (arg, { argv, indexRef, state }) => parseDirectiveAliasFileArg(arg, argv, indexRef, state),
325
381
  (arg, { argv, indexRef, state }) => parseRegisterContractsArg(arg, argv, indexRef, state),
326
382
  (arg, { argv, indexRef, state }) => parseRegisterProfileArg(arg, argv, indexRef, state),
383
+ (arg, { argv, indexRef, state }) => parseRegisterReportFormatArg(arg, argv, indexRef, state),
384
+ (arg, { argv, indexRef, state }) => parseRegisterInferenceFormatArg(arg, argv, indexRef, state),
385
+ (arg, { argv, indexRef, state }) => parseRegisterBaselineArg(arg, argv, indexRef, state),
327
386
  (arg, { argv, indexRef, state }) => parseAcceptOutputArg(arg, argv, indexRef, state),
328
387
  (arg, { argv, indexRef, state }) => parseRegisterInterfaceArg(arg, argv, indexRef, state),
329
388
  (arg, { argv, indexRef, state }) => parseIncludeArg(arg, argv, indexRef, state),
@@ -19,11 +19,11 @@ export async function runCli(argv) {
19
19
  }
20
20
  if (sortedDiagnostics.some((diagnostic) => diagnostic.severity === 'error')) {
21
21
  if (compileResult.artifacts.length > 0) {
22
- await writeArtifacts(base, compileResult.artifacts, parsed.outputType);
22
+ await writeArtifacts(base, compileResult.artifacts, parsed.outputType, parsed.registerContractsReportFormat);
23
23
  }
24
24
  return 1;
25
25
  }
26
- const primaryPath = await writeArtifacts(base, compileResult.artifacts, parsed.outputType);
26
+ const primaryPath = await writeArtifacts(base, compileResult.artifacts, parsed.outputType, parsed.registerContractsReportFormat);
27
27
  if (primaryPath !== undefined) {
28
28
  process.stdout.write(primaryPath);
29
29
  }
@@ -12,7 +12,12 @@ export function cliUsage() {
12
12
  ' --register-contracts <m> Register contracts mode: off|audit|warn|error|strict',
13
13
  ' --rc <m> Register contracts mode alias for --register-contracts',
14
14
  ' --reg-report Emit register contracts report artifact',
15
+ ' --reg-report-format <f> Report format: text|json (default: text)',
16
+ ' --reg-baseline <f> Compare against a JSON register contracts report',
17
+ ' --reg-ratchet Fail if register contract findings increase',
15
18
  ' --reg-interface Emit inferred register contracts interface (.asmi)',
19
+ ' --reg-infer Emit inferred register contracts review artifact',
20
+ ' --reg-infer-format <f> Inference format: json|markdown (default: json)',
16
21
  ' --contracts Rewrite source with inferred register contracts',
17
22
  ' --fix Enable contract rewrite and conservative fixes',
18
23
  ' --accept-out <x> Accept register contracts output candidates',
@@ -17,5 +17,5 @@ export declare function compareDiagnosticsForCli(a: {
17
17
  message: string;
18
18
  }): number;
19
19
  export declare function artifactBase(entryFile: string, outputType: 'hex' | 'bin', outputPath?: string): string;
20
- export declare function writeArtifacts(base: string, artifacts: readonly Artifact[], outputType: 'hex' | 'bin'): Promise<string | undefined>;
20
+ export declare function writeArtifacts(base: string, artifacts: readonly Artifact[], outputType: 'hex' | 'bin', registerContractsReportFormat?: 'text' | 'json'): Promise<string | undefined>;
21
21
  export declare function buildCompileOptions(parsed: CliOptions, base: string): CompileNextFunctionOptions;
@@ -46,14 +46,20 @@ export function artifactBase(entryFile, outputType, outputPath) {
46
46
  const entryExt = extname(resolvedEntry);
47
47
  return entryExt.length > 0 ? resolvedEntry.slice(0, -entryExt.length) : resolvedEntry;
48
48
  }
49
- export async function writeArtifacts(base, artifacts, outputType) {
49
+ export async function writeArtifacts(base, artifacts, outputType, registerContractsReportFormat = 'text') {
50
+ const registerContractsReportExt = registerContractsReportFormat === 'json' ? 'json' : 'txt';
51
+ const inference = artifacts.find((artifact) => artifact.kind === 'register-contracts-inference');
52
+ const registerContractsInferenceExt = inference?.kind === 'register-contracts-inference' && inference.format === 'markdown'
53
+ ? 'md'
54
+ : 'json';
50
55
  const result = await writeArtifactFiles(artifacts, {
51
56
  hex: `${base}.hex`,
52
57
  bin: `${base}.bin`,
53
58
  d8m: `${base}.d8.json`,
54
59
  asm80: `${base}.z80`,
55
- registerContractsReport: `${base}.regcontracts.txt`,
60
+ registerContractsReport: `${base}.regcontracts.${registerContractsReportExt}`,
56
61
  registerContractsInterface: `${base}.asmi`,
62
+ registerContractsInference: `${base}.regcontracts.inference.${registerContractsInferenceExt}`,
57
63
  }, outputType);
58
64
  return result.primaryPath ?? result.registerContractsPath;
59
65
  }
@@ -70,7 +76,14 @@ export function buildCompileOptions(parsed, base) {
70
76
  caseStyle: parsed.caseStyle,
71
77
  registerContracts: parsed.registerContracts,
72
78
  emitRegisterReport: parsed.emitRegisterReport,
79
+ registerContractsReportFormat: parsed.registerContractsReportFormat,
80
+ ...(parsed.registerContractsBaseline !== undefined
81
+ ? { registerContractsBaseline: parsed.registerContractsBaseline }
82
+ : {}),
83
+ registerContractsRatchet: parsed.registerContractsRatchet,
73
84
  emitRegisterInterface: parsed.emitRegisterInterface,
85
+ emitRegisterInference: parsed.emitRegisterInference,
86
+ registerContractsInferenceFormat: parsed.registerContractsInferenceFormat,
74
87
  emitRegisterAnnotations: parsed.emitRegisterAnnotations,
75
88
  fixRegisterContracts: parsed.fixRegisterContracts,
76
89
  acceptRegisterOutputCandidates: parsed.acceptRegisterOutputCandidates,
@@ -127,7 +127,18 @@ function parseOpBodyTemplates(line, paramNames, diagnostics, parseOptions) {
127
127
  kind: 'label',
128
128
  name: label.name,
129
129
  ...(label.isEntry ? { isEntry: true } : {}),
130
- span: { sourceName: segmentLine.sourceName, line: segmentLine.line, column: label.labelColumn },
130
+ span: {
131
+ sourceName: segmentLine.sourceName,
132
+ line: segmentLine.line,
133
+ column: label.labelColumn,
134
+ ...(segmentLine.sourceUnit !== undefined ? { sourceUnit: segmentLine.sourceUnit } : {}),
135
+ ...(segmentLine.sourceRelation !== undefined
136
+ ? { sourceRelation: segmentLine.sourceRelation }
137
+ : {}),
138
+ ...(segmentLine.sourceUnitRelation !== undefined
139
+ ? { sourceUnitRelation: segmentLine.sourceUnitRelation }
140
+ : {}),
141
+ },
131
142
  },
132
143
  ],
133
144
  }),
@@ -11,5 +11,5 @@ export { compile, defaultFormatWriters, writeHex } from './api-compile.js';
11
11
  export type { AddressRange, Artifact, CompileDependencies, CompileFunctionOptions, CompileResult, CompileNextDependencies, CompileNextFunctionOptions, EmittedByteMap, FormatWriters, CompileNextResult as CompileNextProgramResult, } from './api-compile.js';
12
12
  export type { AnalyzeProgramOptions, AnalyzeProgramResult, LoadProgramOptions, LoadProgramResult, LoadedProgram, AnalyzeProgramNextOptions, AnalyzeProgramNextResult, LoadProgramNextOptions, LoadProgramNextResult, LoadedProgramNext, } from './tooling/api.js';
13
13
  export type { CaseStyleMode } from './tooling/case-style.js';
14
- export type { RegisterCareMode, RegisterCareOutputCandidate, RegisterCareUnit, RegisterContractsMode, RegisterContractsOutputCandidate, RegisterContractsUnit, } from './register-contracts/types.js';
14
+ export type { RegisterCareMode, RegisterCareOutputCandidate, RegisterCareUnit, RegisterContractsMode, RegisterContractsFinding, RegisterContractsFindingKind, RegisterContractsInferenceFormat, RegisterContractsInferenceModel, RegisterContractsInferenceRoutine, RegisterContractsOutputCandidate, RegisterContractsPolicy, RegisterContractsPolicyMode, RegisterContractsReportFormat, RegisterContractsUnit, } from './register-contracts/types.js';
15
15
  export type { D8mArtifact, D8mGenerator, D8mJson, D8mFileEntry, D8mFileSymbol, D8mSegment, D8mSymbol, SymbolEntry, WriteD8mOptions, } from './outputs/types.js';
@@ -1,4 +1,5 @@
1
1
  import type { SourceItem } from '../model/source-item.js';
2
+ import type { RegisterContractsFinding, RegisterContractsInferenceFormat, RegisterContractsInferenceModel, RegisterContractsJsonReportModel, RegisterContractsReportFormat } from '../register-contracts/types.js';
2
3
  /** Half-open address range in the Z80 16-bit address space. */
3
4
  export interface AddressRange {
4
5
  /** Inclusive start address. */
@@ -54,7 +55,10 @@ export interface HexArtifact {
54
55
  export interface RegisterContractsReportArtifact {
55
56
  kind: 'register-contracts-report';
56
57
  path?: string;
58
+ format?: RegisterContractsReportFormat;
57
59
  text: string;
60
+ json?: RegisterContractsJsonReportModel;
61
+ findings?: RegisterContractsFinding[];
58
62
  }
59
63
  /** @deprecated Use RegisterContractsReportArtifact. */
60
64
  export type RegisterCareReportArtifact = RegisterContractsReportArtifact;
@@ -66,6 +70,14 @@ export interface RegisterContractsInterfaceArtifact {
66
70
  }
67
71
  /** @deprecated Use RegisterContractsInterfaceArtifact. */
68
72
  export type RegisterCareInterfaceArtifact = RegisterContractsInterfaceArtifact;
73
+ /** In-memory inferred register contracts review artifact. */
74
+ export interface RegisterContractsInferenceArtifact {
75
+ kind: 'register-contracts-inference';
76
+ path?: string;
77
+ format: RegisterContractsInferenceFormat;
78
+ text: string;
79
+ json?: RegisterContractsInferenceModel;
80
+ }
69
81
  /** In-memory register contracts source annotation artifact. */
70
82
  export interface RegisterContractsAnnotationsArtifact {
71
83
  kind: 'register-contracts-annotations';
@@ -166,7 +178,7 @@ export interface WriteD8mOptions {
166
178
  }
167
179
  export interface WriteAsm80Options {
168
180
  }
169
- export type Artifact = BinArtifact | HexArtifact | D8mArtifact | Asm80Artifact | RegisterContractsReportArtifact | RegisterContractsInterfaceArtifact | RegisterContractsAnnotationsArtifact;
181
+ export type Artifact = BinArtifact | HexArtifact | D8mArtifact | Asm80Artifact | RegisterContractsReportArtifact | RegisterContractsInterfaceArtifact | RegisterContractsInferenceArtifact | RegisterContractsAnnotationsArtifact;
170
182
  /** Writer contract used by the compile API. */
171
183
  export interface FormatWriters {
172
184
  writeHex(map: EmittedByteMap, symbols: readonly SymbolEntry[], opts?: WriteHexOptions): HexArtifact;
@@ -1,6 +1,6 @@
1
1
  import type { Diagnostic } from '../model/diagnostic.js';
2
2
  import { unknownCallList } from './summaries.js';
3
- import type { AnalyzeRegisterContractsOptions, RegisterContractsOutputCandidate, RegisterContractsReportModel, RegisterContractsRoutine, RegisterContractsUnit, RoutineSummary } from './types.js';
3
+ import type { AnalyzeRegisterContractsOptions, RegisterContractsDirectCall, RegisterContractsFinding, RegisterContractsOutputCandidate, RegisterContractsReportModel, RegisterContractsRoutine, RegisterContractsUnit, RoutineSummary } from './types.js';
4
4
  export declare function candidateMessageWithFixability(candidate: RegisterContractsOutputCandidate, autoFixable: boolean): string;
5
5
  export declare function knownRoutineNames(routines: readonly RegisterContractsRoutine[], contractNames: Iterable<string>, profile: AnalyzeRegisterContractsOptions['registerContractsProfile']): Set<string>;
6
6
  export declare function outputCandidatesWithFixability(routines: readonly RegisterContractsRoutine[], outputCandidates: readonly RegisterContractsOutputCandidate[]): {
@@ -14,12 +14,17 @@ export declare function diagnosticsForConflicts(conflicts: readonly {
14
14
  message: string;
15
15
  }[], mode: AnalyzeRegisterContractsOptions['mode']): Diagnostic[];
16
16
  export declare function strictStackDiagnostics(routines: readonly RegisterContractsRoutine[], summaries: readonly RoutineSummary[]): Diagnostic[];
17
+ export declare function strictStackFindings(routines: readonly RegisterContractsRoutine[], summaries: readonly RoutineSummary[]): RegisterContractsFinding[];
18
+ export declare function unknownBoundaryFindings(directBoundaries: readonly RegisterContractsDirectCall[], knownRoutines: ReadonlySet<string>): RegisterContractsFinding[];
19
+ export declare function diagnosticsForFindings(findings: readonly RegisterContractsFinding[], mode: AnalyzeRegisterContractsOptions['mode']): Diagnostic[];
17
20
  export declare function summariesForAnnotations(summariesByName: ReadonlyMap<string, RoutineSummary>, outputCandidates: readonly RegisterContractsOutputCandidate[]): Map<string, RoutineSummary>;
18
21
  export declare function buildRegisterContractsReportModel(input: {
19
22
  entryFile: string;
20
23
  mode: AnalyzeRegisterContractsOptions['mode'];
21
24
  summaries: readonly RoutineSummary[];
22
25
  profileSummaries: readonly RoutineSummary[];
26
+ findings: readonly RegisterContractsFinding[];
27
+ suppressedFindings?: RegisterContractsReportModel['suppressedFindings'];
23
28
  conflicts: RegisterContractsReportModel['conflicts'];
24
29
  outputCandidates: readonly RegisterContractsOutputCandidate[];
25
30
  profile: AnalyzeRegisterContractsOptions['registerContractsProfile'];
@@ -24,6 +24,7 @@ export function outputCandidatesWithFixability(routines, outputCandidates) {
24
24
  const autoFixable = outputCandidateFixability.get(outputCandidateKey(candidate.file, candidate.line, candidate.column)) ?? false;
25
25
  return {
26
26
  ...candidate,
27
+ kind: 'output_candidate',
27
28
  autoFixable,
28
29
  message: candidateMessageWithFixability(candidate, autoFixable),
29
30
  };
@@ -63,6 +64,68 @@ export function strictStackDiagnostics(routines, summaries) {
63
64
  }
64
65
  return diagnostics;
65
66
  }
67
+ export function strictStackFindings(routines, summaries) {
68
+ const routinesByName = new Map(routines.map((routine) => [routine.name, routine]));
69
+ const findings = [];
70
+ for (const summary of summaries) {
71
+ const routine = routinesByName.get(summary.name);
72
+ if (routine === undefined)
73
+ continue;
74
+ const stackIssues = strictStackIssueText(summary);
75
+ if (stackIssues === undefined)
76
+ continue;
77
+ findings.push({
78
+ kind: 'unknown_control_flow',
79
+ routine: summary.name,
80
+ stackBalanced: summary.stackBalanced,
81
+ ...(summary.hasUnknownStackEffect !== undefined
82
+ ? { hasUnknownStackEffect: summary.hasUnknownStackEffect }
83
+ : {}),
84
+ file: routine.span.file,
85
+ line: routine.span.start.line,
86
+ column: routine.span.start.column,
87
+ ...(routine.span.sourceUnit !== undefined ? { sourceUnit: routine.span.sourceUnit } : {}),
88
+ ...(routine.span.sourceRelation !== undefined
89
+ ? { sourceRelation: routine.span.sourceRelation }
90
+ : {}),
91
+ ...(routine.span.sourceUnitRelation !== undefined
92
+ ? { sourceUnitRelation: routine.span.sourceUnitRelation }
93
+ : {}),
94
+ message: `Register contracts cannot prove stack discipline for ${summary.name}: ${stackIssues}. Keep PUSH/POP pairs and stack-changing exits inside one @ routine boundary, or split the code into explicit callable routines.`,
95
+ });
96
+ }
97
+ return findings;
98
+ }
99
+ export function unknownBoundaryFindings(directBoundaries, knownRoutines) {
100
+ return directBoundaries
101
+ .filter((boundary) => !knownRoutines.has(boundary.target))
102
+ .map((boundary) => ({
103
+ kind: 'missing_callee_contract',
104
+ callTarget: boundary.target,
105
+ subject: boundary.subject,
106
+ file: boundary.file,
107
+ line: boundary.line,
108
+ column: boundary.column,
109
+ ...(boundary.sourceUnit !== undefined ? { sourceUnit: boundary.sourceUnit } : {}),
110
+ ...(boundary.sourceRelation !== undefined ? { sourceRelation: boundary.sourceRelation } : {}),
111
+ ...(boundary.sourceUnitRelation !== undefined
112
+ ? { sourceUnitRelation: boundary.sourceUnitRelation }
113
+ : {}),
114
+ message: `Register contracts cannot prove ${boundary.subject}; add a routine body or .asmi extern contract.`,
115
+ }));
116
+ }
117
+ export function diagnosticsForFindings(findings, mode) {
118
+ if (mode === 'audit')
119
+ return [];
120
+ return findings.map((finding) => ({
121
+ severity: mode === 'error' || mode === 'strict' ? 'error' : 'warning',
122
+ code: 'AZMN_REGISTER_CONTRACTS',
123
+ sourceName: finding.file,
124
+ line: finding.line,
125
+ column: finding.column,
126
+ message: finding.message,
127
+ }));
128
+ }
66
129
  function strictStackIssueText(summary) {
67
130
  const issues = [];
68
131
  if (!summary.stackBalanced)
@@ -99,6 +162,10 @@ export function buildRegisterContractsReportModel(input) {
99
162
  entryFile: input.entryFile,
100
163
  mode: input.mode,
101
164
  summaries: [...input.summaries, ...input.profileSummaries],
165
+ findings: [...input.findings],
166
+ ...(input.suppressedFindings !== undefined && input.suppressedFindings.length > 0
167
+ ? { suppressedFindings: [...input.suppressedFindings] }
168
+ : {}),
102
169
  conflicts: [...input.conflicts],
103
170
  outputCandidates: [...input.outputCandidates],
104
171
  ...(input.profile !== undefined ? { profile: input.profile } : {}),
@@ -1,11 +1,18 @@
1
1
  import type { Diagnostic } from '../model/diagnostic.js';
2
2
  import type { SourceItem } from '../model/source-item.js';
3
- import type { AnalyzeRegisterContractsOptions, RegisterContractsAnnotationFile, RegisterContractsOutputCandidate } from './types.js';
3
+ import type { AnalyzeRegisterContractsOptions, RegisterContractsFinding, RegisterContractsAnnotationFile, RegisterContractsJsonReportModel, RegisterContractsOutputCandidate } from './types.js';
4
+ import { buildRegisterContractsInference } from './report.js';
4
5
  interface AnalyzeRegisterContractsResult {
5
6
  diagnostics: Diagnostic[];
7
+ findings?: RegisterContractsFinding[];
6
8
  outputCandidates?: RegisterContractsOutputCandidate[];
7
9
  reportText?: string;
10
+ reportJson?: RegisterContractsJsonReportModel;
11
+ reportFormat?: 'text' | 'json';
8
12
  interfaceText?: string;
13
+ inferenceText?: string;
14
+ inferenceJson?: ReturnType<typeof buildRegisterContractsInference>;
15
+ inferenceFormat?: 'json' | 'markdown';
9
16
  annotations?: readonly RegisterContractsAnnotationFile[];
10
17
  unknownCalls?: string[];
11
18
  }