@filipebraida/adonis-function-points 0.1.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 (80) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +427 -0
  3. package/bin/cli.js +4 -0
  4. package/build/commands/fp_calibrate.d.ts +16 -0
  5. package/build/commands/fp_count.d.ts +11 -0
  6. package/build/commands/fp_diff.d.ts +16 -0
  7. package/build/commands/fp_explain.d.ts +15 -0
  8. package/build/commands/fp_inventory.d.ts +9 -0
  9. package/build/commands/main.d.ts +5 -0
  10. package/build/commands/main.js +120 -0
  11. package/build/commands/printer.d.ts +9 -0
  12. package/build/configure.d.ts +2 -0
  13. package/build/configure.js +10 -0
  14. package/build/define_config-DOqWyPwV.js +19 -0
  15. package/build/index.d.ts +5 -0
  16. package/build/index.js +4 -0
  17. package/build/pipeline-BzP-ITGN.js +2306 -0
  18. package/build/resolvers-CU9HKYpn.js +555 -0
  19. package/build/runners-Bt8tbISi.js +630 -0
  20. package/build/scripts/smoke_package.d.ts +1 -0
  21. package/build/src/albrecht/calibration.d.ts +62 -0
  22. package/build/src/albrecht/counter.d.ts +48 -0
  23. package/build/src/albrecht/data_functions.d.ts +32 -0
  24. package/build/src/albrecht/diff.d.ts +66 -0
  25. package/build/src/albrecht/index.d.ts +13 -0
  26. package/build/src/albrecht/tables.d.ts +19 -0
  27. package/build/src/albrecht/technical_filter.d.ts +25 -0
  28. package/build/src/albrecht/transactional_functions.d.ts +35 -0
  29. package/build/src/cli/load_config.d.ts +28 -0
  30. package/build/src/cli/print.d.ts +19 -0
  31. package/build/src/cli/runners.d.ts +52 -0
  32. package/build/src/cli.d.ts +19 -0
  33. package/build/src/cli.js +198 -0
  34. package/build/src/define_config.d.ts +137 -0
  35. package/build/src/inventory/app_context.d.ts +73 -0
  36. package/build/src/inventory/detectors/lucid.d.ts +77 -0
  37. package/build/src/inventory/graph/call_graph.d.ts +80 -0
  38. package/build/src/inventory/graph/noise.d.ts +9 -0
  39. package/build/src/inventory/index.d.ts +15 -0
  40. package/build/src/inventory/paths.d.ts +22 -0
  41. package/build/src/inventory/resolvers/action_object.d.ts +14 -0
  42. package/build/src/inventory/resolvers/index.d.ts +23 -0
  43. package/build/src/inventory/resolvers/index.js +2 -0
  44. package/build/src/inventory/resolvers/job_dispatch.d.ts +18 -0
  45. package/build/src/inventory/resolvers/module_function.d.ts +11 -0
  46. package/build/src/inventory/resolvers/property_service.d.ts +18 -0
  47. package/build/src/inventory/resolvers/same_class_method.d.ts +17 -0
  48. package/build/src/inventory/resolvers/static_service.d.ts +13 -0
  49. package/build/src/inventory/resolvers/transformer.d.ts +25 -0
  50. package/build/src/inventory/resolvers/types.d.ts +65 -0
  51. package/build/src/inventory/source.d.ts +39 -0
  52. package/build/src/inventory/sources/data_stores.d.ts +31 -0
  53. package/build/src/inventory/sources/json_schemas.d.ts +32 -0
  54. package/build/src/inventory/sources/routes_ast.d.ts +28 -0
  55. package/build/src/metrics/structure.d.ts +72 -0
  56. package/build/src/pipeline.d.ts +44 -0
  57. package/build/src/pipeline.js +2 -0
  58. package/build/src/reporters/table.d.ts +6 -0
  59. package/build/src/types.d.ts +256 -0
  60. package/build/src/types.js +1 -0
  61. package/build/stubs/config.stub +37 -0
  62. package/build/tmp/probe.d.ts +1 -0
  63. package/build/tmp/probe_cli.d.ts +1 -0
  64. package/build/tmp/probe_cmp.d.ts +1 -0
  65. package/build/tmp/probe_count.d.ts +1 -0
  66. package/build/tmp/probe_data.d.ts +1 -0
  67. package/build/tmp/probe_diff.d.ts +1 -0
  68. package/build/tmp/probe_gap.d.ts +1 -0
  69. package/build/tmp/probe_graph.d.ts +1 -0
  70. package/build/tmp/probe_metrics.d.ts +1 -0
  71. package/build/tmp/probe_miss.d.ts +1 -0
  72. package/build/tmp/probe_names.d.ts +1 -0
  73. package/build/tmp/probe_nodata.d.ts +1 -0
  74. package/build/tmp/probe_one.d.ts +1 -0
  75. package/build/tmp/probe_perf.d.ts +1 -0
  76. package/build/tmp/probe_routes.d.ts +1 -0
  77. package/build/tmp/probe_unres.d.ts +1 -0
  78. package/build/tmp/probe_vazquez.d.ts +1 -0
  79. package/build/tsdown.config.d.ts +2 -0
  80. package/package.json +133 -0
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ # MIT License
2
+
3
+ Copyright (c) Filipe Braida
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,427 @@
1
+ # @filipebraida/adonis-function-points
2
+
3
+ Automated function point counting and code metrics for AdonisJS applications.
4
+
5
+ Counts IFPUG function points straight from the source, following the OMG
6
+ **Automated Function Points** standard (ISO/IEC 19515). Every number it prints
7
+ says where it came from: the file, the line, the rule from the standard, and the
8
+ origin of each DET and each FTR.
9
+
10
+ ```bash
11
+ node ace fp:count
12
+ ```
13
+
14
+ ```
15
+ Unadjusted count: 46 FP
16
+ Ruleset: afp@1.0.0
17
+
18
+ type n FP
19
+ ILF 2 14
20
+ EIF 1 5
21
+ EI 4 13
22
+ EO 3 14
23
+
24
+ function type DET FTR FP
25
+ Apontamento ILF 4 1 7
26
+ Justificativa ILF 3 1 7
27
+ Pessoa EIF 4 1 5
28
+ GET /apontamentos EO 4 1 4
29
+ POST /apontamentos EI 3 1 3
30
+ PUT /apontamentos/:param EI 5 2 4
31
+ DELETE /apontamentos/:param EI 1 2 3
32
+ POST /apontamentos/justificar EI 4 2 3
33
+ GET /presenca EO 13 3 5
34
+ GET /presenca/relatorio EO 13 3 5
35
+ ```
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
+ ## Install
50
+
51
+ ```bash
52
+ npm i @filipebraida/adonis-function-points
53
+ node ace configure @filipebraida/adonis-function-points
54
+ ```
55
+
56
+ Requires **AdonisJS 7** and **Lucid 22** (see [Support](#support)).
57
+
58
+ ### Or run it without installing
59
+
60
+ For CI, or a one-off count on a project you do not want to touch:
61
+
62
+ ```bash
63
+ npx @filipebraida/adonis-function-points count --root ./my-app
64
+ ```
65
+
66
+ Nothing is booted either way — the engine only reads files — so a standalone
67
+ run needs no `.env`, no database, and no install inside the analysed project.
68
+ The standalone binary **does not replace installing**: a project that installs
69
+ the package keeps the `node ace fp:*` commands, and both front-ends call the
70
+ same code, so they cannot disagree about a number.
71
+
72
+ ```
73
+ adonis-function-points <command> [options]
74
+
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
+ diff <previous.json> additions / modifications / deletions, and billable FP
79
+ calibrate <samples.csv> correction factors against a manual count
80
+
81
+ --root <path> application to analyse (default: the current directory)
82
+ --out <path> write the result as JSON to this path
83
+ --json print JSON instead of a table
84
+ --min-coverage <0..1> fail below this tracing coverage
85
+ ```
86
+
87
+ Both front-ends exit non-zero when the count cannot be produced — coverage
88
+ below the minimum, an unreadable configuration, a saved count from a different
89
+ ruleset — so a CI job fails instead of publishing a number nobody can defend.
90
+
91
+ #### In CI
92
+
93
+ Every count records what it counted, so the artefact stands on its own once it
94
+ leaves the pipeline:
95
+
96
+ ```json
97
+ "source": {
98
+ "app": "shop",
99
+ "revision": "adef4ee3…",
100
+ "branch": "main",
101
+ "dirty": false,
102
+ "countedAt": "2026-09-24T17:40:11.000Z",
103
+ "config": "/app/config/function_points.ts"
104
+ }
105
+ ```
106
+
107
+ `dirty` is the field that matters in billing: a count taken over uncommitted
108
+ changes cannot be reproduced from any revision, and whoever receives the
109
+ invoice is entitled to know that. `app` is the manifest name, never an absolute
110
+ path — a path would say where your machine keeps its files and travel with
111
+ every count you send anywhere.
112
+
113
+ `fp:diff` refuses two counts of different applications, the same way it refuses
114
+ two different rulesets, and warns when either side is dirty or when both are
115
+ the same revision.
116
+
117
+ Counting an older revision needs no checkout of your working tree and nothing
118
+ installed in it, so a pull request is two counts and a comparison:
119
+
120
+ ```yaml
121
+ - run: git worktree add ../base ${{ github.event.pull_request.base.sha }}
122
+ - run: npx @filipebraida/adonis-function-points count --root ../base --out base.json
123
+ - run: npx @filipebraida/adonis-function-points count --out head.json
124
+ - run: npx @filipebraida/adonis-function-points diff base.json head.json
125
+ ```
126
+
127
+ The package does not deliver the result anywhere — an artifact, a ledger
128
+ branch, a billing endpoint and a PR comment are all yours to choose. What it
129
+ owes you is a number that is still defensible wherever it lands.
130
+
131
+ ## Commands
132
+
133
+ | command | what it does |
134
+ | ------------------------------------- | ---------------------------------------------------------- |
135
+ | `node ace fp:count` | counts unadjusted function points |
136
+ | `node ace fp:inventory` | the raw facts: stores, routes, tracing coverage |
137
+ | `node ace fp:explain <name>` | why one function was counted that way |
138
+ | `node ace fp:diff <previous.json>` | additions / modifications / deletions, and the billable FP |
139
+ | `node ace fp:calibrate <samples.csv>` | correction factors against a manual count |
140
+
141
+ `fp:count --out count.json` saves a count; `fp:diff count.json` compares that
142
+ saved count against the current state of the application. It deliberately does
143
+ **not** take a git ref: booting an older checkout, with possibly different
144
+ dependencies, is a problem not worth solving.
145
+
146
+ ### `fp:explain` — the number has to be defensible
147
+
148
+ ```
149
+ POST /apontamentos — EI, low complexity, 3 FP
150
+ module: ponto
151
+
152
+ Rule applied: afp:6.5.3 modifies a data store -> EI
153
+
154
+ DET = 3
155
+ validator:registrarPontoValidator.marcadoEm
156
+ validator:registrarPontoValidator.pessoaId
157
+ validator:registrarPontoValidator.tipo
158
+
159
+ FTR = 1
160
+ reaches:Apontamento
161
+
162
+ Path walked:
163
+ controllers/apontamentos_controller.ts#store (store)
164
+ actions/registrar_ponto.ts#handle (action-object) [writes]
165
+ ```
166
+
167
+ If function points get invoiced, someone will dispute a number — and a number
168
+ without provenance is indefensible.
169
+
170
+ ## Benchmark
171
+
172
+ The only reference in this project not produced by its own authors is the case
173
+ study published in **Vazquez, Simões & Albert (2011)**, the same one used by the
174
+ COPPE/UFRJ dissertation on the _Ligeiro_ tool (Pinel, 2012).
175
+
176
+ The fixture and the reference count were frozen in their own commit **before**
177
+ the counter was ever run against them, with the transcription choices written
178
+ down first. Without that the independence would be illusory.
179
+
180
+ | | total | vs reference |
181
+ | ----------------------------------- | --------- | ------------ |
182
+ | **Vazquez et al. (2011), manual** | **46 FP** | — |
183
+ | **this package** | **46 FP** | **0%** |
184
+ | Ligeiro, automated (Pinel 2012) | 52 FP | +13% |
185
+ | Ligeiro, manual under its own rules | 43 FP | −6.5% |
186
+
187
+ Eight of the ten functions match exactly. The two that do not were **predicted
188
+ in writing before the run**, and come from the standard rather than from
189
+ defects:
190
+
191
+ - **+1** `Consulta Apontamento Diário` is an EQ in the reference; AFP §6.5.3
192
+ requires collapsing EQ into EO, and an EO weighs more in the same band.
193
+ - **−1** `Apontamento c/ Justificativa`: the IFPUG manual counts 1 DET for the
194
+ user message, AFP does not.
195
+
196
+ They cancel out, which is exactly why the total is reported alongside the
197
+ function-by-function agreement rather than on its own.
198
+
199
+ Reproduce it with `npm test` — the benchmark is
200
+ `tests/acceptance/vazquez.spec.ts`, and the reference is
201
+ [`tests/fixtures/apps/vazquez/REFERENCE.md`](tests/fixtures/apps/vazquez/REFERENCE.md).
202
+
203
+ ## Principles
204
+
205
+ **Traceability.** Every counted function says where it came from: file, line,
206
+ rule applied, origin of each DET and each FTR, and the path walked through the
207
+ call graph. The ruleset is versioned and printed in every report — two counts
208
+ are only comparable if the rules did not change in between.
209
+
210
+ **Say "I don't know" rather than be wrong in silence.** A call the tracer cannot
211
+ follow enters the coverage metric. If coverage falls below the configured
212
+ threshold, the analysis **fails** instead of emitting a number that looks right.
213
+ This is not a preference; AFP §6.5.3 requires it:
214
+
215
+ > "If the transaction execution depends on code that is unknown or unavailable
216
+ > to the automated tool, the code end point shall be cataloged and listed in the
217
+ > generated report in order to detect and quantify the missing patterns and
218
+ > libraries."
219
+
220
+ **Shape must not change the count.** The same logical application written in
221
+ different ways — flat MVC or module-per-domain, fat controller or action object,
222
+ generated artefacts or none — must produce an identical number. That is the
223
+ project's golden invariant, and it is a test
224
+ (`tests/acceptance/golden_invariant.spec.ts`) that was written before the first
225
+ collector.
226
+
227
+ **Extensibility as a requirement.** AdonisJS imposes no code organisation — fat
228
+ controller, action object, static service, injected service, module function,
229
+ job. Tracing strategies are registrable, so a project with its own convention
230
+ registers it (see [Custom code pattern](#custom-code-pattern)).
231
+
232
+ **Function points are not the only number on the dashboard.** If function points
233
+ pay, the team optimises function points: more models, more endpoints, less
234
+ reuse. Coupling, instability and density come free from the same inventory, and
235
+ are the counterweight.
236
+
237
+ ## Configuration
238
+
239
+ Discovery does the technical work — subpath aliases, generated artefacts,
240
+ layout, scan roots are all read from the application, never assumed. What stays
241
+ configurable is what is a **business decision** that no heuristic should make.
242
+
243
+ **Every option here has an effect, and a test proving it.** Configuration the
244
+ code does not honour is worse than none at all.
245
+
246
+ ```ts
247
+ // config/function_points.ts
248
+ import { defineConfig } from '@filipebraida/adonis-function-points'
249
+
250
+ export default defineConfig({
251
+ boundary: {
252
+ infrastructure: ['access_tokens', 'audits'], // excluded, with the reason in the report
253
+ externallyMaintained: ['erp_customers'], // counted as EIF instead of ILF
254
+ ignoreEntryPoints: ['prometheus.metrics'],
255
+ },
256
+
257
+ retStrategy: 'constant', // or 'composition'
258
+ maxDepth: 3, // how far to follow the call graph
259
+ messageDet: 0, // 1 restores the IFPUG confirmation-message DET
260
+ minCoverage: 0.85, // below this, the analysis fails
261
+ })
262
+ ```
263
+
264
+ `complexityTables` and `weights` are also accepted, for calibrating the bands
265
+ against a manual count.
266
+
267
+ Both front-ends load this file from the application root, and every run prints
268
+ which configuration produced it — the file path, or `defaults` when there is
269
+ none. A configuration file that exists and fails to load is an **error**: the
270
+ count is not produced. Falling back to the defaults with a warning would change
271
+ the number without telling anyone, and the number becomes an invoice.
272
+
273
+ ### Custom code pattern
274
+
275
+ ```ts
276
+ import type { CallResolver } from '@filipebraida/adonis-function-points'
277
+
278
+ const repositoryResolver: CallResolver = {
279
+ name: 'my-repository',
280
+ order: 5, // lower runs first; custom strategies run before the built-ins
281
+ resolve(call, ctx) {
282
+ // return the bodies to follow, or [] if this is not your pattern
283
+ return []
284
+ },
285
+ }
286
+
287
+ export default defineConfig({
288
+ resolvers: { call: [repositoryResolver] },
289
+ })
290
+ ```
291
+
292
+ The **first** strategy that claims a call wins. That is not an implementation
293
+ detail: `CreateUserJob.dispatch(p)`, `UserService.create(p)` and `User.find(p)`
294
+ are all `Identifier.method(args)`, and only ordering tells them apart.
295
+
296
+ Built-in strategies, most specific first: `same-class-method`, `action-object`,
297
+ `job-dispatch`, `static-service`, `property-service`, `module-function`.
298
+
299
+ ## Support
300
+
301
+ | | v1 |
302
+ | --------------------- | ------------------------------------------------------------ |
303
+ | AdonisJS 7 + Lucid 22 | **yes** — generated schema, `codegen`, with or without Tuyau |
304
+ | AdonisJS 6 / Lucid 21 | no — detected and reported |
305
+ | Kysely and other ORMs | no — detected and reported |
306
+
307
+ Out of scope, the package says it does not support the application. It never
308
+ counts zero in silence.
309
+
310
+ ## Known limitations
311
+
312
+ Inherited from the AFP standard itself, not from this implementation:
313
+
314
+ - **EQ is collapsed into EO.** Telling an inquiry from an output requires
315
+ knowing whether there is derived data or calculation, which static analysis
316
+ cannot see. AFP mandates the collapse.
317
+ - **RET is approximated.** What a user recognises as a logical subgroup is not
318
+ derivable from code. The default pins it at 1; `composition` derives it from
319
+ composition relations.
320
+ - **Confirmation and error messages** count 1 DET in a manual count and are
321
+ invisible here — a known systematic divergence of −1 DET per transaction.
322
+ `messageDet: 1` restores it.
323
+ - **VAF is not calculated.** The 14 general system characteristics require human
324
+ judgement. AFP fixes VAF = 1, and the unadjusted count is what public
325
+ contracts in Brazil use anyway.
326
+ - **The modification factor in `fp:diff` is 1.** AEP grades it from 0.25 to
327
+ 1.75 using Effort Complexity, which requires cyclomatic complexity. A flat 1
328
+ does not discriminate — it prices a one-line fix and a rewrite the same — and
329
+ the report says so.
330
+
331
+ What it does discriminate is **why** a function changed, which is usually the
332
+ larger question:
333
+
334
+ ```
335
+ changed 74 functions 318 FP × 1
336
+ type 1 functions 3 FP reclassified, e.g. EO -> EI
337
+ size 28 functions 141 FP DET or FTR moved
338
+ implementation 45 functions 174 FP same size, different code
339
+ ```
340
+
341
+ On a real month of work that is 44% of the invoice coming from refactoring.
342
+ Whether that should be billed at full value is a contract decision, not a
343
+ counting one — but it has to be visible before anyone can make it.
344
+
345
+ - **Schema-driven applications undercount their input.** When the fields a user
346
+ fills live in a JSON column whose schema is stored in the database, there is
347
+ nothing for static analysis to read: each opaque column counts as 1 DET.
348
+ Measured on a production application, the effect is about 2% of the total —
349
+ data functions are unaffected, and only the form-submission transaction loses
350
+ complexity.
351
+
352
+ `fp:count` names every opaque column a transaction reaches, so the limitation
353
+ is visible where you read the number rather than only in a design document.
354
+ The way out is to declare the number rather than let the tool guess it:
355
+ `overrides: { 'POST /petitions': { det: 42, reason: '…' } }`. The reason is
356
+ required by the type, `fp:explain` prints it beside the number, and `fp:count`
357
+ reports what share of the total was declared — because an override is right
358
+ where static analysis is blind and poison as a habit. See
359
+ counting-decisions §8.
360
+
361
+ - **Only HTTP routes are collected as entry points.** An ace command that
362
+ imports a spreadsheet and a scheduled job are transactional functions under
363
+ IFPUG; they are out of v1.
364
+
365
+ ## References
366
+
367
+ - **OMG Automated Function Points (AFP) 1.0** — ISO/IEC 19515:2019. The
368
+ normative basis for the count: technical data filter (§6.5.2.1.1), transaction
369
+ detection (§6.5.3), ILF vs EIF by maintenance (§6.5.4), DET/RET/FTR (§7.2,
370
+ §7.3).
371
+ - **OMG Automated Enhancement Points (AEP) 1.0** — the basis for `fp:diff`:
372
+ added / modified / deleted (§6.3) and the complexity factors (§6.5).
373
+ - **IFPUG Counting Practices Manual (CPM) 4.3** — the underlying method AFP
374
+ automates.
375
+ - **Vazquez, C. E., Simões, G. S., Albert, R. M. (2011).** _Análise de Pontos de
376
+ Função: Medição, Estimativas e Gerenciamento de Projetos de Software._ Érica.
377
+ The benchmark case study.
378
+ - **Pinel, B. (2012).** _Ligeiro: uma ferramenta para contagem automática de
379
+ pontos de função._ COPPE/UFRJ.
380
+ [pesc.coppe.ufrj.br](https://pesc.coppe.ufrj.br/uploadfile/1343153707.pdf)
381
+
382
+ ## Design documents
383
+
384
+ The reasoning behind the count lives with the code:
385
+
386
+ - [`docs/design/architecture.md`](docs/design/architecture.md) — the thesis, the
387
+ layers, and what is discovered instead of configured
388
+ - [`docs/design/counting-decisions.md`](docs/design/counting-decisions.md) —
389
+ each edge case, with the AFP rule that settles it
390
+ - [`docs/design/resolvers.md`](docs/design/resolvers.md) — the catalogue of code
391
+ patterns and how each is followed
392
+
393
+ Three further documents are kept as a **dated record** of how the design was
394
+ arrived at, in Portuguese, and are not a reference for current behaviour:
395
+ [`implementation-plan.md`](docs/design/implementation-plan.md) (built phase by
396
+ phase, and what each phase found),
397
+ [`adonisjs-variation.md`](docs/research/adonisjs-variation.md) (what varies
398
+ between real AdonisJS applications) and
399
+ [`external-validation.md`](docs/research/external-validation.md) (the thesis
400
+ tested outside the sample that produced it).
401
+
402
+ ## Contributing
403
+
404
+ ```bash
405
+ pnpm install
406
+ pnpm test # lint + 242 tests, from source
407
+ pnpm run typecheck
408
+ pnpm run compile && pnpm run test:package # the packed tarball, installed and used
409
+ ```
410
+
411
+ `test:package` is separate on purpose: the suite runs from source through
412
+ ts-exec and never loads `build/`, which is the only thing a user gets. A
413
+ release once had every `exports` path pointing at a file the build did not
414
+ emit, with the whole suite green.
415
+
416
+ Two house rules worth knowing before opening a PR:
417
+
418
+ 1. **Example first.** A fixture with a known answer comes before the code. A
419
+ fixture written after the code tests what the code does, not what it should
420
+ do.
421
+ 2. **A silent drop is the worst possible defect.** Anything the tracer cannot
422
+ follow must land in `unresolved` with the _right_ reason, never be quietly
423
+ treated as a read.
424
+
425
+ ## License
426
+
427
+ MIT
package/bin/cli.js ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { main } from '../build/src/cli.js'
3
+
4
+ process.exitCode = await main()
@@ -0,0 +1,16 @@
1
+ import { BaseCommand } from '@adonisjs/core/ace';
2
+ import type { CommandOptions } from '@adonisjs/core/types/ace';
3
+ /**
4
+ * Measures the counter's bias against a manual count.
5
+ *
6
+ * It does NOT apply the factor: calibrating is a decision for whoever signs the
7
+ * contract, and a factor applied silently would stop the count from being
8
+ * reproducible from the code.
9
+ */
10
+ export default class FpCalibrate extends BaseCommand {
11
+ static commandName: string;
12
+ static description: string;
13
+ static options: CommandOptions;
14
+ samples: string;
15
+ run(): Promise<void>;
16
+ }
@@ -0,0 +1,11 @@
1
+ import { BaseCommand } from '@adonisjs/core/ace';
2
+ import type { CommandOptions } from '@adonisjs/core/types/ace';
3
+ export default class FpCount extends BaseCommand {
4
+ static commandName: string;
5
+ static description: string;
6
+ static options: CommandOptions;
7
+ out?: string;
8
+ json?: boolean;
9
+ minCoverage?: number;
10
+ run(): Promise<void>;
11
+ }
@@ -0,0 +1,16 @@
1
+ import { BaseCommand } from '@adonisjs/core/ace';
2
+ import type { CommandOptions } from '@adonisjs/core/types/ace';
3
+ /**
4
+ * Additions, changes and deletions between two counts — what gets invoiced.
5
+ *
6
+ * It works on a SAVED count (`fp:count --out`) compared against the current
7
+ * state, never on two checkouts: booting the older version, with possibly
8
+ * different dependencies, is a problem not worth solving.
9
+ */
10
+ export default class FpDiff extends BaseCommand {
11
+ static commandName: string;
12
+ static description: string;
13
+ static options: CommandOptions;
14
+ previous: string;
15
+ run(): Promise<void>;
16
+ }
@@ -0,0 +1,15 @@
1
+ import { BaseCommand } from '@adonisjs/core/ace';
2
+ import type { CommandOptions } from '@adonisjs/core/types/ace';
3
+ /**
4
+ * Why a function was counted the way it was.
5
+ *
6
+ * Not a convenience: if function points get invoiced, someone will dispute a
7
+ * number, and a number without provenance is indefensible.
8
+ */
9
+ export default class FpExplain extends BaseCommand {
10
+ static commandName: string;
11
+ static description: string;
12
+ static options: CommandOptions;
13
+ name: string;
14
+ run(): Promise<void>;
15
+ }
@@ -0,0 +1,9 @@
1
+ import { BaseCommand } from '@adonisjs/core/ace';
2
+ import type { CommandOptions } from '@adonisjs/core/types/ace';
3
+ export default class FpInventory extends BaseCommand {
4
+ static commandName: string;
5
+ static description: string;
6
+ static options: CommandOptions;
7
+ out?: string;
8
+ run(): Promise<void>;
9
+ }
@@ -0,0 +1,5 @@
1
+ export { default as FpInventory } from './fp_inventory.js';
2
+ export { default as FpCount } from './fp_count.js';
3
+ export { default as FpExplain } from './fp_explain.js';
4
+ export { default as FpDiff } from './fp_diff.js';
5
+ export { default as FpCalibrate } from './fp_calibrate.js';
@@ -0,0 +1,120 @@
1
+ import { a as runInventory, i as runExplain, n as runCount, r as runDiff, s as printResult, t as runCalibrate } from "../runners-Bt8tbISi.js";
2
+ import { BaseCommand, args, flags } from "@adonisjs/core/ace";
3
+ //#region commands/printer.ts
4
+ /**
5
+ * Maps a `RunResult` onto ace's logger.
6
+ *
7
+ * Notes go to `info`, not `log`: they are diagnostics about the run, and under
8
+ * `--json` they must not be mistaken for output.
9
+ */
10
+ function printerFor(command) {
11
+ return {
12
+ log: (message) => command.logger.log(message),
13
+ error: (message) => command.logger.error(message),
14
+ note: (message) => command.logger.info(message)
15
+ };
16
+ }
17
+ //#endregion
18
+ //#region \0@oxc-project+runtime@0.127.0/helpers/decorate.js
19
+ function __decorate(decorators, target, key, desc) {
20
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
21
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
22
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
23
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
24
+ }
25
+ //#endregion
26
+ //#region commands/fp_inventory.ts
27
+ var FpInventory = class extends BaseCommand {
28
+ static commandName = "fp:inventory";
29
+ static description = "Extract the raw facts of the application: stores, routes and tracing";
30
+ static options = { startApp: false };
31
+ async run() {
32
+ this.exitCode = printResult(await runInventory({
33
+ root: this.app.makePath(),
34
+ out: this.out
35
+ }), printerFor(this));
36
+ }
37
+ };
38
+ __decorate([flags.string({ description: "Write the inventory as JSON to the given path" })], FpInventory.prototype, "out", void 0);
39
+ //#endregion
40
+ //#region commands/fp_count.ts
41
+ var FpCount = class extends BaseCommand {
42
+ static commandName = "fp:count";
43
+ static description = "Count the unadjusted function points of the application";
44
+ static options = { startApp: false };
45
+ async run() {
46
+ this.exitCode = printResult(await runCount({
47
+ root: this.app.makePath(),
48
+ out: this.out,
49
+ json: this.json,
50
+ minCoverage: this.minCoverage
51
+ }), printerFor(this));
52
+ }
53
+ };
54
+ __decorate([flags.string({ description: "Write the result as JSON to the given path" })], FpCount.prototype, "out", void 0);
55
+ __decorate([flags.boolean({ description: "Print JSON instead of a table" })], FpCount.prototype, "json", void 0);
56
+ __decorate([flags.number({ description: "Minimum tracing coverage (0 to 1)" })], FpCount.prototype, "minCoverage", void 0);
57
+ //#endregion
58
+ //#region commands/fp_explain.ts
59
+ /**
60
+ * Why a function was counted the way it was.
61
+ *
62
+ * Not a convenience: if function points get invoiced, someone will dispute a
63
+ * number, and a number without provenance is indefensible.
64
+ */
65
+ var FpExplain = class extends BaseCommand {
66
+ static commandName = "fp:explain";
67
+ static description = "Show the provenance of a function's count";
68
+ static options = { startApp: false };
69
+ async run() {
70
+ this.exitCode = printResult(await runExplain({
71
+ root: this.app.makePath(),
72
+ name: this.name
73
+ }), printerFor(this));
74
+ }
75
+ };
76
+ __decorate([args.string({ description: "Function name, e.g. \"POST /books\" or \"Invite\"" })], FpExplain.prototype, "name", void 0);
77
+ //#endregion
78
+ //#region commands/fp_diff.ts
79
+ /**
80
+ * Additions, changes and deletions between two counts — what gets invoiced.
81
+ *
82
+ * It works on a SAVED count (`fp:count --out`) compared against the current
83
+ * state, never on two checkouts: booting the older version, with possibly
84
+ * different dependencies, is a problem not worth solving.
85
+ */
86
+ var FpDiff = class extends BaseCommand {
87
+ static commandName = "fp:diff";
88
+ static description = "Compare a saved count against the current state of the application";
89
+ static options = { startApp: false };
90
+ async run() {
91
+ this.exitCode = printResult(await runDiff({
92
+ root: this.app.makePath(),
93
+ previous: this.previous
94
+ }), printerFor(this));
95
+ }
96
+ };
97
+ __decorate([args.string({ description: "Path to the JSON produced by `fp:count --out`" })], FpDiff.prototype, "previous", void 0);
98
+ //#endregion
99
+ //#region commands/fp_calibrate.ts
100
+ /**
101
+ * Measures the counter's bias against a manual count.
102
+ *
103
+ * It does NOT apply the factor: calibrating is a decision for whoever signs the
104
+ * contract, and a factor applied silently would stop the count from being
105
+ * reproducible from the code.
106
+ */
107
+ var FpCalibrate = class extends BaseCommand {
108
+ static commandName = "fp:calibrate";
109
+ static description = "Compare the automatic count against manual counts";
110
+ static options = { startApp: false };
111
+ async run() {
112
+ this.exitCode = printResult(await runCalibrate({
113
+ root: this.app.makePath(),
114
+ samples: this.samples
115
+ }), printerFor(this));
116
+ }
117
+ };
118
+ __decorate([args.string({ description: "CSV of `function,fp` counted by hand" })], FpCalibrate.prototype, "samples", void 0);
119
+ //#endregion
120
+ export { FpCalibrate, FpCount, FpDiff, FpExplain, FpInventory };
@@ -0,0 +1,9 @@
1
+ import type { BaseCommand } from '@adonisjs/core/ace';
2
+ import type { Printer } from '../src/cli/print.js';
3
+ /**
4
+ * Maps a `RunResult` onto ace's logger.
5
+ *
6
+ * Notes go to `info`, not `log`: they are diagnostics about the run, and under
7
+ * `--json` they must not be mistaken for output.
8
+ */
9
+ export declare function printerFor(command: BaseCommand): Printer;
@@ -0,0 +1,2 @@
1
+ import type Configure from '@adonisjs/core/commands/configure';
2
+ export declare function configure(command: Configure): Promise<void>;