@erclx/aitk 0.107.1 → 0.107.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aitk",
3
3
  "description": "Automated governance, versioning, and discovery tools for Claude Code.",
4
- "version": "0.107.1",
4
+ "version": "0.107.2",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -102,6 +102,8 @@ For each doc with relevant changes, apply updates following these rules. Read a
102
102
 
103
103
  The verb resolves the board at the main worktree root in-process, which is the route because this is an edit inside an existing file and the file-editing tools refuse that path from a linked worktree.
104
104
 
105
+ Read `ok` and `reason` out of that record rather than the exit, for the reason the plans sweep below states at length. A refusal arriving as success leaves the outcome unmarked while the chain moves on, so the board reports shipped work as open and the next session re-plans it.
106
+
105
107
  **REQUIREMENTS.md, ARCHITECTURE.md, DESIGN.md, `.claude/wireframes/<surface>.md`**
106
108
 
107
109
  - Update only the sections affected by session decisions.
@@ -114,7 +114,7 @@ Write no shape for a correction. A correction is a sentence, and a format for ad
114
114
  - Learning that a PR moved is the mechanical half, so read `${CLAUDE_SKILL_DIR}/references/orchestrator-poll.md` and start the poll it carries on the first dispatch rather than checking the board by hand. That runbook holds the routing, and a summary of it here is a second source that drifts from it.
115
115
  7. Dispatch the handback. A pass posting a finding at any severity tells the session holding that branch to run `claude-address-review`, rather than waiting for a person to relay it. Re-review when the answer lands, then the human merges. Tell the trailing worker to rebase when its branch shares a seam with the merged one.
116
116
  - Read the threshold off `claude-pr-review`, which states it once and governs the heading with it, so an open heading and an owed dispatch answer the same question and either one is enough to send
117
- - Resolve the target from a fresh session listing at the moment of sending, never from a mapping written down earlier, since names rotate as sessions end and one recorded earlier in a session has failed inside the hour
117
+ - Resolve the target at the moment of sending with `aitk sessions list --branch`, never from a mapping written down earlier, since names rotate as sessions end and one recorded earlier in a session has failed inside the hour. The runbook read at step 6 routes on the count and the confidence it answers with
118
118
  - Open the message with the worktree and branch the sender believes the reader holds, asking to be corrected, whenever that mapping is inferred rather than confirmed
119
119
  - Name the skill for the reader to run rather than writing an invocation, which arrives as text
120
120
  - Read the pull request's own draft flag rather than the state a worker reports, since two reported a draft that read ready inside ninety seconds
@@ -80,10 +80,12 @@ The board is gitignored, so an archived task has no history behind it and nothin
80
80
  Pass the task's filename stem, or the pull request number when the request names one:
81
81
 
82
82
  ```bash
83
- aitk tasks archive <stem>
83
+ aitk tasks archive <stem> --json
84
84
  ```
85
85
 
86
- The command refuses rather than reports, so read its exit code. On success it prints what moved, what row it cleared, and whether the index changed.
86
+ The command refuses rather than reports, and the refusal reaches this skill through the record rather than through the exit. Branch on `ok`, then on `reason`. An operator's shell profile may wrap `aitk` in a function that runs the binary and then a second command and takes the second status, which masks every non-zero exit rather than only an absent verb. The binary exits 1 for an unknown subcommand and 1 for an ordinary refusal alike, so the record is the only signal that survives the wrapper.
87
+
88
+ On success the record carries `from`, `to`, `priorityRowRemoved`, and `indexRegenerated`, which is what moved, what row it cleared, and whether the index changed.
87
89
 
88
90
  ### Step 3: route on a refusal
89
91
 
@@ -27,6 +27,7 @@ Load `${CLAUDE_SKILL_DIR}/references/template.md` for the base skeleton. Copy it
27
27
  - Include actionable context in error messages.
28
28
  - Guard commands that return non-zero on a valid empty result with `|| true`.
29
29
  - Set explicit exit codes. Reserve 0 for success.
30
+ - Read a called command's machine-readable record where it emits one. An exit status separates success from failure and never names which failure, so a script routing on the reason reads the record and keeps the exit for the pass-fail decision alone.
30
31
 
31
32
  ## Code style
32
33
 
@@ -53,6 +53,8 @@ Decide from what the entry says the command does, not from its name. When the en
53
53
 
54
54
  If a command fails or never comes up, report the failure and the last output. Do not retry with a different command, and do not abandon the others.
55
55
 
56
+ An exit status here is part of the report rather than the basis for one. Step 3 confirms a service came up against what it printed, so the number is passed through to the reader and decides nothing. A documented command reaching `aitk` is the case worth naming, since an operator's shell profile may wrap the binary in a function that takes its status from a later command, and judging that run by its exit would call a refusal a success.
57
+
56
58
  ## Step 4: stop
57
59
 
58
60
  Report and end the turn. Specifically do not:
@@ -88,10 +88,10 @@ aitk indexes regen --dry-run --json
88
88
  Parse the JSON. On any `error` action, surface the file and reason and stop. On `would-write` for the bootstrapped folders, the dry-run is healthy. Run for real:
89
89
 
90
90
  ```bash
91
- aitk indexes regen
91
+ aitk indexes regen --json
92
92
  ```
93
93
 
94
- Report the `written` count.
94
+ Count the `written` actions in that record and report the number. Read the real run the same way the dry run is read, since the exit carries nothing reliable back to a session. An operator's shell profile may wrap `aitk` in a function that runs the binary and then a second command and takes the second status, so a regen that errored on a sibling missing `title` arrives here as a clean pass.
95
95
 
96
96
  ## Offer the convention seed
97
97
 
@@ -41,7 +41,7 @@ The measure counts fenced blocks and frontmatter, so a reference-heavy entry ran
41
41
 
42
42
  Every entry past the checkpoint carries the three questions the standard asks of it rather than a count alone: whether it still covers one domain, whether it has filled with content `ls` or `--help` reproduces, and whether it has accumulated the history of its own changes. The checkpoint is not a cap, so the count is not the finding. What a reader acts on is which question came back true, and the standard directs the fix at that rather than at the number.
43
43
 
44
- Only the third is mechanical, and it is the provenance check already reported below, so the length finding cites that count rather than measuring it again. An entry outside the governed folder reads open there too, since provenance is scoped to the standard stating it and a clean list there is a scan that never ran. The other two stay open beside every entry, because whether a domain is still one domain is a judgment about its subject and recognizing reproduced content needs a reader who knows what those commands emit. Omitting them would read as an entry nothing found rather than one nothing checked.
44
+ Only the third is mechanical, and it is the provenance check already reported below, so the length finding cites that count rather than measuring it again. What it cites is a count of change markers, which is why an entry recording what its own runs cost answers `no` there while carrying dates throughout. An entry outside the governed folder reads open there too, since provenance is scoped to the standard stating it and a clean list there is a scan that never ran. The other two stay open beside every entry, because whether a domain is still one domain is a judgment about its subject and recognizing reproduced content needs a reader who knows what those commands emit. Omitting them would read as an entry nothing found rather than one nothing checked.
45
45
 
46
46
  It reports and never gates, which the standard settles rather than the usual judgment-against-fact line: there is no hard cap, so an entry that answers all three and is still long is a correct outcome. The JSON record carries the joined findings as `length`, each with a `causes` array in the standard's order, and the jurisdiction the join reads as `entries[].governed`. That flag is what parts a provenance list measured and empty from one never scanned, which the count alone cannot say. The join is published rather than left to a consumer, since deriving it means restating which question the provenance count answers.
47
47
 
@@ -55,6 +55,10 @@ The table check reports a catalog that grows a row per shipped thing, not a tabl
55
55
 
56
56
  The provenance check reports the markers narrating how a domain reached its shape rather than describing what it is: a date, a change number, or a release label. The standard admits a rejected alternative and the reasoning that killed it while refusing the provenance attached to it, so a marker names a line to read rather than a line to delete. Findings group by entry and sort left to right within a line, since what a reader acts on is which file to open.
57
57
 
58
+ A date stamping a measurement is excluded, because the standard cuts a date attached to a change and permits one dating a figure. The check reads the clause in front of the date, back to the nearest sentence boundary, for one of five verbs: measured, verified, driven, passed, and fired. The noun `run` counts only where it sits against the date, so `A run on 2026-08-14` is excluded and `Runs on #632 and #634 landed 2026-08-02` reports. The set is closed, and a date it cannot place reports as a change marker rather than as a state of its own, which names one date too many rather than clearing one the rule cuts.
59
+
60
+ A release label reports with or without its leading `v` at three segments, since the rule cuts the label rather than a spelling of it. Two segments still need the `v`, which keeps a dollar cost and a duration out. Another tool's version reports too, and the check cannot tell one from a release, so treat a version beside a tool name as a line to read rather than one to cut.
61
+
58
62
  Fenced blocks are excluded, which keeps a pinned version in an install command from reading as a claim the entry makes. Frontmatter is excluded with them, since the content checks read the body alone, and that is what keeps a diagram entry's dated `verified` stamp a record of its last check rather than a marker to settle. Length is the exception, counting the whole file, so a reader applying the 150-rendered-line checkpoint against the body alone lands a few lines under what the tool reports.
59
63
 
60
64
  ## Narration
@@ -22,6 +22,8 @@ Each is driven by a `PostToolUse` hook matching `Write|Edit|MultiEdit`, so a fil
22
22
 
23
23
  Exit codes: `0` clean, `1` frontmatter error or missing index, `2` drift found in `--dry-run`.
24
24
 
25
+ An exit code says nothing about a call made from a session, since a shell profile may wrap the binary in a function taking its status from a later command. Read the record's `results` array rather than the exit when a skill consumes this, on the write pass as well as the dry run.
26
+
25
27
  When positional paths are passed inside a git repo, modified `index.md` files are staged so lint-staged and Claude `PostToolUse` hooks commit the regenerated catalog. Whole-repo walks never auto-stage, and neither does a path git ignores, since staging one always fails and the warning would fire on every edit.
26
28
 
27
29
  Skills can parse drift without branching on exit code:
@@ -60,6 +60,8 @@ An item already carrying an answer refuses rather than being overwritten, and on
60
60
 
61
61
  Exit codes: `0` every named item now carries its answer, `1` refused. The `reason` field carries `no-intake`, `no-folder`, `no-cluster`, `no-item`, `answered`, or `bad-input`.
62
62
 
63
+ An exit code says nothing about a call made from a session, since a shell profile may wrap the binary in a function taking its status from a later command. Read the record's `reason` rather than the exit when a skill consumes this, which matters most here because the verb writes.
64
+
63
65
  `bad-input` covers a malformed command line: no cluster, no selection, a selection that parses to no label and answer, an empty answer, an answer carrying a line break, or two answers for one item. It is separate from the reasons describing the folder, so a caller that mistyped a flag is not sent to repair a file that is fine.
64
66
 
65
67
  An empty answer refuses rather than writing an empty slot. The slot means unread while it is empty, so writing one back would report an item as answered that nobody decided.
@@ -58,6 +58,8 @@ A section runs to the next marker-shaped line whatever it names, so a plan carry
58
58
 
59
59
  Exit codes: `0` every check passed, `1` refused, `2` at least one record carries a finding. A `reason` field carries which gate fired: `no-folder` when none of the kind's directories exist, and `unknown-kind` when the argument names no published kind. A `no-folder` message names every candidate, so the `standards` refusal names both roots.
60
60
 
61
+ An exit code says nothing about a call made from a session, since a shell profile may wrap the binary in a function taking its status from a later command. Read the record's `findings` array and its `reason` rather than the exit when a skill consumes this.
62
+
61
63
  The four record folders are shared scratch at the main worktree root, so `--root` defaults to the first entry of `git worktree list` rather than the working directory. A linked worktree validates the same records every other session reads.
62
64
 
63
65
  `standards` takes the other default, the root of the checkout the caller stands in. The corpus is tracked, so a linked worktree holds its own edited copy, and resolving the main root there would report on a tree the session never touched while saying nothing about which one it read. A session that adds or renames a standard inside a worktree is the case, and it is the one the check exists for.
@@ -24,6 +24,8 @@ It reads and never writes. The question it answers is which session to address w
24
24
 
25
25
  Exit codes: `0` the roster was read, `1` refused. The refusal carries a `reason` of `no-registry` or `no-repository`.
26
26
 
27
+ An exit code says nothing about a call made from a session, since a shell profile may wrap the binary in a function taking its status from a later command. Read the record's `reason` rather than the exit when a skill consumes this.
28
+
27
29
  ## Scope and count
28
30
 
29
31
  `--branch` scopes the match to the repository the command runs in, and refuses outside one. A branch name identifies a branch inside a repository and nothing across a machine, so an unscoped match reaches a session working in a different project, and `main` collides on every machine running two of them.
@@ -40,7 +40,7 @@ One feature travels this path end to end.
40
40
  2. Orchestrator plans the next feature with `claude-feature`, writing a plan to `.claude/plans/`. Planning stays in the warm session because good planning is cross-feature. It needs the contract other features consume and the shared wiring seam. A cold session would re-derive or guess.
41
41
  3. The human opens a worker worktree with `claude-worktree` and runs `claude-autoship` against the plan. The worker builds, self-checks, opens a PR, and stops at the PR boundary.
42
42
  4. Orchestrator reviews the PR with `claude-pr-review` and posts findings to it.
43
- 5. Orchestrator tells the session holding that branch to run `claude-address-review` once the pass posted a finding at any severity, resolving the target from a session listing taken at that moment and reporting the invocation for the human when no live session holds it. The worker addresses the findings, rebases onto `origin/main` when a sibling landed first and left the branch unable to merge, then pushes a follow-up. A pass carrying only minor findings dispatches too, since the grade runs low often enough that a floor at should-fix loses fixes a worker would have made. `claude-pr-review` states that threshold and the heading follows it, so an open heading is itself the signal to send.
43
+ 5. Orchestrator tells the session holding that branch to run `claude-address-review` once the pass posted a finding at any severity, resolving the target then with `aitk sessions list --branch` and reporting the invocation for the human when no live session holds it. The worker addresses the findings, rebases onto `origin/main` when a sibling landed first and left the branch unable to merge, then pushes a follow-up. A pass carrying only minor findings dispatches too, since the grade runs low often enough that a floor at should-fix loses fixes a worker would have made. `claude-pr-review` states that threshold and the heading follows it, so an open heading is itself the signal to send.
44
44
  6. Orchestrator closes the review out with `claude-pr-review` again. The second pass reads only the commits the follow-up added, or the worker's response alone when the follow-up added none, and posts under `## Review` when it finds anything and under `## Review closed` when it finds nothing, so a reader learns from the heading whether work is still owed and takes the merge decision from the counts on the line under it. Repeat from step 5 until a pass closes the review.
45
45
  7. The human reads the result and merges. The orchestrator tells any trailing worker whose branch shares a seam with the merged one to run `claude-address-review`, which rebases whether or not the review left anything open.
46
46
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "0.107.1",
4
+ "version": "0.107.2",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -503,7 +503,7 @@ function readCause(cause: LengthCause): string {
503
503
  if (cause.state === 'unanswered') return `${label}: open`
504
504
  if (cause.state === 'no') return `${label}: no`
505
505
 
506
- return `${label}: yes, ${plural(cause.markers ?? 0, 'marker')}`
506
+ return `${label}: yes, ${plural(cause.markers ?? 0, 'change marker')}`
507
507
  }
508
508
 
509
509
  /**
@@ -534,7 +534,7 @@ function reportLength(over: readonly LengthFinding[]): void {
534
534
  'The checkpoint is not a cap. Each entry carries the three questions the standard asks past it, and the fix goes to whichever is true.',
535
535
  )
536
536
  logInfo(
537
- 'Own history is answered from the provenance markers below. The other two are judgments no measure settles, so they stay open for a reader.',
537
+ 'Own history is answered from the change markers below. A date stamping a measurement is not one of them, so an entry recording what its runs cost answers no. The other two are judgments no measure settles, so they stay open for a reader.',
538
538
  )
539
539
  pipeOutput(
540
540
  over
@@ -595,7 +595,9 @@ function reportProvenance(
595
595
  logInfo(
596
596
  `Covers .claude/${PROVENANCE_FOLDER}/ alone, whose standard carries the rule. The sibling standards do not restate it.`,
597
597
  )
598
- logInfo('Fenced blocks are excluded. A marker is a judgment, never a defect.')
598
+ logInfo(
599
+ 'Fenced blocks are excluded, and so is a date whose clause stamps a measurement. A marker is a judgment, never a defect.',
600
+ )
599
601
 
600
602
  const carrying = entries
601
603
  .filter((entry) => entry.provenance.length > 0)
@@ -60,13 +60,59 @@ const INSIDE_LIST = /^\s+\S/
60
60
  * when it records the second: when a change landed, which change carried it,
61
61
  * and which release labelled it. A marker is a judgment rather than a defect,
62
62
  * so this is measured and reported and never gates.
63
+ *
64
+ * The release pattern accepts three segments without a leading `v`, since the
65
+ * standard cuts a release label rather than a spelling of one and `a CLI at
66
+ * 0.83.0` names a release exactly as `v0.83.0` does. Two segments still require
67
+ * the `v`, because an unprefixed pair is a dollar cost or a duration far more
68
+ * often than a release in a corpus that records what its own runs cost. What
69
+ * the widening reaches by accident is another tool's version, which the
70
+ * standard asks nobody to cut, and those report rather than being excluded:
71
+ * an exclusion keyed on a nearby tool name goes stale with nothing saying so.
63
72
  */
64
73
  const PROVENANCE: readonly { kind: ProvenanceKind; pattern: RegExp }[] = [
65
74
  { kind: 'date', pattern: /\b\d{4}-\d{2}-\d{2}\b/g },
66
75
  { kind: 'change', pattern: /#\d{3,}\b/g },
67
- { kind: 'release', pattern: /\bv\d+\.\d+(?:\.\d+)?\b/g },
76
+ { kind: 'release', pattern: /\b(?:v\d+\.\d+(?:\.\d+)?|\d+\.\d+\.\d+)\b/g },
68
77
  ]
69
78
 
79
+ /**
80
+ * Verbs that read the date behind them as stamping a measurement.
81
+ *
82
+ * `standards/context.md` cuts a date attached to a change and permits one
83
+ * stamping a measurement, so a pattern matching every ISO date reports against
84
+ * a rule that admits half of what it finds. Separating the two needs the clause
85
+ * rather than the token, since `measured on 2026-08-19` and `overturned on
86
+ * 2026-08-19` differ only in the verb.
87
+ *
88
+ * These five are unambiguous wherever they appear in the clause, which is what
89
+ * lets them match at any distance from the date. `Measured across the corpus on
90
+ * 2026-08-14` puts four words between the two, and no line in the corpus uses
91
+ * any of them as a noun a change date could attach to.
92
+ *
93
+ * The set is closed and drawn from what the corpus already writes, so a
94
+ * phrasing nobody enumerated goes unmatched. That direction is the safe one: an
95
+ * unmatched date falls back to the change marker it is today, which reports one
96
+ * date too many rather than clearing one the standard cuts. Widening the set is
97
+ * how a missed phrasing is fixed, and a date the clause cannot place is never a
98
+ * third state, because the length finding already spends `unanswered` on the
99
+ * two questions nothing measures at all.
100
+ */
101
+ const STAMPING = /\b(?:measured|verified|driven|passed|fired)\b/i
102
+
103
+ /**
104
+ * The noun form, which has to sit against the date to count.
105
+ *
106
+ * `A run on 2026-08-14` stamps a measurement and `Runs on #632 and #634 landed
107
+ * 2026-08-02` dates a change, and the two differ only in what follows the noun.
108
+ * Requiring the connector and then the date immediately is what separates them,
109
+ * so this is anchored to the end of the clause where `STAMPING` is not.
110
+ */
111
+ const STAMPING_RUN = /\b(?:an?\s+)?runs?\s+(?:on|at|in)\s+$/i
112
+
113
+ /** Sentence boundary, so a clause is read rather than the whole line. */
114
+ const SENTENCE_END = /[.!?:;]\s(?=[^.!?:;]*$)/
115
+
70
116
  /**
71
117
  * The folder whose standard carries the exclusion above.
72
118
  *
@@ -200,9 +246,11 @@ export interface LengthCause {
200
246
  readonly question: LengthQuestion
201
247
  readonly state: QuestionState
202
248
  /**
203
- * Markers behind a `yes`, and absent wherever nothing was counted. It cites
204
- * the provenance finding rather than restating it, so the two sections of the
205
- * report describe the same measurement once.
249
+ * Change markers behind a `yes`, and absent wherever nothing was counted. It
250
+ * cites the provenance finding rather than restating it, so the two sections
251
+ * of the report describe the same measurement once. A date stamping a
252
+ * measurement never reaches that list, so an entry whose only dates are
253
+ * measurement anchors answers `no` here.
206
254
  */
207
255
  readonly markers?: number
208
256
  }
@@ -284,6 +332,12 @@ function catalogTables(entry: readonly BodyLine[]): TableFinding[] {
284
332
  * sample command or a fixture inside an example is content the entry displays
285
333
  * rather than a claim it makes, and a version pinned in an install line is the
286
334
  * ordinary shape of one.
335
+ *
336
+ * A date stamping a measurement is dropped rather than reported under a kind of
337
+ * its own. One list with one meaning is what lets every consumer read it
338
+ * without filtering: the report names what the standard cuts, and the length
339
+ * finding counts the same thing. A separate kind would put the split in three
340
+ * places and leave each free to read it differently.
287
341
  */
288
342
  function provenance(lines: readonly BodyLine[]): ProvenanceFinding[] {
289
343
  // Scanning one pattern at a time emits a line's markers grouped by kind, so
@@ -297,6 +351,10 @@ function provenance(lines: readonly BodyLine[]): ProvenanceFinding[] {
297
351
 
298
352
  for (const { kind, pattern } of PROVENANCE) {
299
353
  for (const match of line.text.matchAll(pattern)) {
354
+ if (kind === 'date' && stampsMeasurement(line.text, match.index)) {
355
+ continue
356
+ }
357
+
300
358
  found.push({
301
359
  finding: { line: line.number, kind, text: match[0] },
302
360
  column: match.index,
@@ -310,6 +368,23 @@ function provenance(lines: readonly BodyLine[]): ProvenanceFinding[] {
310
368
  .map((each) => each.finding)
311
369
  }
312
370
 
371
+ /**
372
+ * Reads the clause in front of a date for a verb that stamps a measurement.
373
+ *
374
+ * The clause rather than the line, because a sentence recording a measurement
375
+ * and a later one dating a change sit side by side often enough that a
376
+ * line-wide read would clear the second from the first. A date opening its own
377
+ * line has no clause in front of it and stays a change marker, which is the
378
+ * fallback rather than a separate answer.
379
+ */
380
+ function stampsMeasurement(text: string, index: number): boolean {
381
+ const before = text.slice(0, index)
382
+ const boundary = before.search(SENTENCE_END)
383
+ const clause = boundary === -1 ? before : before.slice(boundary + 1)
384
+
385
+ return STAMPING.test(clause) || STAMPING_RUN.test(clause)
386
+ }
387
+
313
388
  function escape(term: string): string {
314
389
  return term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
315
390
  }
@@ -536,10 +611,13 @@ export function measureEntry(
536
611
  * answered as far as anything measures them.
537
612
  *
538
613
  * Only accumulated history is mechanical, and it is already measured by the
539
- * provenance check, so this joins that count rather than counting again. The
540
- * other two are read by a person: whether an entry still covers one domain is a
541
- * judgment about its subject, and recognizing content `ls` or `--help`
542
- * reproduces needs a reader who knows what those emit.
614
+ * provenance check, so this joins that count rather than counting again. What
615
+ * it joins is a count of change markers, since a date stamping a measurement is
616
+ * not one and an entry recording what its runs cost would otherwise report
617
+ * accumulated history on the anchors dating those runs. The other two are read
618
+ * by a person: whether an entry still covers one domain is a judgment about its
619
+ * subject, and recognizing content `ls` or `--help` reproduces needs a reader
620
+ * who knows what those emit.
543
621
  *
544
622
  * An entry outside the governed folder has no measured question at all, since
545
623
  * provenance is scoped to the standard stating it, and reporting `no` there