@timiagama/ecr 0.0.1 → 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.
Files changed (138) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +16 -0
  3. package/README.md +411 -7
  4. package/dist/bin.d.ts +20 -0
  5. package/dist/bin.d.ts.map +1 -0
  6. package/dist/bin.js +26 -0
  7. package/dist/bin.js.map +1 -0
  8. package/dist/cli/corpus-loader.d.ts +198 -0
  9. package/dist/cli/corpus-loader.d.ts.map +1 -0
  10. package/dist/cli/corpus-loader.js +351 -0
  11. package/dist/cli/corpus-loader.js.map +1 -0
  12. package/dist/cli/corpus-statistics.d.ts +113 -0
  13. package/dist/cli/corpus-statistics.d.ts.map +1 -0
  14. package/dist/cli/corpus-statistics.js +119 -0
  15. package/dist/cli/corpus-statistics.js.map +1 -0
  16. package/dist/cli/diagnostic-reporter.d.ts +103 -0
  17. package/dist/cli/diagnostic-reporter.d.ts.map +1 -0
  18. package/dist/cli/diagnostic-reporter.js +254 -0
  19. package/dist/cli/diagnostic-reporter.js.map +1 -0
  20. package/dist/cli/project-ignore.d.ts +113 -0
  21. package/dist/cli/project-ignore.d.ts.map +1 -0
  22. package/dist/cli/project-ignore.js +199 -0
  23. package/dist/cli/project-ignore.js.map +1 -0
  24. package/dist/cli/safe-text.d.ts +25 -0
  25. package/dist/cli/safe-text.d.ts.map +1 -0
  26. package/dist/cli/safe-text.js +34 -0
  27. package/dist/cli/safe-text.js.map +1 -0
  28. package/dist/cli/supervisor.d.ts +108 -0
  29. package/dist/cli/supervisor.d.ts.map +1 -0
  30. package/dist/cli/supervisor.js +139 -0
  31. package/dist/cli/supervisor.js.map +1 -0
  32. package/dist/cli.d.ts +247 -0
  33. package/dist/cli.d.ts.map +1 -0
  34. package/dist/cli.js +591 -0
  35. package/dist/cli.js.map +1 -0
  36. package/dist/corpus-validator.d.ts +183 -0
  37. package/dist/corpus-validator.d.ts.map +1 -0
  38. package/dist/corpus-validator.js +479 -0
  39. package/dist/corpus-validator.js.map +1 -0
  40. package/dist/document-identity-rule.d.ts +198 -0
  41. package/dist/document-identity-rule.d.ts.map +1 -0
  42. package/dist/document-identity-rule.js +244 -0
  43. package/dist/document-identity-rule.js.map +1 -0
  44. package/dist/ecr.d.ts +103 -0
  45. package/dist/ecr.d.ts.map +1 -0
  46. package/dist/ecr.js +216 -0
  47. package/dist/ecr.js.map +1 -0
  48. package/dist/glob-pattern.d.ts +94 -0
  49. package/dist/glob-pattern.d.ts.map +1 -0
  50. package/dist/glob-pattern.js +188 -0
  51. package/dist/glob-pattern.js.map +1 -0
  52. package/dist/heading-source-form.d.ts +77 -0
  53. package/dist/heading-source-form.d.ts.map +1 -0
  54. package/dist/heading-source-form.js +129 -0
  55. package/dist/heading-source-form.js.map +1 -0
  56. package/dist/identifier-grammar.d.ts +212 -0
  57. package/dist/identifier-grammar.d.ts.map +1 -0
  58. package/dist/identifier-grammar.js +273 -0
  59. package/dist/identifier-grammar.js.map +1 -0
  60. package/dist/index.d.ts +19 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +11 -0
  63. package/dist/index.js.map +1 -0
  64. package/dist/inline-reference-rule.d.ts +557 -0
  65. package/dist/inline-reference-rule.d.ts.map +1 -0
  66. package/dist/inline-reference-rule.js +802 -0
  67. package/dist/inline-reference-rule.js.map +1 -0
  68. package/dist/meta-documents.d.ts +91 -0
  69. package/dist/meta-documents.d.ts.map +1 -0
  70. package/dist/meta-documents.js +133 -0
  71. package/dist/meta-documents.js.map +1 -0
  72. package/dist/per-document-visitor.d.ts +510 -0
  73. package/dist/per-document-visitor.d.ts.map +1 -0
  74. package/dist/per-document-visitor.js +1032 -0
  75. package/dist/per-document-visitor.js.map +1 -0
  76. package/dist/references-section-rule.d.ts +482 -0
  77. package/dist/references-section-rule.d.ts.map +1 -0
  78. package/dist/references-section-rule.js +777 -0
  79. package/dist/references-section-rule.js.map +1 -0
  80. package/dist/section-hierarchy-rule.d.ts +299 -0
  81. package/dist/section-hierarchy-rule.d.ts.map +1 -0
  82. package/dist/section-hierarchy-rule.js +482 -0
  83. package/dist/section-hierarchy-rule.js.map +1 -0
  84. package/dist/source-alignment.d.ts +41 -0
  85. package/dist/source-alignment.d.ts.map +1 -0
  86. package/dist/source-alignment.js +166 -0
  87. package/dist/source-alignment.js.map +1 -0
  88. package/dist/source-lines.d.ts +69 -0
  89. package/dist/source-lines.d.ts.map +1 -0
  90. package/dist/source-lines.js +104 -0
  91. package/dist/source-lines.js.map +1 -0
  92. package/dist/spec-version.d.ts +19 -0
  93. package/dist/spec-version.d.ts.map +1 -0
  94. package/dist/spec-version.js +19 -0
  95. package/dist/spec-version.js.map +1 -0
  96. package/dist/types.d.ts +219 -0
  97. package/dist/types.d.ts.map +1 -0
  98. package/dist/types.js +11 -0
  99. package/dist/types.js.map +1 -0
  100. package/examples/docs/0. Orientation/0.0 - System Overview.md +34 -0
  101. package/examples/docs/0. Orientation/0.0.1 - Glossary.md +21 -0
  102. package/examples/docs/0. Orientation/0.0.2 - Quality Gates.md +22 -0
  103. package/examples/docs/3. Ingestion/3.1 - Ingestion - Validation Rules.md +33 -0
  104. package/examples/docs/4. Payments/4.10 - Settlement.md +22 -0
  105. package/examples/docs/4. Payments/4.2 - Payment Processing Contract.md +33 -0
  106. package/examples/docs/8. Orchestration/8.1 - Workflow Orchestration Contract.md +38 -0
  107. package/examples/docs/8. Orchestration/8.1.3 - Retry Policy.md +17 -0
  108. package/examples/docs/8. Orchestration/8.10 - Dead Letter Queue.md +20 -0
  109. package/examples/docs/README.md +7 -0
  110. package/package.json +65 -7
  111. package/protocol/navigation-protocol.md +223 -0
  112. package/spec/v2/1 - ECR - Structural Specification.md +1467 -0
  113. package/spec/v2/2 - ECR - User Guide.md +845 -0
  114. package/spec/v2/3 - Design Rationale - The Section Separator.md +243 -0
  115. package/src/bin.ts +30 -0
  116. package/src/cli/corpus-loader.ts +482 -0
  117. package/src/cli/corpus-statistics.ts +196 -0
  118. package/src/cli/diagnostic-reporter.ts +344 -0
  119. package/src/cli/project-ignore.ts +238 -0
  120. package/src/cli/safe-text.ts +39 -0
  121. package/src/cli/supervisor.ts +204 -0
  122. package/src/cli.ts +836 -0
  123. package/src/corpus-validator.ts +642 -0
  124. package/src/document-identity-rule.ts +422 -0
  125. package/src/ecr.ts +298 -0
  126. package/src/glob-pattern.ts +242 -0
  127. package/src/heading-source-form.ts +173 -0
  128. package/src/identifier-grammar.ts +380 -0
  129. package/src/index.ts +78 -0
  130. package/src/inline-reference-rule.ts +1235 -0
  131. package/src/meta-documents.ts +159 -0
  132. package/src/per-document-visitor.ts +1528 -0
  133. package/src/references-section-rule.ts +1182 -0
  134. package/src/section-hierarchy-rule.ts +705 -0
  135. package/src/source-alignment.ts +209 -0
  136. package/src/source-lines.ts +123 -0
  137. package/src/spec-version.ts +19 -0
  138. package/src/types.ts +267 -0
@@ -0,0 +1,243 @@
1
+ # 3 - Design Rationale - The Section Separator
2
+
3
+ **Status:** Part of the specification
4
+ **Explains:** why a SectionID carries the `#` separator
5
+ **Audience:** anyone asking why identifiers are not simply dotted
6
+
7
+ ## 3#1 - Summary
8
+
9
+ ECR uses a single character, `#`, marking the boundary between a
10
+ document identifier and a section path.
11
+
12
+ ```
13
+ 0.0.2#1 section 1 of document 0.0.2
14
+ 0.0#2.1 section 2, subsection 1, of document 0.0
15
+ ```
16
+
17
+ This makes SectionIDs globally unique by construction, makes reference
18
+ resolution deterministic without a corpus index, and permits a DocID to be a
19
+ prefix of another DocID — so a numbering scheme may express document hierarchy
20
+ to a human reader without creating identifier collisions.
21
+
22
+ ## 3#2 - The problem
23
+
24
+ The obvious design is for a SectionID to extend its DocID by appending dot-separated
25
+ segments, and DocIDs may themselves contain dots. Nothing marks where one ends
26
+ and the other begins, so a dotted identifier has more than one legal reading.
27
+
28
+ Given a corpus containing both a document `0.0` and a document `0.0.2`, the
29
+ identifier `0.0.2.1` legally denotes either:
30
+
31
+ - section `1` of document `0.0.2`, or
32
+ - section `2.1` of document `0.0`.
33
+
34
+ A dotted-only grammar permits both, so in practice both occur. Validating a
35
+ real 47-document corpus produced 14 duplicate-SectionID errors of exactly this
36
+ form, every one of them legitimately numbered under a dotted-only grammar:
37
+
38
+ ```
39
+ 0.0 - System Overview.md:20 ### 0.0.2.1 - How to Think About a Scenario
40
+ 0.0.2 - Build QA Contract.md:3 ## 0.0.2.1 - Purpose
41
+ ```
42
+
43
+ The collision is structural, not accidental. A document's section namespace
44
+ occupies the same flat numeric space as its sibling documents' identifiers, so
45
+ any corpus that numbers documents hierarchically will collide. Because the
46
+ duplicates block corpus indexing, the effect is total: no identifier in the
47
+ corpus resolves.
48
+
49
+ The same ambiguity degrades reference resolution. `see 0.0.2` cannot be resolved
50
+ without consulting an index, and where both readings exist in the index it
51
+ cannot be resolved at all.
52
+
53
+ ### 3#2.1 - Rejected alternatives
54
+
55
+ Two schemes preserve pure-numeric identifiers. Both fail on **stability** —
56
+ SectionIDs are referenced from other documents, so any scheme in which adding a
57
+ document changes another document's SectionIDs is not viable.
58
+
59
+ | Scheme | Failure |
60
+ |---|---|
61
+ | A parent document numbers its own sections above the range used by its children | Adding a child renumbers the parent's sections, invalidating every reference to them |
62
+ | A parent reserves a `.0` segment for its own sections | Same failure on gaining a first child; `0` is also already in use as a document number |
63
+
64
+ A third option — forbidding any DocID from being a proper prefix of another —
65
+ is stable and needs no new syntax, but purchases uniqueness by prohibiting
66
+ hierarchical document numbering, which is the property this amendment exists to
67
+ preserve.
68
+
69
+ ## 3#3 - The rule
70
+
71
+ > **Rule 2a.** A SectionID consists of a DocID, the separator `#`, and a section
72
+ > path of one or more dot-separated numeric segments. The separator is mandatory
73
+ > in every SectionID, whether or not ambiguity is possible in a given corpus.
74
+
75
+ ```
76
+ DocID ::= Digit+ ( "." Digit+ )*
77
+ SectionPath ::= Digit+ ( "." Digit+ )*
78
+ SectionID ::= DocID "#" SectionPath
79
+ Identifier ::= DocID | SectionID
80
+ ```
81
+
82
+ A heading at Markdown depth *d* (d ≥ 2) carries a section path of exactly
83
+ *d − 1* segments. `## References` remains unnumbered, per Rule 4.
84
+
85
+ ### 3#3.1 - Uniqueness
86
+
87
+ SectionIDs are unique by construction, and no corpus-wide check is needed to
88
+ establish it:
89
+
90
+ 1. DocIDs are globally unique (see 1, corpus-wide rules).
91
+ 2. Section paths are unique within a document (Rule 2).
92
+ 3. A SectionID is a DocID and a section path joined by a character that appears
93
+ in neither.
94
+
95
+ Therefore two SectionIDs are equal only when they name the same section of the
96
+ same document. The corpus duplicate-SectionID check is retained as a safety net
97
+ against malformed input, but under a conforming corpus it cannot fire.
98
+
99
+ ### 3#3.2 - Resolution
100
+
101
+ Resolution requires no index and no disambiguation:
102
+
103
+ - An identifier containing `#` is a SectionID. The text before `#` is the DocID.
104
+ - An identifier containing no `#` is a DocID.
105
+
106
+ A DocID may now be a proper prefix of another DocID. `0.0`, `0.0.1` and `0.0.2`
107
+ may all be documents, and none of their section namespaces can overlap.
108
+
109
+ ## 3#4 - Examples
110
+
111
+ ```markdown
112
+ # 0.0 - System Overview
113
+
114
+ ## 0.0#1 - Purpose
115
+
116
+ ### 0.0#1.1 - Scope
117
+
118
+ ## 0.0#2 - Mental Model
119
+
120
+ Evaluation criteria are defined per 0.0.2#3.
121
+
122
+ ## References
123
+
124
+ - 0.0.2 - Build Quality Assurance Contract (authority - defines the quality gates referenced here)
125
+ ```
126
+
127
+ ```markdown
128
+ # 0.0.2 - Build Quality Assurance Contract
129
+
130
+ ## 0.0.2#1 - Purpose
131
+
132
+ ## 0.0.2#3 - Evaluation Criteria
133
+ ```
134
+
135
+ `0.0#1` and `0.0.2#1` are both "section 1", of different documents, and are
136
+ distinct identifiers. Without the separator the second would be written `0.0.2.1`
137
+ and would have collided with `0.0`'s `### 0.0.2.1`.
138
+
139
+ ## 3#5 - Effect on navigation
140
+
141
+ A dotted-only grammar cannot distinguish a document from a
142
+ similarly-numbered sibling. This pattern:
143
+
144
+ ```bash
145
+ rg -n "\b([Ss]ee|[Pp]er) 8\.1(\.\d+)*\b" docs
146
+ ```
147
+
148
+ matches references to sections of document `8.1`, **and** references to document
149
+ `8.1.3` and all of its sections. The separator distinguishes the cases:
150
+
151
+ | Question | Pattern |
152
+ |---|---|
153
+ | Who references document `8.1`? | `rg --no-ignore -n "(\b\|_)([Ss]ee\|[Pp]er) 8\.1(\.[^0-9A-Za-z.#]\|\.$\|[^0-9A-Za-z.#]\|$)" docs` |
154
+ | Who references any section of `8.1`? | `rg --no-ignore -n "(\b\|_)([Ss]ee\|[Pp]er) 8\.1#" docs` |
155
+ | Who references section `8.1#3` or below? | `rg --no-ignore -n "(\b\|_)([Ss]ee\|[Pp]er) 8\.1#3([^0-9A-Za-z#]\|$)" docs` |
156
+ | Either the document or any section? | `rg --no-ignore -n "(\b\|_)([Ss]ee\|[Pp]er) 8\.1(#[0-9.]*\|\.[^0-9A-Za-z.#]\|\.$\|[^0-9A-Za-z.#]\|$)" docs` |
157
+ | Every section-precise reference in the corpus | `rg --no-ignore -n "(\b\|_)([Ss]ee\|[Pp]er) [0-9.]+#" docs` |
158
+
159
+ The last row is not expressible without the separator.
160
+
161
+ `see` and `per` are matched with an optional capital because a reference at the
162
+ start of a sentence is still a reference. These rows are the same patterns the
163
+ navigation protocol publishes, where they are tested against the example corpus;
164
+ change them there first.
165
+
166
+ ### 3#5.1 - Why the patterns bound a citation explicitly
167
+
168
+ `\b` cannot terminate an identifier, because `.` and `#` are both non-word
169
+ characters: a search for `8\.1\b` matches inside `8.1.3`, `8.1#3` and `8.1.3#1`.
170
+ The trailing group states explicitly what may follow a bare DocID — anything
171
+ that is not an ASCII letter or digit, `.` or `#`. The `\.[^0-9A-Za-z.#]` and
172
+ `\.$` alternatives admit a sentence-ending full stop, so `see 8.1.` is matched
173
+ while `see 8.1.3` and `see 8.1..2` are not.
174
+
175
+ Before the keyword, `\b` alone is not enough either. `_` is a word character to
176
+ both engines, so `\b` finds nothing in `_see 8.1_`, which is ordinary Markdown
177
+ emphasis. The leading group is therefore `(\b|_)`: a word boundary, or an
178
+ underscore. It still refuses `oversee 8.1`. It was chosen over "any character
179
+ but an ASCII letter or digit" by measurement: that class is not matched against
180
+ a character outside the Basic Multilingual Plane by at least one GNU grep, so
181
+ `🔒see 8.1` would have been missed.
182
+
183
+ This is a pre-existing property of dotted identifiers, not a consequence of this
184
+ separator; a dotted-only grammar has the same defect and silently conflates a
185
+ document with its numeric siblings. Every pattern in the table above was
186
+ verified against a corpus containing `8.1`, `8.1.3`, `8.10` and `8.1.30`, in
187
+ both sentence-final and mid-sentence positions.
188
+
189
+ Where ripgrep is built with PCRE2, `-P` permits the clearer lookahead form:
190
+
191
+ ```bash
192
+ rg --no-ignore -P -n "(?<![0-9A-Za-z])([Ss]ee|[Pp]er) 8\.1(?![0-9A-Za-z#])(?!\.[0-9A-Za-z.#])" docs
193
+ ```
194
+
195
+ The default-engine patterns are given as the primary form because they need no
196
+ special build or flag.
197
+
198
+ ## 3#6 - Migration
199
+
200
+ Mechanical, given that DocIDs are recoverable from filenames:
201
+
202
+ 1. Build the DocID set from H1 headings across the corpus.
203
+ 2. For each document, rewrite every sub-heading `## X.Y.Z - Title` as
204
+ `## <DocID>#<remaining segments> - Title`, where `<DocID>` is this document's
205
+ own DocID.
206
+ 3. For each inline `see`/`per` reference, resolve the target against the DocID
207
+ set by longest matching prefix; if segments remain after the matched DocID,
208
+ join them with `#`.
209
+ 4. Leave `## References` entries unchanged — they cite documents, not sections.
210
+ 5. Run `ecr lint` and resolve what remains.
211
+
212
+ Step 3 is the only step requiring corpus-wide knowledge, and it is exactly the
213
+ operation that was previously ambiguous. It is well-defined during migration
214
+ because the DocID set is known.
215
+
216
+ Because migration needs corpus knowledge rather than a blind rewrite, it suits a
217
+ coding agent. See the README's *Using a coding agent to migrate* for the
218
+ recommended session structure: implement and review in separate same-family
219
+ sessions, then a final review by a model from a different family.
220
+
221
+ ## 3#7 - Linter changes
222
+
223
+ | Component | Change |
224
+ |---|---|
225
+ | `IdentifierGrammar` | Parse `DocID ["#" SectionPath]`; `parseSectionId` splits on `#` rather than dropping the last dotted segment |
226
+ | `SectionHierarchyRule` | Require the `#` form in sub-headings; verify section path length equals heading depth − 1 |
227
+ | `InlineReferenceRule` | Reference pattern gains `(?:#\d+(?:\.\d+)*)?`; the "undeclared DocID" check reads the DocID as the text before `#` |
228
+ | `CorpusValidator` | Duplicate-SectionID check retained as a safety net; unresolved-target checks resolve without disambiguation |
229
+
230
+ ## 3#8 - What this does not change
231
+
232
+ - DocID grammar and depth. DocIDs remain dot-separated numerics of any depth.
233
+ - `## References` entries, which cite DocIDs and never carry a section path.
234
+ - The four direction types and their semantics.
235
+ - Rule 1 (numbered H1), Rule 3 (`see`/`per` inline references) and Rule 4
236
+ (mandatory References section).
237
+ - Separator characters in titles, which remain free-form: `-`, `–` and `—` are
238
+ all accepted between an identifier and its title.
239
+
240
+ ## References
241
+
242
+ - 1 - ECR - Structural Specification (authority - defines the identifier grammar this amendment changes)
243
+ - 2 - ECR - User Guide (dependency - the authoring rules this amendment restates for the new grammar)
package/src/bin.ts ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ECR Executable Entry Point
4
+ *
5
+ * This file runs the CLI unconditionally and does nothing else. It must not
6
+ * guard itself with a "was I invoked directly?" check that compares
7
+ * `process.argv[1]` with this module's path: package managers install the
8
+ * binary as a symlink (`node_modules/.bin/ecr`), Node resolves the module to
9
+ * its real path, and the two never match, so a guarded entry point silently
10
+ * exits 0 without running. Keeping the commands in `cli.ts` and the side
11
+ * effect here removes the need for any such check.
12
+ *
13
+ * It is also where supervision is asked for, because this file knows the one
14
+ * thing a supervised run needs: the script a child process would have to run,
15
+ * which is this file itself, at its real path rather than through whatever
16
+ * symlink invoked it. A host embedding the library instead of the command
17
+ * gets no supervision, and bounds its own work.
18
+ */
19
+
20
+ import { fileURLToPath } from 'node:url';
21
+
22
+ import { EcrCommandLine } from './cli.js';
23
+ import type { CommandOutcome } from './cli.js';
24
+
25
+ const result: CommandOutcome = new EcrCommandLine({
26
+ supervision: { entryPoint: fileURLToPath(import.meta.url) },
27
+ }).run(process.argv.slice(2));
28
+
29
+ process[result.stream].write(result.output);
30
+ process.exitCode = result.exitCode;