Your dependencies, as an upgrade plan

This recipe is what an agent made of one prompt, in a repository it had never seen. Every third-party package, the version pinned against the latest on npm, the modules that use it and the upgrade it needs, with the risky ones flagged.

Read https://graview.dev/start.md, then turn this repo's dependencies into a Graview app.

Paste it into Claude Code, Codex or Cursor at the root of your repository. start.md is the page the agent reads.

The app, running

This is the declaration below, compiled in your browser by the same compiler graview check uses, over the seed below. Press a district; open the problems; take a repair.

Your dependencies, as an upgrade plan, loading…

What the agent declared

Dependencies: Larkspur's third-party packages: the version each package.json pins, the latest on npm, which modules use each, and the upgrade each needs. 5 kinds, 24 acts and 3 rules, each rule with the act that repairs it.

KindFieldsRelationsRecords in the seed
modulepath, name, summaryownedBy to person17
personhandlenone7
packagename, pinned, latest, upgrade, planassignedTo to person35
dependencyname, version, section, manifestdeclaredIn to module; pins to package49
decisionnumber, title, status, date, fileaffects to module; supersedes to decision; drops to package14

The rules

  • One version per package. Over each dependency: requires version == pins.pinned. Repaired by “Pin it like the others” (align-pin).
  • A major upgrade has someone on it. Over each package, when upgrade == 'major' && plan != 'drop': requires present(assignedTo). Repaired by “Assign its upgrade” (assign-upgrade).
  • What an accepted decision drops is planned to go. Over each package, when some(in('drops'), status == 'accepted'): requires plan == 'drop'. Repaired by “Plan to drop it” (plan-drop).

What graview check says

Run on every push, against the committed file:

$ npx graview check --document app.json
✓ the document compiles and checks clean

How it was made

An agent was given the prompt above and nothing else, at the root of Larkspur: a booking service for small clinics that does not exist, written as a TypeScript monorepo the size of a real one, with sixteen modules, guides and runbooks with gaps, a CODEOWNERS that misses some, fourteen decision records and pinned dependencies. It followed start.md, scaffolded, declared, ran graview check until it was clean, opened graview preview and reported back. The two files it wrote are committed as they came out of that run.

CI does not run an agent. On every push it checks the committed declaration with graview check --document, loads the seed into a store, and opens this page in a browser to see the app mount. To run the prompt again yourself:

node scripts/fixtures/recipes/larkspur.mjs larkspur
cd larkspur && git init -q && git add -A && git commit -qm larkspur
# then paste the prompt into your agent

The files: app.json and seed.json (122 records and 142 relations).

The declaration, in full

{
  "format": "graview-document",
  "formatVersion": 1,
  "name": "Dependencies",
  "description": "Larkspur's third-party packages: the version each package.json pins, the latest on npm, which modules use each, and the upgrade each needs.",
  "kinds": {
    "module": {
      "noun": "module",
      "plural": "modules",
      "label": "{path}",
      "description": "A directory with its own package.json: the repository root, a service, a package or a tool.",
      "fields": {
        "path": {
          "type": "string",
          "required": true,
          "description": "The directory, from the repository root."
        },
        "name": {
          "type": "string",
          "required": true,
          "description": "The name its package.json gives it."
        },
        "summary": {
          "type": "string",
          "description": "The description its package.json gives it."
        }
      },
      "edges": {
        "ownedBy": {
          "to": [
            "person"
          ],
          "cardinality": "many",
          "description": "who CODEOWNERS says reviews it",
          "inverse": "the modules they review"
        }
      }
    },
    "person": {
      "noun": "person",
      "plural": "people",
      "label": "{handle}",
      "description": "Someone .github/CODEOWNERS names.",
      "fields": {
        "handle": {
          "type": "string",
          "required": true,
          "description": "Their GitHub handle."
        }
      }
    },
    "package": {
      "noun": "package",
      "plural": "packages",
      "label": "{name}",
      "description": "A third-party npm package some package.json depends on.",
      "fields": {
        "name": {
          "type": "string",
          "required": true,
          "description": "Its name on npm."
        },
        "pinned": {
          "type": "string",
          "required": true,
          "description": "The version the repository pins (the highest, where modules differ)."
        },
        "latest": {
          "type": "string",
          "required": true,
          "description": "The latest version on npm (npm view <name> version)."
        },
        "upgrade": {
          "type": "enum",
          "options": [
            "major",
            "minor",
            "patch",
            "none"
          ],
          "required": true,
          "description": "How far pinned is from latest. Major means a breaking change: the first non-zero number of the version moves."
        },
        "plan": {
          "type": "enum",
          "options": [
            "upgrade",
            "hold",
            "drop"
          ],
          "description": "What the team means to do with it."
        }
      },
      "edges": {
        "assignedTo": {
          "to": [
            "person"
          ],
          "cardinality": "one",
          "description": "who is doing its upgrade",
          "inverse": "the upgrades they are doing"
        }
      }
    },
    "dependency": {
      "noun": "dependency",
      "plural": "dependencies",
      "label": "{name}@{version} in {manifest}",
      "description": "One line of one package.json: a module depending on a package at a version.",
      "fields": {
        "name": {
          "type": "string",
          "required": true,
          "description": "The package name as the line writes it."
        },
        "version": {
          "type": "string",
          "required": true,
          "description": "The version the line pins."
        },
        "section": {
          "type": "enum",
          "options": [
            "dependencies",
            "devDependencies"
          ],
          "required": true,
          "description": "Which list of the package.json it is in."
        },
        "manifest": {
          "type": "string",
          "required": true,
          "description": "The package.json it is in."
        }
      },
      "edges": {
        "declaredIn": {
          "to": [
            "module"
          ],
          "cardinality": "one",
          "description": "the module whose package.json has this line",
          "inverse": "its third-party dependencies"
        },
        "pins": {
          "to": [
            "package"
          ],
          "cardinality": "one",
          "description": "the package it pins",
          "inverse": "the modules' lines that pin it"
        }
      }
    },
    "decision": {
      "noun": "decision",
      "plural": "decisions",
      "label": "ADR {number}: {title}",
      "description": "An architecture decision record in docs/adr.",
      "fields": {
        "number": {
          "type": "integer",
          "required": true,
          "min": 1,
          "description": "Its number."
        },
        "title": {
          "type": "string",
          "required": true,
          "description": "Its heading."
        },
        "status": {
          "type": "enum",
          "options": [
            "proposed",
            "accepted",
            "superseded"
          ],
          "required": true,
          "description": "Its Status line."
        },
        "date": {
          "type": "date",
          "description": "Its Date line."
        },
        "file": {
          "type": "string",
          "required": true,
          "description": "Its file, from the repository root."
        }
      },
      "edges": {
        "affects": {
          "to": [
            "module"
          ],
          "cardinality": "many",
          "description": "the modules its Affects line names",
          "inverse": "the decisions that affect it"
        },
        "supersedes": {
          "to": [
            "decision"
          ],
          "cardinality": "many",
          "description": "the decisions it replaces",
          "inverse": "the decision that replaced it"
        },
        "drops": {
          "to": [
            "package"
          ],
          "cardinality": "many",
          "description": "the packages it makes unnecessary",
          "inverse": "the decisions that drop it"
        }
      }
    }
  },
  "acts": {
    "add-module": {
      "creates": "module",
      "title": "Add a module",
      "description": "Record a directory with its own package.json."
    },
    "add-person": {
      "creates": "person",
      "title": "Add a person",
      "description": "Record someone who reviews or upgrades code."
    },
    "add-package": {
      "creates": "package",
      "title": "Add a package",
      "description": "Record a third-party package the repository depends on."
    },
    "add-dependency": {
      "creates": "dependency",
      "title": "Add a dependency",
      "description": "Record one line of one package.json."
    },
    "add-decision": {
      "creates": "decision",
      "title": "Add a decision",
      "description": "Record an architecture decision record."
    },
    "give-owner": {
      "on": "module",
      "connects": "ownedBy",
      "title": "Give it an owner",
      "description": "Say who reviews this module.",
      "fromTheOtherEnd": "Give them a module"
    },
    "take-owner": {
      "on": "module",
      "severs": "ownedBy",
      "title": "Take an owner off",
      "description": "Say this person no longer reviews this module.",
      "fromTheOtherEnd": "Take a module off them"
    },
    "assign-upgrade": {
      "on": "package",
      "connects": "assignedTo",
      "title": "Assign its upgrade",
      "description": "Say who is doing this package's upgrade.",
      "fromTheOtherEnd": "Give them an upgrade"
    },
    "unassign-upgrade": {
      "on": "package",
      "severs": "assignedTo",
      "title": "Unassign its upgrade",
      "description": "Say nobody is doing this package's upgrade now.",
      "fromTheOtherEnd": "Take an upgrade off them"
    },
    "plan-drop": {
      "on": "package",
      "sets": {
        "plan": "drop"
      },
      "title": "Plan to drop it",
      "description": "Say the team means to remove this package."
    },
    "plan-upgrade": {
      "on": "package",
      "sets": {
        "plan": "upgrade"
      },
      "title": "Plan to upgrade it",
      "description": "Say the team means to move this package to its latest version."
    },
    "plan-hold": {
      "on": "package",
      "sets": {
        "plan": "hold"
      },
      "title": "Hold it where it is",
      "description": "Say the team means to keep this package at its pinned version."
    },
    "place-in": {
      "on": "dependency",
      "connects": "declaredIn",
      "title": "Say which module has it",
      "description": "Say whose package.json this line is in.",
      "fromTheOtherEnd": "Add a dependency line"
    },
    "take-out-of": {
      "on": "dependency",
      "severs": "declaredIn",
      "title": "Take it out of its module",
      "description": "Say this line is in no module's package.json now.",
      "fromTheOtherEnd": "Take a dependency line out"
    },
    "pin-package": {
      "on": "dependency",
      "connects": "pins",
      "title": "Say which package it pins",
      "description": "Say which package this line pins.",
      "fromTheOtherEnd": "Add a line that pins it"
    },
    "unpin-package": {
      "on": "dependency",
      "severs": "pins",
      "title": "Unlink its package",
      "description": "Say this line pins no recorded package.",
      "fromTheOtherEnd": "Unlink a line that pins it"
    },
    "align-pin": {
      "on": "dependency",
      "sets": {
        "version": {
          "expr": "pins.pinned"
        }
      },
      "title": "Pin it like the others",
      "description": "Set this line to the version the rest of the repository pins."
    },
    "affect-module": {
      "on": "decision",
      "connects": "affects",
      "title": "Say it affects a module",
      "description": "Add a module to this decision's Affects line.",
      "fromTheOtherEnd": "Name a decision that affects it"
    },
    "unaffect-module": {
      "on": "decision",
      "severs": "affects",
      "title": "Say it no longer affects a module",
      "description": "Take a module off this decision's Affects line.",
      "fromTheOtherEnd": "Take a decision off it"
    },
    "supersede": {
      "on": "decision",
      "connects": "supersedes",
      "title": "Say what it supersedes",
      "description": "Say which decision this one replaces.",
      "fromTheOtherEnd": "Say what replaced it"
    },
    "unsupersede": {
      "on": "decision",
      "severs": "supersedes",
      "title": "Take back what it supersedes",
      "description": "Say this decision no longer replaces that one.",
      "fromTheOtherEnd": "Take back what replaced it"
    },
    "drop-package": {
      "on": "decision",
      "connects": "drops",
      "title": "Say it drops a package",
      "description": "Say this decision makes a package unnecessary.",
      "fromTheOtherEnd": "Name a decision that drops it"
    },
    "keep-package": {
      "on": "decision",
      "severs": "drops",
      "title": "Say it keeps a package",
      "description": "Say this decision does not make that package unnecessary.",
      "fromTheOtherEnd": "Take a decision that drops it off"
    },
    "accept-decision": {
      "on": "decision",
      "sets": {
        "status": "accepted"
      },
      "title": "Accept it",
      "description": "Mark this decision accepted."
    }
  },
  "rules": {
    "one-version-per-package": {
      "title": "One version per package",
      "over": "dependency",
      "require": "version == pins.pinned",
      "says": "{manifest} pins {name} at {version}; the rest of the repository pins another version",
      "repairs": [
        {
          "act": "align-pin",
          "label": "Pin it like the others"
        }
      ]
    },
    "major-upgrade-has-someone": {
      "title": "A major upgrade has someone on it",
      "over": "package",
      "when": "upgrade == 'major' && plan != 'drop'",
      "require": "present(assignedTo)",
      "says": "{name} is pinned at {pinned} and {latest} is out: a major upgrade nobody is assigned",
      "repairs": [
        {
          "act": "assign-upgrade",
          "label": "Assign its upgrade"
        }
      ]
    },
    "dropped-by-a-decision": {
      "title": "What an accepted decision drops is planned to go",
      "over": "package",
      "when": "some(in('drops'), status == 'accepted')",
      "require": "plan == 'drop'",
      "says": "An accepted decision drops {name}, and it is not planned to go",
      "repairs": [
        {
          "act": "plan-drop",
          "label": "Plan to drop it"
        }
      ]
    }
  },
  "brand": {
    "accents": {
      "decision": 175
    }
  }
}