Skip to main content
  1. Posts/

fp-control: AFP, Enhancement Counting, and Designing for Small Agents

A few days after publishing the first version of fp-control, I kept using it on real planning sessions and ran into the edges of what the skill could do. Three things were missing: a way to account for the Value Adjustment Factor, a mode for measuring changes to systems already in production, and a file format that would not strain a small agent on a large system. I added all three.

Adjusted Function Points #

The original skill produced Unadjusted Function Points (UFP) — the raw count before any system-level adjustments. IFPUG also defines Adjusted Function Points (AFP), which apply a Value Adjustment Factor derived from 14 General System Characteristics: things like data communications, performance requirements, transaction rate, reusability, and ease of installation. Each characteristic is rated 0–5, the scores sum to an Influence Degree, and the formula is:

VAF = 0.65 + (ID × 0.01)
AFP = UFP × VAF

The range is 0.65 to 1.35, meaning AFP can be up to 35% lower or higher than UFP.

AFP is optional in the skill — and optional in practice. The IFPUG community has largely moved away from it because the ratings are subjective and the ±35% range rarely changes a decision. But contracts and government projects still ask for it, and some teams use it to factor in characteristics that UFP ignores. So I added it as an opt-in step after the UFP calculation, with the full 14-characteristic table and context on what each one measures if the user is unsure how to rate it.

Enhancement Project counting #

The original skill only handled Development Project counting — measuring a new system from scratch. That covers the first version, but not what comes after deployment.

IFPUG treats enhancements differently. Instead of counting all functions again, you classify each existing one as Added, Changed, or Deleted:

  • ADD — new functions being introduced
  • CHG — existing functions being modified, counted at their new size
  • DEL — existing functions being removed

The enhancement size metric is DEFP (Development Enhancement Function Points):

DEFP = ADD + CHG_after

And the updated application baseline after the enhancement ships is:

Updated UFP = Baseline − DEL − CHG_before + ADD + CHG_after

DEFP and Updated UFP answer different questions. DEFP is the scope of the work being done right now — what you estimate effort against. Updated UFP is the new state of the system after the enhancement deploys, which becomes the baseline for the next round.

The skill now detects which mode to use at the start of a session. If the user references an existing .fpa.yaml file and indicates they are measuring changes, it enters Enhancement Project Mode — loading the baseline, walking through the classification of each existing function, collecting new ones, and producing a reconciliation table. If no file is referenced, it defaults to Development Mode.

Designing for small agents #

One constraint I had not thought through carefully enough in the first version: what happens when the output file gets large?

A 100-function system produces roughly 3,000 tokens of YAML. Add the skill prompt, conversation history, and a system prompt, and a small agent is already at 8,000–10,000 tokens before the user says anything. A 200-function system pushes that into territory where smaller models start to struggle.

I set a threshold of 50 total functions. Below that, everything stays in a single file. Above that, the save step produces an index file plus one detail file per function type:

my-system.fpa.yaml         ← index: totals, AFP, effort, compact function list
my-system.fpa.ilf.yaml     ← full ILF detail
my-system.fpa.ei.yaml      ← full EI detail
...

The index holds everything needed for a summary or a future enhancement baseline — function names and FP values, counts per type, AFP, and effort. The detail files hold the full counting variables (RET, DET, FTR, complexity) and are loaded on demand by the HTML skill, one tab at a time.

The extension also changed from .fpa.md to .fpa.yaml. The files are YAML-only now — there is no Markdown body. The HTML report is the human-readable artifact; the YAML file is strictly machine-readable. Keeping them at their actual format extension avoids a small but persistent confusion.

Another optimization for the enhancement chain: every enhancement file carries a baseline_functions block — a compact snapshot of all functions with just their name and FP value. When a future enhancement loads this file, it reads that block directly and never needs to open any prior file. The chain stays self-contained one file at a time, regardless of how many enhancements accumulate over time.

Two skills instead of one #

HTML report generation is now a separate skill: fp-control-html. The main /fp-control skill handles the counting session and saves the result to a .fpa.yaml file. Generating a visual report is a separate step: /fp-control-html reads any .fpa.yaml — development or enhancement, single-file or split — and produces a self-contained HTML file with tabbed navigation, SVG charts, dark/light mode, and print support. The output filename is derived directly from the YAML filename: my-system.fpa.yaml becomes my-system.html.

The split keeps both files smaller and independently updatable. It also means you can regenerate the report at any time without re-running the counting session.

Tracking what falls outside the count #

Counting sessions surface more than in-scope functions. Stakeholders routinely agree to push something to a later phase, explicitly cut something from the current round, or leave a note about a negotiation that shaped the final number. None of that fit in assumptions, which is about counting decisions and risks — not scope decisions.

I added three optional fields to the YAML schema:

  • deferred — features agreed to handle in a future phase (a plain list of strings)
  • rejected — features explicitly excluded from scope, with the rationale folded into the string
  • notes — a dated negotiation log, each entry a {date, text} pair

They live in the index file only — never in detail files or baseline snapshots — because they describe the planning conversation, not the function inventory. The HTML report surfaces them in a new Scope tab: deferred items get an amber “pending” treatment, rejected items a muted red/gray one, and notes render as a Date · Note table sorted newest first. The tab, and each of its three sections, only appears when there’s something to show.

Sharpening the HTML report #

A few rounds of generating real reports surfaced rough edges worth fixing in the skill itself rather than improvising each time:

  • Zero-item types disappear entirely. If a system has no EIF, the report no longer shows an empty EIF tab — the tab, its radio input, and its panel are all omitted. The same rule applies to any function type.
  • Subtotals show the complexity mix. Each function-type table now ends with something like Low×28 · Avg×25 · High×26 next to the FP total, so the shape of the count is visible at a glance, not just its sum.
  • The Overview table shows proportion, not just total. A % of UFP column makes it immediately clear which function type dominates the count.

I also settled on the layout I actually want these reports to look like, after comparing two generated side by side: a centered “printed document” card — soft gray page, ~960px container, gradient indigo header banner, a compact UFP badge — rather than a full-width dashboard. That’s now the skill’s default, and before generating, it briefly shows the user these defaults (layout, accent color, starting theme) and asks if they’d like anything changed.

Finally, for systems large enough to need the split YAML format, the skill now builds the HTML incrementally — writing the page shell first, then appending one function-type panel at a time — instead of attempting it in a single pass. It mirrors how the detail files themselves are loaded during a session: one function type at a time, each kept to a manageable size.

Agent context files #

Two small files were added to the repository root: agents.md and CLAUDE.md.

agents.md is vendor-agnostic: it explains what both skills do and how to invoke them, and it handles auto-installation — writing both fp-control.md and fp-control-html.md to the appropriate global location for the detected platform. CLAUDE.md is a single line — @agents.md — which is Claude Code’s import syntax. When Claude Code opens this repository directory, it loads CLAUDE.md automatically, which pulls in agents.md, triggering the install and making both skills available without any manual setup.

Other platforms do not auto-load CLAUDE.md, so Cursor and Windsurf users still rely on the self-install mechanism in agents.md. But the substance — what the skills do and how to use them — lives in the vendor-neutral agents.md, readable by any platform.

The repository is at github.com/adautomeira/fp-control.