Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ body:
attributes:
label: Version
description: Release tag or commit you ran.
placeholder: v0.2.0
placeholder: latest release tag or commit sha
validations:
required: true
- type: dropdown
Expand Down
21 changes: 21 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,27 @@ jobs:
- name: skills CLI install smoke test (all agents)
run: .github/scripts/smoke-install.sh

portability:
# The skill must work where its users are: Windows consoles use legacy
# codepages (cp437/cp1252), macOS ships Python 3 via xcode/clang tooling.
# `python3` is on PATH on both runners; no shell:true needed since the
# command is a single invocation.
strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.x"
- name: interface-matrix self-check (${{ matrix.os }})
working-directory: skills/interface-matrix
run: |
python3 scripts/test_interface_matrix.py
python3 -O scripts/test_interface_matrix.py

commit-emails:
runs-on: ubuntu-latest
steps:
Expand Down
25 changes: 24 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@

## Unreleased

- Windows is a supported platform: the script reconfigures stdout to UTF-8, so the report
is byte-identical on legacy Windows console codepages (cp437/cp1252) instead of crashing
with `UnicodeEncodeError` mid-report. Regression-tested; a CI `portability` job runs the
full self-check on `windows-latest` and `macos-latest` in addition to Linux.
- SKILL.md: description cut to 483 characters — released OpenAI Codex builds reject
descriptions over 500; paths now say "run scripts from the directory containing this
SKILL.md" so a symlinked install works; `py -3` documented for Windows; the §2
`--source` paragraph and the §1 rules-audit sentence broken into readable form; the
unexplained "(P7's I14 and I15)" replaced with a concrete example.
- README: worked example now ships as committed files (`examples/example.md`,
`examples/example-output.md`, byte-reproducible); the four finding classes counted
correctly; N²/DSM glossed in plain English; Windows commands documented; tests count 84.
- RUNBOOK: section-9 detail now points at SKILL.md §1 instead of restating it; the
`--source` citation rule split into three sentences.
- Security policy "Supported version" now names the supported version (the latest
release) without stale first-release trivia.
- Issue-form version placeholder is no longer a hardcoded release tag.
- CI installs and self-tests the skill for every agent the `skills` CLI supports, with the
agent list read from the CLI at run time.
- Copyright holder in `LICENSE` and the README is now `macblackstuff`.
Expand All @@ -14,7 +31,13 @@
- The skill description carries an explicit Not-for clause.
- CI workflow vendored into the repository.

## 0.2.1 — 2026-09-27
## 0.2.2 — 2026-09-28

- Shipped the conduct and licence changes below: `CODE_OF_CONDUCT.md` names
`conduct@macblackstuff.com`, a live mailbox, so the published policy needed a release.
- Nothing else changed in this release; v0.2.1 remains the functional baseline.

## 0.2.1 — 2026-09-26

- Moved to [github.com/macblackstuff/interface-matrix](https://github.com/macblackstuff/interface-matrix);
every link and install command now uses the new owner. The old `macblackstuff-labs` URLs redirect.
Expand Down
39 changes: 27 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,20 @@ you or by an agent from a transcript, spec or code read. Each interface row name
producer, a consumer and four attributes: flows, format, trigger, owner.

The script builds the directed graph of the stated interfaces, partitions it (strongly
connected components, then a topological order of the condensation) and reports what the
list does not say out loud: interface rows with missing attributes, rows whose producer or
consumer is still `?`, internal components nothing feeds, outputs nothing consumes,
isolated components, feedback loops, and every ordered component pair nobody has stated
either way. With `--source FILE` it also reports the lines of the source document that no
cell cites.
connected components, then a topological order of the condensation) and reports four
classes of finding — missing components, interface gaps, boundary problems (unconsumed
outputs and isolated components) and feedback loops — plus every component pair nobody
has stated either way. With `--source FILE` it also reports the lines of the source
document that no cell cites.

The output is one Markdown report on stdout: numbered sections plus the matrix itself, row
feeds column. Nothing is inferred and no gap is filled in for you — a gap stays a gap until
a human resolves it.

An N² matrix (N-squared; a design structure matrix, or DSM) lists every component on both
axes, so each of the N×N cells is a yes/no/unknown about one directed pair — that is what
a flat interface list cannot show.

## Who it is for

Systems engineers, architects and technical leads planning or auditing a system of roughly
Expand All @@ -33,7 +36,10 @@ and it works on its own.

## Example

From `skills/interface-matrix`, write this to `example.md`:
A ready-made input lives at [`examples/example.md`](examples/example.md), and the real
report it produces is committed beside it as
[`examples/example-output.md`](examples/example-output.md). To follow along, from
`skills/interface-matrix` write this to `example.md`:

```markdown
## Components
Expand Down Expand Up @@ -79,13 +85,13 @@ Eight sections come back. The summary, the two finding tables and the matrix:

| line | producer | consumer | flows |
|---|---|---|---|
| line 16 | ? | Analyst | weekly digest |
| line 23 | ? | Analyst | weekly digest |

## 3. Interface gaps

| line | producer | consumer | missing |
|---|---|---|---|
| line 15 | Store | Scorer | Format, Trigger |
| line 22 | Store | Scorer | Format, Trigger |
```

```
Expand Down Expand Up @@ -120,18 +126,25 @@ component pairs nobody has ruled in or out.
Verify the install from inside the installed folder with [`scripts/test_interface_matrix.py`](skills/interface-matrix/scripts/test_interface_matrix.py):

```bash
python3 scripts/test_interface_matrix.py # Ran 83 tests ... OK
python3 scripts/test_interface_matrix.py # Ran 84 tests ... OK
```

On Windows the interpreter is `py -3` (`py -3 scripts/interface_matrix.py example.md`);
the script writes UTF-8 whatever the console codepage is, so the report is identical on
every platform.

### Harnesses tested

CI installs the skill with the [`skills` CLI`](https://github.com/vercel-labs/skills) on every push
and pull request, once per agent in its own throwaway home, and runs the 83 tests from each
CI installs the skill with the [`skills` CLI](https://github.com/vercel-labs/skills) on every push
and pull request, once per agent in its own throwaway home, and runs the 84 tests from each
installed copy. Every agent the CLI supports is covered — 79 at the time of writing (`skills`
1.7.0), of which 77 are installed and tested; the list is read from the CLI at run time. Two
agents are excluded with reasons recorded in `.github/scripts/smoke-install.sh`: `eve` and
`promptscript` — the CLI reports that neither supports global skill installation.

A separate CI job runs the same tests on Windows and macOS, so the skill is verified on the
platforms its users actually run, not just Linux.

The skill itself is harness-neutral: it is a `SKILL.md` plus standard-library Python, with no
agent-specific commands.

Expand All @@ -149,6 +162,8 @@ python3 scripts/interface_matrix.py INPUT.md --sample 0
python3 scripts/interface_matrix.py INPUT.md --source TRANSCRIPT.txt
```

On Windows use `py -3` in place of `python3`.

`--sample N` sets how many unstated pairs are printed (default 20, `0` = all).
`--source FILE` adds the coverage section over the document the inventory was read from.

Expand Down
4 changes: 2 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

## Supported version

Only the latest release is supported. The first release is v0.1.0. Fixes are published as a new
release; older tags do not receive backports.
Only the latest release is supported. Fixes are published as a new release; older tags do
not receive backports.

## Reporting a vulnerability

Expand Down
75 changes: 75 additions & 0 deletions examples/example-output.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Interface matrix report

## 1. Summary

- components: 4 (3 internal, 1 external)
- specified interfaces: 1
- interfaces with gaps: 1
- explicit none: 0
- missing-component candidates: 1
- unstated pairs: 10
- feedback loops: 0
- self-dependencies: 0
- superseded rows: 0 (interfaces 0, components 0)

## 2. Missing-component candidates

| line | producer | consumer | flows |
|---|---|---|---|
| line 23 | ? | Analyst | weekly digest |

## 3. Interface gaps

| line | producer | consumer | missing |
|---|---|---|---|
| line 22 | Store | Scorer | Format, Trigger |

## 4. Boundary check

External components are exempt.

- nothing feeds (internal): Ingest
- output nothing consumes (internal): Scorer
- isolated (internal): none

## 5. Feedback loops

No feedback loops.

Self-dependencies: none

## 6. Partitioned order

1. Ingest
2. Analyst
3. Store
4. Scorer

## 7. Unstated pairs

showing 10 of 10 (neither an interface nor `none`; external-to-external excluded)

- Ingest -> Scorer
- Ingest -> Analyst
- Store -> Ingest
- Store -> Analyst
- Scorer -> Ingest
- Scorer -> Store
- Scorer -> Analyst
- Analyst -> Ingest
- Analyst -> Store
- Analyst -> Scorer

## 8. Matrix

Row feeds column. Legend: `X` specified, `g` gap, `-` none, blank unstated, `S` self.

```
1 2 3 4
1 Ingest . X
2 Analyst .
3 Store . g
4 Scorer .
```

below-diagonal check: every below-diagonal mark lies inside a loop block.
23 changes: 23 additions & 0 deletions examples/example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Example inventory for interface-matrix

The input from the README's Example section, as a real file. Run from
`skills/interface-matrix`:

python3 scripts/interface_matrix.py ../../examples/example.md

## Components

| Component | Kind | Notes |
|---|---|---|
| Ingest | | pulls raw events |
| Store | | event store |
| Scorer | | scores events |
| Analyst | external | reads the digest |

## Interfaces

| Producer | Consumer | Flows | Format | Trigger | Owner | Source | Status |
|---|---|---|---|---|---|---|---|
| Ingest | Store | raw event rows | ndjson file | nightly cron | platform | S:L42 | |
| Store | Scorer | event batches | ? | ? | platform | S:L44 | |
| ? | Analyst | weekly digest | ? | ? | ? | | |
59 changes: 35 additions & 24 deletions skills/interface-matrix/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
name: interface-matrix
description: "Builds an N-squared interface matrix (DSM) from a Markdown component and interface inventory: finds missing components, interface gaps, unconsumed outputs, feedback loops and never-stated component pairs. Use when planning or auditing a system of roughly eight or more components, once the components are known and before work packages are cut, or on any request for an N2 diagram, design structure matrix, interface list, or gap analysis between components. Not for dependency graphs of source files, for drawing diagrams, or for discovering components on its own: it needs a hand-written Components and Interfaces inventory in Markdown."
description: "Builds an N-squared interface matrix (DSM) from a Markdown component and interface inventory: finds missing components, interface gaps, unconsumed outputs, feedback loops and never-stated component pairs. Use when planning or auditing a system of eight or more components, before work packages are cut, or on any N2 diagram, design structure matrix or gap-analysis request. Not for source-code dependency graphs, drawing, or discovering components: it needs a hand-written inventory."
license: MIT
compatibility: Requires Python 3.9 or newer. Standard library only — no dependencies and no install step.
compatibility: "Requires Python 3.9 or newer (Windows: py -3). Standard library only — no dependencies and no install step."
metadata:
author: macblackstuff
version: 0.2.1
Expand All @@ -18,7 +18,8 @@ Pass 3 of 7 in the systems-engineering decomposition pipeline.
purpose, declared inputs/outputs and owner.
- **Output:** the interface list plus four finding classes. Gaps feed the
SOURCE/RESEARCH/USER/DEFAULT gap register; the partitioned order and the loop
blocks feed the work packages and their sequencing.
blocks feed the work packages (the units of planned work the packaging pass cuts
from this output) and their sequencing.

Do not run this before the components are known, and do not cut work packages before
it has run — an interface discovered after packaging re-opens the packaging.
Expand Down Expand Up @@ -79,14 +80,17 @@ Rules:
(`*` = any classed component): not listed, not sampled. A component with a blank `Class`
matches no rule, not even `*`, so a forgotten Class cell can never drop pairs from
review — section 9 names every unclassed component. `review` wins where both match. An
explicit interface or `none` row always beats a rule. Section 9 reports, per rule, the
pairs it settled `none` and the pairs it matched at all (a pair any `review` rule
reclaimed is matched but not settled; a pair settled by several `none`
rules is counted under each, so the settled column sums to at least the total), dead rules, `none` rules that match an explicit interface, the unclassed
components, and the residue left for pair-by-pair review. An unknown `Disposition`, or a
class no component has, exits 1. A table is read as the Rules table only if its header
names both `Producer class` and `Consumer class`, so a foreign `| Disposition | Reason |`
table is left alone.
explicit interface or `none` row always beats a rule.
- Section 9 (rules audit, printed only when a Rules table is present) reports, per rule:
- the pairs it settled `none`;
- the pairs it matched at all — a pair any `review` rule reclaimed is matched but not
settled, and a pair settled by several `none` rules is counted under each, so the
settled column sums to at least the total;
- dead rules, and `none` rules that match an explicit interface;
- the unclassed components, and the residue left for pair-by-pair review.
- An unknown `Disposition`, or a class no component has, exits 1. A table is read as the
Rules table only if its header names both `Producer class` and `Consumer class`, so a
foreign `| Disposition | Reason |` table is left alone.
- `Source` and `Status` are optional; the other six interface columns are required.
A `Status` starting `superseded` retires the row (see §5).
- One Components table and one Interfaces table per file. A second table of either
Expand All @@ -98,8 +102,9 @@ Rules:

## 2. Run it

Run from this skill's own directory; every `scripts/...` path below is relative to it.
Python 3.9 or newer, standard library only.
Run scripts from the directory containing this SKILL.md — every `scripts/...` path below
is relative to it, wherever the skill is installed and whatever the working directory is.
Python 3.9 or newer, standard library only (`python3`; on Windows `py -3`).

```bash
python3 scripts/interface_matrix.py INPUT.md > OUTPUT.md
Expand All @@ -110,16 +115,20 @@ python3 scripts/interface_matrix.py INPUT.md --sample 0
python3 scripts/interface_matrix.py INPUT.md --source TRANSCRIPT.txt
```

`--source FILE` checks coverage of the file the inventory was read from: every `L<n>`,
`L<a>-<b>` and `L7,11-12` in any cell of any active Components or Interfaces row counts
as cited — a Rules row's `Reason` justifies the rule and models nothing, and a superseded
row models nothing any more, so their citations do not cover a line, though they are still
range-checked — and section 10
lists the uncited lines as contiguous spans (blank lines ignored) with counts. Those spans
are where an unmodelled component or interface hides. A citation past the file's last line exits 1, as
does `L0` (source line numbers start at 1); both need `--source` to be caught. A reversed
range such as `L9-7` exits 1 while parsing, with or without `--source`. Every one of these
errors names the input line of the row that carries the citation.
`--source FILE` checks coverage of the file the inventory was read from:

- Every `L<n>`, `L<a>-<b>` and `L7,11-12` in any cell of any **active** Components or
Interfaces row counts as cited.
- A Rules row's `Reason` justifies the rule and models nothing, and a superseded row
models nothing any more, so their citations do not cover a line — though they are
still range-checked.
- Section 10 lists the uncited lines as contiguous spans (blank lines ignored) with
counts. Those spans are where an unmodelled component or interface hides.

Errors, each naming the input line of the row that carries the citation: a citation past
the file's last line exits 1, as does `L0` (source line numbers start at 1); both need
`--source` to be caught. A reversed range such as `L9-7` exits 1 while parsing, with or
without `--source`.

`--sample N` sets how many unstated pairs are printed (default 20, `0` = all). The
sample is drawn deterministically: round-robin across the producer rows that have
Expand All @@ -146,7 +155,9 @@ So review, cell by cell:
1. Every listed row: are the four attributes right, and does the source support them?
Then, per row: can the named producer actually produce this flow, and can the named
consumer actually use it? If not, replace that endpoint with `?` and rerun — a named
but incapable endpoint is how a missing component hides (P7's I14 and I15).
but incapable endpoint is how a missing component hides (for example, a component
"Ingest" named as consumer of an agent's reply: it cannot hold that conversation, so
the real endpoint is a component nobody declared yet).
2. The rules (section 9): is each `none` rule true of every pair it matched? A dead rule
is wrong or premature; a rule that also matches an explicit interface contradicts it.
3. The residue (section 7): for each pair, is "no interface" actually true? Raise
Expand Down
Loading
Loading