@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/scaffold.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Writing a new brief: the next free id, a file name, and a body with every\n * section the configuration requires, each holding a hint in a comment.\n * Comments are not content, so a freshly scaffolded brief reads as unwritten\n * until somebody writes it.\n */\n\nimport { type Config, DEFAULT_CONFIG, type SectionRule } from './config.js';\nimport type { Corpus } from './corpus.js';\nimport { renderScalar } from './frontmatter.js';\nimport { fillTemplate, slugify } from './text.js';\n\nexport interface NewBrief {\n readonly id: string;\n readonly title: string;\n readonly date: string;\n readonly type?: string | undefined;\n readonly wave?: number | undefined;\n readonly dependsOn?: readonly string[] | undefined;\n}\n\n/**\n * The next id: one more than the highest numeric id, live or archived, at the\n * configured width. Ids are never reused, so an archived brief still holds\n * its number. `null` when the corpus has ids but none of them are numbers.\n */\nexport function nextId(corpus: Corpus): string | null {\n const ids = corpus.briefs.map((b) => b.id).filter((id): id is string => id !== null);\n const numeric = ids.filter((id) => /^\\d+$/.test(id)).map(Number);\n if (numeric.length === 0 && ids.length > 0) return null;\n const next = numeric.length === 0 ? 1 : Math.max(...numeric) + 1;\n return String(next).padStart(corpus.config.id.digits, '0');\n}\n\nexport function fileNameFor(config: Config, id: string, title: string): string {\n const slug = slugify(title);\n if (config.id.source === 'frontmatter') return `${slug === '' ? id : slug}.md`;\n return slug === '' ? `${id}.md` : `${id}${config.id.separator}${slug}.md`;\n}\n\nconst DEFAULT_RULES: readonly SectionRule[] = [...DEFAULT_CONFIG.sections, ...Object.values(DEFAULT_CONFIG.types).flat()];\n\n/**\n * What a new brief says under a heading: the section's configured hint, or the\n * default section's of the same name - a configuration written before hints\n * existed names its sections without one - or a plain instruction. A \"-->\"\n * would end the comment early and leave the rest as content, so it is broken.\n */\nfunction hint(rule: SectionRule): string {\n const text = rule.hint ?? DEFAULT_RULES.find((d) => d.name.toLowerCase() === rule.name.toLowerCase())?.hint ?? `Write the ${rule.name}.`;\n return text.replaceAll('-->', '-- >');\n}\n\nfunction frontMatter(config: Config, brief: NewBrief): string[] {\n const lines = ['---'];\n if (config.id.source === 'frontmatter') lines.push(`id: ${renderScalar(brief.id)}`);\n if (config.status.field !== null) {\n lines.push(`${config.status.field}: ${renderScalar(config.status.draft ?? config.status.active)}`);\n }\n lines.push(`date: ${brief.date}`);\n if (brief.type !== undefined) lines.push(`type: ${renderScalar(brief.type)}`);\n if (brief.wave !== undefined) lines.push(`wave: ${brief.wave}`);\n if (brief.dependsOn !== undefined && brief.dependsOn.length > 0) {\n lines.push(`dependsOn: [${brief.dependsOn.map((d) => JSON.stringify(d)).join(', ')}]`);\n }\n lines.push('affectedFiles: []', 'protectedFiles: []', '---');\n return lines;\n}\n\n/** The text of a new brief, from the configured template or from the section list. */\nexport function renderNewBrief(config: Config, brief: NewBrief, template: string | null): string {\n if (template !== null) {\n return fillTemplate(template, {\n id: brief.id,\n title: brief.title,\n date: brief.date,\n type: brief.type ?? '',\n wave: brief.wave === undefined ? '' : String(brief.wave),\n status: config.status.draft ?? config.status.active,\n });\n }\n const typed = brief.type === undefined ? [] : (config.types[brief.type] ?? []);\n const lines = [...frontMatter(config, brief), '', `# ${brief.id} \\u2014 ${brief.title}`];\n for (const rule of [...config.sections, ...typed]) {\n lines.push('', `## ${rule.name}`, '', `<!-- ${hint(rule)} -->`);\n // Text a section must contain is written in, so it is not forgotten.\n if (rule.mustContain.length > 0) lines.push('', ...rule.mustContain.map((text) => `- ${text}`));\n if (rule.checklist) lines.push('', '- [ ] <!-- one check -->');\n }\n return `${lines.join('\\n')}\\n`;\n}\n"]}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Waves, computed: which live briefs can run side by side, so that no two in
|
|
3
|
+
* one wave can write the same file and every brief runs after the briefs it
|
|
4
|
+
* depends on.
|
|
5
|
+
*
|
|
6
|
+
* Deciding what the rounds of a goal are is prose, and an agent's work. Given
|
|
7
|
+
* the rounds, placing them is not: it is precedence-constrained colouring, and
|
|
8
|
+
* a tool answers it the same way every time (ADR-0011). Briefs are taken in
|
|
9
|
+
* dependency order, ties broken by id, and each goes into the lowest wave that
|
|
10
|
+
* is after every live dependency's and holds nothing it collides with. A brief
|
|
11
|
+
* with no scope cannot be proved apart from anything, so it shares its wave
|
|
12
|
+
* with nobody. The greedy answer is not always the fewest waves - no fast
|
|
13
|
+
* method promises that - but it is always a valid one, and the same one.
|
|
14
|
+
*
|
|
15
|
+
* Nothing is written here. `planWaves` says what `--write` would write, and
|
|
16
|
+
* the engine writes it as the archive writes, as one transaction.
|
|
17
|
+
*/
|
|
18
|
+
import type { FileOp } from './archive.js';
|
|
19
|
+
import { type Brief } from './brief.js';
|
|
20
|
+
import { type Corpus } from './corpus.js';
|
|
21
|
+
import { type Overlap } from './scope.js';
|
|
22
|
+
import type { Finding } from './types.js';
|
|
23
|
+
export interface ScheduleOptions {
|
|
24
|
+
/** The files the tree holds, which say whether a literal path is a file or a directory. */
|
|
25
|
+
readonly repoFiles?: readonly string[] | null;
|
|
26
|
+
/** How many states one witness search may visit before it answers `undecided`. */
|
|
27
|
+
readonly budget?: number;
|
|
28
|
+
}
|
|
29
|
+
/** A wave a brief could not join, and the brief already there that kept it out. */
|
|
30
|
+
export type Passed = {
|
|
31
|
+
readonly wave: number;
|
|
32
|
+
readonly reason: 'collision';
|
|
33
|
+
readonly brief: Brief;
|
|
34
|
+
readonly overlaps: readonly Overlap[];
|
|
35
|
+
} | {
|
|
36
|
+
readonly wave: number;
|
|
37
|
+
readonly reason: 'undecided';
|
|
38
|
+
readonly brief: Brief;
|
|
39
|
+
readonly patterns: readonly (readonly [string, string])[];
|
|
40
|
+
}
|
|
41
|
+
/** One of the two declares no affectedFiles, and an unscoped brief runs alone. */
|
|
42
|
+
| {
|
|
43
|
+
readonly wave: number;
|
|
44
|
+
readonly reason: 'unscoped';
|
|
45
|
+
readonly brief: Brief;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Why a declared wave does not hold: what `matrix` or `lint` would report as
|
|
49
|
+
* an error if the brief ran there.
|
|
50
|
+
*/
|
|
51
|
+
export type Breach =
|
|
52
|
+
/** A brief the matrix compares, declaring the same wave, whose scope meets this one's. Overlaps name this brief's pattern first. */
|
|
53
|
+
{
|
|
54
|
+
readonly reason: 'collision';
|
|
55
|
+
readonly brief: Brief;
|
|
56
|
+
readonly overlaps: readonly Overlap[];
|
|
57
|
+
}
|
|
58
|
+
/** A live dependency declaring the same wave or a later one. */
|
|
59
|
+
| {
|
|
60
|
+
readonly reason: 'dependency';
|
|
61
|
+
readonly brief: Brief;
|
|
62
|
+
readonly wave: number;
|
|
63
|
+
}
|
|
64
|
+
/** A live brief depending on this one, declaring the same wave or an earlier one. */
|
|
65
|
+
| {
|
|
66
|
+
readonly reason: 'dependent';
|
|
67
|
+
readonly brief: Brief;
|
|
68
|
+
readonly wave: number;
|
|
69
|
+
};
|
|
70
|
+
export interface Placement {
|
|
71
|
+
readonly brief: Brief;
|
|
72
|
+
/** The wave its front matter declares; `null` when it declares none it can read. */
|
|
73
|
+
readonly declared: number | null;
|
|
74
|
+
readonly proposed: number;
|
|
75
|
+
/** The live dependency in the latest wave, which sets the earliest wave this one may run in. */
|
|
76
|
+
readonly after: {
|
|
77
|
+
readonly brief: Brief;
|
|
78
|
+
readonly wave: number;
|
|
79
|
+
} | null;
|
|
80
|
+
/** Every wave from that earliest one that it could not join, in order. Overlaps and patterns name its own pattern first. */
|
|
81
|
+
readonly passed: readonly Passed[];
|
|
82
|
+
/** Declares no affectedFiles, so it shares its wave with nobody. */
|
|
83
|
+
readonly unscoped: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Why the declared wave does not hold, when it is not the proposed one. Empty
|
|
86
|
+
* when it holds - a person moved the brief, and `lint` and `matrix` accept
|
|
87
|
+
* the wave - and when there is no declared wave to judge.
|
|
88
|
+
*/
|
|
89
|
+
readonly breaches: readonly Breach[];
|
|
90
|
+
}
|
|
91
|
+
/** A live brief that can be placed in no wave, and the dependency that holds it. */
|
|
92
|
+
export interface Unplaced {
|
|
93
|
+
readonly brief: Brief;
|
|
94
|
+
readonly waitsOn: Brief;
|
|
95
|
+
/** The dependency is deferred, is in a dependency cycle, or is itself unplaced. */
|
|
96
|
+
readonly because: 'deferred' | 'cycle' | 'unplaced';
|
|
97
|
+
}
|
|
98
|
+
export interface Schedule {
|
|
99
|
+
/** The first wave: the lowest any scheduled brief declares, or 1 when none declares one. */
|
|
100
|
+
readonly first: number;
|
|
101
|
+
/** In the order they were placed: dependency order, ties by id. */
|
|
102
|
+
readonly placements: readonly Placement[];
|
|
103
|
+
/** By id. */
|
|
104
|
+
readonly unplaced: readonly Unplaced[];
|
|
105
|
+
/** Dependency cycles among the scheduled briefs, each as a path back to its start. */
|
|
106
|
+
readonly cycles: readonly (readonly Brief[])[];
|
|
107
|
+
/** Deferred briefs, which wait for their trigger and run in no wave. By id. */
|
|
108
|
+
readonly deferred: readonly Brief[];
|
|
109
|
+
}
|
|
110
|
+
export { byId } from './corpus.js';
|
|
111
|
+
export declare function schedule(corpus: Corpus, options?: ScheduleOptions): Schedule;
|
|
112
|
+
/** The briefs whose declared wave the schedule would change. */
|
|
113
|
+
export declare function moves(s: Schedule): Placement[];
|
|
114
|
+
/**
|
|
115
|
+
* Whether a brief's declared wave holds: it is the proposed one, or one a
|
|
116
|
+
* person chose that `lint` and `matrix` accept. The schedule is one valid
|
|
117
|
+
* answer, not the only one (ADR-0011, amended).
|
|
118
|
+
*/
|
|
119
|
+
export declare function holds(p: Placement): boolean;
|
|
120
|
+
/** Why a declared wave does not hold, a clause each. */
|
|
121
|
+
export declare function breachReasons(p: Placement): string[];
|
|
122
|
+
/** Why a brief is in its wave and not an earlier one, a clause each. */
|
|
123
|
+
export declare function reasons(p: Placement): string[];
|
|
124
|
+
/** The schedule as findings: cycles, briefs whose waves would move, briefs that run alone, and pairs it could not decide. */
|
|
125
|
+
export declare function scheduleFindings(corpus: Corpus, s: Schedule): Finding[];
|
|
126
|
+
export interface WavePlan {
|
|
127
|
+
/** A write of each brief whose wave changes: its front matter's `wave`, and every other line as it was. */
|
|
128
|
+
readonly ops: readonly FileOp[];
|
|
129
|
+
/** Briefs whose wave cannot be written, which refuse the whole write. */
|
|
130
|
+
readonly refused: readonly Finding[];
|
|
131
|
+
}
|
|
132
|
+
/** What `--write` writes. */
|
|
133
|
+
export declare function planWaves(s: Schedule): WavePlan;
|
package/dist/schedule.js
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Waves, computed: which live briefs can run side by side, so that no two in
|
|
3
|
+
* one wave can write the same file and every brief runs after the briefs it
|
|
4
|
+
* depends on.
|
|
5
|
+
*
|
|
6
|
+
* Deciding what the rounds of a goal are is prose, and an agent's work. Given
|
|
7
|
+
* the rounds, placing them is not: it is precedence-constrained colouring, and
|
|
8
|
+
* a tool answers it the same way every time (ADR-0011). Briefs are taken in
|
|
9
|
+
* dependency order, ties broken by id, and each goes into the lowest wave that
|
|
10
|
+
* is after every live dependency's and holds nothing it collides with. A brief
|
|
11
|
+
* with no scope cannot be proved apart from anything, so it shares its wave
|
|
12
|
+
* with nobody. The greedy answer is not always the fewest waves - no fast
|
|
13
|
+
* method promises that - but it is always a valid one, and the same one.
|
|
14
|
+
*
|
|
15
|
+
* Nothing is written here. `planWaves` says what `--write` would write, and
|
|
16
|
+
* the engine writes it as the archive writes, as one transaction.
|
|
17
|
+
*/
|
|
18
|
+
import { lineOfField } from './brief.js';
|
|
19
|
+
import { undecidedFinding, unscopedFinding } from './collisions.js';
|
|
20
|
+
import { byId, dependencyCycles, liveDependencies, waitingOnDeferred } from './corpus.js';
|
|
21
|
+
import { editable, setEntryKeepingComment } from './frontmatter.js';
|
|
22
|
+
import { readingIn, WITNESS_BUDGET } from './glob.js';
|
|
23
|
+
import { configuredSeverity } from './lint.js';
|
|
24
|
+
import { meet, scopeOf } from './scope.js';
|
|
25
|
+
import { encodeLike } from './text.js';
|
|
26
|
+
function label(brief) {
|
|
27
|
+
return brief.id ?? brief.name;
|
|
28
|
+
}
|
|
29
|
+
export { byId } from './corpus.js';
|
|
30
|
+
/** The first brief in a wave this one cannot share it with, or `null` when it can join. */
|
|
31
|
+
function clash(own, wave, here, budget) {
|
|
32
|
+
for (const other of here) {
|
|
33
|
+
if (own.affected.length === 0 || other.affected.length === 0)
|
|
34
|
+
return { wave, reason: 'unscoped', brief: other.brief };
|
|
35
|
+
const meeting = meet(own, other, budget);
|
|
36
|
+
if (meeting.overlaps.length > 0)
|
|
37
|
+
return { wave, reason: 'collision', brief: other.brief, overlaps: meeting.overlaps };
|
|
38
|
+
if (meeting.undecided.length > 0)
|
|
39
|
+
return { wave, reason: 'undecided', brief: other.brief, patterns: meeting.undecided };
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Why a brief's declared wave does not hold, judged as `lint` and `matrix`
|
|
45
|
+
* judge it: against the waves the other briefs declare, not the ones proposed.
|
|
46
|
+
* Collisions are with the briefs the matrix compares; the dependency order is
|
|
47
|
+
* wave-order's, both ways, over every live brief.
|
|
48
|
+
*/
|
|
49
|
+
function breachesOf(brief, wave, judge) {
|
|
50
|
+
const out = [];
|
|
51
|
+
for (const dependency of judge.dependencies.get(brief)) {
|
|
52
|
+
if (dependency.wave !== null && dependency.wave >= wave)
|
|
53
|
+
out.push({ reason: 'dependency', brief: dependency, wave: dependency.wave });
|
|
54
|
+
}
|
|
55
|
+
for (const other of judge.dependents.get(brief) ?? []) {
|
|
56
|
+
if (other.wave !== null && other.wave <= wave)
|
|
57
|
+
out.push({ reason: 'dependent', brief: other, wave: other.wave });
|
|
58
|
+
}
|
|
59
|
+
for (const other of judge.compared) {
|
|
60
|
+
if (other === brief || other.wave !== wave)
|
|
61
|
+
continue;
|
|
62
|
+
const overlaps = meet(judge.scope(brief), judge.scope(other), judge.budget).overlaps;
|
|
63
|
+
if (overlaps.length > 0)
|
|
64
|
+
out.push({ reason: 'collision', brief: other, overlaps });
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
export function schedule(corpus, options = {}) {
|
|
69
|
+
const reading = readingIn(options.repoFiles ?? null);
|
|
70
|
+
const budget = options.budget ?? WITNESS_BUDGET;
|
|
71
|
+
const deferred = corpus.live.filter((b) => b.status === 'deferred').sort(byId);
|
|
72
|
+
const briefs = corpus.live.filter((b) => b.status !== 'deferred').sort(byId);
|
|
73
|
+
const dependencies = new Map(briefs.map((b) => [b, liveDependencies(corpus, b)]));
|
|
74
|
+
const declared = briefs.flatMap((b) => (b.wave === null ? [] : [b.wave]));
|
|
75
|
+
const first = declared.length === 0 ? 1 : Math.min(...declared);
|
|
76
|
+
const scopes = new Map();
|
|
77
|
+
const scope = (b) => {
|
|
78
|
+
const known = scopes.get(b) ?? scopeOf(b, reading);
|
|
79
|
+
scopes.set(b, known);
|
|
80
|
+
return known;
|
|
81
|
+
};
|
|
82
|
+
const waiting = new Set(waitingOnDeferred(corpus).map((w) => w.brief));
|
|
83
|
+
// Deferred briefs too: lint's wave-order reads every live brief's wave.
|
|
84
|
+
const everyDependency = new Map(corpus.live.map((b) => [b, liveDependencies(corpus, b)]));
|
|
85
|
+
const dependents = new Map();
|
|
86
|
+
for (const b of [...corpus.live].sort(byId)) {
|
|
87
|
+
for (const d of everyDependency.get(b))
|
|
88
|
+
dependents.set(d, [...(dependents.get(d) ?? []), b]);
|
|
89
|
+
}
|
|
90
|
+
const judge = { compared: briefs.filter((b) => !waiting.has(b)), dependencies: everyDependency, dependents, scope, budget };
|
|
91
|
+
const waveOf = new Map();
|
|
92
|
+
const occupants = new Map();
|
|
93
|
+
const placements = [];
|
|
94
|
+
const pending = [...briefs];
|
|
95
|
+
// Kahn's order, taking the least id whose dependencies are all placed. A
|
|
96
|
+
// brief waiting on a deferred one, or on a cycle, is never taken.
|
|
97
|
+
for (;;) {
|
|
98
|
+
const next = pending.find((b) => dependencies.get(b).every((d) => waveOf.has(d)));
|
|
99
|
+
if (next === undefined)
|
|
100
|
+
break;
|
|
101
|
+
pending.splice(pending.indexOf(next), 1);
|
|
102
|
+
let after = null;
|
|
103
|
+
for (const dependency of dependencies.get(next)) {
|
|
104
|
+
const wave = waveOf.get(dependency);
|
|
105
|
+
if (after === null || wave > after.wave)
|
|
106
|
+
after = { brief: dependency, wave };
|
|
107
|
+
}
|
|
108
|
+
const own = scope(next);
|
|
109
|
+
const passed = [];
|
|
110
|
+
let wave = after === null ? first : after.wave + 1;
|
|
111
|
+
// Ends by construction: a wave past every occupied one is empty, and an empty wave takes anyone.
|
|
112
|
+
for (;;) {
|
|
113
|
+
const found = clash(own, wave, occupants.get(wave) ?? [], budget);
|
|
114
|
+
if (found === null)
|
|
115
|
+
break;
|
|
116
|
+
passed.push(found);
|
|
117
|
+
wave += 1;
|
|
118
|
+
}
|
|
119
|
+
waveOf.set(next, wave);
|
|
120
|
+
occupants.set(wave, [...(occupants.get(wave) ?? []), own]);
|
|
121
|
+
const breaches = next.wave === null || next.wave === wave ? [] : breachesOf(next, next.wave, judge);
|
|
122
|
+
placements.push({ brief: next, declared: next.wave, proposed: wave, after, passed, unscoped: own.affected.length === 0, breaches });
|
|
123
|
+
}
|
|
124
|
+
const cycles = dependencyCycles(corpus).filter((cycle) => cycle.every((b) => b.status !== 'deferred'));
|
|
125
|
+
const inCycle = new Set(cycles.flat());
|
|
126
|
+
const unplaced = pending.map((brief) => {
|
|
127
|
+
const waitsOn = dependencies.get(brief).find((d) => !waveOf.has(d));
|
|
128
|
+
const because = waitsOn.status === 'deferred' ? 'deferred' : inCycle.has(waitsOn) ? 'cycle' : 'unplaced';
|
|
129
|
+
return { brief, waitsOn, because };
|
|
130
|
+
});
|
|
131
|
+
return { first, placements, unplaced, cycles, deferred };
|
|
132
|
+
}
|
|
133
|
+
/** The briefs whose declared wave the schedule would change. */
|
|
134
|
+
export function moves(s) {
|
|
135
|
+
return s.placements.filter((p) => p.proposed !== p.declared);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Whether a brief's declared wave holds: it is the proposed one, or one a
|
|
139
|
+
* person chose that `lint` and `matrix` accept. The schedule is one valid
|
|
140
|
+
* answer, not the only one (ADR-0011, amended).
|
|
141
|
+
*/
|
|
142
|
+
export function holds(p) {
|
|
143
|
+
return p.declared !== null && p.breaches.length === 0;
|
|
144
|
+
}
|
|
145
|
+
/** Why a declared wave does not hold, a clause each. */
|
|
146
|
+
export function breachReasons(p) {
|
|
147
|
+
return p.breaches.map((b) => {
|
|
148
|
+
if (b.reason === 'collision')
|
|
149
|
+
return `${label(b.brief)} there also writes ${b.overlaps[0].witness}`;
|
|
150
|
+
if (b.reason === 'dependency')
|
|
151
|
+
return `it depends on ${label(b.brief)}, in wave ${b.wave}`;
|
|
152
|
+
return `${label(b.brief)} depends on it, in wave ${b.wave}`;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/** Why a brief is in its wave and not an earlier one, a clause each. */
|
|
156
|
+
export function reasons(p) {
|
|
157
|
+
const out = [];
|
|
158
|
+
if (p.after !== null)
|
|
159
|
+
out.push(`after ${label(p.after.brief)}, in wave ${p.after.wave}`);
|
|
160
|
+
if (p.unscoped && p.passed.length > 0)
|
|
161
|
+
out.push('it declares no affectedFiles, so it runs in a wave of its own');
|
|
162
|
+
for (const passed of p.passed) {
|
|
163
|
+
const there = `not wave ${passed.wave}, where ${label(passed.brief)}`;
|
|
164
|
+
if (passed.reason === 'collision') {
|
|
165
|
+
const [overlap] = passed.overlaps;
|
|
166
|
+
out.push(`${there} also writes ${overlap.witness} ("${overlap.patterns[0]}" and "${overlap.patterns[1]}")`);
|
|
167
|
+
}
|
|
168
|
+
else if (passed.reason === 'undecided') {
|
|
169
|
+
out.push(`${there} may write the same files: the search could not decide within its budget`);
|
|
170
|
+
}
|
|
171
|
+
else if (!p.unscoped) {
|
|
172
|
+
out.push(`${there} declares no affectedFiles and runs alone`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (out.length === 0)
|
|
176
|
+
out.push('nothing holds it later');
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
/** The schedule as findings: cycles, briefs whose waves would move, briefs that run alone, and pairs it could not decide. */
|
|
180
|
+
export function scheduleFindings(corpus, s) {
|
|
181
|
+
const findings = [];
|
|
182
|
+
const cycle = configuredSeverity(corpus, 'dependency-cycle');
|
|
183
|
+
if (cycle !== null) {
|
|
184
|
+
for (const members of s.cycles) {
|
|
185
|
+
const start = members[0];
|
|
186
|
+
findings.push({
|
|
187
|
+
rule: 'dependency-cycle',
|
|
188
|
+
severity: cycle,
|
|
189
|
+
message: `dependencies form a cycle, so no wave can hold them: ${members.map(label).join(' -> ')}`,
|
|
190
|
+
file: start.file,
|
|
191
|
+
line: lineOfField(start, 'dependsOn') + 1,
|
|
192
|
+
brief: start.id ?? undefined,
|
|
193
|
+
hint: 'a cycle can never become ready; remove the dependency that is not real',
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const change = configuredSeverity(corpus, 'wave-schedule');
|
|
198
|
+
if (change !== null) {
|
|
199
|
+
// A wave a person chose that lint and matrix accept is theirs to keep: a
|
|
200
|
+
// note, so that CI passes it. One missing, or one that does not hold, is
|
|
201
|
+
// the rule's finding at its configured severity.
|
|
202
|
+
for (const p of moves(s)) {
|
|
203
|
+
const valid = holds(p);
|
|
204
|
+
const was = p.declared === null
|
|
205
|
+
? 'declares no wave'
|
|
206
|
+
: valid
|
|
207
|
+
? `declares wave ${p.declared}, which holds`
|
|
208
|
+
: `declares wave ${p.declared}, which does not hold (${breachReasons(p).join('; ')})`;
|
|
209
|
+
findings.push({
|
|
210
|
+
rule: 'wave-schedule',
|
|
211
|
+
severity: valid ? 'note' : change,
|
|
212
|
+
message: `${was}; the schedule puts it in wave ${p.proposed}: ${reasons(p).join('; ')}`,
|
|
213
|
+
file: p.brief.file,
|
|
214
|
+
line: lineOfField(p.brief, 'wave') + 1,
|
|
215
|
+
brief: p.brief.id ?? undefined,
|
|
216
|
+
hint: valid
|
|
217
|
+
? `keep wave ${p.declared}, or run "spec-brief schedule --write" for wave ${p.proposed}`
|
|
218
|
+
: `run "spec-brief schedule --write", or set "wave: ${p.proposed}"`,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const unscoped = configuredSeverity(corpus, 'unscoped');
|
|
223
|
+
if (unscoped !== null && s.placements.length > 1) {
|
|
224
|
+
for (const p of s.placements.filter((x) => x.unscoped)) {
|
|
225
|
+
findings.push(unscopedFinding(unscoped, p.brief, `the ${s.placements.length - 1} other brief(s) the schedule places; it runs alone in wave ${p.proposed}`));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const undecided = configuredSeverity(corpus, 'collision-undecided');
|
|
229
|
+
if (undecided !== null) {
|
|
230
|
+
for (const p of s.placements) {
|
|
231
|
+
for (const passed of p.passed) {
|
|
232
|
+
if (passed.reason !== 'undecided')
|
|
233
|
+
continue;
|
|
234
|
+
const theirsFirst = passed.patterns.map(([own, theirs]) => [theirs, own]);
|
|
235
|
+
findings.push(undecidedFinding(undecided, passed.brief, p.brief, theirsFirst, passed.wave));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return findings;
|
|
240
|
+
}
|
|
241
|
+
/** What `--write` writes. */
|
|
242
|
+
export function planWaves(s) {
|
|
243
|
+
const ops = [];
|
|
244
|
+
const refused = [];
|
|
245
|
+
for (const p of moves(s)) {
|
|
246
|
+
const { brief } = p;
|
|
247
|
+
if (brief.frontMatter !== null && !editable(brief.frontMatter)) {
|
|
248
|
+
const open = brief.frontMatter.close < 0;
|
|
249
|
+
refused.push({
|
|
250
|
+
rule: 'front-matter',
|
|
251
|
+
severity: 'error',
|
|
252
|
+
message: `the front matter is ${open ? 'never closed' : 'TOML'}, so wave ${p.proposed} cannot be written into it`,
|
|
253
|
+
file: brief.file,
|
|
254
|
+
line: 1,
|
|
255
|
+
brief: brief.id ?? undefined,
|
|
256
|
+
hint: open ? 'close the front matter with a "---" line, and schedule again' : 'write the front matter as YAML between "---" lines, and schedule again',
|
|
257
|
+
});
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
const lines = setEntryKeepingComment(brief.lines, brief.frontMatter, 'wave', String(p.proposed));
|
|
261
|
+
ops.push({ kind: 'write', path: brief.file, content: encodeLike(brief.source, lines), before: brief.source });
|
|
262
|
+
}
|
|
263
|
+
return { ops, refused };
|
|
264
|
+
}
|
|
265
|
+
//# sourceMappingURL=schedule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schedule.js","sourceRoot":"","sources":["../src/schedule.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Waves, computed: which live briefs can run side by side, so that no two in\n * one wave can write the same file and every brief runs after the briefs it\n * depends on.\n *\n * Deciding what the rounds of a goal are is prose, and an agent's work. Given\n * the rounds, placing them is not: it is precedence-constrained colouring, and\n * a tool answers it the same way every time (ADR-0011). Briefs are taken in\n * dependency order, ties broken by id, and each goes into the lowest wave that\n * is after every live dependency's and holds nothing it collides with. A brief\n * with no scope cannot be proved apart from anything, so it shares its wave\n * with nobody. The greedy answer is not always the fewest waves - no fast\n * method promises that - but it is always a valid one, and the same one.\n *\n * Nothing is written here. `planWaves` says what `--write` would write, and\n * the engine writes it as the archive writes, as one transaction.\n */\n\nimport type { FileOp } from './archive.js';\nimport { type Brief, lineOfField } from './brief.js';\nimport { undecidedFinding, unscopedFinding } from './collisions.js';\nimport { byId, type Corpus, dependencyCycles, liveDependencies, waitingOnDeferred } from './corpus.js';\nimport { editable, setEntryKeepingComment } from './frontmatter.js';\nimport { readingIn, WITNESS_BUDGET } from './glob.js';\nimport { configuredSeverity } from './lint.js';\nimport { meet, type Overlap, type Scope, scopeOf } from './scope.js';\nimport { encodeLike } from './text.js';\nimport type { Finding } from './types.js';\n\nexport interface ScheduleOptions {\n /** The files the tree holds, which say whether a literal path is a file or a directory. */\n readonly repoFiles?: readonly string[] | null;\n /** How many states one witness search may visit before it answers `undecided`. */\n readonly budget?: number;\n}\n\n/** A wave a brief could not join, and the brief already there that kept it out. */\nexport type Passed =\n | { readonly wave: number; readonly reason: 'collision'; readonly brief: Brief; readonly overlaps: readonly Overlap[] }\n | { readonly wave: number; readonly reason: 'undecided'; readonly brief: Brief; readonly patterns: readonly (readonly [string, string])[] }\n /** One of the two declares no affectedFiles, and an unscoped brief runs alone. */\n | { readonly wave: number; readonly reason: 'unscoped'; readonly brief: Brief };\n\n/**\n * Why a declared wave does not hold: what `matrix` or `lint` would report as\n * an error if the brief ran there.\n */\nexport type Breach =\n /** A brief the matrix compares, declaring the same wave, whose scope meets this one's. Overlaps name this brief's pattern first. */\n | { readonly reason: 'collision'; readonly brief: Brief; readonly overlaps: readonly Overlap[] }\n /** A live dependency declaring the same wave or a later one. */\n | { readonly reason: 'dependency'; readonly brief: Brief; readonly wave: number }\n /** A live brief depending on this one, declaring the same wave or an earlier one. */\n | { readonly reason: 'dependent'; readonly brief: Brief; readonly wave: number };\n\nexport interface Placement {\n readonly brief: Brief;\n /** The wave its front matter declares; `null` when it declares none it can read. */\n readonly declared: number | null;\n readonly proposed: number;\n /** The live dependency in the latest wave, which sets the earliest wave this one may run in. */\n readonly after: { readonly brief: Brief; readonly wave: number } | null;\n /** Every wave from that earliest one that it could not join, in order. Overlaps and patterns name its own pattern first. */\n readonly passed: readonly Passed[];\n /** Declares no affectedFiles, so it shares its wave with nobody. */\n readonly unscoped: boolean;\n /**\n * Why the declared wave does not hold, when it is not the proposed one. Empty\n * when it holds - a person moved the brief, and `lint` and `matrix` accept\n * the wave - and when there is no declared wave to judge.\n */\n readonly breaches: readonly Breach[];\n}\n\n/** A live brief that can be placed in no wave, and the dependency that holds it. */\nexport interface Unplaced {\n readonly brief: Brief;\n readonly waitsOn: Brief;\n /** The dependency is deferred, is in a dependency cycle, or is itself unplaced. */\n readonly because: 'deferred' | 'cycle' | 'unplaced';\n}\n\nexport interface Schedule {\n /** The first wave: the lowest any scheduled brief declares, or 1 when none declares one. */\n readonly first: number;\n /** In the order they were placed: dependency order, ties by id. */\n readonly placements: readonly Placement[];\n /** By id. */\n readonly unplaced: readonly Unplaced[];\n /** Dependency cycles among the scheduled briefs, each as a path back to its start. */\n readonly cycles: readonly (readonly Brief[])[];\n /** Deferred briefs, which wait for their trigger and run in no wave. By id. */\n readonly deferred: readonly Brief[];\n}\n\nfunction label(brief: Brief): string {\n return brief.id ?? brief.name;\n}\n\nexport { byId } from './corpus.js';\n\n/** The first brief in a wave this one cannot share it with, or `null` when it can join. */\nfunction clash(own: Scope, wave: number, here: readonly Scope[], budget: number): Passed | null {\n for (const other of here) {\n if (own.affected.length === 0 || other.affected.length === 0) return { wave, reason: 'unscoped', brief: other.brief };\n const meeting = meet(own, other, budget);\n if (meeting.overlaps.length > 0) return { wave, reason: 'collision', brief: other.brief, overlaps: meeting.overlaps };\n if (meeting.undecided.length > 0) return { wave, reason: 'undecided', brief: other.brief, patterns: meeting.undecided };\n }\n return null;\n}\n\n/** What a declared wave is judged against. */\ninterface Judge {\n /** The briefs the matrix compares: none deferred, none waiting on deferred work. */\n readonly compared: readonly Brief[];\n /** Every live brief's live dependencies, and the live briefs that depend on each, by id. */\n readonly dependencies: ReadonlyMap<Brief, readonly Brief[]>;\n readonly dependents: ReadonlyMap<Brief, readonly Brief[]>;\n readonly scope: (b: Brief) => Scope;\n readonly budget: number;\n}\n\n/**\n * Why a brief's declared wave does not hold, judged as `lint` and `matrix`\n * judge it: against the waves the other briefs declare, not the ones proposed.\n * Collisions are with the briefs the matrix compares; the dependency order is\n * wave-order's, both ways, over every live brief.\n */\nfunction breachesOf(brief: Brief, wave: number, judge: Judge): Breach[] {\n const out: Breach[] = [];\n for (const dependency of judge.dependencies.get(brief) as Brief[]) {\n if (dependency.wave !== null && dependency.wave >= wave) out.push({ reason: 'dependency', brief: dependency, wave: dependency.wave });\n }\n for (const other of judge.dependents.get(brief) ?? []) {\n if (other.wave !== null && other.wave <= wave) out.push({ reason: 'dependent', brief: other, wave: other.wave });\n }\n for (const other of judge.compared) {\n if (other === brief || other.wave !== wave) continue;\n const overlaps = meet(judge.scope(brief), judge.scope(other), judge.budget).overlaps;\n if (overlaps.length > 0) out.push({ reason: 'collision', brief: other, overlaps });\n }\n return out;\n}\n\nexport function schedule(corpus: Corpus, options: ScheduleOptions = {}): Schedule {\n const reading = readingIn(options.repoFiles ?? null);\n const budget = options.budget ?? WITNESS_BUDGET;\n const deferred = corpus.live.filter((b) => b.status === 'deferred').sort(byId);\n const briefs = corpus.live.filter((b) => b.status !== 'deferred').sort(byId);\n const dependencies = new Map(briefs.map((b) => [b, liveDependencies(corpus, b)]));\n const declared = briefs.flatMap((b) => (b.wave === null ? [] : [b.wave]));\n const first = declared.length === 0 ? 1 : Math.min(...declared);\n const scopes = new Map<Brief, Scope>();\n const scope = (b: Brief): Scope => {\n const known = scopes.get(b) ?? scopeOf(b, reading);\n scopes.set(b, known);\n return known;\n };\n const waiting = new Set(waitingOnDeferred(corpus).map((w) => w.brief));\n // Deferred briefs too: lint's wave-order reads every live brief's wave.\n const everyDependency = new Map(corpus.live.map((b) => [b, liveDependencies(corpus, b)]));\n const dependents = new Map<Brief, Brief[]>();\n for (const b of [...corpus.live].sort(byId)) {\n for (const d of everyDependency.get(b) as Brief[]) dependents.set(d, [...(dependents.get(d) ?? []), b]);\n }\n const judge: Judge = { compared: briefs.filter((b) => !waiting.has(b)), dependencies: everyDependency, dependents, scope, budget };\n\n const waveOf = new Map<Brief, number>();\n const occupants = new Map<number, Scope[]>();\n const placements: Placement[] = [];\n const pending = [...briefs];\n // Kahn's order, taking the least id whose dependencies are all placed. A\n // brief waiting on a deferred one, or on a cycle, is never taken.\n for (;;) {\n const next = pending.find((b) => (dependencies.get(b) as Brief[]).every((d) => waveOf.has(d)));\n if (next === undefined) break;\n pending.splice(pending.indexOf(next), 1);\n let after: Placement['after'] = null;\n for (const dependency of dependencies.get(next) as Brief[]) {\n const wave = waveOf.get(dependency) as number;\n if (after === null || wave > after.wave) after = { brief: dependency, wave };\n }\n const own = scope(next);\n const passed: Passed[] = [];\n let wave = after === null ? first : after.wave + 1;\n // Ends by construction: a wave past every occupied one is empty, and an empty wave takes anyone.\n for (;;) {\n const found = clash(own, wave, occupants.get(wave) ?? [], budget);\n if (found === null) break;\n passed.push(found);\n wave += 1;\n }\n waveOf.set(next, wave);\n occupants.set(wave, [...(occupants.get(wave) ?? []), own]);\n const breaches = next.wave === null || next.wave === wave ? [] : breachesOf(next, next.wave, judge);\n placements.push({ brief: next, declared: next.wave, proposed: wave, after, passed, unscoped: own.affected.length === 0, breaches });\n }\n\n const cycles = dependencyCycles(corpus).filter((cycle) => cycle.every((b) => b.status !== 'deferred'));\n const inCycle = new Set(cycles.flat());\n const unplaced = pending.map((brief): Unplaced => {\n const waitsOn = (dependencies.get(brief) as Brief[]).find((d) => !waveOf.has(d)) as Brief;\n const because = waitsOn.status === 'deferred' ? 'deferred' : inCycle.has(waitsOn) ? 'cycle' : 'unplaced';\n return { brief, waitsOn, because };\n });\n return { first, placements, unplaced, cycles, deferred };\n}\n\n/** The briefs whose declared wave the schedule would change. */\nexport function moves(s: Schedule): Placement[] {\n return s.placements.filter((p) => p.proposed !== p.declared);\n}\n\n/**\n * Whether a brief's declared wave holds: it is the proposed one, or one a\n * person chose that `lint` and `matrix` accept. The schedule is one valid\n * answer, not the only one (ADR-0011, amended).\n */\nexport function holds(p: Placement): boolean {\n return p.declared !== null && p.breaches.length === 0;\n}\n\n/** Why a declared wave does not hold, a clause each. */\nexport function breachReasons(p: Placement): string[] {\n return p.breaches.map((b) => {\n if (b.reason === 'collision') return `${label(b.brief)} there also writes ${(b.overlaps[0] as Overlap).witness}`;\n if (b.reason === 'dependency') return `it depends on ${label(b.brief)}, in wave ${b.wave}`;\n return `${label(b.brief)} depends on it, in wave ${b.wave}`;\n });\n}\n\n/** Why a brief is in its wave and not an earlier one, a clause each. */\nexport function reasons(p: Placement): string[] {\n const out: string[] = [];\n if (p.after !== null) out.push(`after ${label(p.after.brief)}, in wave ${p.after.wave}`);\n if (p.unscoped && p.passed.length > 0) out.push('it declares no affectedFiles, so it runs in a wave of its own');\n for (const passed of p.passed) {\n const there = `not wave ${passed.wave}, where ${label(passed.brief)}`;\n if (passed.reason === 'collision') {\n const [overlap] = passed.overlaps as [Overlap];\n out.push(`${there} also writes ${overlap.witness} (\"${overlap.patterns[0]}\" and \"${overlap.patterns[1]}\")`);\n } else if (passed.reason === 'undecided') {\n out.push(`${there} may write the same files: the search could not decide within its budget`);\n } else if (!p.unscoped) {\n out.push(`${there} declares no affectedFiles and runs alone`);\n }\n }\n if (out.length === 0) out.push('nothing holds it later');\n return out;\n}\n\n/** The schedule as findings: cycles, briefs whose waves would move, briefs that run alone, and pairs it could not decide. */\nexport function scheduleFindings(corpus: Corpus, s: Schedule): Finding[] {\n const findings: Finding[] = [];\n const cycle = configuredSeverity(corpus, 'dependency-cycle');\n if (cycle !== null) {\n for (const members of s.cycles) {\n const start = members[0] as Brief;\n findings.push({\n rule: 'dependency-cycle',\n severity: cycle,\n message: `dependencies form a cycle, so no wave can hold them: ${members.map(label).join(' -> ')}`,\n file: start.file,\n line: lineOfField(start, 'dependsOn') + 1,\n brief: start.id ?? undefined,\n hint: 'a cycle can never become ready; remove the dependency that is not real',\n });\n }\n }\n const change = configuredSeverity(corpus, 'wave-schedule');\n if (change !== null) {\n // A wave a person chose that lint and matrix accept is theirs to keep: a\n // note, so that CI passes it. One missing, or one that does not hold, is\n // the rule's finding at its configured severity.\n for (const p of moves(s)) {\n const valid = holds(p);\n const was =\n p.declared === null\n ? 'declares no wave'\n : valid\n ? `declares wave ${p.declared}, which holds`\n : `declares wave ${p.declared}, which does not hold (${breachReasons(p).join('; ')})`;\n findings.push({\n rule: 'wave-schedule',\n severity: valid ? 'note' : change,\n message: `${was}; the schedule puts it in wave ${p.proposed}: ${reasons(p).join('; ')}`,\n file: p.brief.file,\n line: lineOfField(p.brief, 'wave') + 1,\n brief: p.brief.id ?? undefined,\n hint: valid\n ? `keep wave ${p.declared as number}, or run \"spec-brief schedule --write\" for wave ${p.proposed}`\n : `run \"spec-brief schedule --write\", or set \"wave: ${p.proposed}\"`,\n });\n }\n }\n const unscoped = configuredSeverity(corpus, 'unscoped');\n if (unscoped !== null && s.placements.length > 1) {\n for (const p of s.placements.filter((x) => x.unscoped)) {\n findings.push(unscopedFinding(unscoped, p.brief, `the ${s.placements.length - 1} other brief(s) the schedule places; it runs alone in wave ${p.proposed}`));\n }\n }\n const undecided = configuredSeverity(corpus, 'collision-undecided');\n if (undecided !== null) {\n for (const p of s.placements) {\n for (const passed of p.passed) {\n if (passed.reason !== 'undecided') continue;\n const theirsFirst = passed.patterns.map(([own, theirs]) => [theirs, own] as const);\n findings.push(undecidedFinding(undecided, passed.brief, p.brief, theirsFirst, passed.wave));\n }\n }\n }\n return findings;\n}\n\nexport interface WavePlan {\n /** A write of each brief whose wave changes: its front matter's `wave`, and every other line as it was. */\n readonly ops: readonly FileOp[];\n /** Briefs whose wave cannot be written, which refuse the whole write. */\n readonly refused: readonly Finding[];\n}\n\n/** What `--write` writes. */\nexport function planWaves(s: Schedule): WavePlan {\n const ops: FileOp[] = [];\n const refused: Finding[] = [];\n for (const p of moves(s)) {\n const { brief } = p;\n if (brief.frontMatter !== null && !editable(brief.frontMatter)) {\n const open = brief.frontMatter.close < 0;\n refused.push({\n rule: 'front-matter',\n severity: 'error',\n message: `the front matter is ${open ? 'never closed' : 'TOML'}, so wave ${p.proposed} cannot be written into it`,\n file: brief.file,\n line: 1,\n brief: brief.id ?? undefined,\n hint: open ? 'close the front matter with a \"---\" line, and schedule again' : 'write the front matter as YAML between \"---\" lines, and schedule again',\n });\n continue;\n }\n const lines = setEntryKeepingComment(brief.lines, brief.frontMatter, 'wave', String(p.proposed));\n ops.push({ kind: 'write', path: brief.file, content: encodeLike(brief.source, lines), before: brief.source });\n }\n return { ops, refused };\n}\n"]}
|
package/dist/scope.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A brief's scope, and whether two briefs can write the same file.
|
|
3
|
+
*
|
|
4
|
+
* What a brief may write is its `affectedFiles` less its `protectedFiles`:
|
|
5
|
+
* protection wins, so "all of `src/` but the schema" is written
|
|
6
|
+
* `affectedFiles: [src/**]` with `protectedFiles: [src/db/schema.ts]`. Two
|
|
7
|
+
* briefs meet when some file lies in both writable scopes - in a pattern of
|
|
8
|
+
* each, and in neither brief's protections - and the witness search names
|
|
9
|
+
* the shortest such file (ADR-0005).
|
|
10
|
+
*/
|
|
11
|
+
import type { Brief } from './brief.js';
|
|
12
|
+
import { type Glob, type LiteralReading } from './glob.js';
|
|
13
|
+
/** How a literal path is read, as {@link readingIn} answers for a tree. */
|
|
14
|
+
export type Reading = (path: string) => LiteralReading;
|
|
15
|
+
export interface ScopePattern {
|
|
16
|
+
readonly pattern: string;
|
|
17
|
+
readonly glob: Glob;
|
|
18
|
+
}
|
|
19
|
+
export interface Scope {
|
|
20
|
+
readonly brief: Brief;
|
|
21
|
+
/** The `affectedFiles` that parse. One that does not is the `glob` rule's finding, and scopes nothing. */
|
|
22
|
+
readonly affected: readonly ScopePattern[];
|
|
23
|
+
readonly protected: readonly ScopePattern[];
|
|
24
|
+
}
|
|
25
|
+
/** Patterns that parse, read with one reading. */
|
|
26
|
+
export declare function patternsOf(patterns: readonly string[], reading: Reading): ScopePattern[];
|
|
27
|
+
export declare function scopeOf(brief: Brief, reading: Reading): Scope;
|
|
28
|
+
/** A pattern from each scope, and a file both may write. */
|
|
29
|
+
export interface Overlap {
|
|
30
|
+
readonly patterns: readonly [string, string];
|
|
31
|
+
readonly witness: string;
|
|
32
|
+
}
|
|
33
|
+
export interface Meeting {
|
|
34
|
+
/** Every pair of patterns that meets outside both protections, the first scope's pattern first. */
|
|
35
|
+
readonly overlaps: readonly Overlap[];
|
|
36
|
+
/** Pairs of patterns the search could not decide within its budget. */
|
|
37
|
+
readonly undecided: readonly (readonly [string, string])[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Where two writable scopes meet. Each pair of affected patterns is searched
|
|
41
|
+
* for a file both cover that neither brief protects: a file one brief protects
|
|
42
|
+
* is one that brief will not write, so it is no collision.
|
|
43
|
+
*/
|
|
44
|
+
export declare function meet(a: Scope, b: Scope, budget?: number): Meeting;
|
|
45
|
+
export interface Contradiction {
|
|
46
|
+
/** Affected patterns every file of which is protected: nothing of them is writable. */
|
|
47
|
+
readonly covered: readonly string[];
|
|
48
|
+
/** Affected patterns for which the search could not decide within its budget. */
|
|
49
|
+
readonly undecided: readonly string[];
|
|
50
|
+
}
|
|
51
|
+
/** The affected patterns a brief's own protections cover entirely. */
|
|
52
|
+
export declare function contradictions(scope: Scope, budget?: number): Contradiction;
|
package/dist/scope.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A brief's scope, and whether two briefs can write the same file.
|
|
3
|
+
*
|
|
4
|
+
* What a brief may write is its `affectedFiles` less its `protectedFiles`:
|
|
5
|
+
* protection wins, so "all of `src/` but the schema" is written
|
|
6
|
+
* `affectedFiles: [src/**]` with `protectedFiles: [src/db/schema.ts]`. Two
|
|
7
|
+
* briefs meet when some file lies in both writable scopes - in a pattern of
|
|
8
|
+
* each, and in neither brief's protections - and the witness search names
|
|
9
|
+
* the shortest such file (ADR-0005).
|
|
10
|
+
*/
|
|
11
|
+
import { globCovers, globWitness, parseGlob, WITNESS_BUDGET } from './glob.js';
|
|
12
|
+
/** Patterns that parse, read with one reading. */
|
|
13
|
+
export function patternsOf(patterns, reading) {
|
|
14
|
+
return patterns.flatMap((pattern) => {
|
|
15
|
+
const parsed = parseGlob(pattern, { literal: reading });
|
|
16
|
+
return parsed.ok ? [{ pattern, glob: parsed.glob }] : [];
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
export function scopeOf(brief, reading) {
|
|
20
|
+
return { brief, affected: patternsOf(brief.affectedFiles, reading), protected: patternsOf(brief.protectedFiles, reading) };
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Where two writable scopes meet. Each pair of affected patterns is searched
|
|
24
|
+
* for a file both cover that neither brief protects: a file one brief protects
|
|
25
|
+
* is one that brief will not write, so it is no collision.
|
|
26
|
+
*/
|
|
27
|
+
export function meet(a, b, budget = WITNESS_BUDGET) {
|
|
28
|
+
const avoid = [...a.protected, ...b.protected].map((p) => p.glob);
|
|
29
|
+
const overlaps = [];
|
|
30
|
+
const undecided = [];
|
|
31
|
+
for (const x of a.affected) {
|
|
32
|
+
for (const y of b.affected) {
|
|
33
|
+
const witness = globWitness([x.glob, y.glob], avoid, budget);
|
|
34
|
+
if (witness.kind === 'found')
|
|
35
|
+
overlaps.push({ patterns: [x.pattern, y.pattern], witness: witness.path });
|
|
36
|
+
else if (witness.kind === 'undecided')
|
|
37
|
+
undecided.push([x.pattern, y.pattern]);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { overlaps, undecided };
|
|
41
|
+
}
|
|
42
|
+
/** The affected patterns a brief's own protections cover entirely. */
|
|
43
|
+
export function contradictions(scope, budget = WITNESS_BUDGET) {
|
|
44
|
+
const covered = [];
|
|
45
|
+
const undecided = [];
|
|
46
|
+
const outer = scope.protected.map((p) => p.glob);
|
|
47
|
+
for (const { pattern, glob } of scope.affected) {
|
|
48
|
+
const answer = globCovers(outer, glob, budget);
|
|
49
|
+
if (answer === true)
|
|
50
|
+
covered.push(pattern);
|
|
51
|
+
else if (answer === 'undecided')
|
|
52
|
+
undecided.push(pattern);
|
|
53
|
+
}
|
|
54
|
+
return { covered, undecided };
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=scope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope.js","sourceRoot":"","sources":["../src/scope.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A brief's scope, and whether two briefs can write the same file.\n *\n * What a brief may write is its `affectedFiles` less its `protectedFiles`:\n * protection wins, so \"all of `src/` but the schema\" is written\n * `affectedFiles: [src/**]` with `protectedFiles: [src/db/schema.ts]`. Two\n * briefs meet when some file lies in both writable scopes - in a pattern of\n * each, and in neither brief's protections - and the witness search names\n * the shortest such file (ADR-0005).\n */\n\nimport type { Brief } from './brief.js';\nimport { type Glob, globCovers, globWitness, type LiteralReading, parseGlob, WITNESS_BUDGET } from './glob.js';\n\n/** How a literal path is read, as {@link readingIn} answers for a tree. */\nexport type Reading = (path: string) => LiteralReading;\n\nexport interface ScopePattern {\n readonly pattern: string;\n readonly glob: Glob;\n}\n\nexport interface Scope {\n readonly brief: Brief;\n /** The `affectedFiles` that parse. One that does not is the `glob` rule's finding, and scopes nothing. */\n readonly affected: readonly ScopePattern[];\n readonly protected: readonly ScopePattern[];\n}\n\n/** Patterns that parse, read with one reading. */\nexport function patternsOf(patterns: readonly string[], reading: Reading): ScopePattern[] {\n return patterns.flatMap((pattern) => {\n const parsed = parseGlob(pattern, { literal: reading });\n return parsed.ok ? [{ pattern, glob: parsed.glob }] : [];\n });\n}\n\nexport function scopeOf(brief: Brief, reading: Reading): Scope {\n return { brief, affected: patternsOf(brief.affectedFiles, reading), protected: patternsOf(brief.protectedFiles, reading) };\n}\n\n/** A pattern from each scope, and a file both may write. */\nexport interface Overlap {\n readonly patterns: readonly [string, string];\n readonly witness: string;\n}\n\nexport interface Meeting {\n /** Every pair of patterns that meets outside both protections, the first scope's pattern first. */\n readonly overlaps: readonly Overlap[];\n /** Pairs of patterns the search could not decide within its budget. */\n readonly undecided: readonly (readonly [string, string])[];\n}\n\n/**\n * Where two writable scopes meet. Each pair of affected patterns is searched\n * for a file both cover that neither brief protects: a file one brief protects\n * is one that brief will not write, so it is no collision.\n */\nexport function meet(a: Scope, b: Scope, budget: number = WITNESS_BUDGET): Meeting {\n const avoid = [...a.protected, ...b.protected].map((p) => p.glob);\n const overlaps: Overlap[] = [];\n const undecided: (readonly [string, string])[] = [];\n for (const x of a.affected) {\n for (const y of b.affected) {\n const witness = globWitness([x.glob, y.glob], avoid, budget);\n if (witness.kind === 'found') overlaps.push({ patterns: [x.pattern, y.pattern], witness: witness.path });\n else if (witness.kind === 'undecided') undecided.push([x.pattern, y.pattern]);\n }\n }\n return { overlaps, undecided };\n}\n\nexport interface Contradiction {\n /** Affected patterns every file of which is protected: nothing of them is writable. */\n readonly covered: readonly string[];\n /** Affected patterns for which the search could not decide within its budget. */\n readonly undecided: readonly string[];\n}\n\n/** The affected patterns a brief's own protections cover entirely. */\nexport function contradictions(scope: Scope, budget: number = WITNESS_BUDGET): Contradiction {\n const covered: string[] = [];\n const undecided: string[] = [];\n const outer = scope.protected.map((p) => p.glob);\n for (const { pattern, glob } of scope.affected) {\n const answer = globCovers(outer, glob, budget);\n if (answer === true) covered.push(pattern);\n else if (answer === 'undecided') undecided.push(pattern);\n }\n return { covered, undecided };\n}\n"]}
|