@nielspeter/eess-md 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +124 -0
- package/dist/builders/docs.d.ts +28 -0
- package/dist/builders/docs.d.ts.map +1 -0
- package/dist/builders/docs.js +44 -0
- package/dist/builders/docs.js.map +1 -0
- package/dist/builders/links.d.ts +26 -0
- package/dist/builders/links.d.ts.map +1 -0
- package/dist/builders/links.js +42 -0
- package/dist/builders/links.js.map +1 -0
- package/dist/builders/pointers.d.ts +26 -0
- package/dist/builders/pointers.d.ts.map +1 -0
- package/dist/builders/pointers.js +43 -0
- package/dist/builders/pointers.js.map +1 -0
- package/dist/builders/rows.d.ts +36 -0
- package/dist/builders/rows.d.ts.map +1 -0
- package/dist/builders/rows.js +42 -0
- package/dist/builders/rows.js.map +1 -0
- package/dist/builders/task-items.d.ts +21 -0
- package/dist/builders/task-items.d.ts.map +1 -0
- package/dist/builders/task-items.js +33 -0
- package/dist/builders/task-items.js.map +1 -0
- package/dist/builders/vocabulary.d.ts +66 -0
- package/dist/builders/vocabulary.d.ts.map +1 -0
- package/dist/builders/vocabulary.js +118 -0
- package/dist/builders/vocabulary.js.map +1 -0
- package/dist/conditions/pointer-resolve.d.ts +51 -0
- package/dist/conditions/pointer-resolve.d.ts.map +1 -0
- package/dist/conditions/pointer-resolve.js +164 -0
- package/dist/conditions/pointer-resolve.js.map +1 -0
- package/dist/conditions/resolve.d.ts +36 -0
- package/dist/conditions/resolve.d.ts.map +1 -0
- package/dist/conditions/resolve.js +108 -0
- package/dist/conditions/resolve.js.map +1 -0
- package/dist/conditions/structure.d.ts +13 -0
- package/dist/conditions/structure.d.ts.map +1 -0
- package/dist/conditions/structure.js +42 -0
- package/dist/conditions/structure.js.map +1 -0
- package/dist/conditions/table-rows.d.ts +28 -0
- package/dist/conditions/table-rows.d.ts.map +1 -0
- package/dist/conditions/table-rows.js +25 -0
- package/dist/conditions/table-rows.js.map +1 -0
- package/dist/corpus.d.ts +40 -0
- package/dist/corpus.d.ts.map +1 -0
- package/dist/corpus.js +73 -0
- package/dist/corpus.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/model/document.d.ts +78 -0
- package/dist/model/document.d.ts.map +1 -0
- package/dist/model/document.js +68 -0
- package/dist/model/document.js.map +1 -0
- package/dist/model/links.d.ts +32 -0
- package/dist/model/links.d.ts.map +1 -0
- package/dist/model/links.js +53 -0
- package/dist/model/links.js.map +1 -0
- package/dist/model/pointers.d.ts +29 -0
- package/dist/model/pointers.d.ts.map +1 -0
- package/dist/model/pointers.js +65 -0
- package/dist/model/pointers.js.map +1 -0
- package/dist/model/query.d.ts +6 -0
- package/dist/model/query.d.ts.map +1 -0
- package/dist/model/query.js +15 -0
- package/dist/model/query.js.map +1 -0
- package/dist/model/rows.d.ts +52 -0
- package/dist/model/rows.d.ts.map +1 -0
- package/dist/model/rows.js +42 -0
- package/dist/model/rows.js.map +1 -0
- package/dist/model/task-items.d.ts +28 -0
- package/dist/model/task-items.d.ts.map +1 -0
- package/dist/model/task-items.js +47 -0
- package/dist/model/task-items.js.map +1 -0
- package/dist/model/violation.d.ts +23 -0
- package/dist/model/violation.d.ts.map +1 -0
- package/dist/model/violation.js +22 -0
- package/dist/model/violation.js.map +1 -0
- package/dist/predicates/document.d.ts +8 -0
- package/dist/predicates/document.d.ts.map +1 -0
- package/dist/predicates/document.js +22 -0
- package/dist/predicates/document.js.map +1 -0
- package/dist/rules/adr.d.ts +34 -0
- package/dist/rules/adr.d.ts.map +1 -0
- package/dist/rules/adr.js +103 -0
- package/dist/rules/adr.js.map +1 -0
- package/dist/rules/ledger.d.ts +44 -0
- package/dist/rules/ledger.d.ts.map +1 -0
- package/dist/rules/ledger.js +119 -0
- package/dist/rules/ledger.js.map +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Niels Peter
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# @nielspeter/eess-md
|
|
2
|
+
|
|
3
|
+
Architecture testing for a **Markdown corpus** — the Markdown dialect of the [eess](../../README.md) family. _Specifications you can run._
|
|
4
|
+
|
|
5
|
+
A repo's markdown (docs, ADRs, plans) should stay honest: cross-links resolve, code pointers point at real lines, and — if you use them — ADRs declare how they're enforced. `eess-md` validates all of that on the shared [`@nielspeter/eess`](../core) kernel, so you write a declarative rules file, not a custom validator script.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -D @nielspeter/eess-md
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Example
|
|
14
|
+
|
|
15
|
+
Neutral, universal conventions — a `docs/**` corpus, English headers, a minimal frozen set:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { corpus, docs, links, pointers } from '@nielspeter/eess-md'
|
|
19
|
+
|
|
20
|
+
const c = corpus({
|
|
21
|
+
roots: ['docs/**'],
|
|
22
|
+
frozen: ['**/completed/**', '**/archived/**'], // historical records: reported, never failed
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
// markdown-to-markdown links resolve
|
|
26
|
+
links(c).that().areInternal().should().resolve().check()
|
|
27
|
+
|
|
28
|
+
// `path:line` code pointers ground against real files
|
|
29
|
+
// (resolves by unique path-suffix: `admin/index.vue` matches the one file
|
|
30
|
+
// ending with it; pass `{ paths: 'exact' }` to require full repo paths)
|
|
31
|
+
pointers(c).that().areLive().should().resolve().check()
|
|
32
|
+
|
|
33
|
+
// documents have the sections/tables they should
|
|
34
|
+
docs(c).that().resideInFolder('docs/adr/**').should().haveSection('Context').check()
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`docs()`, `links()`, and `pointers()` are entry points on the same fluent
|
|
38
|
+
`.that().should().check()` chain as every other eess dialect — with `.because()`,
|
|
39
|
+
`.warn()`, `.excluding()`, and baseline mode all working unchanged.
|
|
40
|
+
|
|
41
|
+
## Binding a spec table to code: `rows()` + `correspondence()`
|
|
42
|
+
|
|
43
|
+
A markdown table is a **spec** — a package list, an ADR index, a field
|
|
44
|
+
reference. `rows()` turns its body rows into first-class elements, and
|
|
45
|
+
`.select()` (inherited from the kernel, on every eess builder) turns any
|
|
46
|
+
selection into one side of a `correspondence()`. Bind a table to what it
|
|
47
|
+
describes and drift in either direction fails the build:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { corpus, rows } from '@nielspeter/eess-md'
|
|
51
|
+
import { correspondence } from '@nielspeter/eess'
|
|
52
|
+
|
|
53
|
+
const c = corpus({ roots: ['README.md'] })
|
|
54
|
+
|
|
55
|
+
// each body row of the "Packages" table becomes a selectable element
|
|
56
|
+
const packageRows = rows(c, {
|
|
57
|
+
section: /^Packages$/,
|
|
58
|
+
columns: { pkg: /^Package$/, status: /^Status$/ }, // role → header pattern
|
|
59
|
+
}).select({
|
|
60
|
+
label: 'README package row',
|
|
61
|
+
identify: (r) => ({ name: r.get('pkg'), file: r.doc.relPath, line: r.line }),
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// `workspacePackages` is a plain Selection you build from the filesystem
|
|
65
|
+
correspondence({ left: packageRows, right: workspacePackages, keyBy: (e) => e.name })
|
|
66
|
+
.should()
|
|
67
|
+
.beComplete({ direction: 'both' }) // every row has a package AND vice versa
|
|
68
|
+
.because('the README package table must not drift from the workspace')
|
|
69
|
+
.check()
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
A row carries `get(role)` (the located cell text), its real source `line`, and
|
|
73
|
+
back references to its `doc` and `table`. Rows are drawn from **every** table
|
|
74
|
+
that matches the section and columns — a correspondence source never silently
|
|
75
|
+
ignores a second matching table. Cell text is flattened (bold/links become
|
|
76
|
+
plain text); a link's URL is not recoverable from a row, so verify link targets
|
|
77
|
+
with a `links()` rule alongside.
|
|
78
|
+
|
|
79
|
+
`direction: 'both'` is doing two jobs: left-to-right catches a table row with no
|
|
80
|
+
package (a _lying_ spec), and right-to-left catches a package with no row
|
|
81
|
+
(_unclaimed_ code — coverage). Drop to a single direction when you only want
|
|
82
|
+
one. Coverage is the same "gate on declaredness" move the ADR `## Enforcement`
|
|
83
|
+
table makes, pointed at code.
|
|
84
|
+
|
|
85
|
+
## The ADR gate is an opt-in, opinionated preset
|
|
86
|
+
|
|
87
|
+
`@nielspeter/eess-md/rules/adr` ships `adrEnforcement()`, which implements **one
|
|
88
|
+
specific methodology** — the EESS enforcement-tier model (a `## Enforcement`
|
|
89
|
+
table with tier + mechanism + status). Most teams' ADRs (MADR, Nygard) have none
|
|
90
|
+
of that, and **you don't need it to get value from `eess-md`** — links, pointers,
|
|
91
|
+
sections, and tables all validate through the generic primitives above. Reach for
|
|
92
|
+
`adrEnforcement()` only if your ADRs follow the tier model; otherwise compose your
|
|
93
|
+
own gate from `haveSection` / `haveTable` / `haveTableRowsSatisfying` / `resolve`.
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { adrEnforcement } from '@nielspeter/eess-md/rules/adr'
|
|
97
|
+
|
|
98
|
+
adrEnforcement(c, { dir: 'docs/adr/**' }) // section+table present, valid tiers, citations resolve
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
An index/schema doc in the ADR directory (`README.md` or `index.md`) is exempt —
|
|
102
|
+
it isn't an ADR, so it isn't required to carry an `## Enforcement` table.
|
|
103
|
+
|
|
104
|
+
## Custom conventions (localization, project-specific lifecycle)
|
|
105
|
+
|
|
106
|
+
Everything project-specific is configuration, not a fork — column headers match
|
|
107
|
+
by pattern (non-English works), and frozen folders are globs:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
const c = corpus({
|
|
111
|
+
roots: ['work/**', 'docs/**'], // plans live under work/
|
|
112
|
+
frozen: ['**/completed/**', '**/delivered/**', '**/wont-do/**', '**/archived/**'],
|
|
113
|
+
ignore: ['node_modules/**', '.git/**', '.nuxt/**'],
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
adrEnforcement(c, {
|
|
117
|
+
section: /håndhævelse/i,
|
|
118
|
+
columns: { tier: /tier/i, mechanism: /mekanisme/i, status: /status/i }, // Danish headers
|
|
119
|
+
})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
MIT
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { RuleBuilder } from '@nielspeter/eess';
|
|
2
|
+
import type { Corpus } from '../corpus.js';
|
|
3
|
+
import type { MdDocument } from '../model/document.js';
|
|
4
|
+
import { type HaveTableOptions } from '../conditions/structure.js';
|
|
5
|
+
/**
|
|
6
|
+
* Rule builder over the corpus's markdown documents.
|
|
7
|
+
*
|
|
8
|
+
* `.that()` methods filter documents (predicates); `.should()` methods assert
|
|
9
|
+
* over them (conditions). `haveSection` is a single phase-dispatched method —
|
|
10
|
+
* a predicate after `.that()`, a condition after `.should()` — mirroring the
|
|
11
|
+
* dual-use methods in the TS dialect (plan 0041).
|
|
12
|
+
*/
|
|
13
|
+
export declare class DocsRuleBuilder extends RuleBuilder<MdDocument, Corpus> {
|
|
14
|
+
protected getElements(): MdDocument[];
|
|
15
|
+
/** Filter to documents whose repo-relative path matches the folder glob. */
|
|
16
|
+
resideInFolder(glob: string): this;
|
|
17
|
+
/** Filter to documents whose repo-relative path matches the file glob. */
|
|
18
|
+
resideInFile(glob: string): this;
|
|
19
|
+
/** Filter to documents whose file name matches the pattern. */
|
|
20
|
+
haveNameMatching(re: RegExp): this;
|
|
21
|
+
/** Dual-use: predicate after `.that()`, condition after `.should()`. */
|
|
22
|
+
haveSection(name: string | RegExp): this;
|
|
23
|
+
/** Assert each matched document contains a GFM table with the required columns. */
|
|
24
|
+
haveTable(opts: HaveTableOptions): this;
|
|
25
|
+
}
|
|
26
|
+
/** Entry point: build rules over the corpus's markdown documents. */
|
|
27
|
+
export declare function docs(corpus: Corpus): DocsRuleBuilder;
|
|
28
|
+
//# sourceMappingURL=docs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../src/builders/docs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAOtD,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,4BAA4B,CAAA;AAEnC;;;;;;;GAOG;AACH,qBAAa,eAAgB,SAAQ,WAAW,CAAC,UAAU,EAAE,MAAM,CAAC;IAClE,SAAS,CAAC,WAAW,IAAI,UAAU,EAAE;IAIrC,4EAA4E;IAC5E,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIlC,0EAA0E;IAC1E,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIhC,+DAA+D;IAC/D,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAIlC,wEAAwE;IACxE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAOxC,mFAAmF;IACnF,SAAS,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI;CAGxC;AAED,qEAAqE;AACrE,wBAAgB,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CAEpD"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { RuleBuilder } from '@nielspeter/eess';
|
|
2
|
+
import { resideInFolder, resideInFile, haveNameMatching, haveSectionPredicate, } from '../predicates/document.js';
|
|
3
|
+
import { haveSection as haveSectionCondition, haveTable as haveTableCondition, } from '../conditions/structure.js';
|
|
4
|
+
/**
|
|
5
|
+
* Rule builder over the corpus's markdown documents.
|
|
6
|
+
*
|
|
7
|
+
* `.that()` methods filter documents (predicates); `.should()` methods assert
|
|
8
|
+
* over them (conditions). `haveSection` is a single phase-dispatched method —
|
|
9
|
+
* a predicate after `.that()`, a condition after `.should()` — mirroring the
|
|
10
|
+
* dual-use methods in the TS dialect (plan 0041).
|
|
11
|
+
*/
|
|
12
|
+
export class DocsRuleBuilder extends RuleBuilder {
|
|
13
|
+
getElements() {
|
|
14
|
+
return this.project.documents();
|
|
15
|
+
}
|
|
16
|
+
/** Filter to documents whose repo-relative path matches the folder glob. */
|
|
17
|
+
resideInFolder(glob) {
|
|
18
|
+
return this.addPredicate(resideInFolder(glob));
|
|
19
|
+
}
|
|
20
|
+
/** Filter to documents whose repo-relative path matches the file glob. */
|
|
21
|
+
resideInFile(glob) {
|
|
22
|
+
return this.addPredicate(resideInFile(glob));
|
|
23
|
+
}
|
|
24
|
+
/** Filter to documents whose file name matches the pattern. */
|
|
25
|
+
haveNameMatching(re) {
|
|
26
|
+
return this.addPredicate(haveNameMatching(re));
|
|
27
|
+
}
|
|
28
|
+
/** Dual-use: predicate after `.that()`, condition after `.should()`. */
|
|
29
|
+
haveSection(name) {
|
|
30
|
+
if (this._phase === 'condition') {
|
|
31
|
+
return this.addCondition(haveSectionCondition(name));
|
|
32
|
+
}
|
|
33
|
+
return this.addPredicate(haveSectionPredicate(name));
|
|
34
|
+
}
|
|
35
|
+
/** Assert each matched document contains a GFM table with the required columns. */
|
|
36
|
+
haveTable(opts) {
|
|
37
|
+
return this.addCondition(haveTableCondition(opts));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Entry point: build rules over the corpus's markdown documents. */
|
|
41
|
+
export function docs(corpus) {
|
|
42
|
+
return new DocsRuleBuilder(corpus);
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=docs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docs.js","sourceRoot":"","sources":["../../src/builders/docs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAG9C,OAAO,EACL,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACL,WAAW,IAAI,oBAAoB,EACnC,SAAS,IAAI,kBAAkB,GAEhC,MAAM,4BAA4B,CAAA;AAEnC;;;;;;;GAOG;AACH,MAAM,OAAO,eAAgB,SAAQ,WAA+B;IACxD,WAAW;QACnB,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAA;IACjC,CAAC;IAED,4EAA4E;IAC5E,cAAc,CAAC,IAAY;QACzB,OAAO,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAA;IAChD,CAAC;IAED,0EAA0E;IAC1E,YAAY,CAAC,IAAY;QACvB,OAAO,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAA;IAC9C,CAAC;IAED,+DAA+D;IAC/D,gBAAgB,CAAC,EAAU;QACzB,OAAO,IAAI,CAAC,YAAY,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC,CAAA;IAChD,CAAC;IAED,wEAAwE;IACxE,WAAW,CAAC,IAAqB;QAC/B,IAAI,IAAI,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;YAChC,OAAO,IAAI,CAAC,YAAY,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAA;QACtD,CAAC;QACD,OAAO,IAAI,CAAC,YAAY,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAA;IACtD,CAAC;IAED,mFAAmF;IACnF,SAAS,CAAC,IAAsB;QAC9B,OAAO,IAAI,CAAC,YAAY,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAA;IACpD,CAAC;CACF;AAED,qEAAqE;AACrE,MAAM,UAAU,IAAI,CAAC,MAAc;IACjC,OAAO,IAAI,eAAe,CAAC,MAAM,CAAC,CAAA;AACpC,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { RuleBuilder } from '@nielspeter/eess';
|
|
2
|
+
import type { Corpus } from '../corpus.js';
|
|
3
|
+
import { type MdLink } from '../model/links.js';
|
|
4
|
+
import { type LinkResolveOptions } from '../conditions/resolve.js';
|
|
5
|
+
export type { LinkResolveOptions } from '../conditions/resolve.js';
|
|
6
|
+
/** A link element: a link occurrence plus its containing document. */
|
|
7
|
+
export type { MdLink } from '../model/links.js';
|
|
8
|
+
/**
|
|
9
|
+
* Rule builder over the corpus's markdown links.
|
|
10
|
+
*/
|
|
11
|
+
export declare class LinkRuleBuilder extends RuleBuilder<MdLink, Corpus> {
|
|
12
|
+
protected getElements(): MdLink[];
|
|
13
|
+
/** Filter to links that point within the repo (no scheme). */
|
|
14
|
+
areInternal(): this;
|
|
15
|
+
/** Filter to links with an external scheme (http, mailto, protocol-relative). */
|
|
16
|
+
areExternal(): this;
|
|
17
|
+
/**
|
|
18
|
+
* Condition: internal links resolve to an existing repo file. Static-site
|
|
19
|
+
* corpora with extensionless links pass `tryExtensions`/`tryIndex`, e.g.
|
|
20
|
+
* `resolve({ tryExtensions: ['.md'], tryIndex: 'index.md' })`.
|
|
21
|
+
*/
|
|
22
|
+
resolve(options?: LinkResolveOptions): this;
|
|
23
|
+
}
|
|
24
|
+
/** Entry point: build rules over the corpus's markdown links. */
|
|
25
|
+
export declare function links(corpus: Corpus): LinkRuleBuilder;
|
|
26
|
+
//# sourceMappingURL=links.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"links.d.ts","sourceRoot":"","sources":["../../src/builders/links.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAkB,MAAM,kBAAkB,CAAA;AAC9D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAgB,KAAK,MAAM,EAAkB,MAAM,mBAAmB,CAAA;AAC7E,OAAO,EAAgB,KAAK,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAEhF,YAAY,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAElE,sEAAsE;AACtE,YAAY,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAW/C;;GAEG;AACH,qBAAa,eAAgB,SAAQ,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC;IAC9D,SAAS,CAAC,WAAW,IAAI,MAAM,EAAE;IAQjC,8DAA8D;IAC9D,WAAW,IAAI,IAAI;IAInB,iFAAiF;IACjF,WAAW,IAAI,IAAI;IAInB;;;;OAIG;IACH,OAAO,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,IAAI;CAG5C;AAED,iEAAiE;AACjE,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CAErD"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { RuleBuilder } from '@nielspeter/eess';
|
|
2
|
+
import { collectLinks } from '../model/links.js';
|
|
3
|
+
import { linkResolves } from '../conditions/resolve.js';
|
|
4
|
+
const areInternal = () => ({
|
|
5
|
+
description: 'are internal',
|
|
6
|
+
test: (l) => !l.external,
|
|
7
|
+
});
|
|
8
|
+
const areExternal = () => ({
|
|
9
|
+
description: 'are external',
|
|
10
|
+
test: (l) => l.external,
|
|
11
|
+
});
|
|
12
|
+
/**
|
|
13
|
+
* Rule builder over the corpus's markdown links.
|
|
14
|
+
*/
|
|
15
|
+
export class LinkRuleBuilder extends RuleBuilder {
|
|
16
|
+
getElements() {
|
|
17
|
+
return this.project
|
|
18
|
+
.documents()
|
|
19
|
+
.flatMap((doc) => collectLinks(doc.root, doc.text).map((link) => ({ ...link, doc })));
|
|
20
|
+
}
|
|
21
|
+
/** Filter to links that point within the repo (no scheme). */
|
|
22
|
+
areInternal() {
|
|
23
|
+
return this.addPredicate(areInternal());
|
|
24
|
+
}
|
|
25
|
+
/** Filter to links with an external scheme (http, mailto, protocol-relative). */
|
|
26
|
+
areExternal() {
|
|
27
|
+
return this.addPredicate(areExternal());
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Condition: internal links resolve to an existing repo file. Static-site
|
|
31
|
+
* corpora with extensionless links pass `tryExtensions`/`tryIndex`, e.g.
|
|
32
|
+
* `resolve({ tryExtensions: ['.md'], tryIndex: 'index.md' })`.
|
|
33
|
+
*/
|
|
34
|
+
resolve(options) {
|
|
35
|
+
return this.addCondition(linkResolves(this.project, options));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Entry point: build rules over the corpus's markdown links. */
|
|
39
|
+
export function links(corpus) {
|
|
40
|
+
return new LinkRuleBuilder(corpus);
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=links.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"links.js","sourceRoot":"","sources":["../../src/builders/links.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAkB,MAAM,kBAAkB,CAAA;AAE9D,OAAO,EAAE,YAAY,EAA+B,MAAM,mBAAmB,CAAA;AAC7E,OAAO,EAAE,YAAY,EAA2B,MAAM,0BAA0B,CAAA;AAOhF,MAAM,WAAW,GAAG,GAAsB,EAAE,CAAC,CAAC;IAC5C,WAAW,EAAE,cAAc;IAC3B,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ;CACzB,CAAC,CAAA;AACF,MAAM,WAAW,GAAG,GAAsB,EAAE,CAAC,CAAC;IAC5C,WAAW,EAAE,cAAc;IAC3B,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ;CACxB,CAAC,CAAA;AAEF;;GAEG;AACH,MAAM,OAAO,eAAgB,SAAQ,WAA2B;IACpD,WAAW;QACnB,OAAO,IAAI,CAAC,OAAO;aAChB,SAAS,EAAE;aACX,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CACf,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAe,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,CAC9E,CAAA;IACL,CAAC;IAED,8DAA8D;IAC9D,WAAW;QACT,OAAO,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE,CAAC,CAAA;IACzC,CAAC;IAED,iFAAiF;IACjF,WAAW;QACT,OAAO,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE,CAAC,CAAA;IACzC,CAAC;IAED;;;;OAIG;IACH,OAAO,CAAC,OAA4B;QAClC,OAAO,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;IAC/D,CAAC;CACF;AAED,iEAAiE;AACjE,MAAM,UAAU,KAAK,CAAC,MAAc;IAClC,OAAO,IAAI,eAAe,CAAC,MAAM,CAAC,CAAA;AACpC,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { RuleBuilder } from '@nielspeter/eess';
|
|
2
|
+
import type { Corpus } from '../corpus.js';
|
|
3
|
+
import { type MdPointer } from '../model/pointers.js';
|
|
4
|
+
import { type PointerResolveOptions } from '../conditions/pointer-resolve.js';
|
|
5
|
+
/** A code-pointer element: a pointer occurrence plus its containing document. */
|
|
6
|
+
export type { MdPointer } from '../model/pointers.js';
|
|
7
|
+
/**
|
|
8
|
+
* Rule builder over the corpus's `path:line` code pointers.
|
|
9
|
+
*/
|
|
10
|
+
export declare class PointerRuleBuilder extends RuleBuilder<MdPointer, Corpus> {
|
|
11
|
+
protected getElements(): MdPointer[];
|
|
12
|
+
/** Filter to pointers in live documents (not in a frozen folder). */
|
|
13
|
+
areLive(): this;
|
|
14
|
+
/** Filter to pointers in frozen documents (historical records). */
|
|
15
|
+
areFrozen(): this;
|
|
16
|
+
/**
|
|
17
|
+
* Condition: pointers resolve to a real file with the line in range. By
|
|
18
|
+
* default a pointer resolves by unique path-suffix (`admin/index.vue` matches
|
|
19
|
+
* the one file ending in it); pass `{ paths: 'exact' }` to require full
|
|
20
|
+
* repo-relative paths.
|
|
21
|
+
*/
|
|
22
|
+
resolve(options?: PointerResolveOptions): this;
|
|
23
|
+
}
|
|
24
|
+
/** Entry point: build rules over the corpus's code pointers. */
|
|
25
|
+
export declare function pointers(corpus: Corpus): PointerRuleBuilder;
|
|
26
|
+
//# sourceMappingURL=pointers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pointers.d.ts","sourceRoot":"","sources":["../../src/builders/pointers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAkB,MAAM,kBAAkB,CAAA;AAC9D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAmB,KAAK,SAAS,EAAqB,MAAM,sBAAsB,CAAA;AACzF,OAAO,EAAmB,KAAK,qBAAqB,EAAE,MAAM,kCAAkC,CAAA;AAE9F,iFAAiF;AACjF,YAAY,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAWrD;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,WAAW,CAAC,SAAS,EAAE,MAAM,CAAC;IACpE,SAAS,CAAC,WAAW,IAAI,SAAS,EAAE;IAQpC,qEAAqE;IACrE,OAAO,IAAI,IAAI;IAIf,mEAAmE;IACnE,SAAS,IAAI,IAAI;IAIjB;;;;;OAKG;IACH,OAAO,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,IAAI;CAG/C;AAED,gEAAgE;AAChE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB,CAE3D"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { RuleBuilder } from '@nielspeter/eess';
|
|
2
|
+
import { extractPointers } from '../model/pointers.js';
|
|
3
|
+
import { pointerResolves } from '../conditions/pointer-resolve.js';
|
|
4
|
+
const areLive = () => ({
|
|
5
|
+
description: 'are live (not in a frozen folder)',
|
|
6
|
+
test: (p) => !p.doc.frozen,
|
|
7
|
+
});
|
|
8
|
+
const areFrozen = () => ({
|
|
9
|
+
description: 'are frozen (historical record)',
|
|
10
|
+
test: (p) => p.doc.frozen,
|
|
11
|
+
});
|
|
12
|
+
/**
|
|
13
|
+
* Rule builder over the corpus's `path:line` code pointers.
|
|
14
|
+
*/
|
|
15
|
+
export class PointerRuleBuilder extends RuleBuilder {
|
|
16
|
+
getElements() {
|
|
17
|
+
return this.project
|
|
18
|
+
.documents()
|
|
19
|
+
.flatMap((doc) => extractPointers(doc.text, doc.root).map((p) => ({ ...p, doc })));
|
|
20
|
+
}
|
|
21
|
+
/** Filter to pointers in live documents (not in a frozen folder). */
|
|
22
|
+
areLive() {
|
|
23
|
+
return this.addPredicate(areLive());
|
|
24
|
+
}
|
|
25
|
+
/** Filter to pointers in frozen documents (historical records). */
|
|
26
|
+
areFrozen() {
|
|
27
|
+
return this.addPredicate(areFrozen());
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Condition: pointers resolve to a real file with the line in range. By
|
|
31
|
+
* default a pointer resolves by unique path-suffix (`admin/index.vue` matches
|
|
32
|
+
* the one file ending in it); pass `{ paths: 'exact' }` to require full
|
|
33
|
+
* repo-relative paths.
|
|
34
|
+
*/
|
|
35
|
+
resolve(options) {
|
|
36
|
+
return this.addCondition(pointerResolves(this.project, options));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** Entry point: build rules over the corpus's code pointers. */
|
|
40
|
+
export function pointers(corpus) {
|
|
41
|
+
return new PointerRuleBuilder(corpus);
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=pointers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pointers.js","sourceRoot":"","sources":["../../src/builders/pointers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAkB,MAAM,kBAAkB,CAAA;AAE9D,OAAO,EAAE,eAAe,EAAqC,MAAM,sBAAsB,CAAA;AACzF,OAAO,EAAE,eAAe,EAA8B,MAAM,kCAAkC,CAAA;AAK9F,MAAM,OAAO,GAAG,GAAyB,EAAE,CAAC,CAAC;IAC3C,WAAW,EAAE,mCAAmC;IAChD,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM;CAC3B,CAAC,CAAA;AACF,MAAM,SAAS,GAAG,GAAyB,EAAE,CAAC,CAAC;IAC7C,WAAW,EAAE,gCAAgC;IAC7C,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM;CAC1B,CAAC,CAAA;AAEF;;GAEG;AACH,MAAM,OAAO,kBAAmB,SAAQ,WAA8B;IAC1D,WAAW;QACnB,OAAO,IAAI,CAAC,OAAO;aAChB,SAAS,EAAE;aACX,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CACf,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAe,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAC9E,CAAA;IACL,CAAC;IAED,qEAAqE;IACrE,OAAO;QACL,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC,CAAA;IACrC,CAAC;IAED,mEAAmE;IACnE,SAAS;QACP,OAAO,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC,CAAA;IACvC,CAAC;IAED;;;;;OAKG;IACH,OAAO,CAAC,OAA+B;QACrC,OAAO,IAAI,CAAC,YAAY,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;IAClE,CAAC;CACF;AAED,gEAAgE;AAChE,MAAM,UAAU,QAAQ,CAAC,MAAc;IACrC,OAAO,IAAI,kBAAkB,CAAC,MAAM,CAAC,CAAA;AACvC,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { RuleBuilder } from '@nielspeter/eess';
|
|
2
|
+
import type { Corpus } from '../corpus.js';
|
|
3
|
+
import { type MdRow, type RowMatchOptions } from '../model/rows.js';
|
|
4
|
+
/**
|
|
5
|
+
* Rule builder over table rows drawn from the corpus.
|
|
6
|
+
*
|
|
7
|
+
* A row is a first-class element (`MdRow`): its located-column accessor
|
|
8
|
+
* (`get(role)`), its real source line, and back references to its document and
|
|
9
|
+
* table. Rows are drawn from **every** table (across all documents) that sits
|
|
10
|
+
* under the requested section and has all the requested columns.
|
|
11
|
+
*
|
|
12
|
+
* The primary use is `.select()` — turning spec-table rows into one side of a
|
|
13
|
+
* kernel `correspondence()` so a table can be bound to code and fail on drift.
|
|
14
|
+
* The standard `.that().satisfy(…)` / `.should().satisfy(…)` surface is
|
|
15
|
+
* inherited, so rows are also directly rule-able.
|
|
16
|
+
*/
|
|
17
|
+
export declare class RowsRuleBuilder extends RuleBuilder<MdRow, Corpus> {
|
|
18
|
+
private readonly opts;
|
|
19
|
+
constructor(corpus: Corpus, opts: RowMatchOptions);
|
|
20
|
+
protected getElements(): MdRow[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Entry point: build rules over table rows in the corpus.
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* const packageRows = rows(c, {
|
|
27
|
+
* section: /^Packages$/,
|
|
28
|
+
* columns: { pkg: /^Package$/, status: /^Status$/ },
|
|
29
|
+
* }).select({
|
|
30
|
+
* label: 'README package row',
|
|
31
|
+
* identify: (r) => ({ name: r.get('pkg'), file: r.doc.relPath, line: r.line }),
|
|
32
|
+
* })
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare function rows(corpus: Corpus, opts: RowMatchOptions): RowsRuleBuilder;
|
|
36
|
+
//# sourceMappingURL=rows.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rows.d.ts","sourceRoot":"","sources":["../../src/builders/rows.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAkB,KAAK,KAAK,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAEnF;;;;;;;;;;;;GAYG;AACH,qBAAa,eAAgB,SAAQ,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC;IAG3D,OAAO,CAAC,QAAQ,CAAC,IAAI;gBADrB,MAAM,EAAE,MAAM,EACG,IAAI,EAAE,eAAe;IAKxC,SAAS,CAAC,WAAW,IAAI,KAAK,EAAE;CAMjC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,eAAe,CAE3E"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { RuleBuilder } from '@nielspeter/eess';
|
|
2
|
+
import { matchTableRows } from '../model/rows.js';
|
|
3
|
+
/**
|
|
4
|
+
* Rule builder over table rows drawn from the corpus.
|
|
5
|
+
*
|
|
6
|
+
* A row is a first-class element (`MdRow`): its located-column accessor
|
|
7
|
+
* (`get(role)`), its real source line, and back references to its document and
|
|
8
|
+
* table. Rows are drawn from **every** table (across all documents) that sits
|
|
9
|
+
* under the requested section and has all the requested columns.
|
|
10
|
+
*
|
|
11
|
+
* The primary use is `.select()` — turning spec-table rows into one side of a
|
|
12
|
+
* kernel `correspondence()` so a table can be bound to code and fail on drift.
|
|
13
|
+
* The standard `.that().satisfy(…)` / `.should().satisfy(…)` surface is
|
|
14
|
+
* inherited, so rows are also directly rule-able.
|
|
15
|
+
*/
|
|
16
|
+
export class RowsRuleBuilder extends RuleBuilder {
|
|
17
|
+
opts;
|
|
18
|
+
constructor(corpus, opts) {
|
|
19
|
+
super(corpus);
|
|
20
|
+
this.opts = opts;
|
|
21
|
+
}
|
|
22
|
+
getElements() {
|
|
23
|
+
return this.project.documents().flatMap((doc) => matchTableRows(doc, this.opts));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Entry point: build rules over table rows in the corpus.
|
|
28
|
+
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* const packageRows = rows(c, {
|
|
31
|
+
* section: /^Packages$/,
|
|
32
|
+
* columns: { pkg: /^Package$/, status: /^Status$/ },
|
|
33
|
+
* }).select({
|
|
34
|
+
* label: 'README package row',
|
|
35
|
+
* identify: (r) => ({ name: r.get('pkg'), file: r.doc.relPath, line: r.line }),
|
|
36
|
+
* })
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export function rows(corpus, opts) {
|
|
40
|
+
return new RowsRuleBuilder(corpus, opts);
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=rows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rows.js","sourceRoot":"","sources":["../../src/builders/rows.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAE9C,OAAO,EAAE,cAAc,EAAoC,MAAM,kBAAkB,CAAA;AAEnF;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,eAAgB,SAAQ,WAA0B;IAG1C;IAFnB,YACE,MAAc,EACG,IAAqB;QAEtC,KAAK,CAAC,MAAM,CAAC,CAAA;QAFI,SAAI,GAAJ,IAAI,CAAiB;IAGxC,CAAC;IAES,WAAW;QACnB,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;IAClF,CAAC;CAIF;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,IAAI,CAAC,MAAc,EAAE,IAAqB;IACxD,OAAO,IAAI,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;AAC1C,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { RuleBuilder } from '@nielspeter/eess';
|
|
2
|
+
import type { Corpus } from '../corpus.js';
|
|
3
|
+
import type { MdDocument } from '../model/document.js';
|
|
4
|
+
import { type MdTaskItemRef } from '../model/task-items.js';
|
|
5
|
+
/** A task-item element: a task-list item plus its containing document. */
|
|
6
|
+
export interface MdTaskItem extends MdTaskItemRef {
|
|
7
|
+
readonly doc: MdDocument;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Rule builder over the corpus's GFM task-list items (`- [ ]` / `- [x]`).
|
|
11
|
+
*/
|
|
12
|
+
export declare class TaskItemRuleBuilder extends RuleBuilder<MdTaskItem, Corpus> {
|
|
13
|
+
protected getElements(): MdTaskItem[];
|
|
14
|
+
/** Filter to open boxes (`- [ ]`). */
|
|
15
|
+
areOpen(): this;
|
|
16
|
+
/** Filter to checked boxes (`- [x]`). */
|
|
17
|
+
areChecked(): this;
|
|
18
|
+
}
|
|
19
|
+
/** Entry point: build rules over the corpus's task-list items. */
|
|
20
|
+
export declare function taskItems(corpus: Corpus): TaskItemRuleBuilder;
|
|
21
|
+
//# sourceMappingURL=task-items.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-items.d.ts","sourceRoot":"","sources":["../../src/builders/task-items.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAkB,MAAM,kBAAkB,CAAA;AAC9D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAE7E,0EAA0E;AAC1E,MAAM,WAAW,UAAW,SAAQ,aAAa;IAC/C,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAA;CACzB;AAWD;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,WAAW,CAAC,UAAU,EAAE,MAAM,CAAC;IACtE,SAAS,CAAC,WAAW,IAAI,UAAU,EAAE;IAMrC,sCAAsC;IACtC,OAAO,IAAI,IAAI;IAIf,yCAAyC;IACzC,UAAU,IAAI,IAAI;CAGnB;AAED,kEAAkE;AAClE,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,mBAAmB,CAE7D"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { RuleBuilder } from '@nielspeter/eess';
|
|
2
|
+
import { collectTaskItems } from '../model/task-items.js';
|
|
3
|
+
const areOpen = () => ({
|
|
4
|
+
description: 'are open',
|
|
5
|
+
test: (t) => !t.checked,
|
|
6
|
+
});
|
|
7
|
+
const areChecked = () => ({
|
|
8
|
+
description: 'are checked',
|
|
9
|
+
test: (t) => t.checked,
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* Rule builder over the corpus's GFM task-list items (`- [ ]` / `- [x]`).
|
|
13
|
+
*/
|
|
14
|
+
export class TaskItemRuleBuilder extends RuleBuilder {
|
|
15
|
+
getElements() {
|
|
16
|
+
return this.project
|
|
17
|
+
.documents()
|
|
18
|
+
.flatMap((doc) => collectTaskItems(doc.root).map((t) => ({ ...t, doc })));
|
|
19
|
+
}
|
|
20
|
+
/** Filter to open boxes (`- [ ]`). */
|
|
21
|
+
areOpen() {
|
|
22
|
+
return this.addPredicate(areOpen());
|
|
23
|
+
}
|
|
24
|
+
/** Filter to checked boxes (`- [x]`). */
|
|
25
|
+
areChecked() {
|
|
26
|
+
return this.addPredicate(areChecked());
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Entry point: build rules over the corpus's task-list items. */
|
|
30
|
+
export function taskItems(corpus) {
|
|
31
|
+
return new TaskItemRuleBuilder(corpus);
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=task-items.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-items.js","sourceRoot":"","sources":["../../src/builders/task-items.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAkB,MAAM,kBAAkB,CAAA;AAG9D,OAAO,EAAE,gBAAgB,EAAsB,MAAM,wBAAwB,CAAA;AAO7E,MAAM,OAAO,GAAG,GAA0B,EAAE,CAAC,CAAC;IAC5C,WAAW,EAAE,UAAU;IACvB,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO;CACxB,CAAC,CAAA;AACF,MAAM,UAAU,GAAG,GAA0B,EAAE,CAAC,CAAC;IAC/C,WAAW,EAAE,aAAa;IAC1B,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO;CACvB,CAAC,CAAA;AAEF;;GAEG;AACH,MAAM,OAAO,mBAAoB,SAAQ,WAA+B;IAC5D,WAAW;QACnB,OAAO,IAAI,CAAC,OAAO;aAChB,SAAS,EAAE;aACX,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAA;IAC7E,CAAC;IAED,sCAAsC;IACtC,OAAO;QACL,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC,CAAA;IACrC,CAAC;IAED,yCAAyC;IACzC,UAAU;QACR,OAAO,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,CAAC,CAAA;IACxC,CAAC;CACF;AAED,kEAAkE;AAClE,MAAM,UAAU,SAAS,CAAC,MAAc;IACtC,OAAO,IAAI,mBAAmB,CAAC,MAAM,CAAC,CAAA;AACxC,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { RuleBuilder } from '@nielspeter/eess';
|
|
2
|
+
import type { Corpus } from '../corpus.js';
|
|
3
|
+
import type { MdDocument } from '../model/document.js';
|
|
4
|
+
/**
|
|
5
|
+
* Controlled-vocabulary primitive (plan 0069 Phase 4): derive a term set from
|
|
6
|
+
* the corpus, then assert that named references in prose resolve against it.
|
|
7
|
+
*
|
|
8
|
+
* Both halves are consumer parameters — where the terms come from
|
|
9
|
+
* (`fromFolders` / `fromHeadings` / explicit `terms`) and what a reference
|
|
10
|
+
* looks like (the `label` pattern). Nothing domain-shaped lives here.
|
|
11
|
+
*/
|
|
12
|
+
export interface VocabularyOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Glob over directory paths (repo-relative, POSIX); each matched
|
|
15
|
+
* directory's basename becomes a term — e.g. folders-as-canonical-names.
|
|
16
|
+
*/
|
|
17
|
+
readonly fromFolders?: string;
|
|
18
|
+
/** Glob over documents; every heading whose depth matches becomes a term. */
|
|
19
|
+
readonly fromHeadings?: {
|
|
20
|
+
readonly files: string;
|
|
21
|
+
readonly depth?: number;
|
|
22
|
+
};
|
|
23
|
+
/** Explicit terms, unioned with the derived ones. */
|
|
24
|
+
readonly terms?: readonly string[];
|
|
25
|
+
/** Normalization applied to terms AND references before comparing. Default: trim. */
|
|
26
|
+
readonly normalize?: (value: string) => string;
|
|
27
|
+
}
|
|
28
|
+
export interface Vocabulary {
|
|
29
|
+
/** The normalized term set. */
|
|
30
|
+
readonly terms: ReadonlySet<string>;
|
|
31
|
+
/** The normalization in force (shared with `resolveAgainst`). */
|
|
32
|
+
readonly normalize: (value: string) => string;
|
|
33
|
+
}
|
|
34
|
+
/** Derive a controlled vocabulary from the corpus. */
|
|
35
|
+
export declare function vocabulary(corpus: Corpus, options: VocabularyOptions): Vocabulary;
|
|
36
|
+
/** A labeled named reference found in prose — e.g. `Bounded Context: Billing`. */
|
|
37
|
+
export interface MdTerm {
|
|
38
|
+
/** The referenced value (markdown emphasis stripped, trimmed). */
|
|
39
|
+
readonly value: string;
|
|
40
|
+
/** The line's text after the label match, before cleanup. */
|
|
41
|
+
readonly raw: string;
|
|
42
|
+
readonly doc: MdDocument;
|
|
43
|
+
readonly line: number;
|
|
44
|
+
}
|
|
45
|
+
export interface TermsOptions {
|
|
46
|
+
/** Pattern locating a labeled reference on a line — e.g. `/Bounded Context:/`. */
|
|
47
|
+
readonly label: RegExp;
|
|
48
|
+
/**
|
|
49
|
+
* Extract the referenced value from the text following the label match.
|
|
50
|
+
* Default: strip markdown emphasis characters and trim.
|
|
51
|
+
*/
|
|
52
|
+
readonly value?: (rest: string) => string;
|
|
53
|
+
}
|
|
54
|
+
/** Rule builder over labeled named references in the corpus's prose. */
|
|
55
|
+
export declare class TermRuleBuilder extends RuleBuilder<MdTerm, Corpus> {
|
|
56
|
+
private readonly opts;
|
|
57
|
+
constructor(corpus: Corpus, opts: TermsOptions);
|
|
58
|
+
protected getElements(): MdTerm[];
|
|
59
|
+
/** Filter to references in documents whose path matches the glob. */
|
|
60
|
+
resideInFile(glob: string): this;
|
|
61
|
+
/** Assert every reference's value is a term of the vocabulary. */
|
|
62
|
+
resolveAgainst(vocab: Vocabulary): this;
|
|
63
|
+
}
|
|
64
|
+
/** Entry point: build rules over labeled named references (`Label: Value` lines). */
|
|
65
|
+
export declare function terms(corpus: Corpus, options: TermsOptions): TermRuleBuilder;
|
|
66
|
+
//# sourceMappingURL=vocabulary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vocabulary.d.ts","sourceRoot":"","sources":["../../src/builders/vocabulary.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAsD,MAAM,kBAAkB,CAAA;AAClG,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAEtD;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,6EAA6E;IAC7E,QAAQ,CAAC,YAAY,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC3E,qDAAqD;IACrD,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,qFAAqF;IACrF,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;CAC/C;AAED,MAAM,WAAW,UAAU;IACzB,+BAA+B;IAC/B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACnC,iEAAiE;IACjE,QAAQ,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;CAC9C;AAID,sDAAsD;AACtD,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,UAAU,CAoCjF;AAED,kFAAkF;AAClF,MAAM,WAAW,MAAM;IACrB,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,6DAA6D;IAC7D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,kFAAkF;IAClF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;CAC1C;AA8DD,wEAAwE;AACxE,qBAAa,eAAgB,SAAQ,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC;IAG5D,OAAO,CAAC,QAAQ,CAAC,IAAI;gBADrB,MAAM,EAAE,MAAM,EACG,IAAI,EAAE,YAAY;IAKrC,SAAS,CAAC,WAAW,IAAI,MAAM,EAAE;IAIjC,qEAAqE;IACrE,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIhC,kEAAkE;IAClE,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;CAGxC;AAED,qFAAqF;AACrF,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,eAAe,CAE5E"}
|