Files
Paul HittandClaude Opus 5.5 5fb8acbc71 Publish to the GitLab package registry on version tags
The README's install line failed: the package was never published, and
nothing told npm where the @extant2000 scope lives. A tag like v1.2.3 now
publishes to this project's registry with CI_JOB_TOKEN, and the README
sets the scope's registry before installing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 16:42:15 -04:00

4.1 KiB
Raw Permalink Blame History

PortfolioAdvisor

Rank a portfolio of capabilities by transition readiness, and say what would move each one.

PortfolioAdvisor takes a list of scored capabilities and returns them ordered by the best next move. It is designed to consume @extant2000/sustain-score output, but it takes a plain shape, so any source of scores can feed it. It is pure, with no clock and no I/O.

What it is built against

A bad ranking is not the main risk. The risk is a ranking that reads as authoritative when the numbers behind it are not comparable. There are three ways that happens.

  1. Unassessed capabilities sort as bad. A capability nobody scored lands at the bottom next to one that was scored and found weak. Those are opposite situations: one needs work, the other needs an assessment. A single ordered list cannot hold both, so unassessed capabilities are listed separately and never interleaved. A null score is not zero.
  2. A composite hides its binding constraint. "Reports: 6" tells you where something sits, not what to do. Every entry carries its binding constraint and next action. An entry with neither is reported as "a position, not advice".
  3. Effort is invisible in a score. Two capabilities at 45, one needing a key rotation and one needing a rewrite, are not equally worth doing next.

So the ranking is by readiness gained per unit of effort, not by score. Each entry gets a leverage value:

(100 - score) × weight ÷ effort

That is the headroom left, scaled by how much the capability matters, divided by what the work costs. Effort weights are trivial 1, small 2, medium 4 and large 8. unknown counts as medium, not trivial. An unestimated task is not a cheap one, and defaulting optimistically is how an unscoped item reaches the top of a plan. Only a capability scoring 90 or more passes; the rest are reported as work outstanding.

Two orders

formatRanking() prints the ranked table first and then the findings worst-first. The two answer different questions ("what should I do next" and "what is most wrong"), and they are not the same order. A ranking whose display order contradicts its own numbering is worse than no numbering, so the table always prints #1 first.

Usage

import { rank, rankingTable, formatRanking, type CapabilityStanding } from '@extant2000/portfolio-advisor'

const portfolio: CapabilityStanding[] = [
  { name: 'docs', score: 90, effort: 'small', bindingConstraint: 'none material' },
  { name: 'gateway', score: 85, effort: 'small', bindingConstraint: 'retry fix unmerged', nextAction: 'merge the retry fix' },
  { name: 'reports', score: 60, effort: 'large', bindingConstraint: 'scheduled exports promised, nothing runs them', nextAction: 'build the scheduler' },
  { name: 'billing', score: 80, effort: 'trivial', bindingConstraint: 'debug logging left on', nextAction: 'turn off debug logging' },
]

const { ranked, unassessed } = rank(portfolio)
console.log(rankingTable(portfolio))

// Table plus the evidence-record findings
console.log(formatRanking(portfolio))

toReport(portfolio) returns a CapabilityReport from @extant2000/evidence-record. Each entry can also carry a weight (default

  1. and a where that is cited as the source of the score.

Sample output

Produced by running rankingTable() on the example above:

  #1  billing           80/100  leverage    20  turn off debug logging
  #2  gateway           85/100  leverage   7.5  merge the retry fix
  #3  reports           60/100  leverage     5  build the scheduler
  #4  docs              90/100  leverage     5  none material

The weakest capability is third. Ordering by score alone would have put the large rebuild ahead of a change that is close to free. A capability with score: null would appear below the table, unnumbered, as unassessed.

Install

The package is published to the GitLab package registry, so point the @extant2000 scope there first:

npm config set @extant2000:registry https://gitlab.com/api/v4/packages/npm/
npm install @extant2000/portfolio-advisor

License

MIT. Copyright (c) 2026 Extant 2000 LLC. See LICENSE.