@erclx/aitk 0.77.0 → 0.77.1

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.77.0",
4
+ "version": "0.77.1",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -45,7 +45,7 @@ A fence answers that test for itself, because every scan here skips a fenced lin
45
45
 
46
46
  The opening delimiter decides that for the whole block rather than each line deciding for itself. A blank line inside an indented fence carries no indentation to read, and a content line may sit at column zero inside one because CommonMark strips the fence's own indent and nothing further. Reading either as unindented ends a run that should have continued, which costs findings rather than inventing them, so no corpus count moves when it is wrong.
47
47
 
48
- Two fenced blocks with nothing between them read as one, so the second takes the first's answer. An unindented block written directly behind an indented one therefore leaves a run standing that it should have ended, which is a false positive rather than a missed finding. Separating the two needs the closing delimiter, which the line walker does not report, and no entry writes adjacent fences today.
48
+ Two fenced blocks with nothing between them are one contiguous run of fenced lines, so the mark alone cannot say where the first ends. The line walker reports which block each line sits in, and the boundary re-reads indentation whenever that number changes, so an unindented block written directly behind an indented one ends the run rather than inheriting the answer above it. No markdown file this repository tracks holds that shape, because a formatter inserts a blank line between two fences on contact, and a target running these commands over an unformatted tree still can.
49
49
 
50
50
  Precision is the whole value, so recall is the accepted exposure, and two shapes are knowingly out of reach. A narration written as one bullet carrying its own before and after slips through, and nothing else sees it either. So does the perfect passive, since `has been superseded` narrates a supersession and the copula guard rejects it with the passives it exists for. A rejected alternative is a back-reference in the past tense by construction, and the standard keeps what was tried and why it lost, so a legitimate hit exists and no measure separates it from a violation. The report states that on every run, which is why the finding names a line to read rather than a line to delete. The JSON record carries the findings per entry as `entries[].narration` and the sets as `checkpoints.narration`, which is absent under `--citations-only` where the run never loads them and null where no rule publishes both.
51
51
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "0.77.0",
4
+ "version": "0.77.1",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -306,7 +306,8 @@ function narration(
306
306
 
307
307
  const findings: NarrationFinding[] = []
308
308
  let following = false
309
- let fenceInsideList: boolean | undefined
309
+ let fenceBlock: number | undefined
310
+ let fenceInsideList = false
310
311
 
311
312
  for (const line of lines) {
312
313
  // A fenced line is never a bullet, but an unindented block still ends the
@@ -314,29 +315,25 @@ function narration(
314
315
  // the bullets around it are two lists and the second has no antecedent
315
316
  // above it. A block indented under its bullet stays inside the item.
316
317
  //
317
- // The first delimiter of a contiguous fenced run decides for every line of
318
- // it. Reading each line instead ends the run on a blank line inside an
319
- // indented fence, which has no indentation to read, and on a content line
320
- // at column zero, which CommonMark permits since only the fence's own
321
- // indent is stripped.
318
+ // The opening delimiter decides for every line of its own block. Reading
319
+ // each line instead ends the run on a blank line inside an indented fence,
320
+ // which has no indentation to read, and on a content line at column zero,
321
+ // which CommonMark permits since only the fence's own indent is stripped.
322
322
  //
323
- // Two blocks with nothing between them are one such run, so the second
324
- // inherits the first's answer and an unindented block behind an indented
325
- // one leaves a run standing that should have ended. Telling them apart
326
- // needs the closing delimiter, which `BodyLine` does not carry, and parsing
327
- // one here would be the second fence walker this repository consolidated
328
- // away after its first pair disagreed. That fix belongs in
329
- // `src/markdown/scan.ts` rather than in a copy of it.
323
+ // The block index is what re-opens that decision, so two blocks written
324
+ // with nothing between them are answered separately rather than the second
325
+ // inheriting the first. Indentation stays the test here because what
326
+ // interrupts a list is this check's own judgment, and the walker only says
327
+ // which lines share a block.
330
328
  if (line.fenced) {
331
- if (fenceInsideList === undefined) {
329
+ if (line.fenceBlock !== fenceBlock) {
330
+ fenceBlock = line.fenceBlock
332
331
  fenceInsideList = INSIDE_LIST.test(line.text)
333
332
  }
334
333
  if (!fenceInsideList) following = false
335
334
  continue
336
335
  }
337
336
 
338
- fenceInsideList = undefined
339
-
340
337
  if (line.text.trim() === '') continue
341
338
 
342
339
  const bullet = line.text.match(TOP_BULLET)
@@ -39,6 +39,17 @@ export interface BodyLine {
39
39
  readonly text: string
40
40
  /** True on a fence delimiter and on every line between one pair. */
41
41
  readonly fenced: boolean
42
+ /**
43
+ * Which fenced block the line sits in, counted from one, and undefined
44
+ * outside one. Two blocks written with nothing between them are one
45
+ * contiguous run of fenced lines, so this is the only field telling them
46
+ * apart. Counting from one keeps the outside answer from reading as a block.
47
+ *
48
+ * The name carries the fence because `block` alone already names a run of
49
+ * `BodyLine` values in `src/markdown/structure.ts`, and one word meaning both
50
+ * an aggregate of lines and an index on each line reads as a bug.
51
+ */
52
+ readonly fenceBlock: number | undefined
42
53
  }
43
54
 
44
55
  export type BanKind = 'character' | 'word' | 'spelling'
@@ -58,17 +69,25 @@ export interface BanSets {
58
69
  }
59
70
 
60
71
  /**
61
- * Marks which lines sit inside a fence without dropping them.
72
+ * Marks which lines sit inside a fence without dropping them, and which block
73
+ * each of those lines belongs to.
62
74
  *
63
75
  * Each measure excludes a fence for its own reason and needs a different
64
76
  * response. The depth measure skips a fenced line so an example cannot break
65
77
  * the run around it, bullet folding treats one as a break so a bullet does not
66
78
  * absorb the block below it, and the ban scan ignores it outright. Returning
67
79
  * the mark rather than a filtered list is what lets one walk serve all three.
80
+ *
81
+ * The block index answers the consumer that has to decide per block rather
82
+ * than per contiguous run, since two adjacent blocks are indistinguishable
83
+ * through the mark alone. It is counted here because the walk already holds
84
+ * the opening delimiter, and a consumer parsing its own would be the second
85
+ * fence walker this repository consolidated away.
68
86
  */
69
87
  function markFences(texts: readonly string[], offset: number): BodyLine[] {
70
88
  const lines: BodyLine[] = []
71
89
  let fence: string | undefined
90
+ let fenceBlock = 0
72
91
 
73
92
  for (const [index, text] of texts.entries()) {
74
93
  const match = FENCE.exec(text.trim())
@@ -82,9 +101,15 @@ function markFences(texts: readonly string[], offset: number): BodyLine[] {
82
101
  } else if (match) {
83
102
  fenced = true
84
103
  fence = match[1]
104
+ fenceBlock += 1
85
105
  }
86
106
 
87
- lines.push({ number: offset + index + 1, text, fenced })
107
+ lines.push({
108
+ number: offset + index + 1,
109
+ text,
110
+ fenced,
111
+ fenceBlock: fenced ? fenceBlock : undefined,
112
+ })
88
113
  }
89
114
 
90
115
  return lines