@descent-vtt/spec-brief 0.2.7 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +67 -1
  2. package/README.md +162 -29
  3. package/dist/cli.d.ts +1 -1
  4. package/dist/cli.js +61 -43
  5. package/dist/cli.js.map +1 -1
  6. package/dist/collisions.js +13 -8
  7. package/dist/collisions.js.map +1 -1
  8. package/dist/config.js +2 -1
  9. package/dist/config.js.map +1 -1
  10. package/dist/corpus.d.ts +5 -0
  11. package/dist/corpus.js +7 -0
  12. package/dist/corpus.js.map +1 -1
  13. package/dist/glob.d.ts +6 -5
  14. package/dist/glob.js +22 -103
  15. package/dist/glob.js.map +1 -1
  16. package/dist/lint.js +1 -0
  17. package/dist/lint.js.map +1 -1
  18. package/dist/report.d.ts +18 -4
  19. package/dist/report.js +98 -28
  20. package/dist/report.js.map +1 -1
  21. package/dist/rules.d.ts +2 -0
  22. package/dist/rules.js +21 -8
  23. package/dist/rules.js.map +1 -1
  24. package/dist/schedule.js +12 -10
  25. package/dist/schedule.js.map +1 -1
  26. package/dist/schema.d.ts +5 -1
  27. package/dist/schema.js +6 -2
  28. package/dist/schema.js.map +1 -1
  29. package/dist/scope.d.ts +26 -4
  30. package/dist/scope.js +64 -8
  31. package/dist/scope.js.map +1 -1
  32. package/dist/trigger.js +59 -3
  33. package/dist/trigger.js.map +1 -1
  34. package/dist/types.d.ts +9 -0
  35. package/dist/types.js.map +1 -1
  36. package/dist/vendor/spec-core/markdown/frontmatter.d.ts +11 -1
  37. package/dist/vendor/spec-core/markdown/frontmatter.js +20 -3
  38. package/dist/vendor/spec-core/markdown/frontmatter.js.map +1 -1
  39. package/dist/vendor/spec-core/pattern/glob.d.ts +85 -0
  40. package/dist/vendor/spec-core/pattern/glob.js +207 -17
  41. package/dist/vendor/spec-core/pattern/glob.js.map +1 -1
  42. package/dist/vendor/spec-core/pattern/index.d.ts +2 -2
  43. package/dist/vendor/spec-core/pattern/index.js +1 -1
  44. package/dist/vendor/spec-core/pattern/index.js.map +1 -1
  45. package/dist/vendor/spec-core/text/index.d.ts +1 -0
  46. package/dist/vendor/spec-core/text/index.js +1 -0
  47. package/dist/vendor/spec-core/text/index.js.map +1 -1
  48. package/dist/vendor/spec-core/text/width.d.ts +30 -0
  49. package/dist/vendor/spec-core/text/width.js +113 -0
  50. package/dist/vendor/spec-core/text/width.js.map +1 -0
  51. package/package.json +1 -1
@@ -1 +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"]}
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, cycleSubject, 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, witnessText } 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, files: readonly string[] | null): 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, files);\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 readonly files: readonly string[] | null;\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, judge.files).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 files = options.repoFiles ?? null;\n const reading = readingIn(files);\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, files };\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, files);\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 ${witnessText(b.overlaps[0] as Overlap)}`;\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 ${witnessText(overlap)} (\"${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 subject: cycleSubject(members),\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/schema.d.ts CHANGED
@@ -35,7 +35,11 @@ export type Schema = (Described & {
35
35
  });
36
36
  /** The problems with `value`, each naming the path to it. Empty when it conforms. */
37
37
  export declare function validate(schema: Schema, value: unknown, path?: string): string[];
38
- /** The known key nearest to a misspelling, when one is near enough to be what was meant. */
38
+ /**
39
+ * The known key nearest to a misspelling, when one is near enough to be what
40
+ * was meant. One that keeps no character of the word is no misspelling of it,
41
+ * however short both are: `狀態` is two edits from `id`.
42
+ */
39
43
  export declare function closest(word: string, options: readonly string[]): string | undefined;
40
44
  type Json = null | boolean | number | string | Json[] | {
41
45
  [key: string]: Json;
package/dist/schema.js CHANGED
@@ -126,13 +126,17 @@ export function validate(schema, value, path = '') {
126
126
  return [];
127
127
  }
128
128
  }
129
- /** The known key nearest to a misspelling, when one is near enough to be what was meant. */
129
+ /**
130
+ * The known key nearest to a misspelling, when one is near enough to be what
131
+ * was meant. One that keeps no character of the word is no misspelling of it,
132
+ * however short both are: `狀態` is two edits from `id`.
133
+ */
130
134
  export function closest(word, options) {
131
135
  let best;
132
136
  let bestDistance = Math.max(2, Math.floor(word.length / 3)) + 1;
133
137
  for (const option of options) {
134
138
  const d = distance(word.toLowerCase(), option.toLowerCase());
135
- if (d < bestDistance) {
139
+ if (d < bestDistance && d < Math.max(word.length, option.length)) {
136
140
  best = option;
137
141
  bestDistance = d;
138
142
  }
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A small schema language: one description that both validates a value and\n * renders as JSON Schema, so the published `schema.json` and the checks the\n * loader runs cannot disagree about what a configuration may say.\n */\n\ninterface Described {\n readonly description?: string;\n}\n\nexport type Schema =\n | (Described & {\n readonly type: 'string';\n readonly enum?: readonly string[];\n readonly nullable?: boolean;\n readonly minLength?: number;\n })\n | (Described & { readonly type: 'boolean' })\n | (Described & { readonly type: 'integer'; readonly minimum?: number; readonly maximum?: number })\n | (Described & { readonly type: 'array'; readonly items: Schema })\n | (Described & {\n readonly type: 'object';\n readonly properties: Readonly<Record<string, Schema>>;\n readonly required?: readonly string[];\n })\n | (Described & { readonly type: 'map'; readonly values: Schema })\n | (Described & { readonly type: 'anyOf'; readonly options: readonly Schema[] })\n | (Described & { readonly type: 'any' });\n\ntype Kind = 'null' | 'string' | 'boolean' | 'number' | 'array' | 'object';\n\nfunction kindOf(value: unknown): Kind {\n if (value === null) return 'null';\n if (Array.isArray(value)) return 'array';\n switch (typeof value) {\n case 'string':\n return 'string';\n case 'boolean':\n return 'boolean';\n case 'number':\n return 'number';\n default:\n return 'object';\n }\n}\n\nfunction accepts(schema: Schema, kind: Kind): boolean {\n switch (schema.type) {\n case 'string':\n return kind === 'string' || (kind === 'null' && schema.nullable === true);\n case 'boolean':\n return kind === 'boolean';\n case 'integer':\n return kind === 'number';\n case 'array':\n return kind === 'array';\n case 'object':\n case 'map':\n return kind === 'object';\n case 'anyOf':\n return schema.options.some((option) => accepts(option, kind));\n case 'any':\n return true;\n }\n}\n\nfunction noun(schema: Schema): string {\n switch (schema.type) {\n case 'string':\n return schema.nullable === true ? 'a string or null' : 'a string';\n case 'boolean':\n return 'true or false';\n case 'integer':\n return 'an integer';\n case 'array':\n return 'a list';\n case 'object':\n case 'map':\n return 'an object';\n case 'anyOf':\n return schema.options.map(noun).join(' or ');\n /* v8 ignore next 2 -- \"any\" accepts every value, so no message ever names it. */\n case 'any':\n return 'anything';\n }\n}\n\nfunction at(path: string, key: string): string {\n return path === '' ? key : `${path}.${key}`;\n}\n\n/** The problems with `value`, each naming the path to it. Empty when it conforms. */\nexport function validate(schema: Schema, value: unknown, path = ''): string[] {\n const where = path === '' ? 'the configuration' : `\"${path}\"`;\n if (!accepts(schema, kindOf(value))) return [`${where} must be ${noun(schema)}`];\n switch (schema.type) {\n case 'string': {\n if (typeof value !== 'string') return [];\n if (schema.enum !== undefined && !schema.enum.includes(value)) {\n return [`${where} must be one of ${schema.enum.map((e) => `\"${e}\"`).join(', ')}, not \"${value}\"`];\n }\n if (schema.minLength !== undefined && value.length < schema.minLength) return [`${where} must not be empty`];\n return [];\n }\n case 'integer': {\n const number = value as number;\n if (!Number.isInteger(number)) return [`${where} must be an integer`];\n if (schema.minimum !== undefined && number < schema.minimum) return [`${where} must be at least ${schema.minimum}`];\n if (schema.maximum !== undefined && number > schema.maximum) return [`${where} must be at most ${schema.maximum}`];\n return [];\n }\n case 'array':\n return (value as unknown[]).flatMap((item, i) => validate(schema.items, item, `${path}[${i}]`));\n case 'object': {\n const record = value as Record<string, unknown>;\n const known = Object.keys(schema.properties);\n const problems: string[] = [];\n for (const key of Object.keys(record)) {\n const property = schema.properties[key];\n if (property === undefined) {\n const guess = closest(key, known);\n problems.push(`\"${at(path, key)}\" is not a known key${guess === undefined ? '' : `; did you mean \"${guess}\"?`}`);\n continue;\n }\n problems.push(...validate(property, record[key], at(path, key)));\n }\n for (const key of schema.required ?? []) {\n if (!(key in record)) problems.push(`\"${at(path, key)}\" is required`);\n }\n return problems;\n }\n case 'map':\n return Object.entries(value as Record<string, unknown>).flatMap(([key, item]) =>\n validate(schema.values, item, at(path, key)),\n );\n case 'anyOf': {\n const candidates = schema.options.filter((option) => accepts(option, kindOf(value)));\n let first: string[] | undefined;\n for (const option of candidates) {\n const problems = validate(option, value, path);\n if (problems.length === 0) return [];\n first ??= problems;\n }\n return first ?? [];\n }\n case 'boolean':\n case 'any':\n return [];\n }\n}\n\n/** The known key nearest to a misspelling, when one is near enough to be what was meant. */\nexport function closest(word: string, options: readonly string[]): string | undefined {\n let best: string | undefined;\n let bestDistance = Math.max(2, Math.floor(word.length / 3)) + 1;\n for (const option of options) {\n const d = distance(word.toLowerCase(), option.toLowerCase());\n if (d < bestDistance) {\n best = option;\n bestDistance = d;\n }\n }\n return best;\n}\n\nfunction distance(a: string, b: string): number {\n let previous = Array.from({ length: b.length + 1 }, (_, j) => j);\n for (let i = 1; i <= a.length; i += 1) {\n const row = [i];\n for (let j = 1; j <= b.length; j += 1) {\n const cost = a.charAt(i - 1) === b.charAt(j - 1) ? 0 : 1;\n row.push(Math.min((previous[j] as number) + 1, (row[j - 1] as number) + 1, (previous[j - 1] as number) + cost));\n }\n previous = row;\n }\n return previous[b.length] as number;\n}\n\ntype Json = null | boolean | number | string | Json[] | { [key: string]: Json };\n\n/** Renders a schema as JSON Schema (draft 2020-12). */\nexport function toJsonSchema(schema: Schema): { [key: string]: Json } {\n const described = (body: { [key: string]: Json }): { [key: string]: Json } =>\n schema.description === undefined ? body : { description: schema.description, ...body };\n switch (schema.type) {\n case 'string': {\n const body: { [key: string]: Json } = { type: schema.nullable === true ? ['string', 'null'] : 'string' };\n if (schema.enum !== undefined) body['enum'] = [...schema.enum, ...(schema.nullable === true ? [null] : [])];\n if (schema.minLength !== undefined) body['minLength'] = schema.minLength;\n return described(body);\n }\n case 'boolean':\n return described({ type: 'boolean' });\n case 'integer': {\n const body: { [key: string]: Json } = { type: 'integer' };\n if (schema.minimum !== undefined) body['minimum'] = schema.minimum;\n if (schema.maximum !== undefined) body['maximum'] = schema.maximum;\n return described(body);\n }\n case 'array':\n return described({ type: 'array', items: toJsonSchema(schema.items) });\n case 'object': {\n const properties: { [key: string]: Json } = {};\n for (const [key, value] of Object.entries(schema.properties)) properties[key] = toJsonSchema(value);\n const body: { [key: string]: Json } = { type: 'object', properties, additionalProperties: false };\n if (schema.required !== undefined && schema.required.length > 0) body['required'] = [...schema.required];\n return described(body);\n }\n case 'map':\n return described({ type: 'object', additionalProperties: toJsonSchema(schema.values) });\n case 'anyOf':\n return described({ anyOf: schema.options.map(toJsonSchema) });\n case 'any':\n return described({});\n }\n}\n"]}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A small schema language: one description that both validates a value and\n * renders as JSON Schema, so the published `schema.json` and the checks the\n * loader runs cannot disagree about what a configuration may say.\n */\n\ninterface Described {\n readonly description?: string;\n}\n\nexport type Schema =\n | (Described & {\n readonly type: 'string';\n readonly enum?: readonly string[];\n readonly nullable?: boolean;\n readonly minLength?: number;\n })\n | (Described & { readonly type: 'boolean' })\n | (Described & { readonly type: 'integer'; readonly minimum?: number; readonly maximum?: number })\n | (Described & { readonly type: 'array'; readonly items: Schema })\n | (Described & {\n readonly type: 'object';\n readonly properties: Readonly<Record<string, Schema>>;\n readonly required?: readonly string[];\n })\n | (Described & { readonly type: 'map'; readonly values: Schema })\n | (Described & { readonly type: 'anyOf'; readonly options: readonly Schema[] })\n | (Described & { readonly type: 'any' });\n\ntype Kind = 'null' | 'string' | 'boolean' | 'number' | 'array' | 'object';\n\nfunction kindOf(value: unknown): Kind {\n if (value === null) return 'null';\n if (Array.isArray(value)) return 'array';\n switch (typeof value) {\n case 'string':\n return 'string';\n case 'boolean':\n return 'boolean';\n case 'number':\n return 'number';\n default:\n return 'object';\n }\n}\n\nfunction accepts(schema: Schema, kind: Kind): boolean {\n switch (schema.type) {\n case 'string':\n return kind === 'string' || (kind === 'null' && schema.nullable === true);\n case 'boolean':\n return kind === 'boolean';\n case 'integer':\n return kind === 'number';\n case 'array':\n return kind === 'array';\n case 'object':\n case 'map':\n return kind === 'object';\n case 'anyOf':\n return schema.options.some((option) => accepts(option, kind));\n case 'any':\n return true;\n }\n}\n\nfunction noun(schema: Schema): string {\n switch (schema.type) {\n case 'string':\n return schema.nullable === true ? 'a string or null' : 'a string';\n case 'boolean':\n return 'true or false';\n case 'integer':\n return 'an integer';\n case 'array':\n return 'a list';\n case 'object':\n case 'map':\n return 'an object';\n case 'anyOf':\n return schema.options.map(noun).join(' or ');\n /* v8 ignore next 2 -- \"any\" accepts every value, so no message ever names it. */\n case 'any':\n return 'anything';\n }\n}\n\nfunction at(path: string, key: string): string {\n return path === '' ? key : `${path}.${key}`;\n}\n\n/** The problems with `value`, each naming the path to it. Empty when it conforms. */\nexport function validate(schema: Schema, value: unknown, path = ''): string[] {\n const where = path === '' ? 'the configuration' : `\"${path}\"`;\n if (!accepts(schema, kindOf(value))) return [`${where} must be ${noun(schema)}`];\n switch (schema.type) {\n case 'string': {\n if (typeof value !== 'string') return [];\n if (schema.enum !== undefined && !schema.enum.includes(value)) {\n return [`${where} must be one of ${schema.enum.map((e) => `\"${e}\"`).join(', ')}, not \"${value}\"`];\n }\n if (schema.minLength !== undefined && value.length < schema.minLength) return [`${where} must not be empty`];\n return [];\n }\n case 'integer': {\n const number = value as number;\n if (!Number.isInteger(number)) return [`${where} must be an integer`];\n if (schema.minimum !== undefined && number < schema.minimum) return [`${where} must be at least ${schema.minimum}`];\n if (schema.maximum !== undefined && number > schema.maximum) return [`${where} must be at most ${schema.maximum}`];\n return [];\n }\n case 'array':\n return (value as unknown[]).flatMap((item, i) => validate(schema.items, item, `${path}[${i}]`));\n case 'object': {\n const record = value as Record<string, unknown>;\n const known = Object.keys(schema.properties);\n const problems: string[] = [];\n for (const key of Object.keys(record)) {\n const property = schema.properties[key];\n if (property === undefined) {\n const guess = closest(key, known);\n problems.push(`\"${at(path, key)}\" is not a known key${guess === undefined ? '' : `; did you mean \"${guess}\"?`}`);\n continue;\n }\n problems.push(...validate(property, record[key], at(path, key)));\n }\n for (const key of schema.required ?? []) {\n if (!(key in record)) problems.push(`\"${at(path, key)}\" is required`);\n }\n return problems;\n }\n case 'map':\n return Object.entries(value as Record<string, unknown>).flatMap(([key, item]) =>\n validate(schema.values, item, at(path, key)),\n );\n case 'anyOf': {\n const candidates = schema.options.filter((option) => accepts(option, kindOf(value)));\n let first: string[] | undefined;\n for (const option of candidates) {\n const problems = validate(option, value, path);\n if (problems.length === 0) return [];\n first ??= problems;\n }\n return first ?? [];\n }\n case 'boolean':\n case 'any':\n return [];\n }\n}\n\n/**\n * The known key nearest to a misspelling, when one is near enough to be what\n * was meant. One that keeps no character of the word is no misspelling of it,\n * however short both are: `狀態` is two edits from `id`.\n */\nexport function closest(word: string, options: readonly string[]): string | undefined {\n let best: string | undefined;\n let bestDistance = Math.max(2, Math.floor(word.length / 3)) + 1;\n for (const option of options) {\n const d = distance(word.toLowerCase(), option.toLowerCase());\n if (d < bestDistance && d < Math.max(word.length, option.length)) {\n best = option;\n bestDistance = d;\n }\n }\n return best;\n}\n\nfunction distance(a: string, b: string): number {\n let previous = Array.from({ length: b.length + 1 }, (_, j) => j);\n for (let i = 1; i <= a.length; i += 1) {\n const row = [i];\n for (let j = 1; j <= b.length; j += 1) {\n const cost = a.charAt(i - 1) === b.charAt(j - 1) ? 0 : 1;\n row.push(Math.min((previous[j] as number) + 1, (row[j - 1] as number) + 1, (previous[j - 1] as number) + cost));\n }\n previous = row;\n }\n return previous[b.length] as number;\n}\n\ntype Json = null | boolean | number | string | Json[] | { [key: string]: Json };\n\n/** Renders a schema as JSON Schema (draft 2020-12). */\nexport function toJsonSchema(schema: Schema): { [key: string]: Json } {\n const described = (body: { [key: string]: Json }): { [key: string]: Json } =>\n schema.description === undefined ? body : { description: schema.description, ...body };\n switch (schema.type) {\n case 'string': {\n const body: { [key: string]: Json } = { type: schema.nullable === true ? ['string', 'null'] : 'string' };\n if (schema.enum !== undefined) body['enum'] = [...schema.enum, ...(schema.nullable === true ? [null] : [])];\n if (schema.minLength !== undefined) body['minLength'] = schema.minLength;\n return described(body);\n }\n case 'boolean':\n return described({ type: 'boolean' });\n case 'integer': {\n const body: { [key: string]: Json } = { type: 'integer' };\n if (schema.minimum !== undefined) body['minimum'] = schema.minimum;\n if (schema.maximum !== undefined) body['maximum'] = schema.maximum;\n return described(body);\n }\n case 'array':\n return described({ type: 'array', items: toJsonSchema(schema.items) });\n case 'object': {\n const properties: { [key: string]: Json } = {};\n for (const [key, value] of Object.entries(schema.properties)) properties[key] = toJsonSchema(value);\n const body: { [key: string]: Json } = { type: 'object', properties, additionalProperties: false };\n if (schema.required !== undefined && schema.required.length > 0) body['required'] = [...schema.required];\n return described(body);\n }\n case 'map':\n return described({ type: 'object', additionalProperties: toJsonSchema(schema.values) });\n case 'anyOf':\n return described({ anyOf: schema.options.map(toJsonSchema) });\n case 'any':\n return described({});\n }\n}\n"]}
package/dist/scope.d.ts CHANGED
@@ -5,8 +5,9 @@
5
5
  * protection wins, so "all of `src/` but the schema" is written
6
6
  * `affectedFiles: [src/**]` with `protectedFiles: [src/db/schema.ts]`. Two
7
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).
8
+ * each, and in neither brief's protections - and the witness is such a file
9
+ * the tree holds, or else the shortest such path the search builds, as an
10
+ * example (ADR-0005).
10
11
  */
11
12
  import type { Brief } from './brief.js';
12
13
  import { type Glob, type LiteralReading } from './glob.js';
@@ -29,19 +30,40 @@ export declare function scopeOf(brief: Brief, reading: Reading): Scope;
29
30
  export interface Overlap {
30
31
  readonly patterns: readonly [string, string];
31
32
  readonly witness: string;
33
+ /**
34
+ * `true` when the witness is a file the tree holds; `false` when the tree
35
+ * holds none both patterns match outside the protections, and the witness
36
+ * is an example the search built; `null` when no tree was given.
37
+ */
38
+ readonly inTree: boolean | null;
32
39
  }
33
40
  export interface Meeting {
34
41
  /** Every pair of patterns that meets outside both protections, the first scope's pattern first. */
35
42
  readonly overlaps: readonly Overlap[];
36
- /** Pairs of patterns the search could not decide within its budget. */
43
+ /** Pairs of patterns the search could not decide within its budget, and no file of the tree decides. */
37
44
  readonly undecided: readonly (readonly [string, string])[];
38
45
  }
39
46
  /**
40
47
  * Where two writable scopes meet. Each pair of affected patterns is searched
41
48
  * for a file both cover that neither brief protects: a file one brief protects
42
49
  * is one that brief will not write, so it is no collision.
50
+ *
51
+ * The search names the shortest such path, which the tree need not hold:
52
+ * `src/Shop/OrderService.cs` where the tree has `src/Shop/Orders/OrderService.cs`.
53
+ * So where the search finds a meeting, or cannot decide one, the tree's files
54
+ * are read, and the first both patterns match outside the protections is the
55
+ * witness (ADR-0005, amended 2026-09-30). A file that exists and both briefs
56
+ * may write settles a pair the search could not: it is a collision, proved by
57
+ * the file, not a guess. Where the tree holds none, the search's path stands,
58
+ * as an example. A pair the search proves apart has no such file, and the tree
59
+ * is not read for it.
60
+ */
61
+ export declare function meet(a: Scope, b: Scope, budget?: number, files?: readonly string[] | null): Meeting;
62
+ /**
63
+ * A witness as a reader is told it: a file of the tree by its path, and a path
64
+ * the search built with a word that it is an example.
43
65
  */
44
- export declare function meet(a: Scope, b: Scope, budget?: number): Meeting;
66
+ export declare function witnessText(overlap: Overlap): string;
45
67
  export interface Contradiction {
46
68
  /** Affected patterns every file of which is protected: nothing of them is writable. */
47
69
  readonly covered: readonly string[];
package/dist/scope.js CHANGED
@@ -5,10 +5,11 @@
5
5
  * protection wins, so "all of `src/` but the schema" is written
6
6
  * `affectedFiles: [src/**]` with `protectedFiles: [src/db/schema.ts]`. Two
7
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).
8
+ * each, and in neither brief's protections - and the witness is such a file
9
+ * the tree holds, or else the shortest such path the search builds, as an
10
+ * example (ADR-0005).
10
11
  */
11
- import { globCovers, globWitness, parseGlob, WITNESS_BUDGET } from './glob.js';
12
+ import { globCovers, globWitness, matchGlob, parseGlob, WITNESS_BUDGET } from './glob.js';
12
13
  /** Patterns that parse, read with one reading. */
13
14
  export function patternsOf(patterns, reading) {
14
15
  return patterns.flatMap((pattern) => {
@@ -19,26 +20,81 @@ export function patternsOf(patterns, reading) {
19
20
  export function scopeOf(brief, reading) {
20
21
  return { brief, affected: patternsOf(brief.affectedFiles, reading), protected: patternsOf(brief.protectedFiles, reading) };
21
22
  }
23
+ const matched = new WeakMap();
24
+ /**
25
+ * The files of a tree a pattern matches, in the tree's order. Found once per
26
+ * pattern and tree, which every pair a brief is in shares; kept or not, the
27
+ * answer is the same, and only the time differs.
28
+ */
29
+ function matching(files, glob) {
30
+ let byGlob = matched.get(files);
31
+ if (byGlob === undefined) {
32
+ byGlob = new WeakMap();
33
+ matched.set(files, byGlob);
34
+ }
35
+ let found = byGlob.get(glob);
36
+ if (found === undefined) {
37
+ found = new Set(files.filter((file) => matchGlob(glob, file)));
38
+ byGlob.set(glob, found);
39
+ }
40
+ return found;
41
+ }
42
+ /**
43
+ * The first file of the tree, in its order, that both patterns match and no
44
+ * protection does. The engine's list is sorted, so the file named is the same
45
+ * on every run.
46
+ */
47
+ function heldInBoth(files, x, y, avoid) {
48
+ const other = matching(files, y);
49
+ for (const file of matching(files, x)) {
50
+ if (other.has(file) && !avoid.some((glob) => matchGlob(glob, file)))
51
+ return file;
52
+ }
53
+ return undefined;
54
+ }
22
55
  /**
23
56
  * Where two writable scopes meet. Each pair of affected patterns is searched
24
57
  * for a file both cover that neither brief protects: a file one brief protects
25
58
  * is one that brief will not write, so it is no collision.
59
+ *
60
+ * The search names the shortest such path, which the tree need not hold:
61
+ * `src/Shop/OrderService.cs` where the tree has `src/Shop/Orders/OrderService.cs`.
62
+ * So where the search finds a meeting, or cannot decide one, the tree's files
63
+ * are read, and the first both patterns match outside the protections is the
64
+ * witness (ADR-0005, amended 2026-09-30). A file that exists and both briefs
65
+ * may write settles a pair the search could not: it is a collision, proved by
66
+ * the file, not a guess. Where the tree holds none, the search's path stands,
67
+ * as an example. A pair the search proves apart has no such file, and the tree
68
+ * is not read for it.
26
69
  */
27
- export function meet(a, b, budget = WITNESS_BUDGET) {
70
+ export function meet(a, b, budget = WITNESS_BUDGET, files = null) {
28
71
  const avoid = [...a.protected, ...b.protected].map((p) => p.glob);
29
72
  const overlaps = [];
30
73
  const undecided = [];
31
74
  for (const x of a.affected) {
32
75
  for (const y of b.affected) {
33
76
  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]);
77
+ if (witness.kind === 'none')
78
+ continue;
79
+ const patterns = [x.pattern, y.pattern];
80
+ const held = files === null ? undefined : heldInBoth(files, x.glob, y.glob, avoid);
81
+ if (held !== undefined)
82
+ overlaps.push({ patterns, witness: held, inTree: true });
83
+ else if (witness.kind === 'found')
84
+ overlaps.push({ patterns, witness: witness.path, inTree: files === null ? null : false });
85
+ else
86
+ undecided.push(patterns);
38
87
  }
39
88
  }
40
89
  return { overlaps, undecided };
41
90
  }
91
+ /**
92
+ * A witness as a reader is told it: a file of the tree by its path, and a path
93
+ * the search built with a word that it is an example.
94
+ */
95
+ export function witnessText(overlap) {
96
+ return overlap.inTree === false ? `${overlap.witness}, an example not in the tree` : overlap.witness;
97
+ }
42
98
  /** The affected patterns a brief's own protections cover entirely. */
43
99
  export function contradictions(scope, budget = WITNESS_BUDGET) {
44
100
  const covered = [];
package/dist/scope.js.map CHANGED
@@ -1 +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"]}
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 is such a file\n * the tree holds, or else the shortest such path the search builds, as an\n * example (ADR-0005).\n */\n\nimport type { Brief } from './brief.js';\nimport { type Glob, globCovers, globWitness, type LiteralReading, matchGlob, 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 * `true` when the witness is a file the tree holds; `false` when the tree\n * holds none both patterns match outside the protections, and the witness\n * is an example the search built; `null` when no tree was given.\n */\n readonly inTree: boolean | null;\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, and no file of the tree decides. */\n readonly undecided: readonly (readonly [string, string])[];\n}\n\nconst matched = new WeakMap<readonly string[], WeakMap<Glob, ReadonlySet<string>>>();\n\n/**\n * The files of a tree a pattern matches, in the tree's order. Found once per\n * pattern and tree, which every pair a brief is in shares; kept or not, the\n * answer is the same, and only the time differs.\n */\nfunction matching(files: readonly string[], glob: Glob): ReadonlySet<string> {\n let byGlob = matched.get(files);\n if (byGlob === undefined) {\n byGlob = new WeakMap();\n matched.set(files, byGlob);\n }\n let found = byGlob.get(glob);\n if (found === undefined) {\n found = new Set(files.filter((file) => matchGlob(glob, file)));\n byGlob.set(glob, found);\n }\n return found;\n}\n\n/**\n * The first file of the tree, in its order, that both patterns match and no\n * protection does. The engine's list is sorted, so the file named is the same\n * on every run.\n */\nfunction heldInBoth(files: readonly string[], x: Glob, y: Glob, avoid: readonly Glob[]): string | undefined {\n const other = matching(files, y);\n for (const file of matching(files, x)) {\n if (other.has(file) && !avoid.some((glob) => matchGlob(glob, file))) return file;\n }\n return undefined;\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 *\n * The search names the shortest such path, which the tree need not hold:\n * `src/Shop/OrderService.cs` where the tree has `src/Shop/Orders/OrderService.cs`.\n * So where the search finds a meeting, or cannot decide one, the tree's files\n * are read, and the first both patterns match outside the protections is the\n * witness (ADR-0005, amended 2026-09-30). A file that exists and both briefs\n * may write settles a pair the search could not: it is a collision, proved by\n * the file, not a guess. Where the tree holds none, the search's path stands,\n * as an example. A pair the search proves apart has no such file, and the tree\n * is not read for it.\n */\nexport function meet(a: Scope, b: Scope, budget: number = WITNESS_BUDGET, files: readonly string[] | null = null): 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 === 'none') continue;\n const patterns = [x.pattern, y.pattern] as const;\n const held = files === null ? undefined : heldInBoth(files, x.glob, y.glob, avoid);\n if (held !== undefined) overlaps.push({ patterns, witness: held, inTree: true });\n else if (witness.kind === 'found') overlaps.push({ patterns, witness: witness.path, inTree: files === null ? null : false });\n else undecided.push(patterns);\n }\n }\n return { overlaps, undecided };\n}\n\n/**\n * A witness as a reader is told it: a file of the tree by its path, and a path\n * the search built with a word that it is an example.\n */\nexport function witnessText(overlap: Overlap): string {\n return overlap.inTree === false ? `${overlap.witness}, an example not in the tree` : overlap.witness;\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"]}
package/dist/trigger.js CHANGED
@@ -38,12 +38,68 @@ const WHEN = new Set([
38
38
  * its numbers: `2026-10-01` and `10/01/2026` are read a number at a time.
39
39
  */
40
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. */
41
+ /**
42
+ * Chinese is written without spaces, so a trigger in it is one run of Han
43
+ * characters that says when or what: `下個月` (next month), `2026年10月`,
44
+ * `月結完成後` (after the month-end close). A run says only when if taking out
45
+ * the words and characters below leaves nothing, Traditional and Simplified
46
+ * alike.
47
+ *
48
+ * The words come out first, since one may hold a character that alone says
49
+ * nothing about time: `稍後` (later) is `稍` and `後`, and `稍` is not on the
50
+ * list. A word whose every character is on the list, such as `之後`, needs no
51
+ * entry of its own.
52
+ */
53
+ const HAN_WHEN_WORDS = [
54
+ // A weekday, and the stretches: `星期一`, `禮拜三`, a quarter, a year, an hour, a minute.
55
+ ...['星期', '禮拜', '礼拜', '季度', '年度', '小時', '小时', '分鐘', '分钟'],
56
+ // Soon, later, one day, in the future, about.
57
+ ...['近期', '稍後', '稍后', '以後', '以后', '將來', '将来', '未來', '未来', '改天', '馬上', '马上', '很快', '不久', '左右'],
58
+ ];
59
+ /**
60
+ * Characters that say when, and the ones that join them. `時`, `期` and `每`
61
+ * are not among them: `到期時` (when it expires) and `每月結算完成時` are
62
+ * events, and so is a trigger that starts with `當` (when).
63
+ */
64
+ const HAN_WHEN_CHARACTERS = new Set([
65
+ // Units: year, month, day, week, quarter, a ten-day stretch, the day of the month, noon.
66
+ ...['年', '月', '日', '天', '週', '周', '季', '旬', '號', '号', '午'],
67
+ // Where in one: its start, end, middle, half.
68
+ ...['初', '底', '末', '中', '半'],
69
+ // Which one: this, next, last, the one before or after, early, late, the nth.
70
+ ...['今', '明', '去', '上', '下', '本', '這', '这', '次', '前', '後', '后', '早', '晚', '第', '個', '个'],
71
+ // How many.
72
+ ...['〇', '零', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '百', '兩', '两', '幾', '几'],
73
+ // The words that join them: at, in, until, of.
74
+ ...['在', '於', '于', '到', '的', '之'],
75
+ ]);
76
+ /** A run of Han characters, or of other letters and digits. */
77
+ const WORD = /\p{Script=Han}+|(?:(?!\p{Script=Han})[\p{L}\p{N}])+/gu;
78
+ /** A word is all Han or none of it, so one character says which. */
79
+ const HAN = /\p{Script=Han}/u;
80
+ /**
81
+ * The words of a trigger, in lower case: runs of letters and digits, with Han
82
+ * characters in runs of their own, so that `10月` is `10` and `月`. `mid-2027`
83
+ * is two words, and punctuation none. Read in NFKC first, so that full-width
84
+ * `Q3` is `q3` and `2026` is `2026`.
85
+ */
42
86
  function words(text) {
43
- return text.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
87
+ return text.normalize('NFKC').toLowerCase().match(WORD) ?? [];
88
+ }
89
+ /** Whether a run of Han characters says only when. */
90
+ function hanSaysWhen(run) {
91
+ let rest = run;
92
+ for (const word of HAN_WHEN_WORDS)
93
+ rest = rest.replaceAll(word, '');
94
+ return [...rest].every((character) => HAN_WHEN_CHARACTERS.has(character));
95
+ }
96
+ function saysWhen(word) {
97
+ if (HAN.test(word))
98
+ return hanSaysWhen(word);
99
+ return WHEN.has(word) || DATE.test(word);
44
100
  }
45
101
  /** Whether a trigger has a word that says what must happen, rather than only when. */
46
102
  export function namesAnEvent(trigger) {
47
- return words(trigger).some((word) => !WHEN.has(word) && !DATE.test(word));
103
+ return words(trigger).some((word) => !saysWhen(word));
48
104
  }
49
105
  //# sourceMappingURL=trigger.js.map
@@ -1 +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"]}
1
+ {"version":3,"file":"trigger.js","sourceRoot":"","sources":["../src/trigger.ts"],"names":[],"mappings":"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","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/**\n * Chinese is written without spaces, so a trigger in it is one run of Han\n * characters that says when or what: `下個月` (next month), `2026年10月`,\n * `月結完成後` (after the month-end close). A run says only when if taking out\n * the words and characters below leaves nothing, Traditional and Simplified\n * alike.\n *\n * The words come out first, since one may hold a character that alone says\n * nothing about time: `稍後` (later) is `稍` and `後`, and `稍` is not on the\n * list. A word whose every character is on the list, such as `之後`, needs no\n * entry of its own.\n */\nconst HAN_WHEN_WORDS: readonly string[] = [\n // A weekday, and the stretches: `星期一`, `禮拜三`, a quarter, a year, an hour, a minute.\n ...['星期', '禮拜', '礼拜', '季度', '年度', '小時', '小时', '分鐘', '分钟'],\n // Soon, later, one day, in the future, about.\n ...['近期', '稍後', '稍后', '以後', '以后', '將來', '将来', '未來', '未来', '改天', '馬上', '马上', '很快', '不久', '左右'],\n];\n\n/**\n * Characters that say when, and the ones that join them. `時`, `期` and `每`\n * are not among them: `到期時` (when it expires) and `每月結算完成時` are\n * events, and so is a trigger that starts with `當` (when).\n */\nconst HAN_WHEN_CHARACTERS = new Set([\n // Units: year, month, day, week, quarter, a ten-day stretch, the day of the month, noon.\n ...['年', '月', '日', '天', '週', '周', '季', '旬', '號', '号', '午'],\n // Where in one: its start, end, middle, half.\n ...['初', '底', '末', '中', '半'],\n // Which one: this, next, last, the one before or after, early, late, the nth.\n ...['今', '明', '去', '上', '下', '本', '這', '这', '次', '前', '後', '后', '早', '晚', '第', '個', '个'],\n // How many.\n ...['〇', '零', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '百', '兩', '两', '幾', '几'],\n // The words that join them: at, in, until, of.\n ...['在', '於', '于', '到', '的', '之'],\n]);\n\n/** A run of Han characters, or of other letters and digits. */\nconst WORD = /\\p{Script=Han}+|(?:(?!\\p{Script=Han})[\\p{L}\\p{N}])+/gu;\n\n/** A word is all Han or none of it, so one character says which. */\nconst HAN = /\\p{Script=Han}/u;\n\n/**\n * The words of a trigger, in lower case: runs of letters and digits, with Han\n * characters in runs of their own, so that `10月` is `10` and `月`. `mid-2027`\n * is two words, and punctuation none. Read in NFKC first, so that full-width\n * `Q3` is `q3` and `2026` is `2026`.\n */\nfunction words(text: string): readonly string[] {\n return text.normalize('NFKC').toLowerCase().match(WORD) ?? [];\n}\n\n/** Whether a run of Han characters says only when. */\nfunction hanSaysWhen(run: string): boolean {\n let rest = run;\n for (const word of HAN_WHEN_WORDS) rest = rest.replaceAll(word, '');\n return [...rest].every((character) => HAN_WHEN_CHARACTERS.has(character));\n}\n\nfunction saysWhen(word: string): boolean {\n if (HAN.test(word)) return hanSaysWhen(word);\n return WHEN.has(word) || DATE.test(word);\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) => !saysWhen(word));\n}\n"]}
package/dist/types.d.ts CHANGED
@@ -36,4 +36,13 @@ export interface Finding {
36
36
  readonly path?: string | undefined;
37
37
  /** The paths of a finding about several, such as the files a round changed outside its scope. */
38
38
  readonly paths?: readonly string[] | undefined;
39
+ /**
40
+ * What the finding is about within its brief, where a rule can report more
41
+ * than one thing there: a section, a pattern, a dependency, the other brief
42
+ * of a pair. With the rule, the file and the brief it is the finding's
43
+ * identity, which the GitLab fingerprint is built from, so it holds no
44
+ * count, no witness and nothing else a message says that can change while
45
+ * the problem stays.
46
+ */
47
+ readonly subject?: string | undefined;
39
48
  }
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` 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"]}
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 * What the finding is about within its brief, where a rule can report more\n * than one thing there: a section, a pattern, a dependency, the other brief\n * of a pair. With the rule, the file and the brief it is the finding's\n * identity, which the GitLab fingerprint is built from, so it holds no\n * count, no witness and nothing else a message says that can change while\n * the problem stays.\n */\n readonly subject?: string | undefined;\n}\n"]}