@descent-vtt/spec-brief 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +269 -0
- package/bin/spec-brief.js +19 -0
- package/dist/apply.d.ts +26 -0
- package/dist/apply.js +71 -0
- package/dist/apply.js.map +1 -0
- package/dist/archive.d.ts +81 -0
- package/dist/archive.js +333 -0
- package/dist/archive.js.map +1 -0
- package/dist/brief.d.ts +60 -0
- package/dist/brief.js +152 -0
- package/dist/brief.js.map +1 -0
- package/dist/cli.d.ts +35 -0
- package/dist/cli.js +411 -0
- package/dist/cli.js.map +1 -0
- package/dist/collisions.d.ts +50 -0
- package/dist/collisions.js +127 -0
- package/dist/collisions.js.map +1 -0
- package/dist/config.d.ts +94 -0
- package/dist/config.js +353 -0
- package/dist/config.js.map +1 -0
- package/dist/corpus.d.ts +41 -0
- package/dist/corpus.js +154 -0
- package/dist/corpus.js.map +1 -0
- package/dist/engine.d.ts +121 -0
- package/dist/engine.js +276 -0
- package/dist/engine.js.map +1 -0
- package/dist/frontmatter.d.ts +68 -0
- package/dist/frontmatter.js +311 -0
- package/dist/frontmatter.js.map +1 -0
- package/dist/fs.d.ts +59 -0
- package/dist/fs.js +189 -0
- package/dist/fs.js.map +1 -0
- package/dist/git.d.ts +59 -0
- package/dist/git.js +131 -0
- package/dist/git.js.map +1 -0
- package/dist/glob.d.ts +79 -0
- package/dist/glob.js +465 -0
- package/dist/glob.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/integrity.d.ts +11 -0
- package/dist/integrity.js +20 -0
- package/dist/integrity.js.map +1 -0
- package/dist/links.d.ts +38 -0
- package/dist/links.js +142 -0
- package/dist/links.js.map +1 -0
- package/dist/lint.d.ts +38 -0
- package/dist/lint.js +90 -0
- package/dist/lint.js.map +1 -0
- package/dist/markdown.d.ts +65 -0
- package/dist/markdown.js +274 -0
- package/dist/markdown.js.map +1 -0
- package/dist/plugins.d.ts +16 -0
- package/dist/plugins.js +77 -0
- package/dist/plugins.js.map +1 -0
- package/dist/report.d.ts +38 -0
- package/dist/report.js +244 -0
- package/dist/report.js.map +1 -0
- package/dist/rules.d.ts +58 -0
- package/dist/rules.js +448 -0
- package/dist/rules.js.map +1 -0
- package/dist/scaffold.d.ts +25 -0
- package/dist/scaffold.js +81 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/schema.d.ts +47 -0
- package/dist/schema.js +195 -0
- package/dist/schema.js.map +1 -0
- package/dist/text.d.ts +40 -0
- package/dist/text.js +95 -0
- package/dist/text.js.map +1 -0
- package/dist/types.d.ts +30 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +76 -0
- package/schema.json +321 -0
|
@@ -0,0 +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 { 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\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 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\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 },\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 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', archived: 'archived' },\n sections: [\n section('Intent', { aliases: [\"Commander's Intent\", 'Mission', 'Objective'] }),\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 }),\n section('Not Empowered', { aliases: ['Non-Empowerment', 'Non-Empowerment List'], optional: true }),\n section('Invariants', { aliases: ['Invariant Checklist', 'Definition of Done'], checklist: true }),\n ],\n sectionOrder: false,\n types: {\n feature: [section('Acceptance Criteria', { checklist: true })],\n defect: [section('The Defect, Measured', { aliases: ['Reproduction'] })],\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 });\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/** 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 const words = [config.status.draft, config.status.active, config.status.archived].filter((w) => w !== null);\n if (new Set(words.map((w) => w.toLowerCase())).size !== words.length) {\n problems.push('the status words for draft, active and archived must differ');\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 return Object.keys(out).length === 1 ? s.name : out;\n };\n return {\n $schema: SCHEMA_URL,\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.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every brief of a repository at once, and the dependency graph between them.
|
|
3
|
+
*/
|
|
4
|
+
import { type Brief } from './brief.js';
|
|
5
|
+
import type { Config } from './config.js';
|
|
6
|
+
import type { Phase } from './types.js';
|
|
7
|
+
export interface SourceFile {
|
|
8
|
+
readonly path: string;
|
|
9
|
+
readonly text: string;
|
|
10
|
+
readonly phase: Phase;
|
|
11
|
+
}
|
|
12
|
+
export interface Corpus {
|
|
13
|
+
readonly config: Config;
|
|
14
|
+
/** Sorted by path. */
|
|
15
|
+
readonly briefs: readonly Brief[];
|
|
16
|
+
readonly live: readonly Brief[];
|
|
17
|
+
readonly archived: readonly Brief[];
|
|
18
|
+
}
|
|
19
|
+
export declare function buildCorpus(files: readonly SourceFile[], config: Config): Corpus;
|
|
20
|
+
/**
|
|
21
|
+
* The form in which ids are compared. Numeric ids compare by value, so a
|
|
22
|
+
* dependency written `34` - which YAML would read as a number anyway - names
|
|
23
|
+
* brief `034`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function idKey(id: string): string;
|
|
26
|
+
/** Briefs an id, a file name or a path names. More than one is an ambiguity the caller reports. */
|
|
27
|
+
export declare function findBriefs(corpus: Corpus, reference: string): Brief[];
|
|
28
|
+
/** The brief a dependency names, preferring a live one when an id is ambiguous. */
|
|
29
|
+
export declare function resolveDependency(corpus: Corpus, id: string): Brief | undefined;
|
|
30
|
+
/** Briefs grouped by id, for the ids more than one file claims. */
|
|
31
|
+
export declare function duplicateIds(corpus: Corpus): Map<string, Brief[]>;
|
|
32
|
+
/** The live dependencies of a brief that are not archived yet. */
|
|
33
|
+
export declare function pendingDependencies(corpus: Corpus, brief: Brief): string[];
|
|
34
|
+
/** A live brief whose every dependency is archived can be executed now. */
|
|
35
|
+
export declare function isReady(corpus: Corpus, brief: Brief): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Dependency cycles among live briefs, each as the ids along it with the first
|
|
38
|
+
* repeated at the end. Found with Tarjan's algorithm; each strongly connected
|
|
39
|
+
* component is reported once, starting from its first brief by path.
|
|
40
|
+
*/
|
|
41
|
+
export declare function dependencyCycles(corpus: Corpus): Brief[][];
|
package/dist/corpus.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every brief of a repository at once, and the dependency graph between them.
|
|
3
|
+
*/
|
|
4
|
+
import { parseBrief } from './brief.js';
|
|
5
|
+
import { normalisePath } from './links.js';
|
|
6
|
+
export function buildCorpus(files, config) {
|
|
7
|
+
// Paths are unique, so no two compare equal.
|
|
8
|
+
const briefs = [...files]
|
|
9
|
+
.sort((a, b) => (a.path < b.path ? -1 : 1))
|
|
10
|
+
.map((file) => parseBrief(file.path, file.text, config, file.phase));
|
|
11
|
+
return {
|
|
12
|
+
config,
|
|
13
|
+
briefs,
|
|
14
|
+
live: briefs.filter((b) => b.phase === 'live'),
|
|
15
|
+
archived: briefs.filter((b) => b.phase === 'archived'),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The form in which ids are compared. Numeric ids compare by value, so a
|
|
20
|
+
* dependency written `34` - which YAML would read as a number anyway - names
|
|
21
|
+
* brief `034`.
|
|
22
|
+
*/
|
|
23
|
+
export function idKey(id) {
|
|
24
|
+
return /^\d+$/.test(id) ? String(Number(id)) : id;
|
|
25
|
+
}
|
|
26
|
+
/** Briefs an id, a file name or a path names. More than one is an ambiguity the caller reports. */
|
|
27
|
+
export function findBriefs(corpus, reference) {
|
|
28
|
+
const key = idKey(reference.trim());
|
|
29
|
+
const byId = corpus.briefs.filter((b) => b.id !== null && idKey(b.id) === key);
|
|
30
|
+
if (byId.length > 0)
|
|
31
|
+
return byId;
|
|
32
|
+
let path;
|
|
33
|
+
try {
|
|
34
|
+
path = normalisePath(reference);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
return corpus.briefs.filter((b) => b.file === path || b.name === path);
|
|
40
|
+
}
|
|
41
|
+
/** The brief a dependency names, preferring a live one when an id is ambiguous. */
|
|
42
|
+
export function resolveDependency(corpus, id) {
|
|
43
|
+
const found = findBriefs(corpus, id);
|
|
44
|
+
return found.find((b) => b.phase === 'live') ?? found[0];
|
|
45
|
+
}
|
|
46
|
+
/** Briefs grouped by id, for the ids more than one file claims. */
|
|
47
|
+
export function duplicateIds(corpus) {
|
|
48
|
+
const groups = new Map();
|
|
49
|
+
for (const brief of corpus.briefs) {
|
|
50
|
+
if (brief.id === null)
|
|
51
|
+
continue;
|
|
52
|
+
const key = idKey(brief.id);
|
|
53
|
+
groups.set(key, [...(groups.get(key) ?? []), brief]);
|
|
54
|
+
}
|
|
55
|
+
return new Map([...groups].filter(([, members]) => members.length > 1));
|
|
56
|
+
}
|
|
57
|
+
/** The live dependencies of a brief that are not archived yet. */
|
|
58
|
+
export function pendingDependencies(corpus, brief) {
|
|
59
|
+
return brief.dependsOn.filter((dependency) => {
|
|
60
|
+
const target = resolveDependency(corpus, dependency);
|
|
61
|
+
return target !== undefined && target.phase === 'live';
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/** A live brief whose every dependency is archived can be executed now. */
|
|
65
|
+
export function isReady(corpus, brief) {
|
|
66
|
+
if (brief.phase !== 'live' || brief.status === 'draft')
|
|
67
|
+
return false;
|
|
68
|
+
return brief.dependsOn.every((dependency) => resolveDependency(corpus, dependency)?.phase === 'archived');
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Dependency cycles among live briefs, each as the ids along it with the first
|
|
72
|
+
* repeated at the end. Found with Tarjan's algorithm; each strongly connected
|
|
73
|
+
* component is reported once, starting from its first brief by path.
|
|
74
|
+
*/
|
|
75
|
+
export function dependencyCycles(corpus) {
|
|
76
|
+
const nodes = corpus.live;
|
|
77
|
+
const edges = new Map();
|
|
78
|
+
for (const brief of nodes) {
|
|
79
|
+
// A brief that depends on itself is the dependency rule's finding; left
|
|
80
|
+
// in, the edge would be the shortest way round any cycle it is part of.
|
|
81
|
+
const targets = brief.dependsOn
|
|
82
|
+
.map((d) => resolveDependency(corpus, d))
|
|
83
|
+
.filter((t) => t !== undefined && t !== brief && t.phase === 'live');
|
|
84
|
+
edges.set(brief, targets);
|
|
85
|
+
}
|
|
86
|
+
const index = new Map();
|
|
87
|
+
const low = new Map();
|
|
88
|
+
const stack = [];
|
|
89
|
+
const onStack = new Set();
|
|
90
|
+
const components = [];
|
|
91
|
+
let counter = 0;
|
|
92
|
+
// Recursion depth is bounded by the number of live briefs.
|
|
93
|
+
const connect = (v) => {
|
|
94
|
+
index.set(v, counter);
|
|
95
|
+
low.set(v, counter);
|
|
96
|
+
counter += 1;
|
|
97
|
+
stack.push(v);
|
|
98
|
+
onStack.add(v);
|
|
99
|
+
for (const w of edges.get(v) ?? []) {
|
|
100
|
+
if (!index.has(w)) {
|
|
101
|
+
connect(w);
|
|
102
|
+
low.set(v, Math.min(low.get(v), low.get(w)));
|
|
103
|
+
}
|
|
104
|
+
else if (onStack.has(w)) {
|
|
105
|
+
low.set(v, Math.min(low.get(v), index.get(w)));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (low.get(v) === index.get(v)) {
|
|
109
|
+
const component = [];
|
|
110
|
+
for (;;) {
|
|
111
|
+
const w = stack.pop();
|
|
112
|
+
onStack.delete(w);
|
|
113
|
+
component.push(w);
|
|
114
|
+
if (w === v)
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
if (component.length > 1)
|
|
118
|
+
components.push(component);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
for (const brief of nodes)
|
|
122
|
+
if (!index.has(brief))
|
|
123
|
+
connect(brief);
|
|
124
|
+
return components.map((component) => {
|
|
125
|
+
const members = new Set(component);
|
|
126
|
+
const start = [...component].sort((a, b) => (a.file < b.file ? -1 : 1))[0];
|
|
127
|
+
return cyclePath(start, members, edges);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/** A path from `start` back to itself inside one component, found breadth-first. */
|
|
131
|
+
function cyclePath(start, members, edges) {
|
|
132
|
+
const previous = new Map();
|
|
133
|
+
const queue = [start];
|
|
134
|
+
for (let head = 0; head < queue.length; head += 1) {
|
|
135
|
+
const node = queue[head];
|
|
136
|
+
for (const next of edges.get(node) ?? []) {
|
|
137
|
+
if (!members.has(next))
|
|
138
|
+
continue;
|
|
139
|
+
if (next === start) {
|
|
140
|
+
const path = [start];
|
|
141
|
+
for (let at = node; at !== undefined && at !== start; at = previous.get(at))
|
|
142
|
+
path.splice(1, 0, at);
|
|
143
|
+
return [...path, start];
|
|
144
|
+
}
|
|
145
|
+
if (!previous.has(next)) {
|
|
146
|
+
previous.set(next, node);
|
|
147
|
+
queue.push(next);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/* v8 ignore next -- a component of two or more always leads back to its start. */
|
|
152
|
+
return [start, start];
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=corpus.js.map
|
|
@@ -0,0 +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/** A live brief whose every dependency is archived can be executed now. */\nexport function isReady(corpus: Corpus, brief: Brief): boolean {\n if (brief.phase !== 'live' || brief.status === 'draft') return false;\n return brief.dependsOn.every((dependency) => resolveDependency(corpus, dependency)?.phase === 'archived');\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/** 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"]}
|
package/dist/engine.d.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The engine: one repository's briefs, with the operations the CLI offers and
|
|
3
|
+
* the same ones for a harness or an orchestrator that would rather call a
|
|
4
|
+
* function than parse a terminal.
|
|
5
|
+
*
|
|
6
|
+
* This module and the CLI are where the pieces meet their implementations -
|
|
7
|
+
* the real filesystem, the real git, the plugins a configuration names.
|
|
8
|
+
* Everything they compose is a pure function that takes those as arguments,
|
|
9
|
+
* so an engine can equally run over a `MemoryFileSystem` and no git at all.
|
|
10
|
+
*/
|
|
11
|
+
import { type Plan } from './archive.js';
|
|
12
|
+
import type { Brief } from './brief.js';
|
|
13
|
+
import { type CollisionOptions, type CollisionReport } from './collisions.js';
|
|
14
|
+
import { type Config } from './config.js';
|
|
15
|
+
import { type Corpus } from './corpus.js';
|
|
16
|
+
import { type FileSystem } from './fs.js';
|
|
17
|
+
import { type Git } from './git.js';
|
|
18
|
+
import { type Plugin } from './lint.js';
|
|
19
|
+
import type { Finding } from './types.js';
|
|
20
|
+
export declare class EngineError extends Error {
|
|
21
|
+
readonly code: 'not-found' | 'ambiguous' | 'invalid' | 'refused' | 'exists';
|
|
22
|
+
constructor(code: EngineError['code'], message: string);
|
|
23
|
+
}
|
|
24
|
+
export interface EngineSetup {
|
|
25
|
+
/** Absolute root: the directory holding the configuration. */
|
|
26
|
+
readonly root: string;
|
|
27
|
+
readonly config: Config;
|
|
28
|
+
/** Repository-relative configuration path, or `null` when the defaults apply. */
|
|
29
|
+
readonly configFile: string | null;
|
|
30
|
+
readonly fs: FileSystem;
|
|
31
|
+
readonly git: Git | null;
|
|
32
|
+
readonly plugins?: readonly Plugin[];
|
|
33
|
+
}
|
|
34
|
+
export interface OpenOptions {
|
|
35
|
+
/** Where discovery starts. Defaults to the process's directory. */
|
|
36
|
+
readonly cwd?: string;
|
|
37
|
+
/** An explicit configuration file; discovery is skipped. */
|
|
38
|
+
readonly config?: string;
|
|
39
|
+
/** Use the defaults even where a configuration file exists. */
|
|
40
|
+
readonly noConfig?: boolean;
|
|
41
|
+
/** `null` runs without git. Defaults to git when the root is inside a working tree. */
|
|
42
|
+
readonly git?: Git | null;
|
|
43
|
+
readonly fs?: FileSystem;
|
|
44
|
+
}
|
|
45
|
+
export interface ArchiveOptions {
|
|
46
|
+
readonly pr?: number | undefined;
|
|
47
|
+
/** The commit the round landed as. */
|
|
48
|
+
readonly commit?: string | undefined;
|
|
49
|
+
/** The branch the round started from; the diff runs from its merge base. */
|
|
50
|
+
readonly base?: string | undefined;
|
|
51
|
+
readonly summary?: string | undefined;
|
|
52
|
+
readonly date?: string | undefined;
|
|
53
|
+
readonly allowDirty?: boolean | undefined;
|
|
54
|
+
readonly strict?: boolean | undefined;
|
|
55
|
+
/** Leave git out, even where there is a repository. */
|
|
56
|
+
readonly noGit?: boolean | undefined;
|
|
57
|
+
}
|
|
58
|
+
export interface NewOptions {
|
|
59
|
+
readonly title: string;
|
|
60
|
+
readonly id?: string | undefined;
|
|
61
|
+
readonly type?: string | undefined;
|
|
62
|
+
readonly wave?: number | undefined;
|
|
63
|
+
readonly dependsOn?: readonly string[] | undefined;
|
|
64
|
+
readonly date?: string | undefined;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Today, in UTC. `SOURCE_DATE_EPOCH` wins where it is set, which is what makes
|
|
68
|
+
* a banner written in a reproducible pipeline reproducible.
|
|
69
|
+
*/
|
|
70
|
+
export declare function today(env?: Readonly<Record<string, string | undefined>>): string;
|
|
71
|
+
export declare function isDate(text: string): boolean;
|
|
72
|
+
export declare class BriefEngine {
|
|
73
|
+
readonly root: string;
|
|
74
|
+
readonly config: Config;
|
|
75
|
+
readonly configFile: string | null;
|
|
76
|
+
readonly fs: FileSystem;
|
|
77
|
+
readonly git: Git | null;
|
|
78
|
+
private plugins;
|
|
79
|
+
private loaded;
|
|
80
|
+
private missingBriefs;
|
|
81
|
+
constructor(setup: EngineSetup);
|
|
82
|
+
/**
|
|
83
|
+
* Finds the configuration, the root it defines and the repository around it,
|
|
84
|
+
* and reads every brief. The root is the directory holding the
|
|
85
|
+
* configuration; without one it is the working tree's top, or `cwd`.
|
|
86
|
+
*/
|
|
87
|
+
static open(options?: OpenOptions): Promise<BriefEngine>;
|
|
88
|
+
get corpus(): Corpus;
|
|
89
|
+
/** Reads every brief again, as after an archival. */
|
|
90
|
+
load(): Promise<Corpus>;
|
|
91
|
+
/**
|
|
92
|
+
* Refuses to report on a briefs directory that does not exist. A check over
|
|
93
|
+
* nothing looks exactly like a clean one, and the likelier cause is a
|
|
94
|
+
* configuration pointing at the wrong place.
|
|
95
|
+
*/
|
|
96
|
+
requireBriefs(): void;
|
|
97
|
+
/** The plugins the configuration names, loaded once. */
|
|
98
|
+
loadedPlugins(): Promise<readonly Plugin[]>;
|
|
99
|
+
/** The one brief a reference names: an id, a file name or a path. */
|
|
100
|
+
find(reference: string): Brief;
|
|
101
|
+
/** The files git sees, or every file when there is no git; `null` when neither can be read. */
|
|
102
|
+
repoFiles(): Promise<string[] | null>;
|
|
103
|
+
lint(references?: readonly string[]): Promise<Finding[]>;
|
|
104
|
+
collisions(options?: CollisionOptions): Promise<{
|
|
105
|
+
report: CollisionReport;
|
|
106
|
+
findings: Finding[];
|
|
107
|
+
}>;
|
|
108
|
+
/** Live briefs that can run now: not drafts, every dependency archived. */
|
|
109
|
+
ready(): Brief[];
|
|
110
|
+
/** The live dependencies holding a brief back. */
|
|
111
|
+
waitingOn(brief: Brief): string[];
|
|
112
|
+
planArchive(reference: string, options?: ArchiveOptions): Promise<Plan>;
|
|
113
|
+
planUnarchive(reference: string): Plan;
|
|
114
|
+
/** Executes a plan and reads the briefs again. A refused plan is never applied. */
|
|
115
|
+
apply(plan: Plan): Promise<void>;
|
|
116
|
+
/** Scaffolds a brief and writes it; the file is new, or nothing is written. */
|
|
117
|
+
create(options: NewOptions): Promise<{
|
|
118
|
+
file: string;
|
|
119
|
+
content: string;
|
|
120
|
+
}>;
|
|
121
|
+
}
|