@filipebraida/adonis-function-points 0.5.0 → 0.6.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 +102 -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-EAuAtdbq.js} +1 -1
- package/build/{fp_count-ChtblhZV.js → fp_count-CZ0cUUBQ.js} +1 -1
- package/build/{fp_diff-Dt7J4IWu.js → fp_diff-BTg_LX0r.js} +1 -1
- package/build/{fp_explain-DZJ--0-S.js → fp_explain-D6QvDLKQ.js} +1 -1
- package/build/{fp_inventory-CPtmuuke.js → fp_inventory-C43fU39x.js} +1 -1
- package/build/{fp_metrics-et8F1Wvt.js → fp_metrics-DEMPk4xC.js} +1 -1
- package/build/index.d.ts +8 -4
- package/build/index.js +4 -4
- package/build/{pipeline-CNTBhs6o.js → pipeline-Cq4dNTNE.js} +763 -313
- package/build/{resolvers-PJwo2Z8R.js → resolvers-DlKJOZnk.js} +328 -63
- package/build/{runners-DIt1G85i.js → runners-FYmPIPub.js} +6 -3
- package/build/src/albrecht/counter.d.ts +31 -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 +29 -0
- package/build/src/inventory/graph/output_fields.d.ts +99 -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/pipeline.js +1 -1
- package/build/src/types.d.ts +30 -1
- package/build/stubs/config.stub +29 -16
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,108 @@ release moves the number for unchanged code, the rule set version moves with it
|
|
|
6
6
|
otherwise the difference would measure the tool's change rather than the work, and
|
|
7
7
|
that difference becomes an invoice.
|
|
8
8
|
|
|
9
|
+
## 0.6.0
|
|
10
|
+
|
|
11
|
+
**Rule set `afp@1.5.0`.** Six rules change what a number is made of, all of them found
|
|
12
|
+
by counting real applications function by function against what a certified counter
|
|
13
|
+
would write down — and every one was frozen in a fixture with a hand-written reference
|
|
14
|
+
**before** the code. A 0.5.0 baseline has to be recounted; on the three applications
|
|
15
|
+
this release was reviewed against the totals move −18, −8 and −37 FP, and the whole
|
|
16
|
+
of it is EOs losing DETs they never showed and one data function that was four.
|
|
17
|
+
|
|
18
|
+
### Counting
|
|
19
|
+
|
|
20
|
+
- **An output's DETs are what leaves the boundary, not every column read.** A
|
|
21
|
+
transformer decides the output of the store it is FOR (`BaseTransformer<X>`, and the
|
|
22
|
+
resources of the transformers nested in it): the keys the reached method returns, a
|
|
23
|
+
nested transformer's keys once, `this.pick([...])` by name, `xs.map(...)` as a
|
|
24
|
+
repeating group. For every other store touched, what leaves is what the code shows:
|
|
25
|
+
rows whole (every column), the columns a `.select()` names, or **one derived scalar**
|
|
26
|
+
for `.count()` / `.exists()`. A relation preloaded through a covered store and read
|
|
27
|
+
no other way is covered too — it was loaded for the transformer. A spread the walker
|
|
28
|
+
cannot read counts 1 DET as a floor and is reported, like an open input object.
|
|
29
|
+
Before, a detail page through three transformers came out at 67 DET; a dashboard of
|
|
30
|
+
eight counters at 84.
|
|
31
|
+
- **A system timestamp is not a DET.** `autoCreate` / `autoUpdate` say the framework
|
|
32
|
+
stamps the column; the user neither supplies nor recognises it — the ground the key
|
|
33
|
+
was already excluded on. Excluded on the data function, on every output, and on a
|
|
34
|
+
transformer that re-emits it. A `dateTime` the user sets still counts. AFP §7.2 on
|
|
35
|
+
its letter would count both; the departure is now consistent, and counting-decisions
|
|
36
|
+
§6 says so.
|
|
37
|
+
- **A column declared `serializeAs: null` never leaves.** Lucid does not serialise it,
|
|
38
|
+
so it is not an output DET however the store leaves. It stays a DET of the data
|
|
39
|
+
function. Found as `User.password` on an activity log's output.
|
|
40
|
+
- **A detail the user only sees inside its master is a RET, not a data function.** A
|
|
41
|
+
`hasMany` / `hasOne` child that no application code addresses directly — only
|
|
42
|
+
`related()` / `preload()` from the parent — folds into the parent: one ILF with N RET,
|
|
43
|
+
the child's link to the parent excluded from the DETs, one FTR for a transaction
|
|
44
|
+
touching both. A child with a query of its own stays its own file (the Vazquez
|
|
45
|
+
benchmark depends on it); a child hanging off two parents stays apart and the report
|
|
46
|
+
says why. Cascade delete was measured and rejected as the signal: on one application
|
|
47
|
+
11 of 13 cascades pointed at the tenant table. On the three applications the rule
|
|
48
|
+
folds four stores in all — the `Inpi*` mirror of an external registry becomes **one**
|
|
49
|
+
EIF with 4 RET, which is what the CPM says.
|
|
50
|
+
- **A data function is identified by its table**, as counting-decisions §5 always said.
|
|
51
|
+
Keyed by the class, renaming a model billed as a deletion plus an addition.
|
|
52
|
+
- **A token table is technical.** `password_reset_tokens`, `auth_access_tokens`,
|
|
53
|
+
`remember_me_tokens` are the machinery of authentication; `.*tokens?.*` joins the
|
|
54
|
+
naming list. And the list is now configurable in fact — `boundary.technicalPatterns`
|
|
55
|
+
replaces it, `DEFAULT_TECHNICAL_PATTERNS` is exported to start from — as §4 and the
|
|
56
|
+
filter's own comment had claimed since 0.1.0 while `counter.ts` passed nothing.
|
|
57
|
+
|
|
58
|
+
### Configuration
|
|
59
|
+
|
|
60
|
+
- **`opaque.<Store.column | validator.field>`** replaces `overrides.<fn>.detFromSchema`
|
|
61
|
+
and `overrides.<fn>.opaqueReviewed`. A declaration about a DET the analysis cannot read
|
|
62
|
+
is about the column or the validator field — its ORIGIN — and applies to every
|
|
63
|
+
function that carries it: the ILF, the transaction that submits it, each screen that
|
|
64
|
+
shows it. Read from a real configuration, the old shape had the same mapping written
|
|
65
|
+
twice and a `GET` returning the same column still at 1 DET: the same column worth two
|
|
66
|
+
numbers in one count. Reviews are matched exactly; matched by bare name, reviewing
|
|
67
|
+
`Message.schema` reviewed every `schema` column of every store. A column may be keyed
|
|
68
|
+
by model or by table. The two old keys are gone from the type — an old configuration
|
|
69
|
+
fails to typecheck — and, because a configuration file is loaded without types,
|
|
70
|
+
`fp:count` still tells one that carries them that they had no effect.
|
|
71
|
+
`overrides.<fn>.det` / `.refs` remain per function.
|
|
72
|
+
- **`dataFunctions.grouping: 'usage' | 'none'`** replaces `retStrategy`. `none` is the
|
|
73
|
+
0.5.0 behaviour — every table its own data function at RET 1 — for comparing with an
|
|
74
|
+
old count; it is not a preference. `retStrategy` is gone from the type, and `fp:count`
|
|
75
|
+
says so when an untyped configuration still carries it.
|
|
76
|
+
- **`boundary.technicalPatterns`**, see above.
|
|
77
|
+
|
|
78
|
+
### New
|
|
79
|
+
|
|
80
|
+
- **The count names what it cannot decide.** Two transactions of the same type that
|
|
81
|
+
reach the same stores, emit the same DETs and walk the same bodies below the
|
|
82
|
+
controller are reported as look-alikes with the FP at stake — the CPM counts
|
|
83
|
+
identical processing logic once, and `boundary.ignoreEntryPoints` records the
|
|
84
|
+
decision. An EIF only a seeder writes is reported too: code data the team maintains
|
|
85
|
+
is not counted, a mirror of another system's data is a legitimate EIF, and the code
|
|
86
|
+
cannot tell which. Neither moves a number (counting-decisions §11).
|
|
87
|
+
- **`diff.preset: 'sisp'`** prices change by the Roteiro de Métricas de Software do SISP
|
|
88
|
+
v3.0 (Portaria SGD/MGI nº 3656/2026), §7.3 — inclusão 1,00, alteração × FI 0,63 (the
|
|
89
|
+
contractor maintains its own work; 0,84 otherwise, via `factors`), exclusão 0,50 —
|
|
90
|
+
instead of AEP. `fp:diff` prints which preset produced the billable total. Read from
|
|
91
|
+
the guide's PDF: a first draft of this preset said 0,50 / 0,30 from memory, and v2.0
|
|
92
|
+
(2012) priced exclusion at 0,40 — a contract binds to a revision, so check yours.
|
|
93
|
+
- **`ignoreCalls({ name, methods | matching })`** builds a "this reaches no data"
|
|
94
|
+
strategy without the ceremony a real configuration had to carry — a helper to read
|
|
95
|
+
the method name off a ts-morph node, a `resolve` that returns nothing, one comparison.
|
|
96
|
+
The strategy is still named, and its volume is still reported.
|
|
97
|
+
- The configuration stub no longer echoes defaults.
|
|
98
|
+
|
|
99
|
+
### Documented
|
|
100
|
+
|
|
101
|
+
- counting-decisions §6 now describes what the code does for output DETs, row by row,
|
|
102
|
+
and records the two refinements the recounts forced; §4 says plainly that only the
|
|
103
|
+
naming mechanism of the technical filter exists, and why the lookup-structure rule
|
|
104
|
+
was rejected; §9 gains "Declared by origin, not by function"; §10 is the master-detail
|
|
105
|
+
rule, with the cascade measurement that rejected the structural signal; §11 is what
|
|
106
|
+
the count reports because it cannot decide.
|
|
107
|
+
- Five reference fixtures were written before their rules: `transformed_output` (59 FP,
|
|
108
|
+
eight output shapes), `system_timestamps` (18), `mestre_detalhe` (41; 51 with
|
|
109
|
+
grouping off), and additions to `edges_boundary` and `open_object`.
|
|
110
|
+
|
|
9
111
|
## 0.5.0
|
|
10
112
|
|
|
11
113
|
**Rule set `afp@1.4.0`.** Two classification defects are fixed and both move numbers,
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ node ace fp:count
|
|
|
13
13
|
|
|
14
14
|
```
|
|
15
15
|
Unadjusted count: 46 FP
|
|
16
|
-
Ruleset: afp@1.
|
|
16
|
+
Ruleset: afp@1.5.0
|
|
17
17
|
|
|
18
18
|
type n FP
|
|
19
19
|
ILF 2 14
|
|
@@ -34,18 +34,6 @@ GET /presenca EO 13 3 5
|
|
|
34
34
|
GET /presenca/relatorio EO 13 3 5
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
## Why
|
|
38
|
-
|
|
39
|
-
Software factories bill by function point, and the count is manual, slow, and
|
|
40
|
-
varies from counter to counter. Commercial automated counters exist for
|
|
41
|
-
enterprise legacy, but **no modern framework has one** — not Laravel, not Rails,
|
|
42
|
-
not AdonisJS. What those ecosystems do have (`rails stats`, `laravel-stats`,
|
|
43
|
-
`adonisjs-stats`) counts classes and lines, which is a different thing.
|
|
44
|
-
|
|
45
|
-
This package implements the OMG **Automated Function Points** specification,
|
|
46
|
-
which defines how to automate IFPUG CPM by replacing the subjective judgements
|
|
47
|
-
with deterministic rules.
|
|
48
|
-
|
|
49
37
|
## Install
|
|
50
38
|
|
|
51
39
|
```bash
|
|
@@ -53,91 +41,17 @@ npm i @filipebraida/adonis-function-points
|
|
|
53
41
|
node ace configure @filipebraida/adonis-function-points
|
|
54
42
|
```
|
|
55
43
|
|
|
56
|
-
Requires **AdonisJS 7** and **Lucid 22** (see [Support](#support)).
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
For CI, or a one-off count on a project you do not want to touch:
|
|
44
|
+
Requires **AdonisJS 7** and **Lucid 22** (see [Support](#support)). In CI, or
|
|
45
|
+
for a one-off count on a project you do not want to touch, the standalone
|
|
46
|
+
binary needs no install, no `.env` and no database:
|
|
61
47
|
|
|
62
48
|
```bash
|
|
63
49
|
npx @filipebraida/adonis-function-points count --root ./my-app
|
|
64
50
|
```
|
|
65
51
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
**In CI, prefer the standalone binary.** Not for convenience: `node ace` validates
|
|
71
|
-
`start/env.ts` before it runs any command, so `node ace fp:count` fails on a
|
|
72
|
-
missing environment variable that has nothing to do with counting. Measured on a
|
|
73
|
-
production application, it stopped at `Missing environment variable "AUTHZ_STORE"`
|
|
74
|
-
and never reached the command. A pipeline that only checks out the code has no
|
|
75
|
-
secrets, and does not need them to count.
|
|
76
|
-
|
|
77
|
-
The standalone binary **does not replace installing**: a project that installs the
|
|
78
|
-
package keeps the `node ace fp:*` commands — which is the right front-end at a
|
|
79
|
-
developer's terminal, where the `.env` is already there — and both call the same
|
|
80
|
-
code, so they cannot disagree about a number.
|
|
81
|
-
|
|
82
|
-
```
|
|
83
|
-
adonis-function-points <command> [options]
|
|
84
|
-
|
|
85
|
-
count count the unadjusted function points
|
|
86
|
-
inventory the raw facts: stores, routes, tracing coverage
|
|
87
|
-
explain <name> why one function was counted that way
|
|
88
|
-
metrics density, coupling and conformance, from the same run
|
|
89
|
-
diff <previous.json> additions / modifications / deletions, and billable FP
|
|
90
|
-
calibrate <samples.csv> correction factors against a manual count
|
|
91
|
-
|
|
92
|
-
--root <path> application to analyse (default: the current directory)
|
|
93
|
-
--out <path> write the result as JSON to this path
|
|
94
|
-
--json print JSON instead of a table
|
|
95
|
-
--min-coverage <0..1> fail below this tracing coverage
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
Both front-ends exit non-zero when the count cannot be produced — coverage
|
|
99
|
-
below the minimum, an unreadable configuration, a saved count from a different
|
|
100
|
-
ruleset — so a CI job fails instead of publishing a number nobody can defend.
|
|
101
|
-
|
|
102
|
-
#### In CI
|
|
103
|
-
|
|
104
|
-
Every count records what it counted, so the artefact stands on its own once it
|
|
105
|
-
leaves the pipeline:
|
|
106
|
-
|
|
107
|
-
```json
|
|
108
|
-
"source": {
|
|
109
|
-
"app": "shop",
|
|
110
|
-
"revision": "adef4ee3…",
|
|
111
|
-
"branch": "main",
|
|
112
|
-
"dirty": false,
|
|
113
|
-
"countedAt": "2026-09-24T17:40:11.000Z",
|
|
114
|
-
"config": "/app/config/function_points.ts"
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
`dirty` is the field that matters in billing: a count taken over uncommitted
|
|
119
|
-
changes cannot be reproduced from any revision, and whoever receives the
|
|
120
|
-
invoice is entitled to know that. `app` is the manifest name, never an absolute
|
|
121
|
-
path — a path would say where your machine keeps its files and travel with
|
|
122
|
-
every count you send anywhere.
|
|
123
|
-
|
|
124
|
-
`fp:diff` refuses two counts of different applications, the same way it refuses
|
|
125
|
-
two different rulesets, and warns when either side is dirty or when both are
|
|
126
|
-
the same revision.
|
|
127
|
-
|
|
128
|
-
Counting an older revision needs no checkout of your working tree and nothing
|
|
129
|
-
installed in it, so a pull request is two counts and a comparison:
|
|
130
|
-
|
|
131
|
-
```yaml
|
|
132
|
-
- run: git worktree add ../base ${{ github.event.pull_request.base.sha }}
|
|
133
|
-
- run: npx @filipebraida/adonis-function-points count --root ../base --out base.json
|
|
134
|
-
- run: npx @filipebraida/adonis-function-points count --out head.json
|
|
135
|
-
- run: npx @filipebraida/adonis-function-points diff base.json head.json
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
The package does not deliver the result anywhere — an artifact, a ledger
|
|
139
|
-
branch, a billing endpoint and a PR comment are all yours to choose. What it
|
|
140
|
-
owes you is a number that is still defensible wherever it lands.
|
|
52
|
+
Both front-ends call the same code and cannot disagree about a number. Which
|
|
53
|
+
one to use where, what a saved count records, and how a pull request becomes
|
|
54
|
+
two counts and a comparison: [`docs/ci.md`](docs/ci.md).
|
|
141
55
|
|
|
142
56
|
## Commands
|
|
143
57
|
|
|
@@ -155,73 +69,25 @@ saved count against the current state of the application. It deliberately does
|
|
|
155
69
|
**not** take a git ref: booting an older checkout, with possibly different
|
|
156
70
|
dependencies, is a problem not worth solving.
|
|
157
71
|
|
|
158
|
-
### How change is priced
|
|
159
|
-
|
|
160
|
-
AEP §6.5 gives explicit anchors for added (1) and deleted (0.4). For a **modified**
|
|
161
|
-
function it grades the factor from 0.25 to 1.75 through Effort Complexity
|
|
162
|
-
variation, which needs cyclomatic complexity this package does not measure — so it
|
|
163
|
-
defaults to 1, which overestimates, and every diff says so with the amount at
|
|
164
|
-
stake.
|
|
165
|
-
|
|
166
|
-
What the default leaves on the table is a distinction the tool already measures:
|
|
167
|
-
|
|
168
|
-
```
|
|
169
|
-
changed 87 functions 378 FP × 1
|
|
170
|
-
type 4 functions 23 FP
|
|
171
|
-
size 38 functions 204 FP
|
|
172
|
-
implementation 45 functions 151 FP
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
`implementation` means same type, same DET, same FTR, different body — a refactor.
|
|
176
|
-
On a real pair of releases that was 151 of 378 FP billed as change. Pricing it at
|
|
177
|
-
full functional value is not defensible, and pricing it at a number this package
|
|
178
|
-
invented would be worse, so the number comes from the contract:
|
|
179
|
-
|
|
180
|
-
```ts
|
|
181
|
-
export default defineConfig({
|
|
182
|
-
diff: {
|
|
183
|
-
reasonFactors: { implementation: 0.25 },
|
|
184
|
-
},
|
|
185
|
-
})
|
|
186
72
|
```
|
|
73
|
+
adonis-function-points <command> [options]
|
|
187
74
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
75
|
+
count count the unadjusted function points
|
|
76
|
+
inventory the raw facts: stores, routes, tracing coverage
|
|
77
|
+
explain <name> why one function was counted that way
|
|
78
|
+
metrics density, coupling and conformance, from the same run
|
|
79
|
+
diff <previous.json> additions / modifications / deletions, and billable FP
|
|
80
|
+
calibrate <samples.csv> correction factors against a manual count
|
|
193
81
|
|
|
82
|
+
--root <path> application to analyse (default: the current directory)
|
|
83
|
+
--out <path> write the result as JSON to this path
|
|
84
|
+
--json print JSON instead of a table
|
|
85
|
+
--min-coverage <0..1> fail below this tracing coverage
|
|
194
86
|
```
|
|
195
|
-
Density
|
|
196
|
-
FP per data store: 27.0
|
|
197
|
-
transactions per data store: 5.4
|
|
198
|
-
|
|
199
|
-
Conformance
|
|
200
|
-
inputs with a validator 100.0% (30/30)
|
|
201
|
-
entry points with a handler 100.0% (163/163)
|
|
202
|
-
data stores reached 96.7% (29/30)
|
|
203
|
-
tracing coverage 95.1% (15 unresolved calls)
|
|
204
|
-
|
|
205
|
-
module FP trans stores I depends on
|
|
206
|
-
pedidos 211 33 8 0.60 inventores, tecnologias, users
|
|
207
|
-
portal 69 15 0 1.00 contato, documentos, inventores, ...
|
|
208
|
-
inpi 70 11 6 0.00
|
|
209
|
-
|
|
210
|
-
Mutual dependencies (cycle candidates)
|
|
211
|
-
inventores <-> tecnologias
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
The denominator of the first line is the transactions that **take** input, not
|
|
215
|
-
every write. Measured over every write it read 39% on a healthy application, which
|
|
216
|
-
invites the conclusion that 61% of its writes are unvalidated — and they are not:
|
|
217
|
-
most are workflow triggers (`POST /orders/:id/submit`) that carry nothing beyond
|
|
218
|
-
the route parameter. A metric that makes the reader draw a false conclusion is
|
|
219
|
-
worse than no metric.
|
|
220
87
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
the team's decision, and a number would hide it.
|
|
88
|
+
Every command exits non-zero when the count cannot be produced — coverage
|
|
89
|
+
below the minimum, an unreadable configuration, a saved count from a different
|
|
90
|
+
rule set — so a CI job fails instead of publishing a number nobody can defend.
|
|
225
91
|
|
|
226
92
|
### `fp:explain` — the number has to be defensible
|
|
227
93
|
|
|
@@ -247,72 +113,22 @@ Path walked:
|
|
|
247
113
|
If function points get invoiced, someone will dispute a number — and a number
|
|
248
114
|
without provenance is indefensible.
|
|
249
115
|
|
|
250
|
-
|
|
116
|
+
### `fp:metrics` — the counterweight
|
|
251
117
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
Eight of the ten functions match exactly. The two that do not were **predicted
|
|
268
|
-
in writing before the run**, and come from the standard rather than from
|
|
269
|
-
defects:
|
|
270
|
-
|
|
271
|
-
- **+1** `Consulta Apontamento Diário` is an EQ in the reference; AFP §6.5.3
|
|
272
|
-
requires collapsing EQ into EO, and an EO weighs more in the same band.
|
|
273
|
-
- **−1** `Apontamento c/ Justificativa`: the IFPUG manual counts 1 DET for the
|
|
274
|
-
user message, AFP does not.
|
|
275
|
-
|
|
276
|
-
They cancel out, which is exactly why the total is reported alongside the
|
|
277
|
-
function-by-function agreement rather than on its own.
|
|
278
|
-
|
|
279
|
-
Reproduce it with `npm test` — the benchmark is
|
|
280
|
-
`tests/acceptance/vazquez.spec.ts`, and the reference is
|
|
281
|
-
[`tests/fixtures/apps/vazquez/REFERENCE.md`](tests/fixtures/apps/vazquez/REFERENCE.md).
|
|
282
|
-
|
|
283
|
-
## Principles
|
|
284
|
-
|
|
285
|
-
**Traceability.** Every counted function says where it came from: file, line,
|
|
286
|
-
rule applied, origin of each DET and each FTR, and the path walked through the
|
|
287
|
-
call graph. The ruleset is versioned and printed in every report — two counts
|
|
288
|
-
are only comparable if the rules did not change in between.
|
|
289
|
-
|
|
290
|
-
**Say "I don't know" rather than be wrong in silence.** A call the tracer cannot
|
|
291
|
-
follow enters the coverage metric. If coverage falls below the configured
|
|
292
|
-
threshold, the analysis **fails** instead of emitting a number that looks right.
|
|
293
|
-
This is not a preference; AFP §6.5.3 requires it:
|
|
294
|
-
|
|
295
|
-
> "If the transaction execution depends on code that is unknown or unavailable
|
|
296
|
-
> to the automated tool, the code end point shall be cataloged and listed in the
|
|
297
|
-
> generated report in order to detect and quantify the missing patterns and
|
|
298
|
-
> libraries."
|
|
299
|
-
|
|
300
|
-
**Shape must not change the count.** The same logical application written in
|
|
301
|
-
different ways — flat MVC or module-per-domain, fat controller or action object,
|
|
302
|
-
generated artefacts or none — must produce an identical number. That is the
|
|
303
|
-
project's golden invariant, and it is a test
|
|
304
|
-
(`tests/acceptance/golden_invariant.spec.ts`) that was written before the first
|
|
305
|
-
collector.
|
|
306
|
-
|
|
307
|
-
**Extensibility as a requirement.** AdonisJS imposes no code organisation — fat
|
|
308
|
-
controller, action object, static service, injected service, module function,
|
|
309
|
-
job. Tracing strategies are registrable, so a project with its own convention
|
|
310
|
-
registers it (see [Custom code pattern](#custom-code-pattern)).
|
|
311
|
-
|
|
312
|
-
**Function points are not the only number on the dashboard.** If function points
|
|
313
|
-
pay, the team optimises function points: more models, more endpoints, less
|
|
314
|
-
reuse. Coupling, instability and density come free from the same inventory, and
|
|
315
|
-
are the counterweight.
|
|
118
|
+
If function points pay, the team optimises function points: more models, more
|
|
119
|
+
endpoints, less reuse. Density, coupling, instability and conformance come free
|
|
120
|
+
from the same inventory and are reported beside the number — what each metric
|
|
121
|
+
means and what it deliberately does not measure: [`docs/metrics.md`](docs/metrics.md).
|
|
122
|
+
|
|
123
|
+
### `fp:diff` — what change is worth
|
|
124
|
+
|
|
125
|
+
Additions, modifications and deletions between two saved counts, priced by the
|
|
126
|
+
factors the contract names: AEP by default, or `diff: { preset: 'sisp' }` for the
|
|
127
|
+
Roteiro de Métricas do SISP v3.0 (inclusão 1,00, alteração × FI 0,63, exclusão
|
|
128
|
+
0,50; `factors: { changed: 0.84 }` when the contractor did not develop the function). A modification is split by **why** it changed —
|
|
129
|
+
type, size, or implementation only — so a refactor is visible before it is
|
|
130
|
+
billed at full value. The reasoning, and what the default leaves on the table:
|
|
131
|
+
[counting-decisions §5](docs/design/counting-decisions.md#5-identity-of-a-function-across-versions-fpdiff).
|
|
316
132
|
|
|
317
133
|
## Configuration
|
|
318
134
|
|
|
@@ -332,10 +148,11 @@ export default defineConfig({
|
|
|
332
148
|
infrastructure: ['access_tokens', 'audits'], // excluded, with the reason in the report
|
|
333
149
|
externallyMaintained: ['erp_customers'], // counted as EIF instead of ILF
|
|
334
150
|
business: ['chat_sessions'], // the AFP naming filter caught it by accident
|
|
151
|
+
// technicalPatterns: [...DEFAULT_TECHNICAL_PATTERNS], // replaces the filter's naming list
|
|
335
152
|
ignoreEntryPoints: ['prometheus.metrics'],
|
|
336
153
|
},
|
|
337
154
|
|
|
338
|
-
|
|
155
|
+
dataFunctions: { grouping: 'usage' }, // 'none' keeps every table its own data function
|
|
339
156
|
maxDepth: 3, // how far to follow the call graph
|
|
340
157
|
messageDet: 0, // 1 restores the IFPUG confirmation-message DET
|
|
341
158
|
minCoverage: 0.85, // below this, the analysis fails
|
|
@@ -391,22 +208,25 @@ over a rate limiter or an attachment variant is a real example. Without a way
|
|
|
391
208
|
to say so, such a call stays unresolved and drags the coverage gate down.
|
|
392
209
|
|
|
393
210
|
```ts
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
211
|
+
import { ignoreCalls } from '@filipebraida/adonis-function-points'
|
|
212
|
+
|
|
213
|
+
export default defineConfig({
|
|
214
|
+
resolvers: {
|
|
215
|
+
call: [
|
|
216
|
+
ignoreCalls({ name: 'login-limiter', matching: /^this\.loginLimiter\./ }),
|
|
217
|
+
ignoreCalls({ name: 'attachment-variants', methods: ['getUrl', 'getVariant'] }),
|
|
218
|
+
],
|
|
401
219
|
},
|
|
402
|
-
}
|
|
220
|
+
})
|
|
403
221
|
```
|
|
404
222
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
223
|
+
Or, for a shape the factory does not cover, a strategy with `ignores(call)`
|
|
224
|
+
returning `true` — "this is mine, and it touches no data store". `ignores` is
|
|
225
|
+
asked before `resolve`, in the same order, so a later and more generic strategy
|
|
226
|
+
cannot follow the call into a body it has no business reading. It stays a
|
|
227
|
+
**named** strategy on purpose: the volume it declared data-free still appears in
|
|
228
|
+
the confidence block of `fp:count`, because a silent drop is the worst defect
|
|
229
|
+
this package can have — whoever writes it.
|
|
410
230
|
|
|
411
231
|
## Support
|
|
412
232
|
|
|
@@ -426,9 +246,12 @@ Inherited from the AFP standard itself, not from this implementation:
|
|
|
426
246
|
- **EQ is collapsed into EO.** Telling an inquiry from an output requires
|
|
427
247
|
knowing whether there is derived data or calculation, which static analysis
|
|
428
248
|
cannot see. AFP mandates the collapse.
|
|
429
|
-
- **RET
|
|
430
|
-
|
|
431
|
-
|
|
249
|
+
- **RET comes from usage, not from the user's view.** A `hasMany`/`hasOne`
|
|
250
|
+
child that no application code addresses directly folds into its parent as a
|
|
251
|
+
RET; one that has its own queries stays its own data function. That is the
|
|
252
|
+
only signal static analysis has, and it is conservative: it groups only when
|
|
253
|
+
the code cannot see the child apart from the parent. A child hanging off two
|
|
254
|
+
parents stays apart and is reported.
|
|
432
255
|
- **Confirmation and error messages** count 1 DET in a manual count and are
|
|
433
256
|
invisible here — a known systematic divergence of −1 DET per transaction.
|
|
434
257
|
`messageDet: 1` restores it.
|
|
@@ -436,44 +259,42 @@ Inherited from the AFP standard itself, not from this implementation:
|
|
|
436
259
|
judgement. AFP fixes VAF = 1, and the unadjusted count is what public
|
|
437
260
|
contracts in Brazil use anyway.
|
|
438
261
|
- **The modification factor in `fp:diff` is 1.** AEP grades it from 0.25 to
|
|
439
|
-
1.75
|
|
440
|
-
does not
|
|
441
|
-
the report says so.
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
```
|
|
447
|
-
changed 74 functions 318 FP × 1
|
|
448
|
-
type 1 functions 3 FP reclassified, e.g. EO -> EI
|
|
449
|
-
size 28 functions 141 FP DET or FTR moved
|
|
450
|
-
implementation 45 functions 174 FP same size, different code
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
On a real month of work that is 44% of the invoice coming from refactoring.
|
|
454
|
-
Whether that should be billed at full value is a contract decision, not a
|
|
455
|
-
counting one — but it has to be visible before anyone can make it.
|
|
456
|
-
|
|
262
|
+
1.75 through Effort Complexity, which needs cyclomatic complexity this package
|
|
263
|
+
does not measure. A flat 1 prices a one-line fix and a rewrite the same, and
|
|
264
|
+
the report says so with the amount at stake. What it does discriminate is
|
|
265
|
+
**why** a function changed — type, size, or implementation only — and
|
|
266
|
+
`diff.reasonFactors` prices a refactor by the contract rather than by a number
|
|
267
|
+
the tool invented. See counting-decisions §5.
|
|
457
268
|
- **Schema-driven applications undercount their input.** When the fields a user
|
|
458
269
|
fills live in a JSON column whose schema is stored in the database, there is
|
|
459
|
-
nothing for static analysis to read:
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
270
|
+
nothing for static analysis to read: the column counts as 1 DET — a floor,
|
|
271
|
+
and `fp:count` names it on every run. The way out is to declare where the
|
|
272
|
+
fields live, by origin, and the declaration reaches every function that
|
|
273
|
+
carries the column:
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
opaque: {
|
|
277
|
+
'Survey.answers': { schemas: 'surveySchema', reason: 'the form is a JSON Schema in the seed' },
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
The reason is required, `fp:explain` prints it beside the number, and
|
|
282
|
+
`fp:count` reports what share of the total came from a declaration.
|
|
283
|
+
`overrides.<fn>.det` remains for a schema that lives only in the database.
|
|
284
|
+
See counting-decisions §8.
|
|
472
285
|
|
|
473
286
|
- **Only HTTP routes are collected as entry points.** An ace command that
|
|
474
287
|
imports a spreadsheet and a scheduled job are transactional functions under
|
|
475
288
|
IFPUG; they are out of v1.
|
|
476
289
|
|
|
290
|
+
## Benchmark
|
|
291
|
+
|
|
292
|
+
The only reference in this project not produced by its own authors is the case
|
|
293
|
+
study in **Vazquez, Simões & Albert (2011)**: 46 FP by hand, **46 FP** here, 8 of
|
|
294
|
+
10 functions exact and the two that differ predicted in writing before the run.
|
|
295
|
+
How the fixture was frozen, what Ligeiro got, and why the total is more
|
|
296
|
+
defensible than any single function: [`docs/benchmark.md`](docs/benchmark.md).
|
|
297
|
+
|
|
477
298
|
## References
|
|
478
299
|
|
|
479
300
|
- **OMG Automated Function Points (AFP) 1.0** — ISO/IEC 19515:2019. The
|
|
@@ -495,44 +316,32 @@ Inherited from the AFP standard itself, not from this implementation:
|
|
|
495
316
|
|
|
496
317
|
The reasoning behind the count lives with the code:
|
|
497
318
|
|
|
498
|
-
- [`docs/design/architecture.md`](docs/design/architecture.md) — the
|
|
499
|
-
layers, and what is discovered instead of configured
|
|
319
|
+
- [`docs/design/architecture.md`](docs/design/architecture.md) — why the package
|
|
320
|
+
exists, its principles, the layers, and what is discovered instead of configured
|
|
500
321
|
- [`docs/design/counting-decisions.md`](docs/design/counting-decisions.md) —
|
|
501
322
|
each edge case, with the AFP rule that settles it
|
|
502
323
|
- [`docs/design/resolvers.md`](docs/design/resolvers.md) — the catalogue of code
|
|
503
324
|
patterns and how each is followed
|
|
325
|
+
- [`docs/benchmark.md`](docs/benchmark.md), [`docs/ci.md`](docs/ci.md),
|
|
326
|
+
[`docs/metrics.md`](docs/metrics.md)
|
|
504
327
|
|
|
505
328
|
Three further documents are kept as a **dated record** of how the design was
|
|
506
329
|
arrived at, in Portuguese, and are not a reference for current behaviour:
|
|
507
|
-
[`implementation-plan.md`](docs/design/implementation-plan.md)
|
|
508
|
-
|
|
509
|
-
[`
|
|
510
|
-
between real AdonisJS applications) and
|
|
511
|
-
[`external-validation.md`](docs/research/external-validation.md) (the thesis
|
|
512
|
-
tested outside the sample that produced it).
|
|
330
|
+
[`implementation-plan.md`](docs/design/implementation-plan.md),
|
|
331
|
+
[`adonisjs-variation.md`](docs/research/adonisjs-variation.md) and
|
|
332
|
+
[`external-validation.md`](docs/research/external-validation.md).
|
|
513
333
|
|
|
514
334
|
## Contributing
|
|
515
335
|
|
|
516
336
|
```bash
|
|
517
337
|
pnpm install
|
|
518
|
-
pnpm test
|
|
519
|
-
pnpm run
|
|
520
|
-
pnpm run compile && pnpm run test:package # the packed tarball, installed and used
|
|
338
|
+
pnpm test # lint + the suite, from source
|
|
339
|
+
pnpm run compile && pnpm run test:package # the packed tarball, installed and used
|
|
521
340
|
```
|
|
522
341
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
emit, with the whole suite green.
|
|
527
|
-
|
|
528
|
-
Two house rules worth knowing before opening a PR:
|
|
529
|
-
|
|
530
|
-
1. **Example first.** A fixture with a known answer comes before the code. A
|
|
531
|
-
fixture written after the code tests what the code does, not what it should
|
|
532
|
-
do.
|
|
533
|
-
2. **A silent drop is the worst possible defect.** Anything the tracer cannot
|
|
534
|
-
follow must land in `unresolved` with the _right_ reason, never be quietly
|
|
535
|
-
treated as a read.
|
|
342
|
+
Two house rules: a fixture with a known answer comes **before** the code, and a
|
|
343
|
+
silent drop is the worst possible defect. The rest is in
|
|
344
|
+
[`CONTRIBUTING.md`](CONTRIBUTING.md).
|
|
536
345
|
|
|
537
346
|
## License
|
|
538
347
|
|