@filipebraida/adonis-function-points 0.5.0 → 0.7.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 (39) hide show
  1. package/CHANGELOG.md +170 -0
  2. package/README.md +101 -292
  3. package/build/{calibration-8eV8CEix.js → calibration-DVIf8hcE.js} +42 -3
  4. package/build/commands/main.js +6 -6
  5. package/build/{fp_calibrate-DUbHiifm.js → fp_calibrate-3TxGdS1b.js} +1 -1
  6. package/build/{fp_count-ChtblhZV.js → fp_count-arGLnVlY.js} +1 -1
  7. package/build/{fp_diff-Dt7J4IWu.js → fp_diff-DBBvzq5x.js} +1 -1
  8. package/build/{fp_explain-DZJ--0-S.js → fp_explain-aNFApwiT.js} +1 -1
  9. package/build/{fp_inventory-CPtmuuke.js → fp_inventory-DIjKIC9t.js} +1 -1
  10. package/build/{fp_metrics-et8F1Wvt.js → fp_metrics-BpU61waG.js} +1 -1
  11. package/build/index.d.ts +8 -4
  12. package/build/index.js +4 -4
  13. package/build/{pipeline-CNTBhs6o.js → pipeline-DO2301fV.js} +2131 -389
  14. package/build/{resolvers-PJwo2Z8R.js → resolvers-DaU4uAqT.js} +603 -165
  15. package/build/{runners-DIt1G85i.js → runners-Dm7cWGa-.js} +6 -3
  16. package/build/src/albrecht/counter.d.ts +38 -5
  17. package/build/src/albrecht/data_functions.d.ts +49 -3
  18. package/build/src/albrecht/diff.d.ts +27 -0
  19. package/build/src/albrecht/index.d.ts +1 -0
  20. package/build/src/albrecht/opaque.d.ts +90 -0
  21. package/build/src/albrecht/technical_filter.d.ts +18 -11
  22. package/build/src/albrecht/transactional_functions.d.ts +7 -0
  23. package/build/src/cli.js +2 -2
  24. package/build/src/define_config.d.ts +55 -57
  25. package/build/src/inventory/graph/call_graph.d.ts +44 -0
  26. package/build/src/inventory/graph/deliveries.d.ts +88 -0
  27. package/build/src/inventory/graph/output_fields.d.ts +143 -0
  28. package/build/src/inventory/paths.d.ts +2 -0
  29. package/build/src/inventory/resolvers/index.d.ts +21 -0
  30. package/build/src/inventory/resolvers/index.js +2 -2
  31. package/build/src/inventory/resolvers/job_dispatch.d.ts +20 -0
  32. package/build/src/inventory/resolvers/local_function.d.ts +24 -0
  33. package/build/src/inventory/resolvers/transformer.d.ts +0 -23
  34. package/build/src/inventory/sources/commands.d.ts +14 -0
  35. package/build/src/inventory/sources/jobs.d.ts +27 -0
  36. package/build/src/pipeline.js +1 -1
  37. package/build/src/types.d.ts +49 -1
  38. package/build/stubs/config.stub +29 -16
  39. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
- import { c as diffCounts, i as measureStructure, l as DEFAULTS, n as parseSamples, o as IncomparableRulesetsError, r as measureConformance, s as IncomparableSourcesError, t as calibrate, u as defineConfig } from "./calibration-8eV8CEix.js";
2
- import { d as toPosix } from "./resolvers-PJwo2Z8R.js";
3
- import { n as analyze } from "./pipeline-CNTBhs6o.js";
1
+ import { c as IncomparableSourcesError, d as DEFAULTS, f as defineConfig, i as measureStructure, n as parseSamples, o as FACTOR_PRESETS, r as measureConformance, s as IncomparableRulesetsError, t as calibrate, u as diffCounts } from "./calibration-DVIf8hcE.js";
2
+ import { y as toPosix } from "./resolvers-DaU4uAqT.js";
3
+ import { n as analyze } from "./pipeline-DO2301fV.js";
4
4
  import { readFile, writeFile } from "node:fs/promises";
5
5
  import path from "node:path";
6
6
  import { existsSync } from "node:fs";
@@ -225,6 +225,8 @@ function renderDiff(diff) {
225
225
  }
226
226
  lines.push("");
227
227
  lines.push(`Billable FP: ${diff.billable}`);
228
+ /** the total is quoted under a set of factors, so the set is named beside it */
229
+ lines.push(`Factors: ${diff.preset} — ${FACTOR_PRESETS[diff.preset].label}`);
228
230
  /**
229
231
  * Before the per-function list, not after it.
230
232
  *
@@ -415,6 +417,7 @@ async function runDiff(options) {
415
417
  from: options.previous,
416
418
  to
417
419
  },
420
+ preset: config.diff?.preset,
418
421
  factors: config.diff?.factors,
419
422
  reasonFactors: config.diff?.reasonFactors
420
423
  }))
@@ -3,9 +3,13 @@ import type { CollectedDataStore } from '../inventory/sources/data_stores.js';
3
3
  import type { CollectedEntryPoint } from '../inventory/sources/routes_ast.js';
4
4
  import type { Behavior } from '../inventory/graph/call_graph.js';
5
5
  import type { DiscoveredSchema } from '../inventory/sources/json_schemas.js';
6
+ import type { CollectedJob } from '../inventory/sources/jobs.js';
7
+ import type { OpaqueDeclaration } from './opaque.js';
6
8
  import type { Complexity, CountResult, FunctionType } from '../types.js';
7
9
  import type { FunctionOverride } from '../define_config.js';
8
10
  import type { ComplexityTable } from './tables.js';
11
+ import type { GroupingStrategy } from './data_functions.js';
12
+ import type { TechnicalPattern } from './technical_filter.js';
9
13
  /**
10
14
  * Assembles the count from the inventory.
11
15
  *
@@ -26,15 +30,23 @@ export declare const RULESET = "afp";
26
30
  * that is easy to forget. Four such changes landed in 1.1.0 — maintenance read
27
31
  * across the whole project rather than from routes alone, a job followed into
28
32
  * `process`, an event followed into its listeners, and `request.input(…)` counted
29
- * as a DET — and three more in 1.2.0: an open input object counting 1 instead of 0,
33
+ * as a DET — three more in 1.2.0: an open input object counting 1 instead of 0,
30
34
  * `detFromSchema` no longer subtracting a placeholder that was not there, and a
31
- * write through `related(…)` maintaining the related table.
35
+ * write through `related(…)` maintaining the related table — and six in 1.5.0:
36
+ * output DETs read from transformers, selects and aggregates instead of every
37
+ * column; system timestamps and `serializeAs: null` columns leaving the DETs;
38
+ * master-detail folded into one data function; identity by table; token tables
39
+ * technical; and opaque declarations reaching every function carrying the origin.
40
+ * Three in 1.6.0: an output's DETs are what the transaction DELIVERS (the render
41
+ * props, the response payload, what a command prints) read back to their origin;
42
+ * a function of the same file and a `.map(fn)` by reference are followed, so
43
+ * FTRs move; and ace commands are transactions, with flags as input.
32
44
  *
33
45
  * Without the bump, a baseline saved by the previous version compares cleanly
34
46
  * against this one and bills the tool's own improvement as work done. The guard
35
47
  * exists for exactly that, and only this constant arms it.
36
48
  */
37
- export declare const RULESET_VERSION = "1.4.0";
49
+ export declare const RULESET_VERSION = "1.6.0";
38
50
  export type CountInput = {
39
51
  app: AppContext;
40
52
  stores: CollectedDataStore[];
@@ -43,22 +55,43 @@ export type CountInput = {
43
55
  behaviors: Map<string, Behavior>;
44
56
  /** JSON Schema literals found in the code, for `detFromSchema` — §8 */
45
57
  jsonSchemas?: Map<string, DiscoveredSchema>;
58
+ /** the queue jobs and who dispatches each — a job no transaction reaches is reported (plan 0.7 §D) */
59
+ jobs?: CollectedJob[];
46
60
  /**
47
61
  * Stores written anywhere in the application's code, reachable from an entry
48
62
  * point or not — AFP §6.5.4 asks who MAINTAINS the store, and a job or a
49
63
  * seeder is this application just as much as a route is.
50
64
  */
51
65
  writtenAnywhere?: Set<string>;
66
+ /**
67
+ * Stores the application addresses DIRECTLY somewhere in its code — as opposed
68
+ * to reaching only through a parent's relation. Decides which composition
69
+ * children fold into their parent as a RET (counting-decisions §10).
70
+ */
71
+ addressedAnywhere?: Set<string>;
72
+ /**
73
+ * Stores written by a SEEDER — scaffolding, so not maintenance — kept apart
74
+ * because an EIF only a seed populates is one of two things the code cannot
75
+ * tell: code data the team maintains (not counted, CPM) or a mirror of data
76
+ * another system maintains in production (a legitimate EIF). Reported.
77
+ */
78
+ seededAnywhere?: Set<string>;
52
79
  };
53
80
  export type CountOptions = {
54
- retStrategy?: 'constant' | 'composition';
55
- /** declared DET/RET for what static analysis cannot read — see §8 */
81
+ dataFunctions?: {
82
+ grouping?: GroupingStrategy;
83
+ };
84
+ /** what a person declared about a DET the analysis cannot read, by origin — §8 */
85
+ opaque?: Record<string, OpaqueDeclaration>;
86
+ /** a declared DET or RET for one function — the last resort, see §8 */
56
87
  overrides?: Record<string, FunctionOverride>;
57
88
  boundary?: {
58
89
  infrastructure?: string[];
59
90
  externallyMaintained?: string[];
60
91
  /** restores what the AFP naming filter caught by accident */
61
92
  business?: string[];
93
+ /** replaces the filter's naming conventions — §6.5.2.1.3 treats them as user input */
94
+ technicalPatterns?: TechnicalPattern[];
62
95
  ignoreEntryPoints?: string[];
63
96
  };
64
97
  messageDet?: number;
@@ -13,7 +13,9 @@ import type { ComplexityTable } from './tables.js';
13
13
  * application's Transactional Functions, the Data Function shall not be
14
14
  * counted in the application." — AFP §6.5.4
15
15
  *
16
- * That is why this module takes usage, not just the stores.
16
+ * That is why this module takes usage, not just the stores. And the same
17
+ * question — how does the application use it? — decides whether a table is a
18
+ * data function at all or a RET of another one (counting-decisions §10).
17
19
  */
18
20
  export type StoreUsage = {
19
21
  /** does any transaction of the application write to this store? */
@@ -21,9 +23,53 @@ export type StoreUsage = {
21
23
  /** does any transaction reach it at all, reading or writing? */
22
24
  used: boolean;
23
25
  };
26
+ export type GroupingStrategy = 'usage' | 'none';
27
+ export type GroupingOptions = {
28
+ /**
29
+ * `usage` folds a composition child nobody addresses directly into its parent
30
+ * as a RET; `none` keeps every table its own data function, RET 1 — the
31
+ * behaviour of rule sets before 1.5.0, for comparing with an old count.
32
+ */
33
+ grouping: GroupingStrategy;
34
+ /**
35
+ * Stores the application addresses directly anywhere in its own code —
36
+ * `C.query()`, `C.create()`, `new C()` — as opposed to reaching only through
37
+ * a parent's relation. Same pass as `writtenAnywhere`, same exclusions.
38
+ */
39
+ addressedAnywhere: Set<string>;
40
+ };
41
+ /**
42
+ * How the stores fold into data functions.
43
+ *
44
+ * rootOf every counted store -> the store whose data function it belongs to
45
+ * members root -> [root, ...children folded in]
46
+ * linkColumns store -> the foreign keys that are the subgroup's LINK to its
47
+ * parent, and therefore not DETs of the group
48
+ */
49
+ export type StoreGrouping = {
50
+ strategy: GroupingStrategy;
51
+ rootOf: Map<string, string>;
52
+ members: Map<string, string[]>;
53
+ linkColumns: Map<string, Set<string>>;
54
+ warnings: string[];
55
+ };
56
+ /**
57
+ * A store `C` is a RET of `P` when, and only when:
58
+ *
59
+ * 1. `P` declares `hasMany` / `hasOne` -> `C` (collected as `subgroups`);
60
+ * 2. no application code addresses `C` directly — the user only ever reaches
61
+ * it through `P`, so under the CPM it is not a logical file of its own;
62
+ * 3. exactly one `P` satisfies (1). More than one: `C` stays apart, reported.
63
+ *
64
+ * Cascade delete was measured and rejected as the signal: on a real application
65
+ * 11 of 13 cascades pointed at the tenant table. Usage is the rule the rest of
66
+ * the count already runs on.
67
+ */
68
+ export declare function groupStores(stores: CollectedDataStore[], options: GroupingOptions): StoreGrouping;
69
+ /** the DET attributes of one store: not the key, not a system stamp, not a link to its parent */
70
+ export declare function detAttributesOf(store: CollectedDataStore, links?: Set<string>): import("../types.js").Attribute[];
24
71
  export type DataFunctionOptions = {
25
- /** `constant` pins RET at 1; `composition` derives it from composition relations */
26
- retStrategy: 'constant' | 'composition';
72
+ grouping: StoreGrouping;
27
73
  /** stores maintained by another system, by boundary decision */
28
74
  externallyMaintained: Set<string>;
29
75
  /**
@@ -37,6 +37,29 @@ export declare class IncomparableRulesetsError extends Error {
37
37
  */
38
38
  export type ChangeFactors = Record<ChangeType, number>;
39
39
  export declare const AEP_FACTORS: ChangeFactors;
40
+ /**
41
+ * The Roteiro de Métricas de Software do SISP, v3.0 (Portaria SGD/MGI nº 3656,
42
+ * de 2026), §7.3 "Projeto de Melhoria" — what a Brazilian public contract names
43
+ * instead of AEP:
44
+ *
45
+ * PF_MELHORIA = PF_INCLUÍDO + FI × PF_ALTERADO + 0,50 × PF_EXCLUÍDO + PF_CONVERSÃO
46
+ *
47
+ * where FI, the impact factor on an altered function, is 63% when the contractor
48
+ * developed or already maintains the function, and 84% when it did not (and must
49
+ * document it). This preset carries the 63% — a factory billing maintenance of
50
+ * its own work — and `diff.factors: { changed: 0.84 }` is the other case.
51
+ * PF_CONVERSÃO is data conversion, which this package does not count.
52
+ *
53
+ * Read from the guide's own PDF, not from memory: an earlier draft of this
54
+ * preset said 0,50 / 0,30, and v2.0 (2012) priced exclusion at 0,40. A contract
55
+ * binds to a revision, so the report prints which preset priced the total.
56
+ */
57
+ export declare const SISP_FACTORS: ChangeFactors;
58
+ export type FactorPreset = 'aep' | 'sisp';
59
+ export declare const FACTOR_PRESETS: Record<FactorPreset, {
60
+ label: string;
61
+ factors: ChangeFactors;
62
+ }>;
40
63
  /**
41
64
  * Factors for a modified function, by WHAT changed about it.
42
65
  *
@@ -52,6 +75,8 @@ export declare const AEP_FACTORS: ChangeFactors;
52
75
  */
53
76
  export type ChangeReasonFactors = Partial<Record<ChangeReason, number>>;
54
77
  export type DiffOptions = {
78
+ /** which published set of factors to start from; `factors` overrides it field by field */
79
+ preset?: FactorPreset;
55
80
  factors?: Partial<ChangeFactors>;
56
81
  /** per-reason factors for modified functions; each falls back to `factors.changed` */
57
82
  reasonFactors?: ChangeReasonFactors;
@@ -64,6 +89,8 @@ export type DiffOptions = {
64
89
  export type FunctionPointDiff = DiffResult & {
65
90
  /** function points weighted by the factors — this is what gets billed */
66
91
  billable: number;
92
+ /** the preset the factors started from — printed, because the total is quoted under it */
93
+ preset: FactorPreset;
67
94
  factors: ChangeFactors;
68
95
  /** what the modified functions were actually billed at, by reason */
69
96
  reasonFactors: ChangeReasonFactors;
@@ -8,6 +8,7 @@
8
8
  * Normative reference: OMG Automated Function Points 1.0 / ISO/IEC 19515.
9
9
  */
10
10
  export * from './tables.js';
11
+ export * from './technical_filter.js';
11
12
  export * from './counter.js';
12
13
  export * from './diff.js';
13
14
  export * from './calibration.js';
@@ -0,0 +1,90 @@
1
+ import type { CollectedDataStore } from '../inventory/sources/data_stores.js';
2
+ import type { DiscoveredSchema } from '../inventory/sources/json_schemas.js';
3
+ import type { CollectedEntryPoint } from '../inventory/sources/routes_ast.js';
4
+ import type { Behavior } from '../inventory/graph/call_graph.js';
5
+ import type { Complexity, CountedFunction, FunctionType } from '../types.js';
6
+ import type { ComplexityTable } from './tables.js';
7
+ /**
8
+ * DETs the analysis cannot read, and what a person declared about them —
9
+ * counting-decisions §8 and §9.
10
+ *
11
+ * Three shapes are opaque: a JSON column (`ast:surveys.answers`), an open
12
+ * input object (`validator:answerSurveyValidator.answers`), and a spread a
13
+ * transformer emits (`transformer:X.<this.resource.serialize()>`). Each counts
14
+ * 1 DET — a floor, never a zero — and is marked `(opaque)` in the rationale.
15
+ *
16
+ * A declaration is about the ORIGIN of the placeholder, not about a function,
17
+ * and it applies to every function that carries the DET: the data function and
18
+ * each transaction that takes or shows the column. Keyed by function it had to
19
+ * be written twice and still missed the third place, so the same column was
20
+ * worth two numbers in one count — and matching by bare name meant reviewing
21
+ * `Attachment.metadata` reviewed every `metadata` column of every table.
22
+ */
23
+ export declare const OPAQUE_TYPE: RegExp;
24
+ export declare const isOpaqueType: (type?: string) => boolean;
25
+ export type OpaqueDeclaration = {
26
+ /**
27
+ * Name(s) of a JSON Schema declared in the application's code, whose fields
28
+ * are counted by the §7 leaf rules and replace the single DET the placeholder
29
+ * contributed. Several are unioned by leaf path: a field two templates share
30
+ * counts once.
31
+ *
32
+ * Prefer this to `overrides.<fn>.det`. A declared number freezes; naming the
33
+ * schema keeps the number coming from the code, and the only thing maintained
34
+ * by hand is the mapping — which changes when a form is born, not when a field
35
+ * is. A name that matches no schema is a warning, never a silent fallback.
36
+ */
37
+ schemas?: string | string[];
38
+ /**
39
+ * Someone looked, and 1 is the right answer — a copy, a checksum, a bag of
40
+ * metadata. Moves no number; stops the warning for this origin; is printed by
41
+ * `fp:explain` with its reason, and is not counted in the "declared by override"
42
+ * share, because nothing was declared.
43
+ */
44
+ reviewed?: true;
45
+ /** why — required, and printed beside the number */
46
+ reason: string;
47
+ };
48
+ export type ApplyOpaqueOptions = {
49
+ declarations: Record<string, OpaqueDeclaration>;
50
+ stores: CollectedDataStore[];
51
+ schemas: Map<string, DiscoveredSchema>;
52
+ tables: Record<FunctionType, ComplexityTable>;
53
+ weights: Record<FunctionType, Record<Complexity, number>>;
54
+ };
55
+ export type AppliedOpaque = {
56
+ functions: CountedFunction[];
57
+ /** origin -> how it was answered */
58
+ answered: Map<string, 'replaced' | 'reviewed'>;
59
+ warnings: string[];
60
+ };
61
+ /**
62
+ * Applies the declarations to every function carrying the origin they name.
63
+ *
64
+ * schemas the placeholder's 1 DET becomes the schema's leaves, and the line
65
+ * says which schema stood in
66
+ * reviewed the placeholder stays 1, marked reviewed; the warning stops
67
+ *
68
+ * A declaration keyed by the physical table (`surveys.answers`) is accepted
69
+ * as well as one keyed by the model (`Survey.answers`): the count prints
70
+ * the model, `fp:explain` prints the table, and a person copies from either.
71
+ */
72
+ export declare function applyOpaque(functions: CountedFunction[], options: ApplyOpaqueOptions): AppliedOpaque;
73
+ export type OpaqueReportInput = {
74
+ functions: CountedFunction[];
75
+ stores: CollectedDataStore[];
76
+ entryPoints: CollectedEntryPoint[];
77
+ behaviors: Map<string, Behavior>;
78
+ answered: Map<string, 'replaced' | 'reviewed'>;
79
+ };
80
+ /**
81
+ * The floors still standing, by origin — the one blind spot this package used
82
+ * to keep to itself.
83
+ *
84
+ * Grouped by origin because that is what a declaration answers: one line for
85
+ * `Form.definition` however many transactions show it. A transformer's spread has
86
+ * its own warning and is not repeated here. Only what is unanswered is a request
87
+ * to do something; what was answered is counted at the end so the fact is
88
+ * recorded rather than erased.
89
+ */
90
+ export declare function opaqueWarnings(input: OpaqueReportInput): string[];
@@ -9,17 +9,24 @@ import type { CollectedDataStore } from '../inventory/sources/data_stores.js';
9
9
  * Returns the reason when a table is technical, `null` otherwise: the report
10
10
  * must say WHY something was excluded, not merely that it was.
11
11
  */
12
+ export type TechnicalPattern = {
13
+ /** printed in the report beside the exclusion */
14
+ label: string;
15
+ /** tested against the physical table name; a string is compiled case-insensitively */
16
+ pattern: RegExp | string;
17
+ };
12
18
  /**
13
- * Naming conventions, with the defaults given by the spec itself (§6.5.2.1.3).
19
+ * Naming conventions, with the defaults given by the spec itself (§6.5.2.1.3)
20
+ * plus the one AdonisJS asks for.
21
+ *
22
+ * The standard treats these as user-provided inputs, so `boundary.technicalPatterns`
23
+ * REPLACES this list when set — a team that finds `.+types?` catching its
24
+ * business data drops it there — and `boundary.business` restores one table.
14
25
  *
15
- * The standard treats these as user-provided inputs, so they stay overridable
16
- * through the boundary configuration.
26
+ * `token` is not in the spec's list and is here because the framework's own
27
+ * tables are: `auth_access_tokens`, `remember_me_tokens`, `password_reset_tokens`.
28
+ * A token is the machinery of authentication, not data the user maintains, and
29
+ * on two applications it came out as an ILF at 7 PF each.
17
30
  */
18
- export declare const DEFAULT_TECHNICAL_PATTERNS: {
19
- label: string;
20
- pattern: RegExp;
21
- }[];
22
- export declare function isTechnical(store: CollectedDataStore, patterns?: {
23
- label: string;
24
- pattern: RegExp;
25
- }[]): string | null;
31
+ export declare const DEFAULT_TECHNICAL_PATTERNS: TechnicalPattern[];
32
+ export declare function isTechnical(store: CollectedDataStore, patterns?: TechnicalPattern[]): string | null;
@@ -3,6 +3,7 @@ import type { CollectedEntryPoint } from '../inventory/sources/routes_ast.js';
3
3
  import type { Behavior } from '../inventory/graph/call_graph.js';
4
4
  import type { Complexity, CountedFunction, FunctionType } from '../types.js';
5
5
  import type { ComplexityTable } from './tables.js';
6
+ import type { StoreGrouping } from './data_functions.js';
6
7
  /**
7
8
  * Transactional functions: EI and EO.
8
9
  *
@@ -21,6 +22,12 @@ import type { ComplexityTable } from './tables.js';
21
22
  export type TransactionOptions = {
22
23
  /** stores that are counted; anything else contributes no FTR */
23
24
  countedStores: Map<string, CollectedDataStore>;
25
+ /**
26
+ * How the stores fold into data functions — counting-decisions §10. A
27
+ * transaction touching a detail and its master touches ONE logical file: one
28
+ * FTR, and the detail's link to the master is not an output DET.
29
+ */
30
+ grouping: StoreGrouping;
24
31
  /**
25
32
  * Extra DET for the confirmation or error message.
26
33
  *
package/build/src/cli.js CHANGED
@@ -1,5 +1,5 @@
1
- import { t as CoverageTooLowError } from "../pipeline-CNTBhs6o.js";
2
- import { a as runInventory, c as ConfigLoadError, i as runExplain, n as runCount, o as runMetrics, r as runDiff, s as printResult, t as runCalibrate } from "../runners-DIt1G85i.js";
1
+ import { t as CoverageTooLowError } from "../pipeline-DO2301fV.js";
2
+ import { a as runInventory, c as ConfigLoadError, i as runExplain, n as runCount, o as runMetrics, r as runDiff, s as printResult, t as runCalibrate } from "../runners-Dm7cWGa-.js";
3
3
  import path from "node:path";
4
4
  import { existsSync, readFileSync } from "node:fs";
5
5
  import { fileURLToPath } from "node:url";
@@ -1,5 +1,7 @@
1
1
  import type { ComplexityTable } from './albrecht/tables.js';
2
- import type { ChangeFactors, ChangeReasonFactors } from './albrecht/diff.js';
2
+ import type { ChangeFactors, ChangeReasonFactors, FactorPreset } from './albrecht/diff.js';
3
+ import type { TechnicalPattern } from './albrecht/technical_filter.js';
4
+ import type { OpaqueDeclaration } from './albrecht/opaque.js';
3
5
  import type { CallResolver } from './inventory/resolvers/types.js';
4
6
  import type { Complexity, FunctionType } from './types.js';
5
7
  /**
@@ -50,6 +52,16 @@ export type FunctionPointsConfig = {
50
52
  * filter missed, this one restores what it caught by accident.
51
53
  */
52
54
  business?: string[];
55
+ /**
56
+ * The naming conventions of the technical-data filter (AFP §6.5.2.1.3),
57
+ * tested against the physical table name.
58
+ *
59
+ * When set, this list REPLACES the defaults — the spec treats the patterns
60
+ * as user input, and a team whose business tables end in `_types` needs to
61
+ * drop that one, not add to it. `DEFAULT_TECHNICAL_PATTERNS` is exported to
62
+ * start from. Every exclusion still appears in the report with its label.
63
+ */
64
+ technicalPatterns?: TechnicalPattern[];
53
65
  /**
54
66
  * Entry points with no functional value to the user, by route name or by
55
67
  * identity (`GET /health`).
@@ -61,14 +73,17 @@ export type FunctionPointsConfig = {
61
73
  ignoreEntryPoints?: string[];
62
74
  };
63
75
  /**
64
- * Strategy for RET, the logical subgroups of an ILF/EIF.
76
+ * How tables fold into data functions — counting-decisions §10.
65
77
  *
66
- * `constant` pins it at 1, which is honest: what a user recognises as a
67
- * subgroup is not derivable from code. `composition` derives it from
68
- * composition relations; less accurate in general, but captures real
69
- * aggregates.
78
+ * `usage` (the default): a composition child (`hasMany` / `hasOne`) that no
79
+ * application code addresses directly is a RET of its parent, not an ILF of
80
+ * its own — the user only ever sees it inside the parent. `none` keeps every
81
+ * table its own data function at RET 1, which is what rule sets before 1.5.0
82
+ * did; it exists to compare against an old count, not as a preference.
70
83
  */
71
- retStrategy: 'constant' | 'composition';
84
+ dataFunctions?: {
85
+ grouping?: 'usage' | 'none';
86
+ };
72
87
  /**
73
88
  * Maximum depth in the call graph, starting at the handler.
74
89
  *
@@ -103,6 +118,16 @@ export type FunctionPointsConfig = {
103
118
  * this package invented would be worse. So the number comes from the contract.
104
119
  */
105
120
  diff?: {
121
+ /**
122
+ * Which published set of factors prices the change: `aep` (the default —
123
+ * added 1, changed 1, removed 0.4) or `sisp` (Roteiro de Métricas do SISP
124
+ * v3.0 §7.3 — inclusão 1,00, alteração × FI 0,63, exclusão 0,50; what a
125
+ * Brazilian public contract usually names). The FI is 0,84 when the
126
+ * contractor did not develop or maintain the function: set it with
127
+ * `factors: { changed: 0.84 }`. A contract binds to a revision of the guide
128
+ * — v2.0 priced exclusion at 0,40 — so check yours and override if it differs.
129
+ */
130
+ preset?: FactorPreset;
106
131
  factors?: Partial<ChangeFactors>;
107
132
  reasonFactors?: ChangeReasonFactors;
108
133
  };
@@ -126,71 +151,44 @@ export type FunctionPointsConfig = {
126
151
  */
127
152
  minCoverage?: number;
128
153
  /**
129
- * Declared DET or RET/FTR for a function the analysis cannot read, keyed by
130
- * the name it has in the count (`Invoice`, `POST /books`).
154
+ * What a person declares about a DET the analysis cannot read, keyed by its
155
+ * ORIGIN — counting-decisions §8:
156
+ *
157
+ * 'Survey.answers' a JSON column (model or table name)
158
+ * 'answerSurveyValidator.answers' an open field of a validator
131
159
  *
132
- * The case this exists for is a schema-driven application: when the fields a
133
- * user fills live in a JSON column whose schema is stored in the database,
134
- * there is nothing for static analysis to read and the column counts as 1 DET
135
- * (counting-decisions §8). The person who knows the form knows the number.
160
+ * A declaration applies to every function carrying that DET: the data
161
+ * function and each transaction that takes or shows the column. Keyed by
162
+ * function it had to be repeated, and still left the transactions nobody
163
+ * wrote it for at the floor — the same column worth two numbers in one count.
164
+ *
165
+ * `schemas` names the JSON Schema(s) in the code whose fields replace the
166
+ * floor; `reviewed` records that 1 is the right answer. Both require `reason`,
167
+ * and `fp:count` reports how much of the total came from a declaration so it
168
+ * cannot grow unnoticed.
169
+ */
170
+ opaque?: Record<string, OpaqueDeclaration>;
171
+ /**
172
+ * A declared DET or RET for ONE function, keyed by the name it has in the
173
+ * count (`Invoice`, `POST /books`) — the last resort, for a fact that is not
174
+ * in the code at all (a schema that lives only in the database).
136
175
  *
137
176
  * `reason` is required, and that is the whole point. A declared number is
138
177
  * reproducible — it lives in a versioned file, so the same revision yields
139
178
  * the same count — and auditable, because `fp:explain` prints it with its
140
- * justification. A number the tool guessed would be neither.
141
- *
142
- * Use sparingly. If overriding becomes a habit the count stops coming from
143
- * the code, and the report says how much of the total came from here so that
144
- * cannot grow unnoticed.
179
+ * justification. But it freezes: prefer `opaque.<origin>.schemas` whenever the
180
+ * fields are declared anywhere in the code.
145
181
  */
146
182
  overrides?: Record<string, FunctionOverride>;
147
183
  };
148
184
  export type FunctionOverride = {
149
185
  /** declared DET count, replacing what the analysis found */
150
186
  det?: number;
151
- /**
152
- * Name of a JSON Schema declared in the application's code, whose fields are
153
- * counted by the §7 leaf rules and replace the single DET the opaque column
154
- * contributed.
155
- *
156
- * Prefer this to `det`. A declared number freezes: someone adds a field, the
157
- * count does not move, and `fp:diff` reports no change for real functional
158
- * growth — undercounting silently and progressively. Naming the schema keeps
159
- * the number coming from the code; the only thing maintained by hand is the
160
- * mapping, which changes when a form is born rather than when a field is.
161
- *
162
- * A name that matches no schema is a warning, never a silent fallback.
163
- */
164
- /**
165
- * Name of a declared schema, or several whose fields are UNIONED.
166
- *
167
- * An ILF's DETs are the fields the user recognises in the file, and an
168
- * application with one schema per template recognises the fields of all of them.
169
- * Pointing at the largest and justifying it in `reason` gives the same answer
170
- * only while they land in the same complexity band — which is a piece of
171
- * reasoning the configuration should not have to carry.
172
- *
173
- * Unioned by leaf path, so a field two templates share counts once.
174
- */
175
- detFromSchema?: string | string[];
176
187
  /** declared RET (data function) or FTR (transaction) */
177
188
  refs?: number;
178
- /**
179
- * Opaque DETs someone has looked at and decided are correct at 1.
180
- *
181
- * `fp:count` reports every opaque column and open input object, because 1 DET is
182
- * a floor rather than a measurement. But some of them ARE one field — a copy, a
183
- * checksum, a bag of metadata — and there was no way to say so, so the warning
184
- * fired on every run forever. A warning that cannot be answered is a warning the
185
- * team learns to scroll past, which costs more than the one it reports.
186
- *
187
- * It silences nothing else: the count does not move, and `fp:count` still says
188
- * how many were reviewed. Names are matched bare (`schema`) or qualified
189
- * (`Petition.schema`).
190
- */
191
- opaqueReviewed?: string[];
192
189
  /** why — required, and printed by `fp:explain` beside the number */
193
190
  reason: string;
194
191
  };
192
+ export type { OpaqueDeclaration };
195
193
  export declare const DEFAULTS: FunctionPointsConfig;
196
194
  export declare function defineConfig(config: Partial<FunctionPointsConfig>): FunctionPointsConfig;
@@ -53,8 +53,50 @@ export type Behavior = {
53
53
  * not a validator.
54
54
  */
55
55
  requestFields: string[];
56
+ /** an ace command's `@flags.*` / `@args.*`: `flags.limite`, `args.name` — its input DETs */
57
+ commandFields: string[];
56
58
  /** the transaction reads the request in a way that enumerates nothing */
57
59
  opaqueRequest: boolean;
60
+ /**
61
+ * What leaves the boundary, when a transformer on the path says so —
62
+ * counting-decisions §6. Qualified by the transformer: `LivroTransformer.titulo`.
63
+ *
64
+ * Empty means no transformer was reached, and the output DETs fall back to the
65
+ * columns of the stores read. It does NOT mean the transaction emits nothing.
66
+ */
67
+ outputFields: string[];
68
+ /** of those, the spreads the walker could not read: 1 DET each, a floor, reported */
69
+ opaqueOutputFields: string[];
70
+ /** stores a transformer on the path is FOR: their columns are not output DETs, their keys are */
71
+ transformedStores: string[];
72
+ /**
73
+ * How each store was read, over every chain on the path: whether rows left
74
+ * whole, which columns a `.select()` named, and whether an aggregate
75
+ * (`.count()`, `.exists()`) left one scalar. What an output shows when no
76
+ * transformer covers the store.
77
+ */
78
+ outputReads: Record<string, {
79
+ whole: boolean;
80
+ selected: string[];
81
+ aggregate: boolean;
82
+ /** read by a chain of its own, not only preloaded through another store */
83
+ direct: boolean;
84
+ /** stores it was preloaded through */
85
+ via: string[];
86
+ }>;
87
+ /**
88
+ * What the transaction DELIVERS — plan 0.7 §A′. `any` says a delivery point was
89
+ * found at all; without one the output falls back to the stores read. `fields`
90
+ * are derived values and the leaves of literals a followed body returned,
91
+ * `render:total`; `stores` are the ones whose rows were handed on, raw or through
92
+ * a query object; `opaqueFields` are values nobody could read, 1 DET each.
93
+ */
94
+ delivered: {
95
+ any: boolean;
96
+ fields: string[];
97
+ opaqueFields: string[];
98
+ stores: string[];
99
+ };
58
100
  trace: TraceStep[];
59
101
  /** bodies reached, for `fp:diff` */
60
102
  scope: ScopeEntry[];
@@ -88,6 +130,8 @@ export type GraphOptions = {
88
130
  export declare function createAnalyzer(app: AppContext, stores: CollectedDataStore[], options?: GraphOptions): {
89
131
  analyze: (handler: HandlerRef) => Behavior;
90
132
  writtenAnywhere: () => Set<string>;
133
+ addressedAnywhere: () => Set<string>;
134
+ seededAnywhere: () => Set<string>;
91
135
  /** how many files the project loaded — used to prove it does not grow */
92
136
  fileCount: () => number;
93
137
  };