# 14.1 From scripts to programs: modules (/building-ai-workers/structuring-programs/modules)

---
type: Document
title: "14.1 From scripts to programs: modules"
description: "How to find your way around a repository an agent split into modules, by what each file imports, and what to check before it adds a package."
status: stable
order: 314.01
ksor:
  owner: team:panaversity
  audience: [ public ]
  approval:
    by: process:panaversity
    at: 2026-11-02T13:55:25Z
chapter: "14"
part: III
expert_status: builder-only
concepts: [ "14.1" ]
last_verified: 2026-10-30
sources:
  - id: node-typescript
    title: "Modules: TypeScript (Node.js, accessed 2026-10-30)"
    resource: https://nodejs.org/api/typescript.html
  - id: uv-cli
    title: "uv CLI reference (Astral, accessed 2026-10-30)"
    resource: https://docs.astral.sh/uv/reference/cli/
generated:
  at: 2026-11-02T13:55:25Z
  by: process:panaversity
trust_tier: unverified
build_id: sha256:e1fbcb7d9a8f8b9b7cb96875022325c02c9ba4be94cef13b301cbd39a9edad85
dirty: true
ksor_version: 0.0.60
---

**In everyday life.** A small shop keeps everything in one notebook: prices, suppliers, staff phone numbers. It works until the notebook is too thick to find anything. A larger shop splits it into labeled folders, and each folder says on its cover which other folders it refers to. You can then read one folder without reading them all. But the folder many others refer to, such as the price list, is where one wrong entry reaches every order.

**What you will learn.** How to find your way around a project an agent split into files, by what each file uses from the others, and when to let it add code someone else wrote.

**Why it matters.** The files that every other file uses are where one change reaches everything.

### Where do I start in a repository I did not write?

At the top of each file, with the lines that say what it uses from other files. These words name the parts.

| Idea | What it means | In `brightline-ap` |
| --- | --- | --- |
| **Module** | One file, one job | `src/money.ts` turns cents into dollars |
| **Export** | A name other files may use | `export function usd(...)` |
| **Import** | A name this file uses from another | `import { usd } from "./money.ts"` |
| **Package** | A named group of modules | `py/brightline_ap/`, or `zod` from npm |
| **Dependency** | A package your code needs | `zod` in `package.json`, `pydantic` in `pyproject.toml` |
| **Lock file** | The exact version of every dependency, and of theirs | `package-lock.json`, `py/uv.lock` |

**Read a repository by its imports.** The import lines tell you which files depend on which. Drawn as boxes and arrows, they form a picture called an **import graph**. On it, mark where data comes in from outside, the program's **edges**: each line that reads a file or gets another program's answer, and each function that turns what came in into records. Then look for the foundations. A file that many others import is a foundation: a change there reaches everywhere. A file that nothing imports is either an entry point, the file a command starts, or dead code, which nothing uses.

**At work.** Before the agent reorganized the code, without changing what it does, Brightline's scripts each read their own CSV files and repeated the same few lines. After it, there are nine modules in `src/`. Ana does not start with the longest file. She lists every import line, draws the graph on paper, and first marks the edges: the files that read a CSV export, such as `due-by-friday.ts`, and the functions that turn its rows into records, such as `invoiceFromRow`. Five files import `csv.ts`, and three import `due-date.ts`. Those two are the foundations, so she reads them next and tests them hardest.

**In TypeScript.** Predict what this prints. It imports two names from two modules.

```ts
import { usd } from "../src/money.ts";
import { dueDate } from "../src/due-date.ts";

console.log(usd(1922953), dueDate("2026-10-07", "Net 30"));
```

```text
$19,229.53 2026-11-06
```

The path starts with `./` or `../`, so it is a file in this repository, and it ends in `.ts`, which Node requires. A name with no path, such as `"zod"`, is a package from `node_modules/`.

**In Python.** The same import, from the package.

```python
from brightline_ap.due_date import due_date
from brightline_ap.money import usd

print(usd(1922953), due_date("2026-10-07", "Net 30"))
```

```text
$19,229.53 2026-11-06
```

**Where the languages differ.**

| | TypeScript | Python |
| --- | --- | --- |
| What is shared | Only names marked `export` | Every top-level name. A leading underscore means "private by habit". Python allows it, but strict Pyright reports it |
| A local import | A file path, with its `.ts` extension | A dotted name from the package, such as `brightline_ap.money` |
| Packages and lock file | npm, `package.json`, `package-lock.json` | uv, `pyproject.toml`, `uv.lock` |
| Where the code is found | Relative to the importing file | From the folders Python searches, which include the folder you run from. That is why the Python commands run from `py/` |

### Should I let the agent add a package?

Only after three questions. A package the agent installs is code written by someone else, and it can do anything your account can do on this computer. Check it as you would a new vendor: is the name spelled exactly right, is it the real, well-known package and not a made-up or look-alike name, and is it needed at all? You might ask: "Before you install that, tell me who publishes it, how widely it's used, and whether a few lines of our own code would do the same job." Agents sometimes suggest a package for a job three lines of code would do, and attackers publish packages whose names differ from popular ones by one letter.

### Why commit the lock file?

So a clean copy of the repository next month installs exactly what you tested today. A lock file records the exact version of every package installed, including the packages your dependencies need. In this book, `npm ci` and `uv sync` install from the lock files. If a lock file is out of date, `npm ci` stops, and `uv sync` updates it unless you add `--locked`.

**The operating rule:** read a repository's import graph before its functions. Mark where data comes in, read the foundation modules next, and approve a new dependency only when you have checked its exact name and need.

![Figure 14.1. A reading map of brightline-ap in four layers: shared helpers and libraries (due-date, csv, money and zod), records and validation (schemas, vendor and invoice), workflow helpers (load-invoices and invoice-book), and commands (list-vendors, ap-client, export-json, reconcile, due-by-friday and scripts/ap-api). Mark the edges first: load-invoices and ap-client are outlined in red. Then read the shared helpers and the code that uses them. due-date is imported by six files and csv by seven. Two import paths are traced: export-json to load-invoices to schemas, and due-by-friday to invoice-book to invoice.](img/import-graph.png)

*Figure 14.1. A reading map at the end of the chapter, after the chapter adds the schemas, the loader and the API client, so its import counts are higher than the ones above. Red outlines mark its two main edges: `load-invoices` reads the files, and `ap-client` gets the API's answers. After the edges, read the shared files at the bottom. They are imported most, so a change there reaches the files above. Arrows point from a file to what it imports.*

**Practice it.** [Practice: modules](both-ai-vendors.md#practice-modules), in your own tool, fifteen minutes, and a few more the first time, for the setup.
