@descent-vtt/spec-brief 0.4.0 → 0.5.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 +47 -0
  2. package/README.md +1 -0
  3. package/dist/archive.js +31 -18
  4. package/dist/archive.js.map +1 -1
  5. package/dist/brief.js +8 -5
  6. package/dist/brief.js.map +1 -1
  7. package/dist/cli.js +21 -13
  8. package/dist/cli.js.map +1 -1
  9. package/dist/collisions.d.ts +9 -0
  10. package/dist/collisions.js +50 -40
  11. package/dist/collisions.js.map +1 -1
  12. package/dist/config.d.ts +6 -0
  13. package/dist/config.js +17 -6
  14. package/dist/config.js.map +1 -1
  15. package/dist/corpus.js +21 -15
  16. package/dist/corpus.js.map +1 -1
  17. package/dist/engine.d.ts +2 -1
  18. package/dist/engine.js +24 -15
  19. package/dist/engine.js.map +1 -1
  20. package/dist/frontmatter.js +1 -1
  21. package/dist/frontmatter.js.map +1 -1
  22. package/dist/fs.js +9 -5
  23. package/dist/fs.js.map +1 -1
  24. package/dist/git.js +7 -4
  25. package/dist/git.js.map +1 -1
  26. package/dist/glob.d.ts +8 -0
  27. package/dist/glob.js +74 -0
  28. package/dist/glob.js.map +1 -1
  29. package/dist/links.js +2 -1
  30. package/dist/links.js.map +1 -1
  31. package/dist/lint.js +3 -4
  32. package/dist/lint.js.map +1 -1
  33. package/dist/plugins.js +10 -6
  34. package/dist/plugins.js.map +1 -1
  35. package/dist/report.d.ts +8 -1
  36. package/dist/report.js +22 -20
  37. package/dist/report.js.map +1 -1
  38. package/dist/rules.js +10 -6
  39. package/dist/rules.js.map +1 -1
  40. package/dist/scaffold.js +2 -2
  41. package/dist/scaffold.js.map +1 -1
  42. package/dist/schedule.js +10 -20
  43. package/dist/schedule.js.map +1 -1
  44. package/dist/schema.js +9 -13
  45. package/dist/schema.js.map +1 -1
  46. package/dist/scope.js +2 -2
  47. package/dist/scope.js.map +1 -1
  48. package/dist/text.d.ts +4 -1
  49. package/dist/text.js +10 -6
  50. package/dist/text.js.map +1 -1
  51. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Configuration: what it may say, what it says when it says nothing, and how a\n * file becomes a `Config`.\n *\n * Every repository that already keeps briefs keeps them its own way - where\n * they live, what the sections are called, which word means \"done\" - so the\n * conventions are data here rather than code. The defaults describe a brief\n * with the four parts every round needs: an intent, a negative scope, what the\n * round is not empowered to touch, and the invariants that prove it is done.\n *\n * A configuration that does not load stops the run. A typo that silently\n * falls back to defaults produces a clean-looking report about the wrong\n * rules, which is worse than no report.\n */\n\nimport { dirname, join } from 'node:path';\n\nimport { parseGlob } from './glob.js';\nimport { normalisePath } from './links.js';\nimport { type Schema, toJsonSchema, validate } from './schema.js';\nimport { inWords, templateHoles } from './text.js';\nimport type { SeveritySetting } from './types.js';\n\nexport interface SectionRule {\n readonly name: string;\n readonly aliases: readonly string[];\n /** Literal text the section must contain, compared exactly. */\n readonly mustContain: readonly string[];\n /** The section must hold at least one task item. */\n readonly checklist: boolean;\n readonly optional: boolean;\n /**\n * What the section must answer. A new brief carries it as a comment under\n * the heading, a finding that the section is missing or unwritten carries it\n * as its hint, and `list` and `lint` report it, so a tool that helps write a\n * brief can ask for each section without knowing its name in advance.\n */\n readonly hint?: string | undefined;\n}\n\nexport interface PluginReference {\n readonly module: string;\n readonly options: unknown;\n}\n\nexport interface Config {\n /** Directory of live briefs, relative to the root. */\n readonly briefs: string;\n /** Directory of archived briefs, relative to the root. */\n readonly archive: string;\n /** Glob a file name must match to be a brief. */\n readonly files: string;\n /** File-name globs that are never briefs, such as an index. */\n readonly exclude: readonly string[];\n /** A template for `new`, relative to the root; `null` builds one from `sections`. */\n readonly template: string | null;\n readonly id: {\n readonly source: 'filename' | 'frontmatter';\n readonly separator: string;\n readonly digits: number;\n };\n readonly status: {\n readonly field: string | null;\n readonly draft: string | null;\n readonly active: string;\n /** The word for work put off until an event its `trigger` names; `null` when a repository has none. */\n readonly deferred: string | null;\n readonly archived: string;\n };\n readonly sections: readonly SectionRule[];\n readonly sectionOrder: boolean;\n readonly types: Readonly<Record<string, readonly SectionRule[]>>;\n readonly placeholders: readonly string[];\n readonly fields: readonly string[];\n readonly archiving: {\n readonly tasks: 'all' | readonly string[];\n readonly dispositions: readonly string[];\n readonly banner: readonly string[];\n readonly rewriteLinks: boolean;\n readonly freeze: boolean;\n readonly base: string | null;\n };\n readonly rules: Readonly<Record<string, SeveritySetting>>;\n readonly plugins: readonly PluginReference[];\n}\n\nexport const CONFIG_FILES: readonly string[] = ['.spec-brief.json', 'spec-brief.json'];\n\n/** The holes a banner line may use. */\nexport const BANNER_PLACEHOLDERS: readonly string[] = [\n 'date',\n 'summary',\n 'pr',\n 'commit',\n 'diffstat',\n 'links',\n 'id',\n 'title',\n 'author',\n];\n\nexport const SCHEMA_URL = 'https://raw.githubusercontent.com/DescentVTT/spec-brief/main/schema.json';\n\n/**\n * The `$schema` `init` writes, the path the README recommends: the schema of\n * the version installed, which is the one that loads the file. `SCHEMA_URL`\n * follows main, and would flag a key the installed version accepts, or pass\n * one it refuses, as soon as the two differ.\n */\nexport const SCHEMA_PATH = './node_modules/@descent-vtt/spec-brief/schema.json';\n\nconst SEVERITY: Schema = { type: 'string', enum: ['off', 'note', 'warning', 'error'] };\nconst STRINGS: Schema = { type: 'array', items: { type: 'string', minLength: 1 } };\n\nconst SECTION: Schema = {\n type: 'anyOf',\n description: 'A section name, or a section with aliases and content rules.',\n options: [\n { type: 'string', minLength: 1 },\n {\n type: 'object',\n required: ['name'],\n properties: {\n name: { type: 'string', minLength: 1, description: 'The heading text, compared without case or punctuation.' },\n aliases: { ...STRINGS, description: 'Other headings that fill this section.' },\n mustContain: { ...STRINGS, description: 'Literal text the section must contain.' },\n checklist: { type: 'boolean', description: 'The section must hold at least one \"- [ ]\" task item.' },\n optional: { type: 'boolean', description: 'Checked when present, not required.' },\n hint: {\n type: 'string',\n minLength: 1,\n description: 'What the section must answer: a comment under the heading in a new brief, and the hint of a finding that it is missing or unwritten.',\n },\n },\n },\n ],\n};\n\nexport const CONFIG_SCHEMA: Schema = {\n type: 'object',\n properties: {\n $schema: { type: 'string' },\n briefs: { type: 'string', minLength: 1, description: 'Directory of live briefs, relative to this file.' },\n archive: { type: 'string', minLength: 1, description: 'Directory of archived briefs, relative to this file.' },\n files: { type: 'string', minLength: 1, description: 'Glob a file name must match to be a brief.' },\n exclude: { ...STRINGS, description: 'File-name globs that are never briefs.' },\n template: { type: 'string', nullable: true, description: 'Template file for \"new\"; null builds one from the sections.' },\n id: {\n type: 'object',\n properties: {\n source: { type: 'string', enum: ['filename', 'frontmatter'], description: 'Where a brief id comes from.' },\n separator: { type: 'string', minLength: 1, description: 'Separates the id from the slug in a file name.' },\n digits: { type: 'integer', minimum: 1, maximum: 12, description: 'Width of an allocated numeric id.' },\n },\n },\n status: {\n type: 'object',\n properties: {\n field: { type: 'string', nullable: true, description: 'Front-matter key holding the status; null uses location only.' },\n draft: { type: 'string', nullable: true, description: 'The word for a brief still being written, or null.' },\n active: { type: 'string', minLength: 1, description: 'The word for a live brief.' },\n deferred: {\n type: 'string',\n nullable: true,\n description: 'The word for a live brief put off until the event its \"trigger\" names, or null. A deferred brief is never ready and runs in no wave.',\n },\n archived: { type: 'string', minLength: 1, description: 'The word for an archived brief.' },\n },\n },\n sections: { type: 'array', items: SECTION, description: 'Sections every live brief carries.' },\n sectionOrder: { type: 'boolean', description: 'Sections must appear in the order listed.' },\n types: {\n type: 'map',\n description: 'Brief types, each with the sections it adds.',\n values: { type: 'object', properties: { sections: { type: 'array', items: SECTION } } },\n },\n placeholders: { ...STRINGS, description: 'Words that mark a section as unwritten.' },\n fields: { ...STRINGS, description: 'Front-matter keys this repository uses beyond the built-in ones.' },\n archiving: {\n type: 'object',\n properties: {\n tasks: {\n type: 'anyOf',\n description: '\"all\", or the sections whose task items must be closed before archiving.',\n options: [{ type: 'string', enum: ['all'] }, STRINGS],\n },\n dispositions: { ...STRINGS, description: 'Text under an open box that closes it without a tick.' },\n banner: {\n type: 'array',\n items: { type: 'string' },\n description: 'Lines of the frozen banner; a line with an empty {placeholder} is left out, and an empty line separates paragraphs.',\n },\n rewriteLinks: { type: 'boolean', description: 'Rewrite relative links so they resolve from the new directory.' },\n freeze: { type: 'boolean', description: 'Record a content hash so later edits are caught.' },\n base: { type: 'string', nullable: true, description: 'Branch the diff is measured from, such as \"main\".' },\n },\n },\n rules: { type: 'map', values: SEVERITY, description: 'Severity per rule id.' },\n plugins: {\n type: 'array',\n description: 'Modules that contribute rules.',\n items: {\n type: 'anyOf',\n options: [\n { type: 'string', minLength: 1 },\n {\n type: 'object',\n required: ['module'],\n properties: { module: { type: 'string', minLength: 1 }, options: { type: 'any' } },\n },\n ],\n },\n },\n },\n};\n\nfunction section(name: string, extra: Partial<Omit<SectionRule, 'name'>> = {}): SectionRule {\n return { name, aliases: [], mustContain: [], checklist: false, optional: false, ...extra };\n}\n\nexport const DEFAULT_CONFIG: Config = {\n briefs: 'briefs',\n archive: 'briefs/archive',\n files: '[0-9]*.md',\n exclude: [],\n template: null,\n id: { source: 'filename', separator: '_', digits: 3 },\n status: { field: 'status', draft: 'draft', active: 'active', deferred: 'deferred', archived: 'archived' },\n sections: [\n section('Intent', {\n aliases: [\"Commander's Intent\", 'Mission', 'Objective'],\n hint: 'The state of the tree when this round is done, and why it matters. One paragraph.',\n }),\n section('Negative Scope', {\n aliases: ['Out of Scope', 'Non-Goals', 'Not in Scope', 'What this round is NOT', 'What this brief does NOT do'],\n hint: 'What this round must not do, even where it would look helpful.',\n }),\n section('Not Empowered', {\n aliases: ['Non-Empowerment', 'Non-Empowerment List'],\n optional: true,\n hint: 'Files, interfaces and decisions this round may not change.',\n }),\n section('Invariants', {\n aliases: ['Invariant Checklist', 'Definition of Done'],\n checklist: true,\n hint: 'Checks that must hold before the round is called done, each one a task item.',\n }),\n ],\n sectionOrder: false,\n types: {\n feature: [section('Acceptance Criteria', { checklist: true, hint: 'What a reviewer checks to accept the result, each one a task item.' })],\n defect: [\n section('The Defect, Measured', {\n aliases: ['Reproduction'],\n hint: 'How to reproduce the defect, and the measurement that shows it.',\n }),\n ],\n refactor: [],\n chore: [],\n },\n placeholders: ['TBD', 'TBA', 'TODO', 'FIXME', 'XXX', '???', '...', '\\u2026'],\n fields: [],\n archiving: {\n tasks: 'all',\n dispositions: ['**Delegated', '**Accepted debt', '**Rejected'],\n banner: [\n '**Archived {date}.**',\n '{summary}',\n 'Merged in pull request {pr}.',\n 'Recorded at commit `{commit}`: {diffstat}.',\n '{links}',\n 'The body below describes the tree before execution and is not maintained.',\n ],\n rewriteLinks: true,\n freeze: true,\n base: null,\n },\n rules: {},\n plugins: [],\n};\n\ntype Raw = Record<string, unknown>;\n\nfunction sectionFrom(raw: unknown): SectionRule {\n if (typeof raw === 'string') return section(raw);\n const r = raw as Raw;\n return section(r['name'] as string, {\n aliases: (r['aliases'] as string[] | undefined) ?? [],\n mustContain: (r['mustContain'] as string[] | undefined) ?? [],\n checklist: (r['checklist'] as boolean | undefined) ?? false,\n optional: (r['optional'] as boolean | undefined) ?? false,\n hint: r['hint'] as string | undefined,\n });\n}\n\nfunction pluginFrom(raw: unknown): PluginReference {\n if (typeof raw === 'string') return { module: raw, options: undefined };\n const r = raw as Raw;\n return { module: r['module'] as string, options: r['options'] };\n}\n\nexport class ConfigError extends Error {\n readonly problems: readonly string[];\n\n constructor(file: string, problems: readonly string[]) {\n super(`${file}: ${problems.join('; ')}`);\n this.name = 'ConfigError';\n this.problems = problems;\n }\n}\n\n/**\n * Builds a configuration from parsed JSON. Objects merge one level deep over\n * the defaults; lists and `types` replace them, because a repository that\n * names its own sections means those sections and not the defaults as well.\n */\nexport function resolveConfig(raw: unknown, file = 'configuration'): Config {\n const problems = validate(CONFIG_SCHEMA, raw);\n if (problems.length > 0) throw new ConfigError(file, problems);\n const r = raw as Raw;\n const d = DEFAULT_CONFIG;\n const merged = <T extends object>(key: string, base: T): T => ({ ...base, ...((r[key] as Partial<T>) ?? {}) });\n const archiving = merged('archiving', d.archiving);\n const config: Config = {\n briefs: (r['briefs'] as string | undefined) ?? d.briefs,\n archive: (r['archive'] as string | undefined) ?? (r['briefs'] === undefined ? d.archive : `${r['briefs'] as string}/archive`),\n files: (r['files'] as string | undefined) ?? d.files,\n exclude: (r['exclude'] as string[] | undefined) ?? d.exclude,\n template: r['template'] === undefined ? d.template : (r['template'] as string | null),\n id: merged('id', d.id),\n status: merged('status', d.status),\n sections: r['sections'] === undefined ? d.sections : (r['sections'] as unknown[]).map(sectionFrom),\n sectionOrder: (r['sectionOrder'] as boolean | undefined) ?? d.sectionOrder,\n types:\n r['types'] === undefined\n ? d.types\n : Object.fromEntries(\n Object.entries(r['types'] as Record<string, Raw>).map(([name, type]) => [\n name,\n ((type['sections'] as unknown[] | undefined) ?? []).map(sectionFrom),\n ]),\n ),\n placeholders: (r['placeholders'] as string[] | undefined) ?? d.placeholders,\n fields: (r['fields'] as string[] | undefined) ?? d.fields,\n archiving,\n rules: { ...d.rules, ...((r['rules'] as Record<string, SeveritySetting> | undefined) ?? {}) },\n plugins: ((r['plugins'] as unknown[] | undefined) ?? []).map(pluginFrom),\n };\n const semantic = checkConfig(config);\n if (semantic.length > 0) throw new ConfigError(file, semantic);\n return config;\n}\n\n/** The status words a repository uses, in lifecycle order. */\nexport function statusWords(config: Config): string[] {\n const { draft, active, deferred, archived } = config.status;\n return [draft, active, deferred, archived].filter((w) => w !== null);\n}\n\n/** What the schema cannot say: relationships between values. */\nfunction checkConfig(config: Config): string[] {\n const problems: string[] = [];\n const inside = (key: string, path: string): string | null => {\n try {\n return normalisePath(path);\n } catch {\n problems.push(`\"${key}\" must be a directory inside the repository, not \"${path}\"`);\n return null;\n }\n };\n const briefs = inside('briefs', config.briefs);\n const archive = inside('archive', config.archive);\n if (briefs !== null && briefs === archive) problems.push('\"briefs\" and \"archive\" must be different directories');\n // Named by the statuses that share a word: a 0.1 configuration that spelt\n // another status \"deferred\" meets the default for the new one here.\n const sharing = new Map<string, { word: string; keys: string[] }>();\n for (const key of ['draft', 'active', 'deferred', 'archived'] as const) {\n const word = config.status[key];\n if (word === null) continue;\n const group = sharing.get(word.toLowerCase()) ?? { word, keys: [] };\n group.keys.push(key);\n sharing.set(word.toLowerCase(), group);\n }\n for (const { word, keys } of sharing.values()) {\n if (keys.length < 2) continue;\n const orNull = keys.includes('deferred') ? ', or set \"status.deferred\" to null where no work is deferred' : '';\n problems.push(`the status words must differ: \"${word}\" is the word for ${inWords(keys)}; give ${keys.length === 2 ? 'one of them' : 'all but one'} another word${orNull}`);\n }\n // A pattern that does not parse would match nothing, and a run over no briefs reads as clean.\n for (const [key, pattern] of [['files', config.files], ...config.exclude.map((e) => ['exclude', e] as const)] as const) {\n const parsed = parseGlob(pattern);\n if (!parsed.ok) problems.push(`\"${key}\" pattern \"${pattern}\": ${parsed.error}`);\n }\n // An unknown hole is never filled, and a line with an unfilled hole is left\n // out: a misspelt placeholder would silently drop its whole line.\n for (const line of config.archiving.banner) {\n for (const hole of templateHoles(line)) {\n if (!BANNER_PLACEHOLDERS.includes(hole)) {\n problems.push(`\"archiving.banner\" uses {${hole}}; the placeholders are ${BANNER_PLACEHOLDERS.map((p) => `{${p}}`).join(', ')}`);\n }\n }\n }\n return problems;\n}\n\n/** Parses the text of a configuration file. */\nexport function parseConfig(text: string, file: string): Config {\n let raw: unknown;\n try {\n raw = JSON.parse(text.charCodeAt(0) === 0xfeff ? text.slice(1) : text);\n } catch (error) {\n throw new ConfigError(file, [`is not valid JSON (${(error as Error).message})`]);\n }\n return resolveConfig(raw, file);\n}\n\n/**\n * The configuration file for a directory: the nearest one at or above it.\n * `exists` is injected so discovery needs no filesystem of its own.\n */\nexport async function locateConfig(\n start: string,\n exists: (path: string) => Promise<boolean>,\n): Promise<string | undefined> {\n let directory = start;\n // Bounded by the depth of the path: each pass moves one directory up.\n for (let depth = 0; depth < 256; depth += 1) {\n const found: string[] = [];\n for (const name of CONFIG_FILES) {\n const candidate = join(directory, name);\n if (await exists(candidate)) found.push(candidate);\n }\n if (found.length > 1) {\n throw new ConfigError(directory, [`holds both ${CONFIG_FILES.join(' and ')}; keep one`]);\n }\n if (found[0] !== undefined) return found[0];\n const parent = dirname(directory);\n if (parent === directory) return undefined;\n directory = parent;\n }\n /* v8 ignore next -- no path is 256 directories deep; the bound exists so the loop ends by construction. */\n return undefined;\n}\n\n/** The JSON Schema published as `schema.json`. */\nexport function configJsonSchema(): Record<string, unknown> {\n return {\n $schema: 'https://json-schema.org/draft/2020-12/schema',\n $id: SCHEMA_URL,\n title: 'spec-brief configuration',\n ...toJsonSchema(CONFIG_SCHEMA),\n };\n}\n\n/** The configuration `init` writes: every default spelled out, so it can be edited rather than looked up. */\nexport function initialConfig(briefs: string, archive: string): Record<string, unknown> {\n const d = DEFAULT_CONFIG;\n const sectionJson = (s: SectionRule): unknown => {\n const out: Record<string, unknown> = { name: s.name };\n if (s.aliases.length > 0) out['aliases'] = s.aliases;\n if (s.mustContain.length > 0) out['mustContain'] = s.mustContain;\n if (s.checklist) out['checklist'] = true;\n if (s.optional) out['optional'] = true;\n if (s.hint !== undefined) out['hint'] = s.hint;\n return Object.keys(out).length === 1 ? s.name : out;\n };\n return {\n $schema: SCHEMA_PATH,\n briefs,\n archive,\n files: d.files,\n id: d.id,\n status: d.status,\n sections: d.sections.map(sectionJson),\n types: Object.fromEntries(Object.entries(d.types).map(([k, v]) => [k, { sections: v.map(sectionJson) }])),\n archiving: {\n tasks: d.archiving.tasks,\n dispositions: d.archiving.dispositions,\n banner: d.archiving.banner,\n base: d.archiving.base,\n },\n };\n}\n"]}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Configuration: what it may say, what it says when it says nothing, and how a\n * file becomes a `Config`.\n *\n * Every repository that already keeps briefs keeps them its own way - where\n * they live, what the sections are called, which word means \"done\" - so the\n * conventions are data here rather than code. The defaults describe a brief\n * with the four parts every round needs: an intent, a negative scope, what the\n * round is not empowered to touch, and the invariants that prove it is done.\n *\n * A configuration that does not load stops the run. A typo that silently\n * falls back to defaults produces a clean-looking report about the wrong\n * rules, which is worse than no report.\n */\n\nimport { dirname, join } from 'node:path';\n\nimport { parseGlob } from './glob.js';\nimport { normalisePath } from './links.js';\nimport { type Schema, toJsonSchema, validate } from './schema.js';\nimport { inWords, templateHoles } from './text.js';\nimport type { SeveritySetting } from './types.js';\n\nexport interface SectionRule {\n readonly name: string;\n readonly aliases: readonly string[];\n /** Literal text the section must contain, compared exactly. */\n readonly mustContain: readonly string[];\n /** The section must hold at least one task item. */\n readonly checklist: boolean;\n readonly optional: boolean;\n /**\n * What the section must answer. A new brief carries it as a comment under\n * the heading, a finding that the section is missing or unwritten carries it\n * as its hint, and `list` and `lint` report it, so a tool that helps write a\n * brief can ask for each section without knowing its name in advance.\n */\n readonly hint?: string | undefined;\n}\n\nexport interface PluginReference {\n readonly module: string;\n readonly options: unknown;\n}\n\nexport interface Config {\n /** Directory of live briefs, relative to the root. */\n readonly briefs: string;\n /** Directory of archived briefs, relative to the root. */\n readonly archive: string;\n /** Glob a file name must match to be a brief. */\n readonly files: string;\n /** File-name globs that are never briefs, such as an index. */\n readonly exclude: readonly string[];\n /** A template for `new`, relative to the root; `null` builds one from `sections`. */\n readonly template: string | null;\n readonly id: {\n readonly source: 'filename' | 'frontmatter';\n readonly separator: string;\n readonly digits: number;\n };\n readonly status: {\n readonly field: string | null;\n readonly draft: string | null;\n readonly active: string;\n /** The word for work put off until an event its `trigger` names; `null` when a repository has none. */\n readonly deferred: string | null;\n readonly archived: string;\n };\n readonly sections: readonly SectionRule[];\n readonly sectionOrder: boolean;\n readonly types: Readonly<Record<string, readonly SectionRule[]>>;\n readonly placeholders: readonly string[];\n readonly fields: readonly string[];\n readonly archiving: {\n readonly tasks: 'all' | readonly string[];\n readonly dispositions: readonly string[];\n readonly banner: readonly string[];\n readonly rewriteLinks: boolean;\n readonly freeze: boolean;\n readonly base: string | null;\n };\n readonly rules: Readonly<Record<string, SeveritySetting>>;\n readonly plugins: readonly PluginReference[];\n}\n\nexport const CONFIG_FILES: readonly string[] = ['.spec-brief.json', 'spec-brief.json'];\n\n/** The holes a banner line may use. */\nexport const BANNER_PLACEHOLDERS: readonly string[] = [\n 'date',\n 'summary',\n 'pr',\n 'commit',\n 'diffstat',\n 'links',\n 'id',\n 'title',\n 'author',\n];\n\nexport const SCHEMA_URL = 'https://raw.githubusercontent.com/DescentVTT/spec-brief/main/schema.json';\n\n/**\n * The `$schema` `init` writes, the path the README recommends: the schema of\n * the version installed, which is the one that loads the file. `SCHEMA_URL`\n * follows main, and would flag a key the installed version accepts, or pass\n * one it refuses, as soon as the two differ.\n */\nexport const SCHEMA_PATH = './node_modules/@descent-vtt/spec-brief/schema.json';\n\nconst SEVERITY: Schema = { type: 'string', enum: ['off', 'note', 'warning', 'error'] };\nconst STRINGS: Schema = { type: 'array', items: { type: 'string', minLength: 1 } };\n\nconst SECTION: Schema = {\n type: 'anyOf',\n description: 'A section name, or a section with aliases and content rules.',\n options: [\n { type: 'string', minLength: 1 },\n {\n type: 'object',\n required: ['name'],\n properties: {\n name: { type: 'string', minLength: 1, description: 'The heading text, compared without case or punctuation.' },\n aliases: { ...STRINGS, description: 'Other headings that fill this section.' },\n mustContain: { ...STRINGS, description: 'Literal text the section must contain.' },\n checklist: { type: 'boolean', description: 'The section must hold at least one \"- [ ]\" task item.' },\n optional: { type: 'boolean', description: 'Checked when present, not required.' },\n hint: {\n type: 'string',\n minLength: 1,\n description: 'What the section must answer: a comment under the heading in a new brief, and the hint of a finding that it is missing or unwritten.',\n },\n },\n },\n ],\n};\n\nexport const CONFIG_SCHEMA: Schema = {\n type: 'object',\n properties: {\n $schema: { type: 'string' },\n briefs: { type: 'string', minLength: 1, description: 'Directory of live briefs, relative to this file.' },\n archive: { type: 'string', minLength: 1, description: 'Directory of archived briefs, relative to this file.' },\n files: { type: 'string', minLength: 1, description: 'Glob a file name must match to be a brief.' },\n exclude: { ...STRINGS, description: 'File-name globs that are never briefs.' },\n template: { type: 'string', nullable: true, description: 'Template file for \"new\"; null builds one from the sections.' },\n id: {\n type: 'object',\n properties: {\n source: { type: 'string', enum: ['filename', 'frontmatter'], description: 'Where a brief id comes from.' },\n separator: { type: 'string', minLength: 1, description: 'Separates the id from the slug in a file name.' },\n digits: { type: 'integer', minimum: 1, maximum: 12, description: 'Width of an allocated numeric id.' },\n },\n },\n status: {\n type: 'object',\n properties: {\n field: { type: 'string', nullable: true, description: 'Front-matter key holding the status; null uses location only.' },\n draft: { type: 'string', nullable: true, description: 'The word for a brief still being written, or null.' },\n active: { type: 'string', minLength: 1, description: 'The word for a live brief.' },\n deferred: {\n type: 'string',\n nullable: true,\n description: 'The word for a live brief put off until the event its \"trigger\" names, or null. A deferred brief is never ready and runs in no wave.',\n },\n archived: { type: 'string', minLength: 1, description: 'The word for an archived brief.' },\n },\n },\n sections: { type: 'array', items: SECTION, description: 'Sections every live brief carries.' },\n sectionOrder: { type: 'boolean', description: 'Sections must appear in the order listed.' },\n types: {\n type: 'map',\n description: 'Brief types, each with the sections it adds.',\n values: { type: 'object', properties: { sections: { type: 'array', items: SECTION } } },\n },\n placeholders: { ...STRINGS, description: 'Words that mark a section as unwritten.' },\n fields: { ...STRINGS, description: 'Front-matter keys this repository uses beyond the built-in ones.' },\n archiving: {\n type: 'object',\n properties: {\n tasks: {\n type: 'anyOf',\n description: '\"all\", or the sections whose task items must be closed before archiving.',\n options: [{ type: 'string', enum: ['all'] }, STRINGS],\n },\n dispositions: { ...STRINGS, description: 'Text under an open box that closes it without a tick.' },\n banner: {\n type: 'array',\n items: { type: 'string' },\n description: 'Lines of the frozen banner; a line with an empty {placeholder} is left out, and an empty line separates paragraphs.',\n },\n rewriteLinks: { type: 'boolean', description: 'Rewrite relative links so they resolve from the new directory.' },\n freeze: { type: 'boolean', description: 'Record a content hash so later edits are caught.' },\n base: { type: 'string', nullable: true, description: 'Branch the diff is measured from, such as \"main\".' },\n },\n },\n rules: { type: 'map', values: SEVERITY, description: 'Severity per rule id.' },\n plugins: {\n type: 'array',\n description: 'Modules that contribute rules.',\n items: {\n type: 'anyOf',\n options: [\n { type: 'string', minLength: 1 },\n {\n type: 'object',\n required: ['module'],\n properties: { module: { type: 'string', minLength: 1 }, options: { type: 'any' } },\n },\n ],\n },\n },\n },\n};\n\nfunction section(name: string, extra: Partial<Omit<SectionRule, 'name'>> = {}): SectionRule {\n return { name, aliases: [], mustContain: [], checklist: false, optional: false, ...extra };\n}\n\nexport const DEFAULT_CONFIG: Config = {\n briefs: 'briefs',\n archive: 'briefs/archive',\n files: '[0-9]*.md',\n exclude: [],\n template: null,\n id: { source: 'filename', separator: '_', digits: 3 },\n status: { field: 'status', draft: 'draft', active: 'active', deferred: 'deferred', archived: 'archived' },\n sections: [\n section('Intent', {\n aliases: [\"Commander's Intent\", 'Mission', 'Objective'],\n hint: 'The state of the tree when this round is done, and why it matters. One paragraph.',\n }),\n section('Negative Scope', {\n aliases: ['Out of Scope', 'Non-Goals', 'Not in Scope', 'What this round is NOT', 'What this brief does NOT do'],\n hint: 'What this round must not do, even where it would look helpful.',\n }),\n section('Not Empowered', {\n aliases: ['Non-Empowerment', 'Non-Empowerment List'],\n optional: true,\n hint: 'Files, interfaces and decisions this round may not change.',\n }),\n section('Invariants', {\n aliases: ['Invariant Checklist', 'Definition of Done'],\n checklist: true,\n hint: 'Checks that must hold before the round is called done, each one a task item.',\n }),\n ],\n sectionOrder: false,\n types: {\n feature: [section('Acceptance Criteria', { checklist: true, hint: 'What a reviewer checks to accept the result, each one a task item.' })],\n defect: [\n section('The Defect, Measured', {\n aliases: ['Reproduction'],\n hint: 'How to reproduce the defect, and the measurement that shows it.',\n }),\n ],\n refactor: [],\n chore: [],\n },\n placeholders: ['TBD', 'TBA', 'TODO', 'FIXME', 'XXX', '???', '...', '\\u2026'],\n fields: [],\n archiving: {\n tasks: 'all',\n dispositions: ['**Delegated', '**Accepted debt', '**Rejected'],\n banner: [\n '**Archived {date}.**',\n '{summary}',\n 'Merged in pull request {pr}.',\n 'Recorded at commit `{commit}`: {diffstat}.',\n '{links}',\n 'The body below describes the tree before execution and is not maintained.',\n ],\n rewriteLinks: true,\n freeze: true,\n base: null,\n },\n rules: {},\n plugins: [],\n};\n\ntype Raw = Record<string, unknown>;\n\nfunction sectionFrom(raw: unknown): SectionRule {\n if (typeof raw === 'string') return section(raw);\n const r = raw as Raw;\n return section(r['name'] as string, {\n aliases: (r['aliases'] as string[] | undefined) ?? [],\n mustContain: (r['mustContain'] as string[] | undefined) ?? [],\n checklist: (r['checklist'] as boolean | undefined) ?? false,\n optional: (r['optional'] as boolean | undefined) ?? false,\n hint: r['hint'] as string | undefined,\n });\n}\n\nfunction pluginFrom(raw: unknown): PluginReference {\n if (typeof raw === 'string') return { module: raw, options: undefined };\n const r = raw as Raw;\n return { module: r['module'] as string, options: r['options'] };\n}\n\nexport class ConfigError extends Error {\n readonly problems: readonly string[];\n\n constructor(file: string, problems: readonly string[]) {\n super(`${file}: ${problems.join('; ')}`);\n this.name = 'ConfigError';\n this.problems = problems;\n }\n}\n\n/**\n * Builds a configuration from parsed JSON. Objects merge one level deep over\n * the defaults; lists and `types` replace them, because a repository that\n * names its own sections means those sections and not the defaults as well.\n */\nexport function resolveConfig(raw: unknown, file = 'configuration'): Config {\n const problems = validate(CONFIG_SCHEMA, raw);\n if (problems.length > 0) throw new ConfigError(file, problems);\n const r = raw as Raw;\n const d = DEFAULT_CONFIG;\n const merged = <T extends object>(key: string, base: T): T => ({ ...base, ...((r[key] as Partial<T>) ?? {}) });\n const archiving = merged('archiving', d.archiving);\n const config: Config = {\n briefs: (r['briefs'] as string | undefined) ?? d.briefs,\n archive: (r['archive'] as string | undefined) ?? (r['briefs'] === undefined ? d.archive : `${r['briefs'] as string}/archive`),\n files: (r['files'] as string | undefined) ?? d.files,\n exclude: (r['exclude'] as string[] | undefined) ?? d.exclude,\n template: r['template'] === undefined ? d.template : (r['template'] as string | null),\n id: merged('id', d.id),\n status: merged('status', d.status),\n sections: r['sections'] === undefined ? d.sections : (r['sections'] as unknown[]).map(sectionFrom),\n sectionOrder: (r['sectionOrder'] as boolean | undefined) ?? d.sectionOrder,\n types:\n r['types'] === undefined\n ? d.types\n : Object.fromEntries(\n Object.entries(r['types'] as Record<string, Raw>).map(([name, type]) => [\n name,\n ((type['sections'] as unknown[] | undefined) ?? []).map(sectionFrom),\n ]),\n ),\n placeholders: (r['placeholders'] as string[] | undefined) ?? d.placeholders,\n fields: (r['fields'] as string[] | undefined) ?? d.fields,\n archiving,\n rules: { ...d.rules, ...((r['rules'] as Record<string, SeveritySetting> | undefined) ?? {}) },\n plugins: ((r['plugins'] as unknown[] | undefined) ?? []).map(pluginFrom),\n };\n const semantic = checkConfig(config);\n if (semantic.length > 0) throw new ConfigError(file, semantic);\n return config;\n}\n\n/**\n * The sections a brief type adds, or `undefined` for a type the configuration\n * does not define. Its own keys only: every object answers to `constructor`\n * and `toString`, and a brief may say either.\n */\nexport function typeSections(config: Config, type: string): readonly SectionRule[] | undefined {\n return Object.hasOwn(config.types, type) ? config.types[type] : undefined;\n}\n\n/** The status words a repository uses, in lifecycle order. */\nexport function statusWords(config: Config): string[] {\n const { draft, active, deferred, archived } = config.status;\n return [draft, active, deferred, archived].filter((w) => w !== null);\n}\n\n/** What the schema cannot say: relationships between values. */\nfunction checkConfig(config: Config): string[] {\n const problems: string[] = [];\n const inside = (key: string, path: string): string | null => {\n try {\n return normalisePath(path);\n } catch {\n problems.push(`\"${key}\" must be a directory inside the repository, not \"${path}\"`);\n return null;\n }\n };\n const briefs = inside('briefs', config.briefs);\n const archive = inside('archive', config.archive);\n if (briefs !== null && briefs === archive) problems.push('\"briefs\" and \"archive\" must be different directories');\n // Named by the statuses that share a word: a 0.1 configuration that spelt\n // another status \"deferred\" meets the default for the new one here.\n const sharing = new Map<string, { word: string; keys: string[] }>();\n for (const key of ['draft', 'active', 'deferred', 'archived'] as const) {\n const word = config.status[key];\n if (word === null) continue;\n const group = sharing.get(word.toLowerCase()) ?? { word, keys: [] };\n group.keys.push(key);\n sharing.set(word.toLowerCase(), group);\n }\n for (const { word, keys } of sharing.values()) {\n if (keys.length < 2) continue;\n const orNull = keys.includes('deferred') ? ', or set \"status.deferred\" to null where no work is deferred' : '';\n problems.push(`the status words must differ: \"${word}\" is the word for ${inWords(keys)}; give ${keys.length === 2 ? 'one of them' : 'all but one'} another word${orNull}`);\n }\n // A pattern that does not parse would match nothing, and a run over no briefs reads as clean.\n for (const [key, pattern] of [['files', config.files], ...config.exclude.map((e) => ['exclude', e] as const)] as const) {\n const parsed = parseGlob(pattern);\n if (!parsed.ok) problems.push(`\"${key}\" pattern \"${pattern}\": ${parsed.error}`);\n }\n // An unknown hole is never filled, and a line with an unfilled hole is left\n // out: a misspelt placeholder would silently drop its whole line.\n for (const line of config.archiving.banner) {\n for (const hole of templateHoles(line)) {\n if (!BANNER_PLACEHOLDERS.includes(hole)) {\n problems.push(`\"archiving.banner\" uses {${hole}}; the placeholders are ${BANNER_PLACEHOLDERS.map((p) => `{${p}}`).join(', ')}`);\n }\n }\n }\n return problems;\n}\n\n/** Parses the text of a configuration file. */\nexport function parseConfig(text: string, file: string): Config {\n let raw: unknown;\n try {\n raw = JSON.parse(text.charCodeAt(0) === 0xfeff ? text.slice(1) : text);\n } catch (error) {\n throw new ConfigError(file, [`is not valid JSON (${(error as Error).message})`]);\n }\n return resolveConfig(raw, file);\n}\n\n/**\n * The configuration file for a directory: the nearest one at or above it.\n * `exists` is injected so discovery needs no filesystem of its own.\n */\nexport async function locateConfig(\n start: string,\n exists: (path: string) => Promise<boolean>,\n): Promise<string | undefined> {\n let directory = start;\n // Each pass moves one directory up, and no path is 256 deep: the bound is\n // never met, however it is counted. It is there so that the loop ends by\n // construction.\n for (let depth = 0; depth < 256; depth += 1) {\n const found: string[] = [];\n for (const name of CONFIG_FILES) {\n const candidate = join(directory, name);\n if (await exists(candidate)) found.push(candidate);\n }\n if (found.length > 1) {\n throw new ConfigError(directory, [`holds both ${CONFIG_FILES.join(' and ')}; keep one`]);\n }\n if (found[0] !== undefined) return found[0];\n const parent = dirname(directory);\n // The root is its own parent. Left to the bound, every pass after this one would ask the root again and hear the same.\n if (parent === directory) return undefined;\n directory = parent;\n }\n /* v8 ignore next -- no path is 256 directories deep; the bound exists so the loop ends by construction. */\n return undefined;\n}\n\n/** The JSON Schema published as `schema.json`. */\nexport function configJsonSchema(): Record<string, unknown> {\n return {\n $schema: 'https://json-schema.org/draft/2020-12/schema',\n $id: SCHEMA_URL,\n title: 'spec-brief configuration',\n ...toJsonSchema(CONFIG_SCHEMA),\n };\n}\n\n/** The configuration `init` writes: every default spelled out, so it can be edited rather than looked up. */\nexport function initialConfig(briefs: string, archive: string): Record<string, unknown> {\n const d = DEFAULT_CONFIG;\n // What the default sections say, and no more: each has a hint, and none\n // requires text. A default that comes to say more fails the test that this\n // file means the defaults.\n const sectionJson = (s: SectionRule): unknown => {\n const out: Record<string, unknown> = { name: s.name };\n if (s.aliases.length > 0) out['aliases'] = s.aliases;\n if (s.checklist) out['checklist'] = true;\n if (s.optional) out['optional'] = true;\n out['hint'] = s.hint;\n return out;\n };\n return {\n $schema: SCHEMA_PATH,\n briefs,\n archive,\n files: d.files,\n id: d.id,\n status: d.status,\n sections: d.sections.map(sectionJson),\n types: Object.fromEntries(Object.entries(d.types).map(([k, v]) => [k, { sections: v.map(sectionJson) }])),\n archiving: {\n tasks: d.archiving.tasks,\n dispositions: d.archiving.dispositions,\n banner: d.archiving.banner,\n base: d.archiving.base,\n },\n };\n}\n"]}
package/dist/corpus.js CHANGED
@@ -26,17 +26,18 @@ export function idKey(id) {
26
26
  /** Briefs an id, a file name or a path names. More than one is an ambiguity the caller reports. */
27
27
  export function findBriefs(corpus, reference) {
28
28
  const key = idKey(reference.trim());
29
+ // The null test is for the types: a missing id is no key, and equals none.
29
30
  const byId = corpus.briefs.filter((b) => b.id !== null && idKey(b.id) === key);
30
31
  if (byId.length > 0)
31
32
  return byId;
32
- let path;
33
33
  try {
34
- path = normalisePath(reference);
34
+ const path = normalisePath(reference);
35
+ return corpus.briefs.filter((b) => b.file === path || b.name === path);
35
36
  }
36
37
  catch {
38
+ // A path that leaves the repository names no brief in it.
37
39
  return [];
38
40
  }
39
- return corpus.briefs.filter((b) => b.file === path || b.name === path);
40
41
  }
41
42
  /** The brief a dependency names, preferring a live one when an id is ambiguous. */
42
43
  export function resolveDependency(corpus, id) {
@@ -134,6 +135,7 @@ export function dependencyCycles(corpus) {
134
135
  }
135
136
  const index = new Map();
136
137
  const low = new Map();
138
+ // A component is popped down to the brief it started from, so nothing under that brief is ever read.
137
139
  const stack = [];
138
140
  const onStack = new Set();
139
141
  const components = [];
@@ -145,7 +147,7 @@ export function dependencyCycles(corpus) {
145
147
  counter += 1;
146
148
  stack.push(v);
147
149
  onStack.add(v);
148
- for (const w of edges.get(v) ?? []) {
150
+ for (const w of edges.get(v)) {
149
151
  if (!index.has(w)) {
150
152
  connect(w);
151
153
  low.set(v, Math.min(low.get(v), low.get(w)));
@@ -167,13 +169,13 @@ export function dependencyCycles(corpus) {
167
169
  components.push(component);
168
170
  }
169
171
  };
172
+ // Visited again, a brief would be a component of one, which is no cycle: the test saves the visit.
170
173
  for (const brief of nodes)
171
174
  if (!index.has(brief))
172
175
  connect(brief);
173
176
  return components.map((component) => {
174
- const members = new Set(component);
175
177
  const start = [...component].sort((a, b) => (a.file < b.file ? -1 : 1))[0];
176
- return cyclePath(start, members, edges);
178
+ return cyclePath(start, edges);
177
179
  });
178
180
  }
179
181
  /**
@@ -183,18 +185,24 @@ export function dependencyCycles(corpus) {
183
185
  export function cycleSubject(cycle) {
184
186
  return [...new Set(cycle.map((b) => b.id ?? b.name))].sort().join(', ');
185
187
  }
186
- /** A path from `start` back to itself inside one component, found breadth-first. */
187
- function cyclePath(start, members, edges) {
188
+ /**
189
+ * The shortest path from a brief in a cycle back to itself, found
190
+ * breadth-first. Every brief on a way back is in the start's component, and
191
+ * no brief the search reaches outside the component leads back into it, so
192
+ * the search need not be told which briefs those are: the others are visited
193
+ * and lead nowhere.
194
+ */
195
+ function cyclePath(start, edges) {
188
196
  const previous = new Map();
189
197
  const queue = [start];
190
- for (let head = 0; head < queue.length; head += 1) {
198
+ // Ends by construction: a component of two or more always leads back to its start.
199
+ for (let head = 0;; head += 1) {
191
200
  const node = queue[head];
192
- for (const next of edges.get(node) ?? []) {
193
- if (!members.has(next))
194
- continue;
201
+ for (const next of edges.get(node)) {
195
202
  if (next === start) {
196
203
  const path = [start];
197
- for (let at = node; at !== undefined && at !== start; at = previous.get(at))
204
+ // Every brief queued was reached from the start, so the walk back ends there.
205
+ for (let at = node; at !== start; at = previous.get(at))
198
206
  path.splice(1, 0, at);
199
207
  return [...path, start];
200
208
  }
@@ -204,7 +212,5 @@ function cyclePath(start, members, edges) {
204
212
  }
205
213
  }
206
214
  }
207
- /* v8 ignore next -- a component of two or more always leads back to its start. */
208
- return [start, start];
209
215
  }
210
216
  //# sourceMappingURL=corpus.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"corpus.js","sourceRoot":"","sources":["../src/corpus.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Every brief of a repository at once, and the dependency graph between them.\n */\n\nimport { parseBrief, type Brief } from './brief.js';\nimport type { Config } from './config.js';\nimport { normalisePath } from './links.js';\nimport type { Phase } from './types.js';\n\nexport interface SourceFile {\n readonly path: string;\n readonly text: string;\n readonly phase: Phase;\n}\n\nexport interface Corpus {\n readonly config: Config;\n /** Sorted by path. */\n readonly briefs: readonly Brief[];\n readonly live: readonly Brief[];\n readonly archived: readonly Brief[];\n}\n\nexport function buildCorpus(files: readonly SourceFile[], config: Config): Corpus {\n // Paths are unique, so no two compare equal.\n const briefs = [...files]\n .sort((a, b) => (a.path < b.path ? -1 : 1))\n .map((file) => parseBrief(file.path, file.text, config, file.phase));\n return {\n config,\n briefs,\n live: briefs.filter((b) => b.phase === 'live'),\n archived: briefs.filter((b) => b.phase === 'archived'),\n };\n}\n\n/**\n * The form in which ids are compared. Numeric ids compare by value, so a\n * dependency written `34` - which YAML would read as a number anyway - names\n * brief `034`.\n */\nexport function idKey(id: string): string {\n return /^\\d+$/.test(id) ? String(Number(id)) : id;\n}\n\n/** Briefs an id, a file name or a path names. More than one is an ambiguity the caller reports. */\nexport function findBriefs(corpus: Corpus, reference: string): Brief[] {\n const key = idKey(reference.trim());\n const byId = corpus.briefs.filter((b) => b.id !== null && idKey(b.id) === key);\n if (byId.length > 0) return byId;\n let path: string;\n try {\n path = normalisePath(reference);\n } catch {\n return [];\n }\n return corpus.briefs.filter((b) => b.file === path || b.name === path);\n}\n\n/** The brief a dependency names, preferring a live one when an id is ambiguous. */\nexport function resolveDependency(corpus: Corpus, id: string): Brief | undefined {\n const found = findBriefs(corpus, id);\n return found.find((b) => b.phase === 'live') ?? found[0];\n}\n\n/** Briefs grouped by id, for the ids more than one file claims. */\nexport function duplicateIds(corpus: Corpus): Map<string, Brief[]> {\n const groups = new Map<string, Brief[]>();\n for (const brief of corpus.briefs) {\n if (brief.id === null) continue;\n const key = idKey(brief.id);\n groups.set(key, [...(groups.get(key) ?? []), brief]);\n }\n return new Map([...groups].filter(([, members]) => members.length > 1));\n}\n\n/** The live dependencies of a brief that are not archived yet. */\nexport function pendingDependencies(corpus: Corpus, brief: Brief): string[] {\n return brief.dependsOn.filter((dependency) => {\n const target = resolveDependency(corpus, dependency);\n return target !== undefined && target.phase === 'live';\n });\n}\n\n/**\n * A live brief whose every dependency is archived can be executed now, unless\n * it is still being written or is deferred until its trigger.\n */\nexport function isReady(corpus: Corpus, brief: Brief): boolean {\n if (brief.phase !== 'live' || brief.status === 'draft' || brief.status === 'deferred') return false;\n return brief.dependsOn.every((dependency) => resolveDependency(corpus, dependency)?.phase === 'archived');\n}\n\nfunction label(brief: Brief): string {\n return brief.id ?? brief.name;\n}\n\n/** Briefs by id, numbers by value; by path where two ids agree. */\nexport function byId(a: Brief, b: Brief): number {\n const x = label(a);\n const y = label(b);\n const numeric = /^\\d+$/.test(x) && /^\\d+$/.test(y);\n if (numeric && Number(x) !== Number(y)) return Number(x) - Number(y);\n if (x < y) return -1;\n if (x > y) return 1;\n return a.file < b.file ? -1 : a.file > b.file ? 1 : 0;\n}\n\n/** The live briefs a brief depends on, by id. Archived ones are done, and unknown ones are the dependency rule's. */\nexport function liveDependencies(corpus: Corpus, brief: Brief): Brief[] {\n const targets = brief.dependsOn\n .map((d) => resolveDependency(corpus, d))\n .filter((t): t is Brief => t !== undefined && t !== brief && t.phase === 'live');\n return [...new Set(targets)].sort(byId);\n}\n\n/** A live brief that cannot start before deferred work, and the dependency it waits through. */\nexport interface Waiting {\n readonly brief: Brief;\n /** The deferred brief it depends on, or the brief through which it waits on one; the first by id. */\n readonly waitsOn: Brief;\n}\n\n/**\n * Live briefs that wait on deferred work: each depends on a deferred brief,\n * or on a brief that waits on one. Neither can start before an event nobody\n * has scheduled, so neither runs in a wave (ADR-0011): `schedule` places them\n * nowhere and `matrix` compares them with nothing. By id.\n */\nexport function waitingOnDeferred(corpus: Corpus): Waiting[] {\n const candidates = corpus.live.filter((b) => b.status !== 'deferred');\n const dependencies = new Map(candidates.map((b) => [b, liveDependencies(corpus, b)]));\n const held = new Set<Brief>();\n const holds = (d: Brief): boolean => d.status === 'deferred' || held.has(d);\n // Each pass holds another brief or is the last, so there is at most one pass more than there are briefs.\n for (let grew = true; grew; ) {\n grew = false;\n for (const brief of candidates) {\n if (held.has(brief) || !(dependencies.get(brief) as Brief[]).some(holds)) continue;\n held.add(brief);\n grew = true;\n }\n }\n return [...held].sort(byId).map((brief) => ({ brief, waitsOn: (dependencies.get(brief) as Brief[]).find(holds) as Brief }));\n}\n\n/**\n * Dependency cycles among live briefs, each as the ids along it with the first\n * repeated at the end. Found with Tarjan's algorithm; each strongly connected\n * component is reported once, starting from its first brief by path.\n */\nexport function dependencyCycles(corpus: Corpus): Brief[][] {\n const nodes = corpus.live;\n const edges = new Map<Brief, Brief[]>();\n for (const brief of nodes) {\n // A brief that depends on itself is the dependency rule's finding; left\n // in, the edge would be the shortest way round any cycle it is part of.\n const targets = brief.dependsOn\n .map((d) => resolveDependency(corpus, d))\n .filter((t): t is Brief => t !== undefined && t !== brief && t.phase === 'live');\n edges.set(brief, targets);\n }\n\n const index = new Map<Brief, number>();\n const low = new Map<Brief, number>();\n const stack: Brief[] = [];\n const onStack = new Set<Brief>();\n const components: Brief[][] = [];\n let counter = 0;\n\n // Recursion depth is bounded by the number of live briefs.\n const connect = (v: Brief): void => {\n index.set(v, counter);\n low.set(v, counter);\n counter += 1;\n stack.push(v);\n onStack.add(v);\n for (const w of edges.get(v) ?? []) {\n if (!index.has(w)) {\n connect(w);\n low.set(v, Math.min(low.get(v) as number, low.get(w) as number));\n } else if (onStack.has(w)) {\n low.set(v, Math.min(low.get(v) as number, index.get(w) as number));\n }\n }\n if (low.get(v) === index.get(v)) {\n const component: Brief[] = [];\n for (;;) {\n const w = stack.pop() as Brief;\n onStack.delete(w);\n component.push(w);\n if (w === v) break;\n }\n if (component.length > 1) components.push(component);\n }\n };\n for (const brief of nodes) if (!index.has(brief)) connect(brief);\n\n return components.map((component) => {\n const members = new Set(component);\n const start = [...component].sort((a, b) => (a.file < b.file ? -1 : 1))[0] as Brief;\n return cyclePath(start, members, edges);\n });\n}\n\n/**\n * A cycle as a finding's subject: the briefs on it, each once, in order. `lint`\n * and `schedule` word a cycle differently and report it as one problem.\n */\nexport function cycleSubject(cycle: readonly Brief[]): string {\n return [...new Set(cycle.map((b) => b.id ?? b.name))].sort().join(', ');\n}\n\n/** A path from `start` back to itself inside one component, found breadth-first. */\nfunction cyclePath(start: Brief, members: ReadonlySet<Brief>, edges: ReadonlyMap<Brief, Brief[]>): Brief[] {\n const previous = new Map<Brief, Brief>();\n const queue: Brief[] = [start];\n for (let head = 0; head < queue.length; head += 1) {\n const node = queue[head] as Brief;\n for (const next of edges.get(node) ?? []) {\n if (!members.has(next)) continue;\n if (next === start) {\n const path: Brief[] = [start];\n for (let at: Brief | undefined = node; at !== undefined && at !== start; at = previous.get(at)) path.splice(1, 0, at);\n return [...path, start];\n }\n if (!previous.has(next)) {\n previous.set(next, node);\n queue.push(next);\n }\n }\n }\n /* v8 ignore next -- a component of two or more always leads back to its start. */\n return [start, start];\n}\n"]}
1
+ {"version":3,"file":"corpus.js","sourceRoot":"","sources":["../src/corpus.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Every brief of a repository at once, and the dependency graph between them.\n */\n\nimport { parseBrief, type Brief } from './brief.js';\nimport type { Config } from './config.js';\nimport { normalisePath } from './links.js';\nimport type { Phase } from './types.js';\n\nexport interface SourceFile {\n readonly path: string;\n readonly text: string;\n readonly phase: Phase;\n}\n\nexport interface Corpus {\n readonly config: Config;\n /** Sorted by path. */\n readonly briefs: readonly Brief[];\n readonly live: readonly Brief[];\n readonly archived: readonly Brief[];\n}\n\nexport function buildCorpus(files: readonly SourceFile[], config: Config): Corpus {\n // Paths are unique, so no two compare equal.\n const briefs = [...files]\n .sort((a, b) => (a.path < b.path ? -1 : 1))\n .map((file) => parseBrief(file.path, file.text, config, file.phase));\n return {\n config,\n briefs,\n live: briefs.filter((b) => b.phase === 'live'),\n archived: briefs.filter((b) => b.phase === 'archived'),\n };\n}\n\n/**\n * The form in which ids are compared. Numeric ids compare by value, so a\n * dependency written `34` - which YAML would read as a number anyway - names\n * brief `034`.\n */\nexport function idKey(id: string): string {\n return /^\\d+$/.test(id) ? String(Number(id)) : id;\n}\n\n/** Briefs an id, a file name or a path names. More than one is an ambiguity the caller reports. */\nexport function findBriefs(corpus: Corpus, reference: string): Brief[] {\n const key = idKey(reference.trim());\n // The null test is for the types: a missing id is no key, and equals none.\n const byId = corpus.briefs.filter((b) => b.id !== null && idKey(b.id) === key);\n if (byId.length > 0) return byId;\n try {\n const path = normalisePath(reference);\n return corpus.briefs.filter((b) => b.file === path || b.name === path);\n } catch {\n // A path that leaves the repository names no brief in it.\n return [];\n }\n}\n\n/** The brief a dependency names, preferring a live one when an id is ambiguous. */\nexport function resolveDependency(corpus: Corpus, id: string): Brief | undefined {\n const found = findBriefs(corpus, id);\n return found.find((b) => b.phase === 'live') ?? found[0];\n}\n\n/** Briefs grouped by id, for the ids more than one file claims. */\nexport function duplicateIds(corpus: Corpus): Map<string, Brief[]> {\n const groups = new Map<string, Brief[]>();\n for (const brief of corpus.briefs) {\n if (brief.id === null) continue;\n const key = idKey(brief.id);\n groups.set(key, [...(groups.get(key) ?? []), brief]);\n }\n return new Map([...groups].filter(([, members]) => members.length > 1));\n}\n\n/** The live dependencies of a brief that are not archived yet. */\nexport function pendingDependencies(corpus: Corpus, brief: Brief): string[] {\n return brief.dependsOn.filter((dependency) => {\n const target = resolveDependency(corpus, dependency);\n return target !== undefined && target.phase === 'live';\n });\n}\n\n/**\n * A live brief whose every dependency is archived can be executed now, unless\n * it is still being written or is deferred until its trigger.\n */\nexport function isReady(corpus: Corpus, brief: Brief): boolean {\n if (brief.phase !== 'live' || brief.status === 'draft' || brief.status === 'deferred') return false;\n return brief.dependsOn.every((dependency) => resolveDependency(corpus, dependency)?.phase === 'archived');\n}\n\nfunction label(brief: Brief): string {\n return brief.id ?? brief.name;\n}\n\n/** Briefs by id, numbers by value; by path where two ids agree. */\nexport function byId(a: Brief, b: Brief): number {\n const x = label(a);\n const y = label(b);\n const numeric = /^\\d+$/.test(x) && /^\\d+$/.test(y);\n if (numeric && Number(x) !== Number(y)) return Number(x) - Number(y);\n if (x < y) return -1;\n if (x > y) return 1;\n return a.file < b.file ? -1 : a.file > b.file ? 1 : 0;\n}\n\n/** The live briefs a brief depends on, by id. Archived ones are done, and unknown ones are the dependency rule's. */\nexport function liveDependencies(corpus: Corpus, brief: Brief): Brief[] {\n const targets = brief.dependsOn\n .map((d) => resolveDependency(corpus, d))\n .filter((t): t is Brief => t !== undefined && t !== brief && t.phase === 'live');\n return [...new Set(targets)].sort(byId);\n}\n\n/** A live brief that cannot start before deferred work, and the dependency it waits through. */\nexport interface Waiting {\n readonly brief: Brief;\n /** The deferred brief it depends on, or the brief through which it waits on one; the first by id. */\n readonly waitsOn: Brief;\n}\n\n/**\n * Live briefs that wait on deferred work: each depends on a deferred brief,\n * or on a brief that waits on one. Neither can start before an event nobody\n * has scheduled, so neither runs in a wave (ADR-0011): `schedule` places them\n * nowhere and `matrix` compares them with nothing. By id.\n */\nexport function waitingOnDeferred(corpus: Corpus): Waiting[] {\n const candidates = corpus.live.filter((b) => b.status !== 'deferred');\n const dependencies = new Map(candidates.map((b) => [b, liveDependencies(corpus, b)]));\n const held = new Set<Brief>();\n const holds = (d: Brief): boolean => d.status === 'deferred' || held.has(d);\n // Each pass holds another brief or is the last, so there is at most one pass more than there are briefs.\n for (let grew = true; grew; ) {\n grew = false;\n for (const brief of candidates) {\n if (held.has(brief) || !(dependencies.get(brief) as Brief[]).some(holds)) continue;\n held.add(brief);\n grew = true;\n }\n }\n return [...held].sort(byId).map((brief) => ({ brief, waitsOn: (dependencies.get(brief) as Brief[]).find(holds) as Brief }));\n}\n\n/**\n * Dependency cycles among live briefs, each as the ids along it with the first\n * repeated at the end. Found with Tarjan's algorithm; each strongly connected\n * component is reported once, starting from its first brief by path.\n */\nexport function dependencyCycles(corpus: Corpus): Brief[][] {\n const nodes = corpus.live;\n const edges = new Map<Brief, Brief[]>();\n for (const brief of nodes) {\n // A brief that depends on itself is the dependency rule's finding; left\n // in, the edge would be the shortest way round any cycle it is part of.\n const targets = brief.dependsOn\n .map((d) => resolveDependency(corpus, d))\n .filter((t): t is Brief => t !== undefined && t !== brief && t.phase === 'live');\n edges.set(brief, targets);\n }\n\n const index = new Map<Brief, number>();\n const low = new Map<Brief, number>();\n // A component is popped down to the brief it started from, so nothing under that brief is ever read.\n const stack: Brief[] = [];\n const onStack = new Set<Brief>();\n const components: Brief[][] = [];\n let counter = 0;\n\n // Recursion depth is bounded by the number of live briefs.\n const connect = (v: Brief): void => {\n index.set(v, counter);\n low.set(v, counter);\n counter += 1;\n stack.push(v);\n onStack.add(v);\n for (const w of edges.get(v) as Brief[]) {\n if (!index.has(w)) {\n connect(w);\n low.set(v, Math.min(low.get(v) as number, low.get(w) as number));\n } else if (onStack.has(w)) {\n low.set(v, Math.min(low.get(v) as number, index.get(w) as number));\n }\n }\n if (low.get(v) === index.get(v)) {\n const component: Brief[] = [];\n for (;;) {\n const w = stack.pop() as Brief;\n onStack.delete(w);\n component.push(w);\n if (w === v) break;\n }\n if (component.length > 1) components.push(component);\n }\n };\n // Visited again, a brief would be a component of one, which is no cycle: the test saves the visit.\n for (const brief of nodes) if (!index.has(brief)) connect(brief);\n\n return components.map((component) => {\n const start = [...component].sort((a, b) => (a.file < b.file ? -1 : 1))[0] as Brief;\n return cyclePath(start, edges);\n });\n}\n\n/**\n * A cycle as a finding's subject: the briefs on it, each once, in order. `lint`\n * and `schedule` word a cycle differently and report it as one problem.\n */\nexport function cycleSubject(cycle: readonly Brief[]): string {\n return [...new Set(cycle.map((b) => b.id ?? b.name))].sort().join(', ');\n}\n\n/**\n * The shortest path from a brief in a cycle back to itself, found\n * breadth-first. Every brief on a way back is in the start's component, and\n * no brief the search reaches outside the component leads back into it, so\n * the search need not be told which briefs those are: the others are visited\n * and lead nowhere.\n */\nfunction cyclePath(start: Brief, edges: ReadonlyMap<Brief, Brief[]>): Brief[] {\n const previous = new Map<Brief, Brief>();\n const queue: Brief[] = [start];\n // Ends by construction: a component of two or more always leads back to its start.\n for (let head = 0; ; head += 1) {\n const node = queue[head] as Brief;\n for (const next of edges.get(node) as Brief[]) {\n if (next === start) {\n const path: Brief[] = [start];\n // Every brief queued was reached from the start, so the walk back ends there.\n for (let at = node; at !== start; at = previous.get(at) as Brief) path.splice(1, 0, at);\n return [...path, start];\n }\n if (!previous.has(next)) {\n previous.set(next, node);\n queue.push(next);\n }\n }\n }\n}\n"]}
package/dist/engine.d.ts CHANGED
@@ -36,7 +36,7 @@ export interface OpenOptions {
36
36
  /** Where discovery starts. Defaults to the process's directory. */
37
37
  readonly cwd?: string;
38
38
  /** An explicit configuration file; discovery is skipped. */
39
- readonly config?: string;
39
+ readonly config?: string | undefined;
40
40
  /** Use the defaults even where a configuration file exists. */
41
41
  readonly noConfig?: boolean;
42
42
  /** `null` runs without git. Defaults to git when the root is inside a working tree. */
@@ -78,6 +78,7 @@ export declare class BriefEngine {
78
78
  readonly git: Git | null;
79
79
  private plugins;
80
80
  private loaded;
81
+ /** Known once the briefs are read, which every command does before it asks. */
81
82
  private missingBriefs;
82
83
  constructor(setup: EngineSetup);
83
84
  /**
package/dist/engine.js CHANGED
@@ -9,11 +9,11 @@
9
9
  * so an engine can equally run over a `MemoryFileSystem` and no git at all.
10
10
  */
11
11
  import { access } from 'node:fs/promises';
12
- import { dirname, relative, resolve, sep } from 'node:path';
12
+ import { basename, dirname, relative, resolve, sep } from 'node:path';
13
13
  import { applyPlan } from './apply.js';
14
14
  import { applyWaivers, planArchive, planUnarchive, WAIVABLE, } from './archive.js';
15
15
  import { collisionFindings, collisions } from './collisions.js';
16
- import { ConfigError, DEFAULT_CONFIG, locateConfig, parseConfig } from './config.js';
16
+ import { ConfigError, DEFAULT_CONFIG, locateConfig, parseConfig, typeSections } from './config.js';
17
17
  import { buildCorpus, findBriefs, isReady, pendingDependencies } from './corpus.js';
18
18
  import { canonicalPath, NodeFileSystem } from './fs.js';
19
19
  import { NodeGit, pullRequestUrl } from './git.js';
@@ -37,6 +37,8 @@ async function exists(path) {
37
37
  return true;
38
38
  }
39
39
  catch {
40
+ // Absent, or not to be reached: the search goes on either way. An empty
41
+ // catch would answer `undefined`, which the search reads the same.
40
42
  return false;
41
43
  }
42
44
  }
@@ -46,6 +48,7 @@ async function exists(path) {
46
48
  */
47
49
  export function today(env = process.env) {
48
50
  const epoch = env['SOURCE_DATE_EPOCH'];
51
+ // The check for a variable that is not set is the compiler's: unset, it would read as "undefined", which is no number.
49
52
  const when = epoch !== undefined && /^\d+$/.test(epoch) ? new Date(Number(epoch) * 1000) : new Date();
50
53
  return when.toISOString().slice(0, 10);
51
54
  }
@@ -67,6 +70,9 @@ function frozenCopy(value) {
67
70
  return copy;
68
71
  }
69
72
  export function isDate(text) {
73
+ // The shape keeps out the one other text that survives the round trip
74
+ // below: a year past 9999 with a month, "+010000-01". Its anchors are for
75
+ // the reader, since nothing longer than a date comes back as it went in.
70
76
  if (!/^\d{4}-\d{2}-\d{2}$/.test(text))
71
77
  return false;
72
78
  const parsed = new Date(`${text}T00:00:00Z`);
@@ -80,7 +86,8 @@ export class BriefEngine {
80
86
  git;
81
87
  plugins;
82
88
  loaded;
83
- missingBriefs = false;
89
+ /** Known once the briefs are read, which every command does before it asks. */
90
+ missingBriefs;
84
91
  constructor(setup) {
85
92
  this.root = setup.root;
86
93
  this.config = setup.config;
@@ -104,18 +111,20 @@ export class BriefEngine {
104
111
  const root = configPath === undefined ? (toplevel ?? cwd) : dirname(configPath);
105
112
  let config = DEFAULT_CONFIG;
106
113
  if (configPath !== undefined) {
107
- const fs = new NodeFileSystem(dirname(configPath));
108
- const name = configPath.slice(dirname(configPath).length + 1);
109
- const text = await fs.read(name);
114
+ const text = await new NodeFileSystem(root).read(basename(configPath));
110
115
  if (text === null)
111
116
  throw new EngineError('not-found', `${configPath} does not exist`);
112
117
  config = parseConfig(text, relative(cwd, configPath));
113
118
  }
114
- const inTree = toplevel !== null && !relative(toplevel, root).startsWith('..');
119
+ // Outside the work tree is its parent or a path through it, "..", "../x":
120
+ // a directory inside it may itself be named "..drafts".
121
+ const fromTop = toplevel === null ? null : relative(toplevel, root);
122
+ const inTree = fromTop !== null && fromTop !== '..' && !fromTop.startsWith(`..${sep}`);
115
123
  const engine = new BriefEngine({
116
124
  root,
117
125
  config,
118
- configFile: configPath === undefined ? null : relative(root, configPath).split(sep).join('/'),
126
+ // The root is the configuration's directory, so its path from there is its name.
127
+ configFile: configPath === undefined ? null : basename(configPath),
119
128
  fs: options.fs ?? new NodeFileSystem(root),
120
129
  git: options.git === undefined ? (inTree ? new NodeGit(root) : null) : options.git,
121
130
  });
@@ -136,17 +145,18 @@ export class BriefEngine {
136
145
  const read = async (directory, phase) => {
137
146
  const dir = normalisePath(directory);
138
147
  const names = await this.fs.list(dir);
139
- for (const name of names ?? []) {
148
+ if (names === null)
149
+ return false;
150
+ for (const name of names) {
140
151
  if (!isBrief(name))
141
152
  continue;
142
153
  const path = dir === '' ? name : `${dir}/${name}`;
143
154
  const text = await this.fs.read(path);
144
155
  // A file listed a moment ago and gone now was removed under the run; there is nothing to read.
145
- /* v8 ignore next */
146
156
  if (text !== null)
147
157
  files.push({ path, text, phase });
148
158
  }
149
- return names !== null;
159
+ return true;
150
160
  };
151
161
  this.missingBriefs = !(await read(this.config.briefs, 'live'));
152
162
  await read(this.config.archive, 'archived');
@@ -263,9 +273,8 @@ export class BriefEngine {
263
273
  else
264
274
  changes = await git.changes(from, commit.sha);
265
275
  }
266
- else {
267
- unmeasured = { reason: 'revision' };
268
- }
276
+ // With neither a commit nor a base, nothing was read, and the planner
277
+ // says so itself of a request that brings no changes and no reason.
269
278
  dirty = await git.dirty();
270
279
  }
271
280
  else {
@@ -355,7 +364,7 @@ export class BriefEngine {
355
364
  }
356
365
  if (findBriefs(this.corpus, id).some((b) => b.id !== null))
357
366
  throw new EngineError('exists', `the id "${id}" is taken`);
358
- if (options.type !== undefined && this.config.types[options.type] === undefined) {
367
+ if (options.type !== undefined && typeSections(this.config, options.type) === undefined) {
359
368
  const names = Object.keys(this.config.types);
360
369
  throw new EngineError('invalid', `"${options.type}" is not a brief type here; use one of ${names.join(', ') || '(none)'}`);
361
370
  }