Skip to content

feat: link a route's and a scrape's names to the Process and surface they mean - #118

Merged
ExtraToast merged 1 commit into
mainfrom
feat/39-link-references
Sep 15, 2026
Merged

ExtraToast merged 1 commit into
mainfrom
feat/39-link-references

Conversation

@ExtraToast

Copy link
Copy Markdown
Contributor

Closes #39 (parent #35).

What this branch does

A route's and a scrape's process and surface are now references in both implementations, linked when the document is read, and a name that links to nothing is refused with a code at the pointer of what wrote it.

  • TypeScript: src/wire/project-intent/link.ts resolves the names inside the Application that holds them. The domain Route and Scrape hold the Process and Surface objects themselves, and a Process carries surfaces rather than a provides map.
  • Java: Route.process/surface and Scrape.process/surface are non-containment Ecore references, parsed as Xtext cross-references. ProjectIntentScopes offers the Application's Processes and the linked Process's surfaces, and UnlinkedNames turns an unresolved name into its specification code.
  • E_UNKNOWN_PROCESS, new in chapter 10, for a route or scrape naming a Process its Application does not have. E_UNKNOWN_SURFACE now fires for a scrape too.
  • Two refusal fixtures with committed diagnostics.json: unknown-surface (a route) and scrape-unknown-process.
  • The descriptor records references: every feature carries reference, whose one type is the target class, and a map names its entry (Surface), so a reference can point at a map's entries.
  • Chapter 10 states the opt-out convention once (absent where forgetting is harmless, none where it is dangerous), and its class diagram draws Route --> Surface as an association.

Decisions worth naming

Dependency edges stay names. They point into another project file; #46 links them over the composed union.

${exposure:...} placeholders are left for the ticket that first parses env files. Neither implementation reads env files yet.

A surface is not reported when its Process did not link. There is nothing to look it up in, and the Process's own refusal already says what is wrong. Both implementations follow this.

The class diagram is regenerated, not hand-edited. scripts/diagrams/class-diagram.py learns associations and names every distinct link into a shared target; the SVG is exported with the draw.io CLI as the script's usage says, which also retires the extra row the Process box kept after #114. Scrape --> Surface is left to the chapter text: across that row the line would run through six boxes, which is the layout rule the script already enforces for long links.

The Java token source keeps braces as hidden tokens. It used to drop { and } when it turned them into block markers, which left every later node one character off. Nothing read node text until cross-references did: auth-api linked as uth-api,. The coverage test now includes a flow mapping and a flow sequence.

Verification

  • ./mvnw clean verify in emf/: 125 tests, line coverage 100.0%, mutation score 100.0% (258 of 258).
  • npm run verify: 629 tests, src/ at 100%; Stryker 606 of 608 killed, 2 ignored as before. Ratchet raised to statements 99.06, branches 96.17, functions 100, lines 98.97, with README.md matching.
  • Both implementations match every intent oracle, the descriptor, and all ten refusal oracles.
  • npm run lint:codes: 60 codes defined, 11 exercised, 49 pending.

@ExtraToast ExtraToast added type: feature New user-facing or operator-facing capability. area: deploy homelab-deploy, deploy-v2, manifests, and rollout flow. component: build Gradle, npm, packaging, or local build tooling. component: tests Unit, integration, system, or contract test concern. labels Sep 15, 2026
@ExtraToast ExtraToast self-assigned this Sep 15, 2026
@github-actions

Copy link
Copy Markdown

Shape

bucket files additions deletions
production code 14 +321 -39
tests 11 +370 -54
specification 3 +37 -9
documentation 5 +18 -5
examples 5 +76 -1
tooling 4 +22 -15

Coverage

Baseline: main @ 454df5d.

metric this branch main diff
statements 99.06% 99.03% +0.03
branches 96.17% 96.09% +0.08
functions 100.00% 100.00% 0.00
lines 98.97% 98.95% +0.02

Lines this pull request changed: 38/38 covered (100.00%).

@ExtraToast
ExtraToast merged commit 088c931 into main Sep 15, 2026
22 checks passed
@ExtraToast
ExtraToast deleted the feat/39-link-references branch September 15, 2026 12:27
ExtraToast added a commit that referenced this pull request Sep 15, 2026
REQ-029 collided with #118's own row landed on main; this branch's
AGENTS.md script-list requirement moves to REQ-030. #118 also moved the
coverage baseline, so the ratchet in vitest.config.ts and README's quoted
numbers are recomputed against the rebased tree rather than the
pre-rebase measurement.
ExtraToast added a commit that referenced this pull request Sep 15, 2026
* docs: route every agent through AGENTS.md

Add AGENTS.md as the one instruction file every coding agent reads: the
estate pointer, the repository map, setup, every npm script, the testing
strategy, code style, the Boundaries section, the repository skills, the
claiming procedure, and the moved emf/ and MDE coursework sections. CLAUDE.md
becomes an import of it plus Claude-specific notes only.

* docs: configure repository skills and agent tooling

Four skills (adr, spec-change, gate-triage, new-rule) live in
.agents/skills, and .claude/skills is a committed symlink to that directory
(git ls-files -s reads mode 120000). .claude/settings.json holds a
read-only permission allowlist plus the one labelled write the claim
procedure needs, and enables the mattpocock-skills, typescript-lsp and
drawio plugins. .mcp.json holds context7. docs/agents/ configures the
mattpocock-skills engineering skills: the GitHub issue tracker with
cross-repository references written in full, the estate status: labels
mapped to the five canonical triage roles, and the single-context domain
doc layout with spec/v1 normative over an ADR.

* test: fail when a script escapes AGENTS.md's script list

Add lint:agents (scripts/lint-agents.ts), wired into the contracts CI job
and npm run verify, with a fixture (test/agents-contract.test.ts) proving it
fires on an undocumented script. Ledger rows REQ-029 and RULE-069 record it,
and docs/architecture.md's Gates table gains a row and its count moves to
nineteen, per 0102 and 0104.

The new gate's own coverage (96%/94.73%/100%/95.83%) carries the same
one-line command-guard gap every sibling gate script already does; the
suite's aggregate moves to 98.98%/96.06%/100%/98.89%, so the ratchet in
vitest.config.ts and README's quoted numbers move with it.

* feat(ci): claim the issue a pull request names

A non-gating workflow that adds status: in-progress (and removes
status: ready-for-agent) on the issue a pull request's title, body or
branch name references, and on its parent, when the pull request opens;
releases the claim when it closes. Labels only: the project board's own
Status field needs a token scope the default GITHUB_TOKEN does not carry,
and the job says so in its own log rather than claiming a fix it cannot
make.

* docs: extend CONTRIBUTING.md with change shape and release flow

Below the existing contribution policy: the shape of a change, when a
change takes a decision, Conventional Commits, what runs where across the
five non-gating workflows beside ci.yml, and release candidates and
releasing.

* test: recompute the coverage ratchet after rebasing onto main

REQ-029 collided with #118's own row landed on main; this branch's
AGENTS.md script-list requirement moves to REQ-030. #118 also moved the
coverage baseline, so the ratchet in vitest.config.ts and README's quoted
numbers are recomputed against the rebased tree rather than the
pre-rebase measurement.

* test: hold the ratchet at main's floor after the AGENTS.md gate

Cover lint-agents.ts's own entrypoint guard and default output parameter
with an in-process module reload, the way
test/package-contents-contract.test.ts and
test/secret-scan-contract.test.ts already do for theirs, so the new gate
is the first to leave nothing uncovered. Rebased onto main again (#119,
docs-only); REQ-030 and RULE-069 remain free. Every metric now sits above
main's floor (99.06/96.17/100/98.97): statements 99.07%, branches 96.25%,
functions 100%, lines 98.99%, and vitest.config.ts and README.md carry the
measured values.

* fix(ci): claim only what closes, fixes or resolves a pull request

The claim workflow read every bare #NNN in a title or body as a claim,
labelling any issue merely mentioned ("follows #115", "blocked by #39",
"unlike #41"), the exact shape of the labelling incident this fixes. It
also released a claimed issue's parent on every child close, and depended
on gh issue view's --json parent field without confirming it exists on
the runner.

scripts/claim-issue.ts (test/claim-issue.test.ts, no gh call) now extracts
candidates from a GitHub closing keyword (closes, fixes, resolves, and
their inflections) immediately naming an issue, or the branch name's
leading number, stripping fenced code blocks first. The workflow calls it
for the candidate list and keeps every gh call in its own bash: on claim,
the parent is claimed too if unclaimed; on release, only the named issue
is released, never the parent, which stays a human's call or a later
workflow's. Dropped synchronize from the triggers, since a push re-runs
the same extraction over the same fields and can only repeat what already
holds.

Confirmed gh issue view --json parent is real and supported: added in gh
CLI 2.94.0 (2026-06-10 changelog), and the ubuntu-latest runner image
ships 2.100.0. A gh issue view failure is now logged with its real error
text rather than assumed to mean "not an issue".

Coverage rises again with the new file's own full coverage (statements
99.09%, branches 96.32%, functions 100%, lines 99.01%), still above
main's floor; vitest.config.ts and README.md carry the measured values.

* fix(test): keep agent configuration out of the mutation sandbox

Stryker copies the repository into its sandbox before running, and its
copyfile call dies on .claude/skills: a symlink to a directory, which
copyfile refuses outright (EISDIR). ignorePatterns (a real, documented
Stryker option, confirmed against node_modules/@stryker-mutator/core's own
stryker-schema.json for the pinned 10.0.0) now excludes /.claude and
/.agents, neither of which the mutation run needs: Claude Code
configuration and skill content, not src/. The symlink itself is
untouched; only the sandbox copy skips it.

* test: hold the ratchet at main's floor after rebasing onto #120

#120 (the Platform document reader) took REQ-030 for an unrelated
behaviour and added REQ-031; this branch's AGENTS.md script-list
requirement moves to REQ-032, the ledger now states 32 rows. #120 added no
npm script, so AGENTS.md's script list needed no change (lint:agents
confirms all 22 scripts still resolve). Coverage rises with #120's own
new, fully-tested code: statements 99.15%, branches 96.49%, functions
100%, lines 99.08%, still above main's floor (99.06/96.17/100/98.97, which
#120 left untouched); vitest.config.ts and README.md carry the measured
values.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: deploy homelab-deploy, deploy-v2, manifests, and rollout flow. component: build Gradle, npm, packaging, or local build tooling. component: tests Unit, integration, system, or contract test concern. type: feature New user-facing or operator-facing capability.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Link references by name and report those that do not resolve

1 participant