@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.
- package/CHANGELOG.md +170 -0
- package/README.md +101 -292
- package/build/{calibration-8eV8CEix.js → calibration-DVIf8hcE.js} +42 -3
- package/build/commands/main.js +6 -6
- package/build/{fp_calibrate-DUbHiifm.js → fp_calibrate-3TxGdS1b.js} +1 -1
- package/build/{fp_count-ChtblhZV.js → fp_count-arGLnVlY.js} +1 -1
- package/build/{fp_diff-Dt7J4IWu.js → fp_diff-DBBvzq5x.js} +1 -1
- package/build/{fp_explain-DZJ--0-S.js → fp_explain-aNFApwiT.js} +1 -1
- package/build/{fp_inventory-CPtmuuke.js → fp_inventory-DIjKIC9t.js} +1 -1
- package/build/{fp_metrics-et8F1Wvt.js → fp_metrics-BpU61waG.js} +1 -1
- package/build/index.d.ts +8 -4
- package/build/index.js +4 -4
- package/build/{pipeline-CNTBhs6o.js → pipeline-DO2301fV.js} +2131 -389
- package/build/{resolvers-PJwo2Z8R.js → resolvers-DaU4uAqT.js} +603 -165
- package/build/{runners-DIt1G85i.js → runners-Dm7cWGa-.js} +6 -3
- package/build/src/albrecht/counter.d.ts +38 -5
- package/build/src/albrecht/data_functions.d.ts +49 -3
- package/build/src/albrecht/diff.d.ts +27 -0
- package/build/src/albrecht/index.d.ts +1 -0
- package/build/src/albrecht/opaque.d.ts +90 -0
- package/build/src/albrecht/technical_filter.d.ts +18 -11
- package/build/src/albrecht/transactional_functions.d.ts +7 -0
- package/build/src/cli.js +2 -2
- package/build/src/define_config.d.ts +55 -57
- package/build/src/inventory/graph/call_graph.d.ts +44 -0
- package/build/src/inventory/graph/deliveries.d.ts +88 -0
- package/build/src/inventory/graph/output_fields.d.ts +143 -0
- package/build/src/inventory/paths.d.ts +2 -0
- package/build/src/inventory/resolvers/index.d.ts +21 -0
- package/build/src/inventory/resolvers/index.js +2 -2
- package/build/src/inventory/resolvers/job_dispatch.d.ts +20 -0
- package/build/src/inventory/resolvers/local_function.d.ts +24 -0
- package/build/src/inventory/resolvers/transformer.d.ts +0 -23
- package/build/src/inventory/sources/commands.d.ts +14 -0
- package/build/src/inventory/sources/jobs.d.ts +27 -0
- package/build/src/pipeline.js +1 -1
- package/build/src/types.d.ts +49 -1
- package/build/stubs/config.stub +29 -16
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { c as
|
|
2
|
-
import {
|
|
3
|
-
import { n as analyze } from "./pipeline-
|
|
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 —
|
|
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.
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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;
|
|
@@ -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
|
-
*
|
|
16
|
-
*
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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
|
-
*
|
|
76
|
+
* How tables fold into data functions — counting-decisions §10.
|
|
65
77
|
*
|
|
66
|
-
* `
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
130
|
-
*
|
|
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
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
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.
|
|
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
|
};
|