@filipebraida/adonis-function-points 0.1.0 → 0.3.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 (57) hide show
  1. package/CHANGELOG.md +144 -0
  2. package/README.md +103 -2
  3. package/build/calibration-8eV8CEix.js +403 -0
  4. package/build/commands/fp_metrics.d.ts +10 -0
  5. package/build/commands/main.d.ts +6 -5
  6. package/build/commands/main.js +48 -114
  7. package/build/decorate-D6enDn9D.js +24 -0
  8. package/build/fp_calibrate-iFAec0tA.js +25 -0
  9. package/build/fp_count-D21tQ_pv.js +22 -0
  10. package/build/fp_diff-D0pHGMgi.js +25 -0
  11. package/build/fp_explain-BwFs-LW-.js +24 -0
  12. package/build/fp_inventory-Bu6O1Nn0.js +18 -0
  13. package/build/fp_metrics-MGDppfSa.js +20 -0
  14. package/build/index.d.ts +30 -1
  15. package/build/index.js +5 -3
  16. package/build/{pipeline-BzP-ITGN.js → pipeline-CIAydCcT.js} +452 -54
  17. package/build/{resolvers-CU9HKYpn.js → resolvers-CRB6lXoo.js} +474 -207
  18. package/build/{runners-Bt8tbISi.js → runners-CmxNHuuq.js} +146 -342
  19. package/build/src/albrecht/counter.d.ts +21 -1
  20. package/build/src/albrecht/data_functions.d.ts +6 -0
  21. package/build/src/albrecht/diff.d.ts +19 -1
  22. package/build/src/cli/runners.d.ts +12 -0
  23. package/build/src/cli.js +11 -2
  24. package/build/src/define_config.d.ts +35 -1
  25. package/build/src/inventory/detectors/lucid.d.ts +8 -0
  26. package/build/src/inventory/graph/call_graph.d.ts +29 -0
  27. package/build/src/inventory/graph/noise.d.ts +13 -0
  28. package/build/src/inventory/resolvers/event_dispatch.d.ts +17 -0
  29. package/build/src/inventory/resolvers/index.js +1 -1
  30. package/build/src/inventory/resolvers/types.d.ts +35 -0
  31. package/build/src/inventory/sources/event_bindings.d.ts +44 -0
  32. package/build/src/metrics/structure.d.ts +16 -2
  33. package/build/src/pipeline.js +1 -1
  34. package/build/src/reporters/table.d.ts +11 -1
  35. package/build/src/types.d.ts +17 -0
  36. package/build/stubs/config.stub +27 -1
  37. package/package.json +2 -1
  38. package/build/define_config-DOqWyPwV.js +0 -19
  39. package/build/scripts/smoke_package.d.ts +0 -1
  40. package/build/tmp/probe.d.ts +0 -1
  41. package/build/tmp/probe_cli.d.ts +0 -1
  42. package/build/tmp/probe_cmp.d.ts +0 -1
  43. package/build/tmp/probe_count.d.ts +0 -1
  44. package/build/tmp/probe_data.d.ts +0 -1
  45. package/build/tmp/probe_diff.d.ts +0 -1
  46. package/build/tmp/probe_gap.d.ts +0 -1
  47. package/build/tmp/probe_graph.d.ts +0 -1
  48. package/build/tmp/probe_metrics.d.ts +0 -1
  49. package/build/tmp/probe_miss.d.ts +0 -1
  50. package/build/tmp/probe_names.d.ts +0 -1
  51. package/build/tmp/probe_nodata.d.ts +0 -1
  52. package/build/tmp/probe_one.d.ts +0 -1
  53. package/build/tmp/probe_perf.d.ts +0 -1
  54. package/build/tmp/probe_routes.d.ts +0 -1
  55. package/build/tmp/probe_unres.d.ts +0 -1
  56. package/build/tmp/probe_vazquez.d.ts +0 -1
  57. package/build/tsdown.config.d.ts +0 -2
package/build/src/cli.js CHANGED
@@ -1,5 +1,5 @@
1
- import { t as CoverageTooLowError } from "../pipeline-BzP-ITGN.js";
2
- import { a as runInventory, i as runExplain, n as runCount, o as ConfigLoadError, r as runDiff, s as printResult, t as runCalibrate } from "../runners-Bt8tbISi.js";
1
+ import { t as CoverageTooLowError } from "../pipeline-CIAydCcT.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-CmxNHuuq.js";
3
3
  import path from "node:path";
4
4
  import { existsSync, readFileSync } from "node:fs";
5
5
  import { fileURLToPath } from "node:url";
@@ -23,6 +23,7 @@ Usage
23
23
  Commands
24
24
  count count the unadjusted function points
25
25
  inventory the raw facts: stores, routes, tracing coverage
26
+ metrics density, coupling and conformance, from the same run
26
27
  explain <name> why one function was counted that way
27
28
  diff <a.json> [b.json] additions / modifications / deletions, and billable FP
28
29
  one file compares against the current tree; two
@@ -105,6 +106,7 @@ async function run(argv, printer = CONSOLE) {
105
106
  if (![
106
107
  "count",
107
108
  "inventory",
109
+ "metrics",
108
110
  "explain",
109
111
  "diff",
110
112
  "calibrate"
@@ -153,6 +155,13 @@ async function run(argv, printer = CONSOLE) {
153
155
  out: text(flags.get("out"))
154
156
  });
155
157
  break;
158
+ case "metrics":
159
+ result = await runMetrics({
160
+ root,
161
+ out: text(flags.get("out")),
162
+ json: flags.get("json") === true
163
+ });
164
+ break;
156
165
  case "explain":
157
166
  result = await runExplain({
158
167
  root,
@@ -1,4 +1,5 @@
1
1
  import type { ComplexityTable } from './albrecht/tables.js';
2
+ import type { ChangeFactors, ChangeReasonFactors } from './albrecht/diff.js';
2
3
  import type { CallResolver } from './inventory/resolvers/types.js';
3
4
  import type { Complexity, FunctionType } from './types.js';
4
5
  /**
@@ -35,6 +36,20 @@ export type FunctionPointsConfig = {
35
36
  * For example, tables mirrored from an external ERP.
36
37
  */
37
38
  externallyMaintained?: string[];
39
+ /**
40
+ * Stores the AFP naming filter excluded, which are business data here.
41
+ *
42
+ * The filter (§6.5.2.1.1, patterns in §6.5.2.1.3) catches names containing
43
+ * `session`, `template`, `error`, `types` and so on, because in most
44
+ * applications those hold infrastructure. When they hold the business —
45
+ * a chat session the user manages, a document template they maintain — the
46
+ * exclusion is wrong and no heuristic can know it. This wins over the
47
+ * filter, and the report says which stores were brought back.
48
+ *
49
+ * It is the counterpart of `infrastructure`: that one excludes what the
50
+ * filter missed, this one restores what it caught by accident.
51
+ */
52
+ business?: string[];
38
53
  /**
39
54
  * Entry points with no functional value to the user, by route name or by
40
55
  * identity (`GET /health`).
@@ -72,6 +87,25 @@ export type FunctionPointsConfig = {
72
87
  complexityTables?: Partial<Record<FunctionType, ComplexityTable>>;
73
88
  /** Weights per type and complexity. */
74
89
  weights?: Partial<Record<FunctionType, Record<Complexity, number>>>;
90
+ /**
91
+ * How change is priced, for `fp:diff`. A clause of the contract, not a flag.
92
+ *
93
+ * The AEP anchors are explicit for added (1) and deleted (0.4). For a modified
94
+ * function AEP grades the factor from 0.25 to 1.75 through Effort Complexity
95
+ * variation, which needs cyclomatic complexity this package does not measure —
96
+ * so it defaults to 1, which overestimates, and every diff says so.
97
+ *
98
+ * `reasonFactors` is the lever the default leaves on the table. The tool
99
+ * already distinguishes a change of type, a change of size, and a change of
100
+ * implementation only — same type, same DET, same FTR, different body — and on
101
+ * a real pair of releases the last was 151 of 378 FP billed as change. Pricing
102
+ * a refactor at full functional value is not defensible; pricing it at a number
103
+ * this package invented would be worse. So the number comes from the contract.
104
+ */
105
+ diff?: {
106
+ factors?: Partial<ChangeFactors>;
107
+ reasonFactors?: ChangeReasonFactors;
108
+ };
75
109
  /**
76
110
  * Custom tracing strategies, added to the built-in ones and running
77
111
  * **before** them.
@@ -93,7 +127,7 @@ export type FunctionPointsConfig = {
93
127
  minCoverage?: number;
94
128
  /**
95
129
  * Declared DET or RET/FTR for a function the analysis cannot read, keyed by
96
- * the name it has in the count (`Petition`, `POST /books`).
130
+ * the name it has in the count (`Invoice`, `POST /books`).
97
131
  *
98
132
  * The case this exists for is a schema-driven application: when the fields a
99
133
  * user fills live in a JSON column whose schema is stored in the database,
@@ -31,6 +31,14 @@ export type PersistenceAccess = {
31
31
  * relation out of the count (§6.5.4) when it is a legitimate EIF.
32
32
  */
33
33
  viaRelation?: string;
34
+ /**
35
+ * Whether the access WRITES the related table.
36
+ *
37
+ * `preload('author')` reads it; `related('files').create(…)` writes it. Treating
38
+ * every relation access as a read made a table written only through a relation
39
+ * look externally maintained.
40
+ */
41
+ relationWritten?: boolean;
34
42
  /**
35
43
  * Does this access fire the model's hooks?
36
44
  *
@@ -1,6 +1,7 @@
1
1
  import type { ClassDeclaration, SourceFile } from 'ts-morph';
2
2
  import type { AppContext } from '../app_context.js';
3
3
  import type { CollectedDataStore } from '../sources/data_stores.js';
4
+ import type { EventBindings } from '../sources/event_bindings.js';
4
5
  import type { CallResolver } from '../resolvers/types.js';
5
6
  import type { HandlerRef, TraceStep, UnresolvedCall } from '../../types.js';
6
7
  /**
@@ -34,6 +35,16 @@ export type Behavior = {
34
35
  * fields of the VineJS schema — counting-decisions §7.
35
36
  */
36
37
  inputFields: string[];
38
+ /** input fields that enumerate nothing: an open `vine.object` */
39
+ opaqueInputFields: string[];
40
+ /**
41
+ * Fields read straight off the request. Kept apart from `inputFields` so the
42
+ * conformance metric keeps meaning what it says: these are DETs, and they are
43
+ * not a validator.
44
+ */
45
+ requestFields: string[];
46
+ /** the transaction reads the request in a way that enumerates nothing */
47
+ opaqueRequest: boolean;
37
48
  trace: TraceStep[];
38
49
  /** bodies reached, for `fp:diff` */
39
50
  scope: ScopeEntry[];
@@ -49,6 +60,12 @@ export type GraphOptions = {
49
60
  * pattern, so a project with its own convention registers it here.
50
61
  */
51
62
  callResolvers?: CallResolver[];
63
+ /**
64
+ * Which listeners each event reaches. Collected by the caller, because the
65
+ * binding lives in a preload file and is an application-wide fact, like the
66
+ * data stores.
67
+ */
68
+ eventBindings?: EventBindings;
52
69
  };
53
70
  /**
54
71
  * Analyzer with state shared across handlers.
@@ -60,6 +77,7 @@ export type GraphOptions = {
60
77
  */
61
78
  export declare function createAnalyzer(app: AppContext, stores: CollectedDataStore[], options?: GraphOptions): {
62
79
  analyze: (handler: HandlerRef) => Behavior;
80
+ writtenAnywhere: () => Set<string>;
63
81
  /** how many files the project loaded — used to prove it does not grow */
64
82
  fileCount: () => number;
65
83
  };
@@ -78,3 +96,14 @@ export declare function analyzeHandler(app: AppContext, stores: CollectedDataSto
78
96
  * as any import. No type checker is needed.
79
97
  */
80
98
  export declare function injectedFor(owner: ClassDeclaration | undefined, file: SourceFile, app: AppContext): Map<string, string>;
99
+ /**
100
+ * Both maps a file's imports produce: where a local name resolves, and what it
101
+ * was called where it was exported.
102
+ *
103
+ * Exported because the tests need the same answer the pipeline gets: a second
104
+ * implementation in the helpers drifted from this one and missed aliases.
105
+ */
106
+ export declare function importMapsOf(file: SourceFile, app: AppContext): {
107
+ imports: Map<string, string>;
108
+ exportedAs: Map<string, string>;
109
+ };
@@ -1,6 +1,19 @@
1
1
  import type { CallExpression, ClassDeclaration } from 'ts-morph';
2
2
  /** Is this call one that cannot reach a data store? */
3
3
  export declare function isNoise(call: CallExpression, owner?: ClassDeclaration): boolean;
4
+ /**
5
+ * Iteration over a list, checked BEFORE the resolvers run.
6
+ *
7
+ * `PAPEIS_CONCEDIVEIS.map((name) => …)` is `Identifier.method(args)`, the shape
8
+ * `static-service` exists for, so the resolver claimed it, resolved the enum
9
+ * module, found no `map` in it and reported a gap — noise never got asked,
10
+ * because it is only consulted once every resolver has declined.
11
+ *
12
+ * No resolver's pattern is `X.map(callback)`, so refusing this shape up front
13
+ * costs nothing and is not the same as silencing an unresolved call: nothing
14
+ * was ever there to resolve.
15
+ */
16
+ export declare function isIterationCall(call: CallExpression): boolean;
4
17
  /**
5
18
  * The body-not-found path: a symbol resolved to an application file whose
6
19
  * member is not there. For a framework service that is expected — the
@@ -0,0 +1,17 @@
1
+ import type { CallResolver } from './types.js';
2
+ /**
3
+ * "Event" pattern: the handler announces, and listeners act.
4
+ *
5
+ * await events.OrderPlaced.dispatch(order.id)
6
+ *
7
+ * Runs BEFORE `job-dispatch`, which matches `Identifier.dispatch(args)` — the
8
+ * shape the direct form takes. Left to it, the event class was resolved and
9
+ * searched for a `handle` it does not declare (`dispatch` comes from
10
+ * `BaseEvent`), so the call was reported as an unknown and the listeners' reads
11
+ * and writes went uncounted.
12
+ *
13
+ * COUNTING DECISION: the same one taken for a job. The user clicks, the effect
14
+ * happens, and AFP §6.5.3 requires aggregating every path the transaction
15
+ * reaches — the emitter is an implementation detail of how it gets there.
16
+ */
17
+ export declare const eventDispatchResolver: CallResolver;
@@ -1,2 +1,2 @@
1
- import { n as resolveCall, t as BUILTIN_CALL_RESOLVERS } from "../../../resolvers-CU9HKYpn.js";
1
+ import { n as resolveCall, t as BUILTIN_CALL_RESOLVERS } from "../../../resolvers-CRB6lXoo.js";
2
2
  export { BUILTIN_CALL_RESOLVERS, resolveCall };
@@ -1,5 +1,6 @@
1
1
  import type { CallExpression, SourceFile } from 'ts-morph';
2
2
  import type { DataStore, HandlerRef } from '../../types.js';
3
+ import type { EventBindings } from '../sources/event_bindings.js';
3
4
  /**
4
5
  * AdonisJS does not impose a code organisation. The same transaction can be
5
6
  * written in very different ways:
@@ -26,6 +27,16 @@ export type ResolverContext = {
26
27
  depth: number;
27
28
  /** file imports: local identifier -> resolved absolute path */
28
29
  imports: Map<string, string>;
30
+ /**
31
+ * Local identifier -> the name it was exported under, when they differ.
32
+ *
33
+ * `import { createUser as create }` binds `create` locally while the function
34
+ * is `createUser` in its own file. Following the local name looks for a body
35
+ * that does not exist, and the call is reported as unresolved for a reason
36
+ * that is not true. Identity is the (specifier, exported name) pair — the same
37
+ * mistake the data-store collector had to unlearn.
38
+ */
39
+ exportedAs: Map<string, string>;
29
40
  /**
30
41
  * Injected dependencies visible in this body: property name -> class file.
31
42
  * For example `billing` -> `.../billing_service.ts`.
@@ -45,6 +56,15 @@ export type ResolverContext = {
45
56
  * it were business code.
46
57
  */
47
58
  dataStoresBySymbol: Map<string, DataStore>;
59
+ /**
60
+ * Which listeners each event reaches, read from `emitter.on(...)`.
61
+ *
62
+ * Empty when the application declares no bindings. Like the data stores, this
63
+ * is collected BEFORE any handler analysis: a dispatch cannot be followed
64
+ * from the call site alone, because the binding lives in a preload file the
65
+ * handler never imports.
66
+ */
67
+ eventBindings: EventBindings;
48
68
  /** resolves an AdonisJS specifier (`#collect/models/invite`) to a path */
49
69
  resolveSpecifier(specifier: string): string | null;
50
70
  /** loads a file into the project, if it exists */
@@ -62,4 +82,19 @@ export interface CallResolver {
62
82
  /** lower runs first; specific strategies before generic ones */
63
83
  readonly order?: number;
64
84
  resolve(call: CallExpression, ctx: ResolverContext): HandlerRef[];
85
+ /**
86
+ * "This call is mine, and it reaches no data store."
87
+ *
88
+ * `resolve` has two outcomes where three are needed. Returning `[]` means
89
+ * *not recognised*, so a strategy that recognises a call perfectly well and
90
+ * knows it touches nothing countable had no way to say so: the call still
91
+ * landed in `unresolved`, and a project could not answer its own false
92
+ * positives without making the tool claim a body that does not exist.
93
+ *
94
+ * Declaring it here is deliberately louder than a name on a silence list.
95
+ * It costs a named strategy and a reason in the project's own config, and
96
+ * `fp:count` still reports the volume — because a silent drop is the worst
97
+ * defect this package can have, whoever writes it.
98
+ */
99
+ ignores?(call: CallExpression, ctx: ResolverContext): boolean;
65
100
  }
@@ -0,0 +1,44 @@
1
+ import { Node } from 'ts-morph';
2
+ import type { Expression, SourceFile } from 'ts-morph';
3
+ import type { AppContext } from '../app_context.js';
4
+ import type { HandlerRef } from '../../types.js';
5
+ /**
6
+ * Which listeners each event reaches.
7
+ *
8
+ * `events.OrderPlaced.dispatch(id)` in a handler is the user's click, and the
9
+ * write happens in a listener. AFP §6.5.3 requires aggregating every path a
10
+ * transaction reaches, so this is the same decision already taken for a job
11
+ * dispatch: the effect belongs to the transaction that caused it, whatever
12
+ * thread runs it.
13
+ *
14
+ * Without the binding the dispatch resolved to the event class, which declares
15
+ * no `dispatch` of its own — it inherits `BaseEvent` — so the call was reported
16
+ * as an unknown AND every read and write inside the listener went uncounted.
17
+ * That is the worst pairing: the gap is visible and the number is short.
18
+ *
19
+ * The binding is declared, not conventional: `emitter.on(event, [listeners])`
20
+ * in a preload file. It is therefore READ, never inferred from a name — the
21
+ * same rule the subpath imports follow.
22
+ */
23
+ /** event class file (POSIX) -> the listener bodies it reaches */
24
+ export type EventBindings = Map<string, HandlerRef[]>;
25
+ /**
26
+ * All these functions need of the application: how to resolve a specifier.
27
+ *
28
+ * Narrowed on purpose, so the resolver can ask the same question of a call site
29
+ * without being handed the whole context — and so this file cannot start
30
+ * depending on more of it by accident.
31
+ */
32
+ type SpecifierResolver = Pick<AppContext, 'resolveSpecifier'>;
33
+ export declare function collectEventBindings(app: AppContext): EventBindings;
34
+ /**
35
+ * The event class a dispatch or a binding names.
36
+ *
37
+ * Two shapes reach here: the class imported directly, and the generated
38
+ * registry (`events.OrderPlaced`), which is what `node ace make:event` produces
39
+ * and therefore the common one. Exported because the resolver has to ask the
40
+ * same question of a call site, and two implementations of "which event is
41
+ * this" would drift.
42
+ */
43
+ export declare function resolveEventClass(expression: Expression | Node, from: SourceFile, app: SpecifierResolver): string | null;
44
+ export {};
@@ -50,8 +50,22 @@ export declare function measureStructure(inventory: Inventory, count: CountResul
50
50
  * is what turns into a standards audit.
51
51
  */
52
52
  export type Conformance = {
53
- /** write transactions whose input fields come from a validator */
54
- writesWithValidator: {
53
+ /**
54
+ * Of the transactions that TAKE input, how many declare it with a validator.
55
+ *
56
+ * The first version of this measured validators over all writes, and read
57
+ * 39% on a healthy application — which invited the conclusion that 61% of its
58
+ * writes were unvalidated. They were not: most were workflow triggers
59
+ * (`POST /orders/:id/submit`, `POST /orders/:id/clear`) that carry nothing
60
+ * beyond the route parameter, exactly as counting-decisions §7 describes. A
61
+ * metric that makes a reader draw a false conclusion is worse than no metric,
62
+ * and this one made its own author draw it.
63
+ *
64
+ * The denominator is therefore the transactions that read something: a
65
+ * validator, a `request.input(…)`, or an `all()`/`body()` the analysis cannot
66
+ * enumerate.
67
+ */
68
+ inputsWithValidator: {
55
69
  ok: number;
56
70
  total: number;
57
71
  ratio: number;
@@ -1,2 +1,2 @@
1
- import { n as analyze, t as CoverageTooLowError } from "../pipeline-BzP-ITGN.js";
1
+ import { n as analyze, t as CoverageTooLowError } from "../pipeline-CIAydCcT.js";
2
2
  export { CoverageTooLowError, analyze };
@@ -1,6 +1,16 @@
1
- import type { CountResult, CountedFunction } from '../types.js';
1
+ import type { CountResult, CountedFunction, Inventory } from '../types.js';
2
2
  import type { FunctionPointDiff } from '../albrecht/diff.js';
3
+ import type { Conformance, StructureMetrics } from '../metrics/structure.js';
3
4
  export declare function renderCount(result: CountResult): string;
4
5
  /** `fp:explain`: a function's provenance, which is what supports a dispute */
6
+ /**
7
+ * Structure and conformance, beside the count and never instead of it.
8
+ *
9
+ * If function points pay, the team optimises function points — more models, more
10
+ * endpoints, less reuse. Density and coupling on the same report are the
11
+ * counterweight, which is why this shares the inventory rather than collecting
12
+ * anything of its own.
13
+ */
14
+ export declare function renderMetrics(structure: StructureMetrics, conformance: Conformance, coverage: Inventory['coverage']): string;
5
15
  export declare function renderExplain(fn: CountedFunction): string;
6
16
  export declare function renderDiff(diff: FunctionPointDiff): string;
@@ -86,6 +86,23 @@ export type HandlerBehavior = {
86
86
  touches: string[];
87
87
  /** declared input fields (validators) */
88
88
  inputFields: Field[];
89
+ /**
90
+ * Input fields that enumerate nothing — an open `vine.object`.
91
+ *
92
+ * They count 1 DET each, like an opaque column, and are reported: the number
93
+ * is a floor, not a measurement.
94
+ */
95
+ opaqueInputFields: Field[];
96
+ /**
97
+ * Input fields read straight off the request, with no validator.
98
+ *
99
+ * Kept apart from `inputFields` so the conformance metric keeps meaning what it
100
+ * says. They are DETs all the same: `request.input('title')` is a
101
+ * user-recognisable field crossing the boundary, which is §7.2's definition.
102
+ */
103
+ requestFields: Field[];
104
+ /** the transaction reads the request in a way that enumerates nothing */
105
+ opaqueRequest: boolean;
89
106
  /** declared output fields (transformers, DTOs) */
90
107
  outputFields: Field[];
91
108
  /** path walked through the call graph — what `fp:explain` prints */
@@ -12,6 +12,16 @@ export default defineConfig({
12
12
  infrastructure: ['access_tokens', 'reset_password_tokens', 'audits'],
13
13
  ignoreEntryPoints: ['drive.fs.serve', 'prometheus.metrics'],
14
14
  externallyMaintained: [],
15
+
16
+ /**
17
+ * Business data the AFP naming filter excluded by accident.
18
+ *
19
+ * It drops anything containing `session`, `template`, `error` or `types`,
20
+ * which is right for infrastructure and wrong for, say, a chat session the
21
+ * user manages. Check the confidence block of `fp:count` for what it
22
+ * excluded, and list here what is really business.
23
+ */
24
+ business: [],
15
25
  },
16
26
 
17
27
  /** Logical subgroups (RET). `constant` pins it at 1, which is honest. */
@@ -32,6 +42,22 @@ export default defineConfig({
32
42
  * here.
33
43
  */
34
44
  // overrides: {
35
- // 'POST /petitions': { det: 42, reason: 'JSON Schema form; 42 user fields' },
45
+ // 'POST /forms': { det: 42, reason: 'JSON Schema form; 42 user fields' },
46
+ // },
47
+
48
+ /**
49
+ * How change is priced. A clause of the contract, not a flag.
50
+ *
51
+ * AEP anchors added at 1 and deleted at 0.4. For a MODIFIED function it grades
52
+ * the factor from 0.25 to 1.75 through Effort Complexity variation, which needs
53
+ * cyclomatic complexity this package does not measure — so it defaults to 1,
54
+ * which overestimates, and every `fp:diff` says so.
55
+ *
56
+ * `implementation` means same type, same DET, same FTR, different body: a
57
+ * refactor. Whether that is billable, and at what, is a decision for whoever
58
+ * signs the contract — not one this package should invent.
59
+ */
60
+ // diff: {
61
+ // reasonFactors: { implementation: 0.25 },
36
62
  // },
37
63
  })
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@filipebraida/adonis-function-points",
3
3
  "description": "Automated function point counting and code metrics for AdonisJS applications.",
4
- "version": "0.1.0",
4
+ "version": "0.3.0",
5
5
  "engines": {
6
6
  "node": ">=24.0.0"
7
7
  },
@@ -9,6 +9,7 @@
9
9
  "files": [
10
10
  "build",
11
11
  "bin/cli.js",
12
+ "CHANGELOG.md",
12
13
  "!build/bin",
13
14
  "!build/tests"
14
15
  ],
@@ -1,19 +0,0 @@
1
- //#region src/define_config.ts
2
- const DEFAULTS = {
3
- boundary: {},
4
- retStrategy: "constant",
5
- maxDepth: 3,
6
- messageDet: 0
7
- };
8
- function defineConfig(config) {
9
- return {
10
- ...DEFAULTS,
11
- ...config,
12
- boundary: {
13
- ...DEFAULTS.boundary,
14
- ...config.boundary
15
- }
16
- };
17
- }
18
- //#endregion
19
- export { defineConfig as n, DEFAULTS as t };
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1,2 +0,0 @@
1
- declare const _default: import("tsdown").UserConfig;
2
- export default _default;