@descent-vtt/spec-brief 0.1.0 → 0.2.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.
- package/CHANGELOG.md +301 -0
- package/README.md +92 -22
- package/dist/archive.d.ts +52 -2
- package/dist/archive.js +205 -37
- package/dist/archive.js.map +1 -1
- package/dist/brief.d.ts +2 -0
- package/dist/brief.js +5 -1
- package/dist/brief.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +48 -14
- package/dist/cli.js.map +1 -1
- package/dist/collisions.d.ts +43 -12
- package/dist/collisions.js +90 -58
- package/dist/collisions.js.map +1 -1
- package/dist/config.d.ts +18 -0
- package/dist/config.js +66 -11
- package/dist/config.js.map +1 -1
- package/dist/corpus.d.ts +21 -1
- package/dist/corpus.js +51 -2
- package/dist/corpus.js.map +1 -1
- package/dist/engine.d.ts +33 -3
- package/dist/engine.js +112 -9
- package/dist/engine.js.map +1 -1
- package/dist/frontmatter.d.ts +18 -61
- package/dist/frontmatter.js +31 -300
- package/dist/frontmatter.js.map +1 -1
- package/dist/glob.d.ts +77 -54
- package/dist/glob.js +104 -433
- package/dist/glob.js.map +1 -1
- package/dist/index.d.ts +10 -8
- package/dist/index.js +8 -6
- package/dist/index.js.map +1 -1
- package/dist/links.d.ts +1 -1
- package/dist/links.js +3 -4
- package/dist/links.js.map +1 -1
- package/dist/lint.d.ts +33 -2
- package/dist/lint.js +9 -2
- package/dist/lint.js.map +1 -1
- package/dist/markdown.d.ts +27 -23
- package/dist/markdown.js +66 -235
- package/dist/markdown.js.map +1 -1
- package/dist/plugins.d.ts +14 -0
- package/dist/plugins.js +142 -6
- package/dist/plugins.js.map +1 -1
- package/dist/report.d.ts +40 -3
- package/dist/report.js +227 -11
- package/dist/report.js.map +1 -1
- package/dist/rules.d.ts +22 -4
- package/dist/rules.js +175 -45
- package/dist/rules.js.map +1 -1
- package/dist/scaffold.d.ts +1 -1
- package/dist/scaffold.js +10 -9
- package/dist/scaffold.js.map +1 -1
- package/dist/schedule.d.ts +133 -0
- package/dist/schedule.js +265 -0
- package/dist/schedule.js.map +1 -0
- package/dist/scope.d.ts +52 -0
- package/dist/scope.js +56 -0
- package/dist/scope.js.map +1 -0
- package/dist/text.d.ts +13 -0
- package/dist/text.js +23 -0
- package/dist/text.js.map +1 -1
- package/dist/trigger.d.ts +17 -0
- package/dist/trigger.js +49 -0
- package/dist/trigger.js.map +1 -0
- package/dist/types.d.ts +14 -5
- package/dist/types.js.map +1 -1
- package/dist/vendor/spec-core/markdown/frontmatter.d.ts +103 -0
- package/dist/vendor/spec-core/markdown/frontmatter.js +459 -0
- package/dist/vendor/spec-core/markdown/frontmatter.js.map +1 -0
- package/dist/vendor/spec-core/markdown/headings.d.ts +45 -0
- package/dist/vendor/spec-core/markdown/headings.js +199 -0
- package/dist/vendor/spec-core/markdown/headings.js.map +1 -0
- package/dist/vendor/spec-core/markdown/index.d.ts +5 -0
- package/dist/vendor/spec-core/markdown/index.js +4 -0
- package/dist/vendor/spec-core/markdown/index.js.map +1 -0
- package/dist/vendor/spec-core/markdown/layout.d.ts +39 -0
- package/dist/vendor/spec-core/markdown/layout.js +28 -0
- package/dist/vendor/spec-core/markdown/layout.js.map +1 -0
- package/dist/vendor/spec-core/markdown/links.d.ts +25 -0
- package/dist/vendor/spec-core/markdown/links.js +394 -0
- package/dist/vendor/spec-core/markdown/links.js.map +1 -0
- package/dist/vendor/spec-core/markdown/lists.d.ts +22 -0
- package/dist/vendor/spec-core/markdown/lists.js +127 -0
- package/dist/vendor/spec-core/markdown/lists.js.map +1 -0
- package/dist/vendor/spec-core/markdown/scan.d.ts +36 -0
- package/dist/vendor/spec-core/markdown/scan.js +463 -0
- package/dist/vendor/spec-core/markdown/scan.js.map +1 -0
- package/dist/vendor/spec-core/markdown/syntax.d.ts +75 -0
- package/dist/vendor/spec-core/markdown/syntax.js +181 -0
- package/dist/vendor/spec-core/markdown/syntax.js.map +1 -0
- package/dist/vendor/spec-core/markdown/tables.d.ts +18 -0
- package/dist/vendor/spec-core/markdown/tables.js +86 -0
- package/dist/vendor/spec-core/markdown/tables.js.map +1 -0
- package/dist/vendor/spec-core/markdown/types.d.ts +290 -0
- package/dist/vendor/spec-core/markdown/types.js +9 -0
- package/dist/vendor/spec-core/markdown/types.js.map +1 -0
- package/dist/vendor/spec-core/path/index.d.ts +1 -0
- package/dist/vendor/spec-core/path/index.js +2 -0
- package/dist/vendor/spec-core/path/index.js.map +1 -0
- package/dist/vendor/spec-core/path/posix.d.ts +83 -0
- package/dist/vendor/spec-core/path/posix.js +150 -0
- package/dist/vendor/spec-core/path/posix.js.map +1 -0
- package/dist/vendor/spec-core/pattern/automaton.d.ts +129 -0
- package/dist/vendor/spec-core/pattern/automaton.js +340 -0
- package/dist/vendor/spec-core/pattern/automaton.js.map +1 -0
- package/dist/vendor/spec-core/pattern/charset.d.ts +58 -0
- package/dist/vendor/spec-core/pattern/charset.js +116 -0
- package/dist/vendor/spec-core/pattern/charset.js.map +1 -0
- package/dist/vendor/spec-core/pattern/glob.d.ts +121 -0
- package/dist/vendor/spec-core/pattern/glob.js +481 -0
- package/dist/vendor/spec-core/pattern/glob.js.map +1 -0
- package/dist/vendor/spec-core/pattern/index.d.ts +6 -0
- package/dist/vendor/spec-core/pattern/index.js +4 -0
- package/dist/vendor/spec-core/pattern/index.js.map +1 -0
- package/dist/vendor/spec-core/pattern/regex.d.ts +67 -0
- package/dist/vendor/spec-core/pattern/regex.js +813 -0
- package/dist/vendor/spec-core/pattern/regex.js.map +1 -0
- package/dist/vendor/spec-core/text/index.d.ts +2 -0
- package/dist/vendor/spec-core/text/index.js +2 -0
- package/dist/vendor/spec-core/text/index.js.map +1 -0
- package/dist/vendor/spec-core/text/lines.d.ts +72 -0
- package/dist/vendor/spec-core/text/lines.js +156 -0
- package/dist/vendor/spec-core/text/lines.js.map +1 -0
- package/package.json +4 -3
- package/schema.json +17 -0
- package/src/vendor/spec-core/LICENSE +21 -0
- package/src/vendor/spec-core/README.md +8 -0
package/dist/text.d.ts
CHANGED
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
*/
|
|
4
4
|
/** Splits text into lines without their terminators. A final newline adds no line. */
|
|
5
5
|
export declare function splitLines(text: string): string[];
|
|
6
|
+
/**
|
|
7
|
+
* A brief's lines as one text, for spec-core's readers.
|
|
8
|
+
*
|
|
9
|
+
* A brief's lines end at LF or CRLF, and a sealed brief's hash is taken over
|
|
10
|
+
* them. spec-core ends a line at a lone CR as well, as CommonMark does, so a
|
|
11
|
+
* lone CR is handed to it as a space: every line and column it reports is
|
|
12
|
+
* then one of the brief's own.
|
|
13
|
+
*/
|
|
14
|
+
export declare function textOfLines(lines: readonly string[]): string;
|
|
6
15
|
/**
|
|
7
16
|
* The line ending a file already uses, so a rewrite does not flip it. Decided by
|
|
8
17
|
* the first terminator; a file with none is written with LF.
|
|
@@ -10,6 +19,8 @@ export declare function splitLines(text: string): string[];
|
|
|
10
19
|
export declare function lineEnding(text: string): '\n' | '\r\n';
|
|
11
20
|
/** Joins lines with the given ending and a final newline. */
|
|
12
21
|
export declare function joinLines(lines: readonly string[], eol: '\n' | '\r\n'): string;
|
|
22
|
+
/** Lines written back with the source's byte-order mark, line ending and final newline, or lack of one. */
|
|
23
|
+
export declare function encodeLike(source: string, lines: readonly string[]): string;
|
|
13
24
|
export declare function stripBom(text: string): string;
|
|
14
25
|
/** CRLF and a byte-order mark are storage details, not content. */
|
|
15
26
|
export declare function canonicalText(text: string): string;
|
|
@@ -34,6 +45,8 @@ export declare function labelMatches(heading: string, name: string): boolean;
|
|
|
34
45
|
* Letters with diacritics lose them; anything else becomes a separator.
|
|
35
46
|
*/
|
|
36
47
|
export declare function slugify(title: string, maxLength?: number): string;
|
|
48
|
+
/** `a`, `a and b`, `a, b and c`. */
|
|
49
|
+
export declare function inWords(items: readonly string[]): string;
|
|
37
50
|
/** Fills `{name}` holes. An unknown name is left as written. */
|
|
38
51
|
export declare function fillTemplate(template: string, values: Readonly<Record<string, string>>): string;
|
|
39
52
|
/** The `{name}` holes a template line refers to. */
|
package/dist/text.js
CHANGED
|
@@ -8,6 +8,17 @@ export function splitLines(text) {
|
|
|
8
8
|
lines.pop();
|
|
9
9
|
return lines;
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* A brief's lines as one text, for spec-core's readers.
|
|
13
|
+
*
|
|
14
|
+
* A brief's lines end at LF or CRLF, and a sealed brief's hash is taken over
|
|
15
|
+
* them. spec-core ends a line at a lone CR as well, as CommonMark does, so a
|
|
16
|
+
* lone CR is handed to it as a space: every line and column it reports is
|
|
17
|
+
* then one of the brief's own.
|
|
18
|
+
*/
|
|
19
|
+
export function textOfLines(lines) {
|
|
20
|
+
return lines.join('\n').replace(/\r/g, ' ');
|
|
21
|
+
}
|
|
11
22
|
/**
|
|
12
23
|
* The line ending a file already uses, so a rewrite does not flip it. Decided by
|
|
13
24
|
* the first terminator; a file with none is written with LF.
|
|
@@ -19,6 +30,14 @@ export function lineEnding(text) {
|
|
|
19
30
|
export function joinLines(lines, eol) {
|
|
20
31
|
return lines.length === 0 ? '' : `${lines.join(eol)}${eol}`;
|
|
21
32
|
}
|
|
33
|
+
/** Lines written back with the source's byte-order mark, line ending and final newline, or lack of one. */
|
|
34
|
+
export function encodeLike(source, lines) {
|
|
35
|
+
const bom = source.charCodeAt(0) === 0xfeff ? '\uFEFF' : '';
|
|
36
|
+
const eol = lineEnding(source);
|
|
37
|
+
const text = joinLines(lines, eol);
|
|
38
|
+
const finalNewline = source === '' || source.endsWith('\n');
|
|
39
|
+
return `${bom}${finalNewline ? text : text.slice(0, text.length - eol.length)}`;
|
|
40
|
+
}
|
|
22
41
|
export function stripBom(text) {
|
|
23
42
|
return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
24
43
|
}
|
|
@@ -84,6 +103,10 @@ export function slugify(title, maxLength = 80) {
|
|
|
84
103
|
const lastBreak = cut.lastIndexOf('-');
|
|
85
104
|
return lastBreak > 0 ? cut.slice(0, lastBreak) : cut;
|
|
86
105
|
}
|
|
106
|
+
/** `a`, `a and b`, `a, b and c`. */
|
|
107
|
+
export function inWords(items) {
|
|
108
|
+
return items.length < 2 ? (items[0] ?? '') : `${items.slice(0, -1).join(', ')} and ${items[items.length - 1]}`;
|
|
109
|
+
}
|
|
87
110
|
/** Fills `{name}` holes. An unknown name is left as written. */
|
|
88
111
|
export function fillTemplate(template, values) {
|
|
89
112
|
return template.replace(/\{([A-Za-z]+)\}/g, (hole, name) => values[name] ?? hole);
|
package/dist/text.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.js","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Line handling and the normalisations every other module agrees on.\n */\n\n/** Splits text into lines without their terminators. A final newline adds no line. */\nexport function splitLines(text: string): string[] {\n const lines = text.split(/\\r?\\n/);\n if (lines.length > 1 && lines[lines.length - 1] === '') lines.pop();\n return lines;\n}\n\n/**\n * The line ending a file already uses, so a rewrite does not flip it. Decided by\n * the first terminator; a file with none is written with LF.\n */\nexport function lineEnding(text: string): '\\n' | '\\r\\n' {\n return text.charAt(text.indexOf('\\n') - 1) === '\\r' ? '\\r\\n' : '\\n';\n}\n\n/** Joins lines with the given ending and a final newline. */\nexport function joinLines(lines: readonly string[], eol: '\\n' | '\\r\\n'): string {\n return lines.length === 0 ? '' : `${lines.join(eol)}${eol}`;\n}\n\nexport function stripBom(text: string): string {\n return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n}\n\n/** CRLF and a byte-order mark are storage details, not content. */\nexport function canonicalText(text: string): string {\n return stripBom(text).replace(/\\r\\n/g, '\\n');\n}\n\n/**\n * The form in which two section names are compared.\n *\n * Headings are typed by people and pasted from documents, so the comparison\n * forgives what does not change meaning: typographic apostrophes and quotes,\n * emphasis and code markers, a leading number, a trailing colon, case and runs\n * of whitespace. `## 2. Commander’s Intent:` and `Commander's Intent` compare\n * equal. Nothing else is folded: a different word is a different section.\n */\nexport function normaliseLabel(label: string): string {\n return label\n .normalize('NFKC')\n .replace(/[\\u2018\\u2019\\u201b\\u2032\\u00b4]/g, \"'\")\n .replace(/[\\u201c\\u201d\\u201f\\u2033]/g, '\"')\n .replace(/[*_`]/g, '')\n .replace(/^\\s*\\d+(?:\\.\\d+)*[.)]?\\s+/, '')\n .replace(/\\s*:\\s*$/, '')\n .replace(/\\s+/g, ' ')\n .trim()\n .toLowerCase();\n}\n\n/**\n * Whether a heading names a section: equal after normalisation, or the section\n * name followed by a separator and a qualifier, as in `Invariants (must hold)`\n * or `Deliverables — round two`.\n */\nexport function labelMatches(heading: string, name: string): boolean {\n const h = normaliseLabel(heading);\n const n = normaliseLabel(name);\n if (n.length === 0) return false;\n if (h === n) return true;\n if (!h.startsWith(n)) return false;\n const rest = h.slice(n.length);\n return /^(?:\\s*[:(\\u2014\\u2013]|\\s+-\\s)/.test(rest);\n}\n\n/**\n * A file-name slug: lower case, ASCII letters and digits, single hyphens.\n * Letters with diacritics lose them; anything else becomes a separator.\n */\nexport function slugify(title: string, maxLength = 80): string {\n const slug = title\n .normalize('NFKD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n if (slug.length <= maxLength) return slug;\n const cut = slug.slice(0, maxLength);\n // A cut that lands on a break keeps its last word whole.\n if (slug.charAt(maxLength) === '-') return cut;\n const lastBreak = cut.lastIndexOf('-');\n return lastBreak > 0 ? cut.slice(0, lastBreak) : cut;\n}\n\n/** Fills `{name}` holes. An unknown name is left as written. */\nexport function fillTemplate(template: string, values: Readonly<Record<string, string>>): string {\n return template.replace(/\\{([A-Za-z]+)\\}/g, (hole, name: string) => values[name] ?? hole);\n}\n\n/** The `{name}` holes a template line refers to. */\nexport function templateHoles(template: string): string[] {\n return [...template.matchAll(/\\{([A-Za-z]+)\\}/g)].map((match) => match[1] as string);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Line handling and the normalisations every other module agrees on.\n */\n\n/** Splits text into lines without their terminators. A final newline adds no line. */\nexport function splitLines(text: string): string[] {\n const lines = text.split(/\\r?\\n/);\n if (lines.length > 1 && lines[lines.length - 1] === '') lines.pop();\n return lines;\n}\n\n/**\n * A brief's lines as one text, for spec-core's readers.\n *\n * A brief's lines end at LF or CRLF, and a sealed brief's hash is taken over\n * them. spec-core ends a line at a lone CR as well, as CommonMark does, so a\n * lone CR is handed to it as a space: every line and column it reports is\n * then one of the brief's own.\n */\nexport function textOfLines(lines: readonly string[]): string {\n return lines.join('\\n').replace(/\\r/g, ' ');\n}\n\n/**\n * The line ending a file already uses, so a rewrite does not flip it. Decided by\n * the first terminator; a file with none is written with LF.\n */\nexport function lineEnding(text: string): '\\n' | '\\r\\n' {\n return text.charAt(text.indexOf('\\n') - 1) === '\\r' ? '\\r\\n' : '\\n';\n}\n\n/** Joins lines with the given ending and a final newline. */\nexport function joinLines(lines: readonly string[], eol: '\\n' | '\\r\\n'): string {\n return lines.length === 0 ? '' : `${lines.join(eol)}${eol}`;\n}\n\n/** Lines written back with the source's byte-order mark, line ending and final newline, or lack of one. */\nexport function encodeLike(source: string, lines: readonly string[]): string {\n const bom = source.charCodeAt(0) === 0xfeff ? '\\uFEFF' : '';\n const eol = lineEnding(source);\n const text = joinLines(lines, eol);\n const finalNewline = source === '' || source.endsWith('\\n');\n return `${bom}${finalNewline ? text : text.slice(0, text.length - eol.length)}`;\n}\n\nexport function stripBom(text: string): string {\n return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n}\n\n/** CRLF and a byte-order mark are storage details, not content. */\nexport function canonicalText(text: string): string {\n return stripBom(text).replace(/\\r\\n/g, '\\n');\n}\n\n/**\n * The form in which two section names are compared.\n *\n * Headings are typed by people and pasted from documents, so the comparison\n * forgives what does not change meaning: typographic apostrophes and quotes,\n * emphasis and code markers, a leading number, a trailing colon, case and runs\n * of whitespace. `## 2. Commander’s Intent:` and `Commander's Intent` compare\n * equal. Nothing else is folded: a different word is a different section.\n */\nexport function normaliseLabel(label: string): string {\n return label\n .normalize('NFKC')\n .replace(/[\\u2018\\u2019\\u201b\\u2032\\u00b4]/g, \"'\")\n .replace(/[\\u201c\\u201d\\u201f\\u2033]/g, '\"')\n .replace(/[*_`]/g, '')\n .replace(/^\\s*\\d+(?:\\.\\d+)*[.)]?\\s+/, '')\n .replace(/\\s*:\\s*$/, '')\n .replace(/\\s+/g, ' ')\n .trim()\n .toLowerCase();\n}\n\n/**\n * Whether a heading names a section: equal after normalisation, or the section\n * name followed by a separator and a qualifier, as in `Invariants (must hold)`\n * or `Deliverables — round two`.\n */\nexport function labelMatches(heading: string, name: string): boolean {\n const h = normaliseLabel(heading);\n const n = normaliseLabel(name);\n if (n.length === 0) return false;\n if (h === n) return true;\n if (!h.startsWith(n)) return false;\n const rest = h.slice(n.length);\n return /^(?:\\s*[:(\\u2014\\u2013]|\\s+-\\s)/.test(rest);\n}\n\n/**\n * A file-name slug: lower case, ASCII letters and digits, single hyphens.\n * Letters with diacritics lose them; anything else becomes a separator.\n */\nexport function slugify(title: string, maxLength = 80): string {\n const slug = title\n .normalize('NFKD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n if (slug.length <= maxLength) return slug;\n const cut = slug.slice(0, maxLength);\n // A cut that lands on a break keeps its last word whole.\n if (slug.charAt(maxLength) === '-') return cut;\n const lastBreak = cut.lastIndexOf('-');\n return lastBreak > 0 ? cut.slice(0, lastBreak) : cut;\n}\n\n/** `a`, `a and b`, `a, b and c`. */\nexport function inWords(items: readonly string[]): string {\n return items.length < 2 ? (items[0] ?? '') : `${items.slice(0, -1).join(', ')} and ${items[items.length - 1] as string}`;\n}\n\n/** Fills `{name}` holes. An unknown name is left as written. */\nexport function fillTemplate(template: string, values: Readonly<Record<string, string>>): string {\n return template.replace(/\\{([A-Za-z]+)\\}/g, (hole, name: string) => values[name] ?? hole);\n}\n\n/** The `{name}` holes a template line refers to. */\nexport function templateHoles(template: string): string[] {\n return [...template.matchAll(/\\{([A-Za-z]+)\\}/g)].map((match) => match[1] as string);\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a deferral's trigger names an event or only a time.
|
|
3
|
+
*
|
|
4
|
+
* Deferred work comes back when something happens: "when the second tenant
|
|
5
|
+
* signs", "when p95 exceeds 200 ms". A date is not that. It passes whether or
|
|
6
|
+
* not the reason for the work has arrived, and a deferral that fires on a date
|
|
7
|
+
* is a reminder to look again, which the deferral itself already is. So a
|
|
8
|
+
* trigger made only of words that say when - a date, a quarter, a month or a
|
|
9
|
+
* weekday, a stretch of time and the small words that join them - is refused,
|
|
10
|
+
* and one word that says what is enough to pass.
|
|
11
|
+
*
|
|
12
|
+
* The lists are deliberately short. A trigger this reads as an event when it
|
|
13
|
+
* is a time is a miss, and a miss costs less here than refusing a real event
|
|
14
|
+
* for looking like a date.
|
|
15
|
+
*/
|
|
16
|
+
/** Whether a trigger has a word that says what must happen, rather than only when. */
|
|
17
|
+
export declare function namesAnEvent(trigger: string): boolean;
|
package/dist/trigger.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a deferral's trigger names an event or only a time.
|
|
3
|
+
*
|
|
4
|
+
* Deferred work comes back when something happens: "when the second tenant
|
|
5
|
+
* signs", "when p95 exceeds 200 ms". A date is not that. It passes whether or
|
|
6
|
+
* not the reason for the work has arrived, and a deferral that fires on a date
|
|
7
|
+
* is a reminder to look again, which the deferral itself already is. So a
|
|
8
|
+
* trigger made only of words that say when - a date, a quarter, a month or a
|
|
9
|
+
* weekday, a stretch of time and the small words that join them - is refused,
|
|
10
|
+
* and one word that says what is enough to pass.
|
|
11
|
+
*
|
|
12
|
+
* The lists are deliberately short. A trigger this reads as an event when it
|
|
13
|
+
* is a time is a miss, and a miss costs less here than refusing a real event
|
|
14
|
+
* for looking like a date.
|
|
15
|
+
*/
|
|
16
|
+
const MONTHS = ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'];
|
|
17
|
+
const WEEKDAYS = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
|
|
18
|
+
/** Words that say when and nothing else. */
|
|
19
|
+
const WHEN = new Set([
|
|
20
|
+
...MONTHS,
|
|
21
|
+
...MONTHS.map((m) => m.slice(0, 3)),
|
|
22
|
+
'sept',
|
|
23
|
+
...WEEKDAYS,
|
|
24
|
+
...WEEKDAYS.map((d) => d.slice(0, 3)),
|
|
25
|
+
// A stretch of time.
|
|
26
|
+
...['day', 'week', 'weekend', 'month', 'quarter', 'year', 'sprint'].flatMap((unit) => [unit, `${unit}s`]),
|
|
27
|
+
'today',
|
|
28
|
+
'tomorrow',
|
|
29
|
+
'soon',
|
|
30
|
+
'later',
|
|
31
|
+
// How many of them.
|
|
32
|
+
...['a', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'few', 'couple'],
|
|
33
|
+
// The words that join them.
|
|
34
|
+
...['in', 'on', 'by', 'at', 'after', 'before', 'until', 'from', 'of', 'the', 'next', 'this', 'end', 'start', 'early', 'mid', 'late'],
|
|
35
|
+
]);
|
|
36
|
+
/**
|
|
37
|
+
* A number or an ordinal - `2026`, `15`, `15th` - or a quarter, `Q3`. A date is
|
|
38
|
+
* its numbers: `2026-10-01` and `10/01/2026` are read a number at a time.
|
|
39
|
+
*/
|
|
40
|
+
const DATE = /^(?:q[1-4]|\d+(?:st|nd|rd|th)?)$/;
|
|
41
|
+
/** The runs of letters and digits, in lower case: `mid-2027` is two words, and punctuation none. */
|
|
42
|
+
function words(text) {
|
|
43
|
+
return text.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
|
|
44
|
+
}
|
|
45
|
+
/** Whether a trigger has a word that says what must happen, rather than only when. */
|
|
46
|
+
export function namesAnEvent(trigger) {
|
|
47
|
+
return words(trigger).some((word) => !WHEN.has(word) && !DATE.test(word));
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=trigger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trigger.js","sourceRoot":"","sources":["../src/trigger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,MAAM,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;AAC1I,MAAM,QAAQ,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;AAEhG,4CAA4C;AAC5C,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC;IACnB,GAAG,MAAM;IACT,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACnC,MAAM;IACN,GAAG,QAAQ;IACX,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACrC,qBAAqB;IACrB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,GAAG,IAAI,GAAG,CAAC,CAAC;IACzG,OAAO;IACP,UAAU;IACV,MAAM;IACN,OAAO;IACP,oBAAoB;IACpB,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,CAAC;IAC5H,4BAA4B;IAC5B,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC;CACrI,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,IAAI,GAAG,kCAAkC,CAAC;AAEhD,oGAAoG;AACpG,SAAS,KAAK,CAAC,IAAY;IACzB,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC;AAC3D,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAC5E,CAAC","sourcesContent":["/**\n * Whether a deferral's trigger names an event or only a time.\n *\n * Deferred work comes back when something happens: \"when the second tenant\n * signs\", \"when p95 exceeds 200 ms\". A date is not that. It passes whether or\n * not the reason for the work has arrived, and a deferral that fires on a date\n * is a reminder to look again, which the deferral itself already is. So a\n * trigger made only of words that say when - a date, a quarter, a month or a\n * weekday, a stretch of time and the small words that join them - is refused,\n * and one word that says what is enough to pass.\n *\n * The lists are deliberately short. A trigger this reads as an event when it\n * is a time is a miss, and a miss costs less here than refusing a real event\n * for looking like a date.\n */\n\nconst MONTHS = ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'];\nconst WEEKDAYS = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];\n\n/** Words that say when and nothing else. */\nconst WHEN = new Set([\n ...MONTHS,\n ...MONTHS.map((m) => m.slice(0, 3)),\n 'sept',\n ...WEEKDAYS,\n ...WEEKDAYS.map((d) => d.slice(0, 3)),\n // A stretch of time.\n ...['day', 'week', 'weekend', 'month', 'quarter', 'year', 'sprint'].flatMap((unit) => [unit, `${unit}s`]),\n 'today',\n 'tomorrow',\n 'soon',\n 'later',\n // How many of them.\n ...['a', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'few', 'couple'],\n // The words that join them.\n ...['in', 'on', 'by', 'at', 'after', 'before', 'until', 'from', 'of', 'the', 'next', 'this', 'end', 'start', 'early', 'mid', 'late'],\n]);\n\n/**\n * A number or an ordinal - `2026`, `15`, `15th` - or a quarter, `Q3`. A date is\n * its numbers: `2026-10-01` and `10/01/2026` are read a number at a time.\n */\nconst DATE = /^(?:q[1-4]|\\d+(?:st|nd|rd|th)?)$/;\n\n/** The runs of letters and digits, in lower case: `mid-2027` is two words, and punctuation none. */\nfunction words(text: string): readonly string[] {\n return text.toLowerCase().match(/[\\p{L}\\p{N}]+/gu) ?? [];\n}\n\n/** Whether a trigger has a word that says what must happen, rather than only when. */\nexport function namesAnEvent(trigger: string): boolean {\n return words(trigger).some((word) => !WHEN.has(word) && !DATE.test(word));\n}\n"]}
|
package/dist/types.d.ts
CHANGED
|
@@ -7,12 +7,14 @@ export type SeveritySetting = Severity | 'off';
|
|
|
7
7
|
/** Where a brief lives. Location is the authority on whether a brief is archived. */
|
|
8
8
|
export type Phase = 'live' | 'archived';
|
|
9
9
|
/**
|
|
10
|
-
* The lifecycle a brief moves through. `draft`
|
|
11
|
-
* never writes one maps no word to it.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* The lifecycle a brief moves through. `draft` and `deferred` are optional: a
|
|
11
|
+
* repository that never writes one maps no word to it. A deferred brief waits
|
|
12
|
+
* in the live directory for an event its `trigger` names, and runs in no wave
|
|
13
|
+
* until then. There is no persisted "archiving" state; archival is a
|
|
14
|
+
* transaction, and a transaction that stops half-way is rolled back rather
|
|
15
|
+
* than recorded.
|
|
14
16
|
*/
|
|
15
|
-
export type Status = 'draft' | 'active' | 'archived';
|
|
17
|
+
export type Status = 'draft' | 'active' | 'deferred' | 'archived';
|
|
16
18
|
/**
|
|
17
19
|
* One problem, located. Lines are 1-based; a finding about a file as a whole
|
|
18
20
|
* points at line 1.
|
|
@@ -27,4 +29,11 @@ export interface Finding {
|
|
|
27
29
|
readonly brief?: string | undefined;
|
|
28
30
|
/** The concrete next action, where there is one. */
|
|
29
31
|
readonly hint?: string | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* The one repository path a finding is about, where it is about one: a
|
|
34
|
+
* protected file the round changed is one finding per file, and names it.
|
|
35
|
+
*/
|
|
36
|
+
readonly path?: string | undefined;
|
|
37
|
+
/** The paths of a finding about several, such as the files a round changed outside its scope. */
|
|
38
|
+
readonly paths?: readonly string[] | undefined;
|
|
30
39
|
}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG","sourcesContent":["/**\n * Types shared across the pipeline. Type-only: this module compiles to nothing.\n */\n\nexport type Severity = 'error' | 'warning' | 'note';\n\n/** A severity as configuration spells it, where `off` silences a rule. */\nexport type SeveritySetting = Severity | 'off';\n\n/** Where a brief lives. Location is the authority on whether a brief is archived. */\nexport type Phase = 'live' | 'archived';\n\n/**\n * The lifecycle a brief moves through. `draft`
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG","sourcesContent":["/**\n * Types shared across the pipeline. Type-only: this module compiles to nothing.\n */\n\nexport type Severity = 'error' | 'warning' | 'note';\n\n/** A severity as configuration spells it, where `off` silences a rule. */\nexport type SeveritySetting = Severity | 'off';\n\n/** Where a brief lives. Location is the authority on whether a brief is archived. */\nexport type Phase = 'live' | 'archived';\n\n/**\n * The lifecycle a brief moves through. `draft` and `deferred` are optional: a\n * repository that never writes one maps no word to it. A deferred brief waits\n * in the live directory for an event its `trigger` names, and runs in no wave\n * until then. There is no persisted \"archiving\" state; archival is a\n * transaction, and a transaction that stops half-way is rolled back rather\n * than recorded.\n */\nexport type Status = 'draft' | 'active' | 'deferred' | 'archived';\n\n/**\n * One problem, located. Lines are 1-based; a finding about a file as a whole\n * points at line 1.\n */\nexport interface Finding {\n readonly rule: string;\n readonly severity: Severity;\n readonly message: string;\n /** Repository-relative POSIX path. */\n readonly file: string;\n readonly line: number;\n readonly brief?: string | undefined;\n /** The concrete next action, where there is one. */\n readonly hint?: string | undefined;\n /**\n * The one repository path a finding is about, where it is about one: a\n * protected file the round changed is one finding per file, and names it.\n */\n readonly path?: string | undefined;\n /** The paths of a finding about several, such as the files a round changed outside its scope. */\n readonly paths?: readonly string[] | undefined;\n}\n"]}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A reader and editor for the front matter the spec-* tools use: a flat bag
|
|
3
|
+
* of YAML scalars and lists, and optionally one level of nested mapping.
|
|
4
|
+
*
|
|
5
|
+
* Supported: `key: value` lines; plain, single-quoted and double-quoted
|
|
6
|
+
* scalars; inline `[a, b]` sequences; block `- item` sequences; `#` comments.
|
|
7
|
+
* Plain scalars follow the YAML 1.2 core schema, so `yes` is a word and `035`
|
|
8
|
+
* keeps its leading zero for whoever reads it as text. With `nested`, a key
|
|
9
|
+
* whose value is an indented block of keys is read as `parent.child` entries.
|
|
10
|
+
*
|
|
11
|
+
* Anything richer - deeper nesting, block scalars, anchors, tags, a value
|
|
12
|
+
* continued onto the next line - is recognised and reported as unsupported
|
|
13
|
+
* rather than guessed at. A YAML library would parse more, but it would not
|
|
14
|
+
* give back the line and the offset of every value, and a report has to point
|
|
15
|
+
* at the value it is about, and an edit has to leave every other line of the
|
|
16
|
+
* file as it was.
|
|
17
|
+
*/
|
|
18
|
+
import type { FrontMatterKind } from './types.js';
|
|
19
|
+
export interface YamlScalar {
|
|
20
|
+
readonly text: string;
|
|
21
|
+
readonly quoted: boolean;
|
|
22
|
+
}
|
|
23
|
+
export type YamlValue = {
|
|
24
|
+
readonly kind: 'scalar';
|
|
25
|
+
readonly scalar: YamlScalar;
|
|
26
|
+
} | {
|
|
27
|
+
readonly kind: 'list';
|
|
28
|
+
readonly items: readonly YamlScalar[];
|
|
29
|
+
} | {
|
|
30
|
+
readonly kind: 'unsupported';
|
|
31
|
+
readonly reason: string;
|
|
32
|
+
};
|
|
33
|
+
export interface FrontMatterEntry {
|
|
34
|
+
/** The key as written; a nested key is its parent's and its own, joined by a dot. */
|
|
35
|
+
readonly key: string;
|
|
36
|
+
/** The key compared: {@link keyName} of `key`, so `depends-on` is `dependsOn`. */
|
|
37
|
+
readonly name: string;
|
|
38
|
+
/** The parent key as written, for a nested key; `null` at the top level. */
|
|
39
|
+
readonly parent: string | null;
|
|
40
|
+
/** 0-based line of the key. */
|
|
41
|
+
readonly line: number;
|
|
42
|
+
/** 0-based line after the entry's last line. */
|
|
43
|
+
readonly end: number;
|
|
44
|
+
/** Offset of the key in the text read. */
|
|
45
|
+
readonly keyStart: number;
|
|
46
|
+
/**
|
|
47
|
+
* Offsets of the value as written: a quoted scalar with its quotes, an
|
|
48
|
+
* inline list with its brackets, a block list from its first item's text to
|
|
49
|
+
* its last item's. An empty value is the empty range just past the colon.
|
|
50
|
+
*/
|
|
51
|
+
readonly valueStart: number;
|
|
52
|
+
readonly valueEnd: number;
|
|
53
|
+
readonly value: YamlValue;
|
|
54
|
+
}
|
|
55
|
+
export interface FrontMatterProblem {
|
|
56
|
+
/** 0-based line. */
|
|
57
|
+
readonly line: number;
|
|
58
|
+
readonly message: string;
|
|
59
|
+
}
|
|
60
|
+
export interface FrontMatter {
|
|
61
|
+
readonly kind: FrontMatterKind;
|
|
62
|
+
/** 0-based line of the closing delimiter, or -1 when the block is never closed. */
|
|
63
|
+
readonly close: number;
|
|
64
|
+
readonly entries: readonly FrontMatterEntry[];
|
|
65
|
+
readonly problems: readonly FrontMatterProblem[];
|
|
66
|
+
}
|
|
67
|
+
export interface ReadOptions {
|
|
68
|
+
/**
|
|
69
|
+
* Read one level of nested mapping as `parent.child` entries, instead of
|
|
70
|
+
* reporting the parent's value as unsupported.
|
|
71
|
+
*/
|
|
72
|
+
readonly nested?: boolean;
|
|
73
|
+
}
|
|
74
|
+
/** The kind of front matter a document's first line opens, or `null` when it opens none. */
|
|
75
|
+
export declare function frontMatterKind(firstLine: string): FrontMatterKind | null;
|
|
76
|
+
/** Whether a line closes front matter of a kind: `---` or `...` for YAML, `+++` for TOML. */
|
|
77
|
+
export declare function frontMatterCloses(line: string, kind: FrontMatterKind): boolean;
|
|
78
|
+
export declare function keyName(key: string): string;
|
|
79
|
+
/**
|
|
80
|
+
* Reads the front matter at the top of a text, or `null` when there is none.
|
|
81
|
+
*
|
|
82
|
+
* A leading byte-order mark is not part of the text read: offsets count from
|
|
83
|
+
* the character after it, as a scan's do.
|
|
84
|
+
*/
|
|
85
|
+
export declare function readFrontMatter(source: string, options?: ReadOptions): FrontMatter | null;
|
|
86
|
+
/** Parses a value written on the key's own line. `text` is trimmed and non-empty. */
|
|
87
|
+
export declare function parseInline(text: string): YamlValue;
|
|
88
|
+
/** Null in the YAML 1.2 core schema: empty, `~` or `null` in any of its three spellings. */
|
|
89
|
+
export declare function isNull(scalar: YamlScalar): boolean;
|
|
90
|
+
/** Renders a string as a scalar that reads back as the same string. */
|
|
91
|
+
export declare function renderScalar(value: string): string;
|
|
92
|
+
export declare function findEntry(frontMatter: FrontMatter | null, key: string): FrontMatterEntry | undefined;
|
|
93
|
+
/**
|
|
94
|
+
* Sets one key, leaving every other line as it was.
|
|
95
|
+
*
|
|
96
|
+
* `lines` are the lines of the text the front matter was read from, as
|
|
97
|
+
* `splitLines` gives them. An existing entry keeps the spelling and the
|
|
98
|
+
* indentation of its key; a new one goes last, at the top level. A text with
|
|
99
|
+
* no front matter gains a block.
|
|
100
|
+
*/
|
|
101
|
+
export declare function setEntry(lines: readonly string[], frontMatter: FrontMatter | null, key: string, rendered: string): string[];
|
|
102
|
+
/** Removes one key and its value lines; a key that is absent changes nothing. */
|
|
103
|
+
export declare function removeEntry(lines: readonly string[], frontMatter: FrontMatter | null, key: string): string[];
|