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
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
"name": "systemap",
"description": "systemap: the map your coding agent draws of your system. One plugin, one skill.",
"description": "The systemap skill makes a system map from source facts and examined claims. All reader text must use ASD-STE100.",
"owner": {
"name": "0xfauzi",
"email": "wumpini.hussein@gmail.com"
},
"plugins": [
{
"name": "systemap",
"description": "The systemap skill: map a repository with systemap. Extracts the facts, drafts the model, checks it until every module is mapped, renders the page, hands back the judgement list.",
"description": "Make a system map with source facts, component claims, flow evidence, and ordered sequences. Use ASD-STE100 for all reader text.",
"author": {
"name": "0xfauzi",
"email": "wumpini.hussein@gmail.com"
Expand Down
9 changes: 7 additions & 2 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
{
"name": "systemap",
"version": "1.2.0",
"description": "The map your coding agent draws of your system: the systemap skill, which extracts the facts, drafts the model, checks it until every module is mapped, renders the page, makes a second pass with the judgement until nothing changes, and hands back the answers.",
"description": "The systemap skill makes a system map from source facts and examined claims. All reader text must use ASD-STE100.",
"author": {
"name": "0xfauzi",
"email": "wumpini.hussein@gmail.com"
},
"homepage": "https://github.com/0xfauzi/systemap",
"repository": "https://github.com/0xfauzi/systemap",
"license": "MIT",
"keywords": ["architecture", "system map", "documentation", "coding agents"]
"keywords": [
"architecture",
"system map",
"documentation",
"coding agents"
]
}
104 changes: 31 additions & 73 deletions .github/workflows/systemap.yml
Original file line number Diff line number Diff line change
@@ -1,26 +1,18 @@
name: systemap

# Four things are checked here. The system map under docs/map is generated
# from the code and the model module, committed so the page can be served
# as-is and so the diff between two commits of the facts file records what
# changed about the system; the first job fails when the committed map no
# longer matches the tree or the renderer, and the fix is one command,
# named in the failure. The test suite and the type and lint gates run on
# Linux, macOS and Windows with the oldest and the newest Python the
# package supports. The built wheel is installed into a clean virtual
# environment on each of the three and run on a copy of this repository's
# own map from a directory that is not a checkout. And the repository is
# validated, added and installed as a Claude Code plugin with the CLI.
# CI compares committed map output with source and tests the package on three operating systems.
# Delta writes one pull-request comment and changes that comment after each push.
# Fork tokens can prevent comments. The log still contains the delta report.
# Installation uses an empty environment and a map copy outside the source checkout.
# Plugin validation rejects all errors and warnings except the documented root CLAUDE.md warning.


on:
push:
branches: [main]
pull_request:
# By hand, from the Actions tab or `gh workflow run`: the push of 0.11.2
# produced no run, and there was no way to ask for one.
workflow_dispatch:

# Read-only for every job; the delta job widens it for itself alone.
permissions:
contents: read

Expand All @@ -46,40 +38,35 @@ jobs:
- name: Install project
run: uv sync --frozen --all-extras --dev

- name: facts match the tree
- name: Facts agree with source
run: |
uv run systemap extract --check || {
echo "::error title=Map is stale::the facts no longer describe the tree. Run systemap refresh and commit the output directory."
echo "::error title=Map is stale::The facts are different from the source. Run systemap refresh and commit the output directory."
exit 1
}

- name: layout, meaning and coverage are consistent
- name: Do map consistency checks
run: |
uv run systemap check || {
echo "::error title=Map check::the model contradicts itself or leaves a module unmapped; see the lines above."
echo "::error title=Map check::The model has incorrect claims or modules without component claims. See the lines above."
exit 1
}

- name: every judgement line is answered
- name: Do the strict judgement check
run: |
uv run systemap judgement --strict || {
echo "::error title=Judgement::a judgement line is unanswered. Act on it, or answer it under [judgement] answered in systemap.toml."
echo "::error title=Judgement::A judgement line has no answer. Act on it or write its answer under [judgement] answered in systemap.toml."
exit 1
}

- name: page matches the renderer
- name: Do the page output check
run: |
uv run systemap render --check || {
echo "::error title=Map is stale::index.html differs from what systemap renders. Run systemap refresh and commit the output directory."
echo "::error title=Map is stale::index.html is different from the rendered page. Run systemap refresh and commit the output directory."
exit 1
}

delta:
# What the pull request does to the map, as one comment: posted once,
# then updated in place on every push. The base and head commits reach
# the command through the environment, never through the template. A
# pull request from a fork has a read-only token, so the comment step
# warns instead of failing there; the delta is in the log either way.
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 10
Expand All @@ -101,7 +88,7 @@ jobs:
- name: Install project
run: uv sync --frozen --all-extras --dev

- name: what this change does to the map
- name: Show map changes
env:
BASE: ${{ github.event.pull_request.base.sha }}
HEAD: ${{ github.event.pull_request.head.sha }}
Expand All @@ -110,11 +97,11 @@ jobs:
uv run systemap delta --base "$BASE" --head "$HEAD" --format markdown > delta.md || code=$?
echo "$code" > delta.code
if [ ! -s delta.md ]; then
printf '<!-- systemap delta -->\n## What this change does to the map\n\nsystemap delta could not run (exit %s); see the workflow log.\n' "$code" > delta.md
printf '<!-- systemap delta -->\n## What this change does to the map\n\nsystemap delta could not run (exit %s). See the workflow log.\n' "$code" > delta.md
fi
cat delta.md

- name: post or update the comment
- name: Write the report comment
env:
GH_TOKEN: ${{ github.token }}
PR: ${{ github.event.pull_request.number }}
Expand All @@ -125,22 +112,17 @@ jobs:
gh api -X PATCH "repos/$REPO/issues/comments/$existing" -F body=@delta.md > /dev/null
else
gh api -X POST "repos/$REPO/issues/$PR/comments" -F body=@delta.md > /dev/null
fi || echo "::warning title=Map delta::could not post the comment (a pull request from a fork has a read-only token); the delta is in the step above."
fi || echo "::warning title=Map delta::The report comment could not be written. A fork has a read-only token. See the delta in the step above."

- name: every line is acted on
- name: Reject open map decisions
run: |
code="$(cat delta.code)"
if [ "$code" != "0" ]; then
echo "::error title=Map delta::the change needs the map's attention; see the comment on the pull request, act on each line, then run systemap refresh."
echo "::error title=Map delta::The map has open decisions. See the pull-request comment. Act on each line, then run systemap refresh."
fi
exit "$code"

test:
# The suite, the type check and the linter on every operating system the
# package claims, with the oldest and the newest Python it supports.
# UV_PYTHON overrides the repository's .python-version for the whole job,
# so uv creates the environment with the matrix interpreter. Node is on
# the runner for the keyboard test, which drives the page's script.
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
Expand Down Expand Up @@ -168,28 +150,19 @@ jobs:
- name: Install project
run: uv sync --frozen --all-extras --dev

- name: tests
- name: Run tests
run: uv run pytest -q

- name: types
- name: Do the type check
run: uv run mypy src --strict

- name: lint
- name: Do the lint check
run: uv run ruff check .

- name: format
- name: Do the format check
run: uv run ruff format --check src tests map bench

install:
# The wheel, installed with pip into a virtual environment that holds
# nothing else, run on a copy of this repository's own map from a
# directory beside the checkout: no .git, no source tree on the path,
# no uv at run time. init keeps every file that exists and writes the
# skill; extract reads the facts; refresh renders the page and the
# figures, since the check refuses a page that is not rendered and the
# committed page names the commit it was rendered at, which a copy
# without .git cannot reproduce; check and judgement are the gates a
# consumer's workflow runs.
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
Expand All @@ -212,18 +185,18 @@ jobs:
with:
version: latest

- name: build the wheel
- name: Make the wheel
run: uv build --wheel

- name: install the wheel into a clean virtual environment
- name: Install the wheel in an empty environment
run: |
uv venv --seed venv
if [ "$RUNNER_OS" = "Windows" ]; then . venv/Scripts/activate; else . venv/bin/activate; fi
pip install --no-deps dist/systemap-*.whl
systemap --version
python -c "import systemap, sys; print(systemap.__version__, sys.version)"

- name: init, extract, refresh, check on a copy of the self-map beside the checkout
- name: Run map commands on a copy outside the checkout
run: |
if [ "$RUNNER_OS" = "Windows" ]; then . venv/Scripts/activate; else . venv/bin/activate; fi
python -c "import shutil; shutil.copytree('.', '../selfmap', ignore=shutil.ignore_patterns('.git', '.venv', 'venv', 'dist', 'bench', 'node_modules', '__pycache__', '.mypy_cache', '.pytest_cache', '.ruff_cache'))"
Expand All @@ -237,15 +210,6 @@ jobs:
systemap describe

plugin:
# The repository is also a Claude Code plugin and its own marketplace.
# Both manifests, and the skill under skills/, are validated in strict
# mode so an unrecognised field or a missing description fails here.
# Then the checkout is added as a marketplace and the plugin installed
# from it with the CLI, into a configuration directory of its own, and
# the list must show it enabled: the install proof is mechanical.
# Adding a marketplace, installing and listing need no login (measured
# with an empty CLAUDE_CONFIG_DIR on the pinned version), so the job
# fails when any of the three does.
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
Expand All @@ -258,24 +222,18 @@ jobs:
node-version: 22

- name: Install the Claude Code CLI
# A global tool has no lockfile to install from; the version is the
# one the plugin was validated with locally, bumped on purpose.
run: npm install -g @anthropic-ai/claude-code@2.1.278 # zizmor: ignore[adhoc-packages]

- name: plugin manifest
# Strict, through a script that allows one warning and no others:
# the CLI advises against a CLAUDE.md at the plugin root, which is
# advice for a repository that is only a plugin. scripts/check_plugin_manifest.py
# says why, and fails on anything else the validator reports.
- name: Do the plugin manifest check
run: python3 scripts/check_plugin_manifest.py .claude-plugin/plugin.json

- name: marketplace manifest
- name: Do the marketplace manifest check
run: python3 scripts/check_plugin_manifest.py .claude-plugin/marketplace.json

- name: skills
- name: Do the skill check
run: claude plugin validate skills --strict

- name: the checkout as a marketplace, the plugin installed from it, listed
- name: Install the plugin from the checkout marketplace
env:
CLAUDE_CONFIG_DIR: ${{ runner.temp }}/claude-config
run: |
Expand Down
29 changes: 6 additions & 23 deletions .github/workflows/workflow.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,11 @@
# Publishing a release, with no token anywhere.
#
# Pushing a tag that names a version builds the package and uploads it to
# PyPI through trusted publishing: GitHub signs a short-lived identity for
# this workflow in this repository, and PyPI accepts the upload because it
# was told to trust that identity. Nothing long-lived is stored here, in a
# secret, or on anybody's laptop.
#
# The trusted publisher registered on PyPI names this repository, this file
# (workflow.yml) and the environment pypi. All three are part of the identity
# GitHub signs, so renaming this file or changing the environment breaks the
# upload until the publisher on PyPI is changed to match.
# A version tag starts a PyPI release through trusted publishing.
# The PyPI publisher identity includes this repository, workflow.yml, and the pypi environment.
# If an identity field changes, change the PyPI publisher configuration also.
# Release execution must not restore a cache that another job can write.
name: release

on:
push:
# Every tag that starts with v. The job checks the tag against the
# package's own version, which is the rule that matters, and a filter
# pattern is the wrong place to enforce it.
tags:
- "v*"

Expand All @@ -26,32 +15,26 @@ permissions:
jobs:
release:
runs-on: ubuntu-latest
# Without this the job inherits GitHub's six-hour default, so one hang
# bills six hours. Build and upload is a two-minute job.
timeout-minutes: 10
# Named in the identity GitHub signs, and registered on PyPI beside the
# repository and this file's name.
environment: pypi
permissions:
contents: read
# What signs the identity PyPI checks. It is the whole mechanism.
id-token: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
with:
# A release must not restore a cache anything else can write.
enable-cache: false
- name: the tag and the package agree
- name: Compare the tag and package version
run: |
tagged="${GITHUB_REF_NAME#v}"
built="$(uv run python -c 'import systemap; print(systemap.__version__)')"
if [ "$tagged" != "$built" ]; then
echo "::error title=Version mismatch::the tag says $tagged and the package says $built"
exit 1
fi
- name: build
- name: Make the package
run: uv build
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
Loading
Loading