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
5 changes: 5 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
{
"permissions": {
"allow": [
"Bash(backlog:*)"
]
},
"enabledPlugins": {
"superpowers@claude-plugins-official": true
}
Expand Down
22 changes: 19 additions & 3 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,28 @@
// (exact cache-key behavior unconfirmed), so a plain rebuild may
// silently keep the old toolchain.
// Do not "fix" a stale container with `pipx upgrade plcc-ng`; that
// defeats this pin. See
// dev-docs/issues/011-devcontainer-image-stale-plcc-ng-version.md
// defeats this pin. See CR-511 in the ourPLCC/dev tracker.
"image": "ghcr.io/ourplcc/devcontainers/plcc-ng:2.0.2@sha256:ebab585854e2c2dd5133db9616b398f6ede19702b514a5f4ace14757ae63dc9a",
"features": {
"ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {},
"ghcr.io/devcontainers-extra/features/shellcheck:1": {}
},
"postCreateCommand": "bin/install/bats.bash"
// The change-request tracker and org developer docs live in ourPLCC/dev,
// cloned beside this repo on the host and mounted at /workspaces/dev.
// ~/.claude is a named volume so assistant memory and settings survive
// a rebuild.
"initializeCommand": "test -d ../dev || git clone https://github.com/ourPLCC/dev ../dev; git -C ../dev config core.hooksPath .githooks",
"mounts": [
"source=${localWorkspaceFolder}/../dev,target=/workspaces/dev,type=bind",
"source=languages-ng-claude,target=/home/vscode/.claude,type=volume"
],
"containerEnv": {
"BACKLOG_CWD": "/workspaces/dev",
"GIT_CONFIG_COUNT": "2",
"GIT_CONFIG_KEY_0": "safe.directory",
"GIT_CONFIG_VALUE_0": "/workspaces/dev",
"GIT_CONFIG_KEY_1": "safe.directory",
"GIT_CONFIG_VALUE_1": "/workspaces/languages-ng"
},
"postCreateCommand": "${containerWorkspaceFolder}/bin/install/bats.bash && sudo chown vscode:vscode /home/vscode/.claude && npm i -g backlog.md@$(cat /workspaces/dev/.backlog-version) && git -C /workspaces/dev status >/dev/null && git -C ${containerWorkspaceFolder} status >/dev/null && ${containerWorkspaceFolder}/bin/install/claude-imports.bash"
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ plcc-ng/
__pycache__/
.claude/worktrees/
.superpowers/

# Implementation plans live in the worktree and are never committed
.plans/
11 changes: 5 additions & 6 deletions .releaserc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ plugins:
- - "@semantic-release/release-notes-generator"
# Renders ${nextRelease.notes} from those same commits. Without it
# nothing implements generateNotes and every release body is empty
# -- see dev-docs/issues/046-release-notes-generator-not-loaded.md.
# -- see CR-546 in the ourPLCC/dev tracker.
# The preset must match commit-analyzer's, or the notes would be
# grouped by Angular's conventions while the version was computed
# by conventionalcommits'.
Expand All @@ -34,11 +34,10 @@ plugins:
# Creates GitHub release entry and uploads specified assets.
#
# Both writes to issues and pull requests are off. This project
# tracks issues as files under dev-docs/issues/ and refers to them
# in commit messages with the same #NNN syntax GitHub uses for its
# own issues, so the plugin resolves "Closes #26" against GitHub's
# numbering and fails the run with NOT_FOUND -- see
# dev-docs/issues/048-release-success-step-resolves-file-issue-refs.md.
# tracked issues as files and referred to them in commit messages
# with the same #NNN syntax GitHub uses for its own issues, so the
# plugin resolved "Closes #26" against GitHub's numbering and failed
# the run with NOT_FOUND -- see CR-548 in the ourPLCC/dev tracker.
#
# successCommentCondition takes an early branch that skips the
# GraphQL lookups entirely. failCommentCondition suppresses the
Expand Down
56 changes: 56 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
Read these, in order, before making changes:

- @../dev/ORG-AGENTS.md
- @../dev/CONTRIBUTING.md
- @CONTRIBUTING.md

## Agent rules for this repo

### Shell and config embedded in a plan

Review snippets inside a plan as rigorously as code going into the repo.
They read as prose and get transcribed as-is, so a defect in one survives
into `bin/` unless someone catches it first.

CR-525 is the cautionary case: five defects were found across its eight
tasks, and **every one originated in the plan's own snippets** rather than
in the implementation — a false claim about when `tar` aborts, an
`[[ -e ]] && printf` inside a `while` loop that left the loop at exit
status 1 and broke a newly added `set -o pipefail`, `set -euo pipefail`
placed after the `cd` in a destructive script, `.dockerignore` patterns
written with `.gitignore` semantics, and a commit message that ignored
this repo's own issue conventions. The plan's `Amended`/`Corrected` blocks
record each one.

Two habits catch these: mentally execute a snippet for exit-status
propagation, subshell boundaries, and pattern semantics before dispatching
it; and when a snippet asserts a factual claim about tool behavior
("`tar` aborts", "this pattern matches"), verify it in a scratch directory
rather than asserting it. If someone implementing the plan reports being
blocked because reality contradicts it, assume they are right until shown
otherwise — that happened three times on CR-525 and they were correct
every time.

### Verifying a subagent's commits

A subagent's reported SHA is not evidence of which branch the commit is
on. When dispatching implementation agents while working in a git
worktree, check that each commit actually landed on the intended branch
before accepting a DONE report — `git branch --contains <sha>`, or
`git log --oneline -1` in the worktree, not the SHA the agent hands back.

On CR-531 (2026-08-06), a subagent told "do NOT cd to the parent repo" did
exactly that and committed to `main` in `/workspaces/languages-ng`. Its
return looked completely normal — status DONE, a real SHA, a plausible
summary. The commit was real; it was just on the wrong branch, and the
worktree's HEAD had not moved. Nothing in the report could reveal this,
since the SHA exists and resolves either way. Left undetected it would
have merged a phantom commit into `main` and left the branch's history
incomplete.

Put the branch name in the dispatch prompt and require the agent to
confirm `git branch --show-current` before committing, then verify it
yourself afterward. Recovery is cheap when caught early: cherry-pick onto
the correct branch, then `git reset --hard` the parent back to its prior
commit — safe while unpushed, and it stays in the reflog — but confirm
before resetting anyone's checkout.
121 changes: 0 additions & 121 deletions CLAUDE.md

This file was deleted.

61 changes: 46 additions & 15 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,44 @@
# Contributing
# Contributing to languages-ng

This document is the practical guide for working in this repository: how
a change gets from a worktree to `main`, and the commands you run along
the way. Read it before making changes.
This builds on the [ourPLCC developer guide](https://github.com/ourPLCC/dev/blob/main/CONTRIBUTING.md); read that first. Below is what's specific to this repo, and where it differs.

Conventions that have their own documents are linked, not duplicated:
the issue workflow is in
[dev-docs/issue-conventions.md](dev-docs/issue-conventions.md), open work
is listed by [bin/issues/list.bash](bin/issues/list.bash), and changes an
instructor's materials must track are logged in
[dev-docs/course-material-impact.md](dev-docs/course-material-impact.md).
## Where this repo differs from the org guide

Nowhere at present. One leftover from before the org guide is history, not
a deviation: [dev-docs/plans/](dev-docs/plans/) holds kept plans. Merged
plans are frozen, so they stay; add no new ones.

## Development environment

Open the repository in its devcontainer. It runs a digest-pinned image
carrying Java, Python, Node, and `plcc-ng`; `postCreateCommand` adds
bats. Do not install toolchain pieces by hand — see the warning in
[.devcontainer/devcontainer.json](.devcontainer/devcontainer.json) and
[issue #11](dev-docs/issues/011-devcontainer-image-stale-plcc-ng-version.md).
CR-511.

Before the container is created, it clones the
[ourPLCC/dev](https://github.com/ourPLCC/dev) repo beside this one, as
`../dev`, unless that folder already exists. The container mounts it at
`/workspaces/dev`, points the `backlog` CLI at it through `BACKLOG_CWD`,
and installs the pinned `backlog` version. In the tracker, this repo's
`project` value is `languages-ng`. Old languages-ng issue numbers still
resolve at +500: `#12` is `CR-512`.

[AGENTS.md](AGENTS.md) imports files from `../dev`. Claude Code loads
imports from outside the repo only once they are approved, and until then
skips them without an error, so agents never load the org guide. It does
not reliably ask for that approval, and the approval lives in
`~/.claude.json`, which a rebuild discards. So the container approves them
when it is created, by running
[bin/install/claude-imports.bash](bin/install/claude-imports.bash). To
check, run `/memory` in a session and confirm it lists `ORG-AGENTS.md` and
both `CONTRIBUTING.md` files. If they are missing, quit every Claude Code
session, since a running one can overwrite `~/.claude.json`, then run the
script and start a new session.

Keep each import in `AGENTS.md` on its own line with nothing after the
path: Claude Code reads an import up to the next whitespace, so
`@file.md,` looks for a file named `file.md,` and silently skips it.

## The workflow

Expand All @@ -40,10 +61,20 @@ bats. Do not install toolchain pieces by hand — see the warning in
| [bin/test.bash](bin/test.bash) | Run the whole bats suite over `src/` and `bin/`. Exit 0 means every test passed, 1 means real failures, 2 means the harness itself did not finish. |
| [bin/clean.bash](bin/clean.bash) | Remove build output left under `src/`. |
| [bin/install/bats.bash](bin/install/bats.bash) | Install the pinned bats under `~/.local`. Idempotent; run by `postCreateCommand` and by CI. |
| [bin/issues/new.bash](bin/issues/new.bash) | Create an issue. Never assign issue numbers by hand. |
| [bin/issues/close.bash](bin/issues/close.bash) | Close an issue, as the final commit of the branch that does the work. |
| [bin/issues/check.bash](bin/issues/check.bash) | Verify issue-file consistency. |
| [bin/issues/list.bash](bin/issues/list.bash) | Print the path of every open issue, one per line. Pipe it: `\| xargs head -n 50`. |
| [bin/install/claude-imports.bash](bin/install/claude-imports.bash) | Approve [AGENTS.md](AGENTS.md)'s imports from `../dev` in `~/.claude.json`. Run by the devcontainer on creation. Idempotent. |

## Course-material impact log

Some changes made while porting a language don't just change internals —
they change something an instructor's slides, handouts, or lecture notes
would need to match (a renamed field referenced in a semantics
walkthrough, a changed output format, a renamed grammar symbol). Log
these in [dev-docs/course-material-impact.md](dev-docs/course-material-impact.md),
one entry per change, filed under that language's heading, **in the same
commit that makes the change** — not batched up for later, since the
reasoning is freshest right when the change is made. This is distinct
from the CR history: it's curated specifically to answer "what do I need
to update in my course materials," not "what happened in the repo."

## Continuous integration

Expand Down
2 changes: 1 addition & 1 deletion bin/check-run-complete.bash
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ A run killed this way also leaks its whole scratch tree, since
bats only removes it in an EXIT trap. Check for stale
/tmp/bats-run-* directories and remove them.

See dev-docs/issues/031-suite-exhausts-disk-and-reports-spurious-failure.md
See CR-531 in the ourPLCC/dev tracker (backlog task view 531).
ADVICE
printf '%s\n' '================================================================'
} >&2
Expand Down
25 changes: 25 additions & 0 deletions bin/install/claude-imports.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -euo pipefail

# Approve AGENTS.md's imports from ../dev for this repo, so Claude Code loads
# the org guide. Claude Code skips unapproved outside imports silently and
# does not reliably ask, and ~/.claude.json does not survive a container
# rebuild. Run before any Claude Code session starts: a running session can
# overwrite ~/.claude.json. Idempotent.

SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
PROJECT_ROOT="$( cd "${SCRIPT_DIR}/../.." &> /dev/null && pwd )"
CONFIG="${HOME}/.claude.json"

current='{}'
if [[ -s "${CONFIG}" ]]; then
current="$(cat "${CONFIG}")"
fi

tmp="$(mktemp "${CONFIG}.XXXXXX")"
jq --arg p "${PROJECT_ROOT}" '.projects[$p] += {
hasClaudeMdExternalIncludesApproved: true,
hasClaudeMdExternalIncludesWarningShown: true
}' <<< "${current}" > "${tmp}"
chmod 600 "${tmp}"
mv "${tmp}" "${CONFIG}"
Loading
Loading