Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6e892ac4c | ||
|
|
4d6ebb626f | ||
|
|
88a4fa407b | ||
|
|
808af7b020 |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "skills",
|
"name": "skills",
|
||||||
"description": "Mathias's engineering skills library, plus the hooks and MCP servers that make it enforceable: TDD, clean code, debugging, specs, discovery, CI, and secret hygiene.",
|
"description": "Mathias's engineering skills library, plus the hooks and MCP servers that make it enforceable: TDD, clean code, debugging, specs, discovery, CI, and secret hygiene.",
|
||||||
"version": "0.16.0",
|
"version": "0.17.1",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Mathias",
|
"name": "Mathias",
|
||||||
"url": "https://git.d-ma.be/mathias"
|
"url": "https://git.d-ma.be/mathias"
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ the SKILL.md, not the table.
|
|||||||
| `tdd` | Test-Driven Development enforcement | Starting any new feature or fixing a bug | claude-code, library |
|
| `tdd` | Test-Driven Development enforcement | Starting any new feature or fixing a bug | claude-code, library |
|
||||||
| `telos-load` | Load TELOS intention substrate at session start | Starting any koala session; before architectural decisions; CAD pipeline entry | claude-code, library |
|
| `telos-load` | Load TELOS intention substrate at session start | Starting any koala session; before architectural decisions; CAD pipeline entry | claude-code, library |
|
||||||
| `test-design` | Dave Farley test design scoring (Farley Index) | Reviewing or writing tests | claude-code, library |
|
| `test-design` | Dave Farley test design scoring (Farley Index) | Reviewing or writing tests | claude-code, library |
|
||||||
|
| `traceability` | Forward trace — requirement to decision to commit to verification to release, and what cannot be checked | Proving why a change exists; auditing a claim; before an attestation or a compliance ask | claude-code, library |
|
||||||
| `trainer` | Two-phase brain curation (score candidates, write past quality gate) | Periodic brain pruning and curation | claude-code, library |
|
| `trainer` | Two-phase brain curation (score candidates, write past quality gate) | Periodic brain pruning and curation | claude-code, library |
|
||||||
| `user-stories` | Elephant Carpaccio story slicing | Breaking down features | claude-code, library |
|
| `user-stories` | Elephant Carpaccio story slicing | Breaking down features | claude-code, library |
|
||||||
| `web-shot` | Screenshot a running web UI via a koala k3s Playwright job | Visual verification of a running UI when no local browser exists | claude-code, library |
|
| `web-shot` | Screenshot a running web UI via a koala k3s Playwright job | Visual verification of a running UI when no local browser exists | claude-code, library |
|
||||||
|
|||||||
+29
-5
@@ -25,15 +25,39 @@ for dir in "$ROOT"/skills/*/; do
|
|||||||
|
|
||||||
grep -q 'session_log' "$dir/SKILL.md" || continue
|
grep -q 'session_log' "$dir/SKILL.md" || continue
|
||||||
|
|
||||||
if ! grep -q 'session_id' "$dir/SKILL.md"; then
|
# Read the PAYLOAD, not the file. The first version of this gate grepped the
|
||||||
echo "ERROR: skills/$name documents session_log without session_id — the tool's only required field" >&2
|
# whole SKILL.md for "session_id", so a payload missing the field passed as
|
||||||
fail=1
|
# long as the word appeared anywhere — including in prose explaining that the
|
||||||
|
# field is required. A traceability probe demonstrated it in one sed.
|
||||||
|
payloads=$(grep -n 'session_log' "$dir/SKILL.md" | grep '{' || true)
|
||||||
|
|
||||||
|
if [ -z "$payloads" ]; then
|
||||||
|
# Mentions the tool but documents no call. Nothing to check, and nothing to
|
||||||
|
# get wrong.
|
||||||
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if grep -q 'duration_ms:[[:space:]]*<' "$dir/SKILL.md"; then
|
printf '%s\n' "$payloads" | while IFS= read -r line; do
|
||||||
echo "ERROR: skills/$name documents duration_ms as a placeholder an agent cannot measure — omit it or pass a real measurement" >&2
|
[ -n "$line" ] || continue
|
||||||
|
body=$(printf '%s' "$line" | sed 's/^[0-9]*://')
|
||||||
|
lineno=$(printf '%s' "$line" | cut -d: -f1)
|
||||||
|
|
||||||
|
printf '%s' "$body" | grep -q 'session_id' \
|
||||||
|
|| echo "ERROR: skills/$name:$lineno documents a session_log payload with no session_id — the tool's only required field" >&2
|
||||||
|
|
||||||
|
# Any spelling of the key, quoted or not, followed by anything that is not a
|
||||||
|
# bare number. A placeholder nobody can fill gets fabricated or dropped; a
|
||||||
|
# real measurement is fine.
|
||||||
|
if printf '%s' "$body" | grep -Eq '"?duration_ms"?[[:space:]]*:[[:space:]]*[^0-9,}[:space:]]'; then
|
||||||
|
echo "ERROR: skills/$name:$lineno documents duration_ms as something other than a real measurement — an agent does not observe a clock; omit it" >&2
|
||||||
|
fi
|
||||||
|
done > "$ROOT/.session_log_errors.$$" 2>&1
|
||||||
|
|
||||||
|
if [ -s "$ROOT/.session_log_errors.$$" ]; then
|
||||||
|
cat "$ROOT/.session_log_errors.$$" >&2
|
||||||
fail=1
|
fail=1
|
||||||
fi
|
fi
|
||||||
|
rm -f "$ROOT/.session_log_errors.$$"
|
||||||
done
|
done
|
||||||
|
|
||||||
exit $fail
|
exit $fail
|
||||||
|
|||||||
@@ -0,0 +1,170 @@
|
|||||||
|
---
|
||||||
|
name: traceability
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Forward trace — requirement to decision to commit to verification to release, and what cannot be checked"
|
||||||
|
use_when: "Proving why a change exists; auditing a claim; before an attestation or a compliance ask"
|
||||||
|
description: Establish the forward trace for a change — requirement, decision, commit, verification, CI, release, closure — as a chain of links that are each checked rather than asserted. Names the links that cannot be verified from the repository instead of letting them pass as verified. Use when auditing why a change exists, preparing an attestation, or when a claim needs to be provable rather than plausible.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Traceability
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
A trace is not a story about how a change came to be. It is a chain of links, each of
|
||||||
|
which is either **checked** or **named as unchecked**. The value is entirely in that
|
||||||
|
distinction: a trace where every link is asserted is a narrative, and narratives are what
|
||||||
|
audits exist to defeat.
|
||||||
|
|
||||||
|
The chain in this estate:
|
||||||
|
|
||||||
|
```
|
||||||
|
issue (requirement) → comment (decision) → commit → test/gate → CI run on that sha
|
||||||
|
→ release tag → closing comment (evidence)
|
||||||
|
```
|
||||||
|
|
||||||
|
Note where the decisions live: **in issue comments**, not issue bodies. A tool that reads
|
||||||
|
issue titles will trace the wrong thing.
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
- Auditing why a change exists, especially one nobody remembers making
|
||||||
|
- Preparing an attestation, a compliance answer, or a client-facing "how do you know"
|
||||||
|
- Before claiming a requirement is satisfied
|
||||||
|
- When a claim is repeated in several places, which is the condition under which nobody
|
||||||
|
checks it
|
||||||
|
|
||||||
|
**Do not use for:** a change you are about to make. That is `adr` (the decision) and
|
||||||
|
`spec-driven-dev` (the requirement). This skill reconstructs; it does not author.
|
||||||
|
|
||||||
|
## The tool
|
||||||
|
|
||||||
|
```sh
|
||||||
|
skills/traceability/trace.sh <sha> [repo-path]
|
||||||
|
```
|
||||||
|
|
||||||
|
Prints what git can prove — the commit, its files, the test files it touched, the release
|
||||||
|
that contains it, every issue it references as a URL — and then prints, every time, the
|
||||||
|
three things git **cannot** prove. That last section is the point of the tool.
|
||||||
|
|
||||||
|
## The sequence
|
||||||
|
|
||||||
|
**1. Run the tool.** It does the mechanical half in one command.
|
||||||
|
|
||||||
|
**2. Open the issues it printed, and read the comments.** The requirement is in the body;
|
||||||
|
the decision is almost always in a comment, and so is the closure record. A trace built
|
||||||
|
from commit messages alone will confidently cite an issue that says something else.
|
||||||
|
|
||||||
|
**3. Match CI on the head sha, not on the branch.** "The pipeline is green" is not
|
||||||
|
evidence about a commit. Find the run whose `head_sha` is this commit and read its
|
||||||
|
conclusion — and check which *job* and which *step* passed, because a run can be red for a
|
||||||
|
reason that has nothing to do with the gate you care about, and green overall while the
|
||||||
|
gate you care about was skipped.
|
||||||
|
|
||||||
|
The estate's `workflow_run_status` returns only the run-level conclusion — no jobs, no
|
||||||
|
steps. For that breakdown you have to drop to the API:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
op run --env-file ~/.op-env -- sh -c 'curl -sS -H "Authorization: token $DMABE_GITEA_API_TOKEN" \
|
||||||
|
"https://git.d-ma.be/api/v1/repos/<owner>/<repo>/actions/runs/<id>/jobs"'
|
||||||
|
```
|
||||||
|
|
||||||
|
Said explicitly because a step whose tooling dead-ends is exactly where a trace becomes
|
||||||
|
"the run was green".
|
||||||
|
|
||||||
|
**4. Check that the tests actually cover the behaviour.** A commit that touched a test
|
||||||
|
file is not a commit that is verified. Read the assertions and ask what would have to
|
||||||
|
break for them to fail. If nothing would, the link is decorative.
|
||||||
|
|
||||||
|
**5. Probe the load-bearing claim — try to falsify it.** This is the step that separates a
|
||||||
|
trace from a summary, and it is the one that finds things.
|
||||||
|
|
||||||
|
Pick the claim the chain rests on, and attack it in a scratch copy:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git archive <sha> | (mkdir -p /tmp/probe && tar -x -C /tmp/probe)
|
||||||
|
# then break exactly the thing the claim protects, and run the gate
|
||||||
|
```
|
||||||
|
|
||||||
|
If the gate stays green, the claim is false — regardless of how many places assert it.
|
||||||
|
This is not hypothetical: the probe that shaped this skill falsified a "the list only
|
||||||
|
shrinks" claim that was stated in five places in the repository and enforced in none.
|
||||||
|
|
||||||
|
**6. Look for what cites the commit, not only what it cites.** A commit cannot reference
|
||||||
|
an issue filed after it, and that is precisely where an outcome gets falsified. Search the
|
||||||
|
tracker for the sha, across issues *and comments*, and `git log --all --grep=<sha>` for
|
||||||
|
later commits referring back. In the run that shaped this skill, the issue proving the
|
||||||
|
traced commit had *relocated* rather than fixed its defect was filed 90 minutes later and
|
||||||
|
was reachable only this way.
|
||||||
|
|
||||||
|
**7. Report unverifiable links as unverifiable.** Not as weak links, not as caveats at the
|
||||||
|
end. Each gets its own line saying what would be needed to close it.
|
||||||
|
|
||||||
|
## What a broken link looks like
|
||||||
|
|
||||||
|
| Symptom | What it means |
|
||||||
|
|---|---|
|
||||||
|
| The commit cites `#37`, and `#37` describes something else | The reference is decoration; someone renumbered or misremembered |
|
||||||
|
| A claim appears in five places and a check in none | Convention wearing a mechanism's clothes. Probe it |
|
||||||
|
| The gate greps for a heading | Presence is not compliance. A heading with nothing under it passes |
|
||||||
|
| `git describe` says unreleased | Usually stale local tags, not an unreleased commit. `git fetch --tags` first |
|
||||||
|
| The requirement is a bare `#5` with no URL | Recoverable only while the tracker lives. Not traceable from the repo alone |
|
||||||
|
| CI green, but on a different sha | The most common false link, and the easiest to accept by accident |
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
State the chain, then the gaps, in that order:
|
||||||
|
|
||||||
|
```
|
||||||
|
CHAIN
|
||||||
|
requirement #36 item 3 (defect report) → verified, read: body + comments
|
||||||
|
requirement #19 (standing execution bar) → verified, read
|
||||||
|
decision comment 8509 → verified; names the commit
|
||||||
|
commit b616c51 → verified
|
||||||
|
claim "nine skills, none named session_id" → verified against b616c51^: 9/9
|
||||||
|
claim "session_id is the only required field" → verified against the live tool schema
|
||||||
|
gate bin/check-session-log → RED on parent (17 errors), GREEN on it
|
||||||
|
wiring Taskfile check step 5 → verified; CI runs task check
|
||||||
|
verification tests/check_session_log_test.sh → verified, 4 cases, both directions
|
||||||
|
CI run 4880, job "Check / Test" → verified, head_sha matches, success
|
||||||
|
release v0.13.3 → verified against the REMOTE
|
||||||
|
|
||||||
|
GAPS
|
||||||
|
- The gate greps the file, not the payload. Probe: stripped session_id from
|
||||||
|
every payload, left the word in prose — gate returned 0. FALSIFIED.
|
||||||
|
- Defect relocated, not removed: #39, filed 90 minutes later by the same
|
||||||
|
author, says so. The commit's outcome claim does not hold.
|
||||||
|
```
|
||||||
|
|
||||||
|
Four slots that are not optional, each learned by leaving them out:
|
||||||
|
|
||||||
|
- **More than one requirement, and they differ in kind.** A defect report and a standing
|
||||||
|
policy bar are both requirements and collapsing them hides which one was not met.
|
||||||
|
- **The gate, separately from the test.** A commit that ships a check has three artifacts,
|
||||||
|
not two, and the chain usually rests on the one in the middle.
|
||||||
|
- **The RED/GREEN baseline.** Run the gate against the parent commit. Without it you have
|
||||||
|
shown a gate exists, not that it caught anything.
|
||||||
|
- **The commit's factual claims, checked.** "Nine skills, none named it" is falsifiable in
|
||||||
|
one command. Without a slot for it, "commit → verified" means only "this sha exists".
|
||||||
|
|
||||||
|
Gaps ranked by how much they matter, not by where you found them. A trace whose gap
|
||||||
|
section is empty should be suspicious of itself.
|
||||||
|
|
||||||
|
## Escalation
|
||||||
|
|
||||||
|
Read-only. Reports the chain; changes nothing, and closes no issue on the strength of
|
||||||
|
what it finds.
|
||||||
|
|
||||||
|
Stop and hand back when: a link's evidence is outside anything you can reach — a tracker
|
||||||
|
you cannot read, a CI system you have no access to, a decision made in conversation. Say
|
||||||
|
which link and what would close it. An inferred link presented as a traced one is worse
|
||||||
|
than a gap, because the gap is honest and the inference is citable.
|
||||||
|
|
||||||
|
Also stop before *acting* on a falsified claim. Finding that a gate does not enforce what
|
||||||
|
it says is a finding; deciding what to do about it is the owner's call.
|
||||||
|
|
||||||
|
## Cross-References
|
||||||
|
|
||||||
|
- `lineage` — the same discipline backwards: this asks "does the chain hold", that asks
|
||||||
|
"was it tried before and why did it stop".
|
||||||
|
- `adr` — the decision layer of the chain. A trace that reaches an ADR has found the
|
||||||
|
reasoning; a trace that reaches only a commit message has found a summary of it.
|
||||||
Executable
+107
@@ -0,0 +1,107 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# trace.sh — the forward trace for one commit, and an honest list of what it
|
||||||
|
# could not check.
|
||||||
|
#
|
||||||
|
# The chain this estate actually uses:
|
||||||
|
#
|
||||||
|
# issue (requirement) -> comment (decision) -> commit -> test/gate -> CI run
|
||||||
|
# -> release tag -> closing comment
|
||||||
|
#
|
||||||
|
# git can prove the middle of that and nothing else. The issue bodies, the
|
||||||
|
# comments where decisions actually live, and the CI run are all tracker state.
|
||||||
|
# So this prints what git knows and then names, with URLs, the links a human has
|
||||||
|
# to follow — because a link nobody checked must not read like a verified one.
|
||||||
|
#
|
||||||
|
# Usage: trace.sh <sha> [repo-path]
|
||||||
|
#
|
||||||
|
# Read-only.
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
[ $# -ge 1 ] || { echo "usage: trace.sh <sha> [repo-path]" >&2; exit 2; }
|
||||||
|
SHA=$1
|
||||||
|
REPO=${2:-.}
|
||||||
|
|
||||||
|
git -C "$REPO" rev-parse --git-dir >/dev/null 2>&1 || {
|
||||||
|
echo "trace: $REPO is not a git repository" >&2; exit 2; }
|
||||||
|
git -C "$REPO" rev-parse --verify --quiet "$SHA^{commit}" >/dev/null || {
|
||||||
|
echo "trace: $SHA is not a commit in $REPO" >&2; exit 2; }
|
||||||
|
|
||||||
|
TOP=$(git -C "$REPO" rev-parse --show-toplevel)
|
||||||
|
ORIGIN=$(git -C "$REPO" config --get remote.origin.url 2>/dev/null || true)
|
||||||
|
# Never print the URL as configured: url.insteadOf can expand it into a live
|
||||||
|
# token. Reduce it to owner/repo and use that.
|
||||||
|
SLUG=$(printf '%s' "$ORIGIN" | sed -E 's#\.git$##; s#^.*[/:]([^/]+/[^/]+)$#\1#' 2>/dev/null || true)
|
||||||
|
|
||||||
|
section() { printf '\n== %s ==\n' "$1"; }
|
||||||
|
|
||||||
|
section "Commit"
|
||||||
|
git -C "$REPO" log -1 --format='%h %ad %an%n%n%B' --date=short "$SHA"
|
||||||
|
|
||||||
|
section "Files"
|
||||||
|
git -C "$REPO" show --stat --format='' "$SHA"
|
||||||
|
|
||||||
|
section "Verification it touched"
|
||||||
|
tests=$(git -C "$REPO" show --name-only --format='' "$SHA" \
|
||||||
|
| grep -Ei '(^|/)(tests?|spec)/|_test\.|\.test\.|_spec\.' || true)
|
||||||
|
if [ -n "$tests" ]; then
|
||||||
|
printf '%s\n' "$tests"
|
||||||
|
else
|
||||||
|
printf 'none — this commit changed no test or spec file.\n'
|
||||||
|
printf 'That is not automatically wrong (docs, config), but if it changed behaviour\n'
|
||||||
|
printf 'it means the change has no verification of its own.\n'
|
||||||
|
fi
|
||||||
|
|
||||||
|
section "Released in"
|
||||||
|
tag=$(git -C "$REPO" describe --tags --contains "$SHA" 2>/dev/null | sed 's/[~^].*//' || true)
|
||||||
|
if [ -n "$tag" ]; then
|
||||||
|
printf '%s (LOCAL tags only — confirm with: git ls-remote --tags origin)\n' "$tag"
|
||||||
|
else
|
||||||
|
printf 'no release contains this commit in THIS checkout.\n'
|
||||||
|
printf 'Local tags go stale — run `git fetch --tags` and re-check before\n'
|
||||||
|
printf 'concluding it was never released.\n'
|
||||||
|
fi
|
||||||
|
|
||||||
|
section "Referenced issues"
|
||||||
|
refs=$(git -C "$REPO" log -1 --format='%B' "$SHA" | grep -oE '#[0-9]+' | sort -u || true)
|
||||||
|
if [ -n "$refs" ]; then
|
||||||
|
for r in $refs; do
|
||||||
|
n=${r#\#}
|
||||||
|
if [ -n "$SLUG" ]; then
|
||||||
|
printf '%s -> https://git.d-ma.be/%s/issues/%s\n' "$r" "$SLUG" "$n"
|
||||||
|
else
|
||||||
|
printf '%s\n' "$r"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
printf 'none — the commit cites no issue, so its requirement is unrecorded.\n'
|
||||||
|
fi
|
||||||
|
|
||||||
|
section "Issues that cite THIS commit"
|
||||||
|
printf 'Not searchable from the commit — an issue filed after it cannot appear in\n'
|
||||||
|
printf 'its message, and that is exactly where an outcome gets falsified. Check:\n\n'
|
||||||
|
printf ' git -C %s log --all --grep=%s # later commits referring back\n' "$REPO" "$SHA"
|
||||||
|
if [ -n "$SLUG" ]; then
|
||||||
|
printf ' tracker search for "%s" across issues AND comments\n' "$SHA"
|
||||||
|
fi
|
||||||
|
printf '\nIn the run that shaped this tool, the issue that falsified the traced\n'
|
||||||
|
printf 'commit was filed 90 minutes after it and was reachable only this way.\n'
|
||||||
|
|
||||||
|
section "Cannot verify from here"
|
||||||
|
cat <<'TXT'
|
||||||
|
git proves the commit, its files, and which tag contains it. It cannot prove:
|
||||||
|
|
||||||
|
1. That the issues above say what the commit claims they say. The DECISION is
|
||||||
|
usually in a comment, not the issue body — open them and read the comments.
|
||||||
|
2. That CI actually passed on this exact sha. A green pipeline elsewhere is not
|
||||||
|
evidence about this commit; match on head_sha.
|
||||||
|
3. That the tests listed above assert the behaviour this commit added. A test
|
||||||
|
file touched is not a behaviour covered.
|
||||||
|
4. That any gate this commit ships enforces what its message says it does.
|
||||||
|
Probe it: break exactly the thing the claim protects, in a scratch copy,
|
||||||
|
and see whether the gate goes red.
|
||||||
|
|
||||||
|
None of the three is a formality. Each has been the broken link in this estate:
|
||||||
|
a claim asserted in five places and enforced in none, a red run whose gate had
|
||||||
|
passed, and a gate that greps for a heading and calls it a contract.
|
||||||
|
TXT
|
||||||
@@ -63,5 +63,38 @@ expect 1 'documenting duration_ms as wall-clock fails' \
|
|||||||
"$(fixture clock 'Call `session_log` with `{session_id: "<id>", skill: "alpha", duration_ms: <wall-clock>}`')" \
|
"$(fixture clock 'Call `session_log` with `{session_id: "<id>", skill: "alpha", duration_ms: <wall-clock>}`')" \
|
||||||
'duration_ms'
|
'duration_ms'
|
||||||
|
|
||||||
|
# --- the gate must read the PAYLOAD, not the file ---------------------------
|
||||||
|
# A traceability probe falsified the original: it grepped the whole file for the
|
||||||
|
# string "session_id", so stripping the field from every documented payload and
|
||||||
|
# leaving the word once in prose passed cleanly. The exact regression the gate
|
||||||
|
# exists to prevent was back in the file and the gate said nothing.
|
||||||
|
|
||||||
|
expect 1 'a payload without session_id fails even when prose mentions the word' \
|
||||||
|
"$(fixture prose 'The tool has a required session_id field.
|
||||||
|
|
||||||
|
Call `session_log` with `{skill: "alpha", phase: "red", final_status: "pass"}`')" \
|
||||||
|
'session_id'
|
||||||
|
|
||||||
|
expect 1 'one bad payload among several fails' \
|
||||||
|
"$(fixture mixed 'Call `session_log` with `{session_id: "<id>", skill: "alpha", phase: "red"}`
|
||||||
|
|
||||||
|
Then `session_log` with `{skill: "alpha", phase: "green", final_status: "pass"}`')" \
|
||||||
|
'session_id'
|
||||||
|
|
||||||
|
# duration_ms was banned by matching exactly one spelling of it — the historical
|
||||||
|
# one. Both of these passed the original gate.
|
||||||
|
expect 1 'duration_ms with a JSON-quoted key fails' \
|
||||||
|
"$(fixture jsonkey 'Call `session_log` with `{session_id: "<id>", "duration_ms": <wall-clock>}`')" \
|
||||||
|
'duration_ms'
|
||||||
|
|
||||||
|
expect 1 'duration_ms described in prose rather than angle brackets fails' \
|
||||||
|
"$(fixture prosems 'Call `session_log` with `{session_id: "<id>", duration_ms: elapsed wall-clock ms}`')" \
|
||||||
|
'duration_ms'
|
||||||
|
|
||||||
|
# A real measurement is allowed: the objection is to a placeholder no agent can
|
||||||
|
# fill, not to the field.
|
||||||
|
expect 0 'a duration_ms carrying a real value is allowed' \
|
||||||
|
"$(fixture realms 'Call `session_log` with `{session_id: "<id>", duration_ms: 1430}`')"
|
||||||
|
|
||||||
printf '\ncheck_session_log: %d passed, %d failed\n' "$pass" "$fail"
|
printf '\ncheck_session_log: %d passed, %d failed\n' "$pass" "$fail"
|
||||||
[ "$fail" -eq 0 ]
|
[ "$fail" -eq 0 ]
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Tests for skills/traceability/trace.sh.
|
||||||
|
#
|
||||||
|
# The RED run for #10 established what a forward trace costs by hand: sixteen
|
||||||
|
# commands, of which the decisive ones were reading issue comments and running a
|
||||||
|
# probe against a claim. This tool does the mechanical half — what git can prove
|
||||||
|
# — and, more importantly, states what it CANNOT prove, so an unverifiable link
|
||||||
|
# never reads as a verified one.
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
|
SUT="$ROOT/skills/traceability/trace.sh"
|
||||||
|
|
||||||
|
pass=0
|
||||||
|
fail=0
|
||||||
|
WORK=${TMPDIR:-/tmp}/traceability_test.$$
|
||||||
|
|
||||||
|
if [ ! -f "$SUT" ]; then
|
||||||
|
printf 'FAIL: %s does not exist\n' "$SUT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cleanup() { rm -rf "$WORK"; }
|
||||||
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
mkdir -p "$WORK/repo/tests"
|
||||||
|
cd "$WORK/repo" || exit 1
|
||||||
|
git init -q .
|
||||||
|
git config user.email t@example.com
|
||||||
|
git config user.name test
|
||||||
|
# A remote is needed for the tool to turn "#37" into a checkable URL, which is
|
||||||
|
# the point of that section.
|
||||||
|
git remote add origin https://git.d-ma.be/mathias/demo.git
|
||||||
|
|
||||||
|
printf 'baseline\n' > README.md
|
||||||
|
git add -A && git commit -qm "chore: baseline"
|
||||||
|
|
||||||
|
printf 'gate\n' > gate.sh
|
||||||
|
printf 'case\n' > tests/gate_test.sh
|
||||||
|
git add -A && git commit -qm "feat(gate): add the thing
|
||||||
|
|
||||||
|
Implements the decision recorded in #5, filed as #37.
|
||||||
|
|
||||||
|
Closes #37"
|
||||||
|
SHA=$(git rev-parse --short HEAD)
|
||||||
|
git tag -a v1.2.0 -m "release v1.2.0"
|
||||||
|
|
||||||
|
printf 'more\n' >> gate.sh
|
||||||
|
git add -A && git commit -qm "chore: later work"
|
||||||
|
|
||||||
|
cd - >/dev/null || exit 1
|
||||||
|
out=$(sh "$SUT" "$SHA" "$WORK/repo" 2>&1)
|
||||||
|
|
||||||
|
check() {
|
||||||
|
if printf '%s' "$out" | grep -q "$1"; then
|
||||||
|
pass=$((pass + 1)); printf ' ok %s\n' "$2"
|
||||||
|
else
|
||||||
|
fail=$((fail + 1)); printf ' FAIL %s\n output had no %s\n%s\n' "$2" "$1" "$out"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check '#5' 'lists every issue the commit references, not just the closing one'
|
||||||
|
check '#37' 'lists the closing reference'
|
||||||
|
check 'tests/gate_test.sh' 'names the test files the commit touched'
|
||||||
|
check 'v1.2.0' 'names the release that contains it'
|
||||||
|
check 'Cannot verify' 'states what it could not verify'
|
||||||
|
check 'issues/37' 'emits a checkable URL for the tracker link it cannot follow'
|
||||||
|
|
||||||
|
# A commit in no release must say so — "no tag" and "not checked" are different
|
||||||
|
# answers and only one of them is about the commit.
|
||||||
|
last=$(git -C "$WORK/repo" rev-parse --short HEAD)
|
||||||
|
out=$(sh "$SUT" "$last" "$WORK/repo" 2>&1)
|
||||||
|
if printf '%s' "$out" | grep -qi 'no release contains'; then
|
||||||
|
pass=$((pass + 1)); printf ' ok an unreleased commit is reported as unreleased\n'
|
||||||
|
else
|
||||||
|
fail=$((fail + 1)); printf ' FAIL an unreleased commit is reported as unreleased\n%s\n' "$out"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '\ntraceability: %d passed, %d failed\n' "$pass" "$fail"
|
||||||
|
[ "$fail" -eq 0 ]
|
||||||
Reference in New Issue
Block a user