@descent-vtt/spec-harness 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +41 -0
- package/dist/answers.d.ts +54 -0
- package/dist/answers.js +139 -0
- package/dist/answers.js.map +1 -0
- package/dist/branch.d.ts +2 -2
- package/dist/branch.js +3 -1
- package/dist/branch.js.map +1 -1
- package/dist/cli.js +47 -32
- package/dist/cli.js.map +1 -1
- package/dist/commands.js +6 -2
- package/dist/commands.js.map +1 -1
- package/dist/context.d.ts +1 -0
- package/dist/context.js +6 -4
- package/dist/context.js.map +1 -1
- package/dist/formats.js +3 -1
- package/dist/formats.js.map +1 -1
- package/dist/fs.js +8 -5
- package/dist/fs.js.map +1 -1
- package/dist/git.d.ts +12 -6
- package/dist/git.js +25 -11
- package/dist/git.js.map +1 -1
- package/dist/junit.js +13 -2
- package/dist/junit.js.map +1 -1
- package/dist/plugin.js +10 -17
- package/dist/plugin.js.map +1 -1
- package/dist/premises.js +6 -10
- package/dist/premises.js.map +1 -1
- package/dist/round.d.ts +1 -1
- package/dist/round.js +48 -38
- package/dist/round.js.map +1 -1
- package/dist/server.js +16 -6
- package/dist/server.js.map +1 -1
- package/dist/setup.js +14 -14
- package/dist/setup.js.map +1 -1
- package/dist/siblings.js +11 -3
- package/dist/siblings.js.map +1 -1
- package/dist/workspace.d.ts +16 -3
- package/dist/workspace.js +12 -2
- package/dist/workspace.js.map +1 -1
- package/package.json +3 -3
package/dist/fs.js
CHANGED
|
@@ -13,6 +13,7 @@ export async function loadConfig(root) {
|
|
|
13
13
|
return { config: DEFAULT_CONFIG, file: null };
|
|
14
14
|
let raw;
|
|
15
15
|
try {
|
|
16
|
+
// A Buffer parses as its text, so the encoding is equivalent to its mutant.
|
|
16
17
|
raw = JSON.parse(await readFile(file, 'utf8'));
|
|
17
18
|
}
|
|
18
19
|
catch (error) {
|
|
@@ -33,14 +34,17 @@ export function realSpelling(path) {
|
|
|
33
34
|
let current = resolve(path);
|
|
34
35
|
for (;;) {
|
|
35
36
|
try {
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
// join leaves a real path as it is, and reads a segment's leading
|
|
38
|
+
// separator as the one between it and the last.
|
|
39
|
+
return join(realpathSync.native(current), ...missing.reverse());
|
|
38
40
|
}
|
|
39
41
|
catch {
|
|
40
42
|
const parent = dirname(current);
|
|
43
|
+
// Only a root that does not exist fails at the top, a drive Windows
|
|
44
|
+
// does not have; everywhere else the loop ends in the try.
|
|
41
45
|
if (parent === current)
|
|
42
46
|
return resolve(path);
|
|
43
|
-
missing.push(current.slice(parent.length)
|
|
47
|
+
missing.push(current.slice(parent.length));
|
|
44
48
|
current = parent;
|
|
45
49
|
}
|
|
46
50
|
}
|
|
@@ -54,8 +58,6 @@ export function repositoryPath(given, root, cwd) {
|
|
|
54
58
|
const real = realSpelling(absolute);
|
|
55
59
|
const realRoot = realSpelling(root);
|
|
56
60
|
const rel = relative(realRoot, real);
|
|
57
|
-
if (rel === '')
|
|
58
|
-
return '';
|
|
59
61
|
// `..env` at the root is inside it; only a `..` segment climbs out.
|
|
60
62
|
if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel))
|
|
61
63
|
return null;
|
|
@@ -71,6 +73,7 @@ export async function stateDirectory(commonDir, ...parts) {
|
|
|
71
73
|
export async function writeAtomic(file, content) {
|
|
72
74
|
await mkdir(dirname(file), { recursive: true });
|
|
73
75
|
const temporary = `${file}.${process.pid}.tmp`;
|
|
76
|
+
// utf8 is writeFile's own default, so naming it is equivalent to its mutant.
|
|
74
77
|
await writeFile(temporary, content, 'utf8');
|
|
75
78
|
await rename(temporary, file);
|
|
76
79
|
}
|
package/dist/fs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAE9E,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,EAAsB,MAAM,aAAa,CAAC;AAExG,kFAAkF;AAClF,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY;IAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IACrC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACrE,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IACjD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,WAAW,CAAC,GAAG,WAAW,uBAAwB,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;IACzF,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC;AAC5C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,SAAS,CAAC;QACR,IAAI,CAAC;YACH,
|
|
1
|
+
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The edge where the harness meets the disk: the configuration, the state\n * directory outside the work tree, and paths as the filesystem spells them.\n */\n\nimport { existsSync, realpathSync } from 'node:fs';\nimport { mkdir, readFile, rename, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';\n\nimport { ConfigError, CONFIG_FILE, DEFAULT_CONFIG, parseConfig, type HarnessConfig } from './config.js';\n\n/** Reads `.spec-harness.json` at the root, or the defaults when there is none. */\nexport async function loadConfig(root: string): Promise<{ config: HarnessConfig; file: string | null }> {\n const file = join(root, CONFIG_FILE);\n if (!existsSync(file)) return { config: DEFAULT_CONFIG, file: null };\n let raw: unknown;\n try {\n // A Buffer parses as its text, so the encoding is equivalent to its mutant.\n raw = JSON.parse(await readFile(file, 'utf8'));\n } catch (error) {\n throw new ConfigError(`${CONFIG_FILE} is not valid JSON: ${(error as Error).message}`);\n }\n return { config: parseConfig(raw), file };\n}\n\n/**\n * The real spelling of a path: links resolved and, on a filesystem that\n * ignores case, the case the directory entries have. A path that does not\n * exist yet is resolved through its nearest existing ancestor.\n *\n * This is what stops `SRC/db/schema.ts` from walking past a protection on\n * `src/db/schema.ts` on Windows or macOS, where both name one file.\n */\nexport function realSpelling(path: string): string {\n const missing: string[] = [];\n let current = resolve(path);\n for (;;) {\n try {\n // join leaves a real path as it is, and reads a segment's leading\n // separator as the one between it and the last.\n return join(realpathSync.native(current), ...missing.reverse());\n } catch {\n const parent = dirname(current);\n // Only a root that does not exist fails at the top, a drive Windows\n // does not have; everywhere else the loop ends in the try.\n if (parent === current) return resolve(path);\n missing.push(current.slice(parent.length));\n current = parent;\n }\n }\n}\n\n/**\n * A path as the repository names it - relative to the root, POSIX-separated,\n * real spelling - or `null` when it lies outside the root.\n */\nexport function repositoryPath(given: string, root: string, cwd: string): string | null {\n const absolute = isAbsolute(given) ? given : resolve(cwd, given);\n const real = realSpelling(absolute);\n const realRoot = realSpelling(root);\n const rel = relative(realRoot, real);\n // `..env` at the root is inside it; only a `..` segment climbs out.\n if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return null;\n return rel.split(sep).join('/');\n}\n\n/** The harness's own directory under the git common directory, created on demand (ADR-0003). */\nexport async function stateDirectory(commonDir: string, ...parts: string[]): Promise<string> {\n const directory = join(commonDir, 'spec-harness', ...parts);\n await mkdir(directory, { recursive: true });\n return directory;\n}\n\n/** Writes a file whole or not at all: a temporary file renamed over the target. */\nexport async function writeAtomic(file: string, content: string): Promise<void> {\n await mkdir(dirname(file), { recursive: true });\n const temporary = `${file}.${process.pid}.tmp`;\n // utf8 is writeFile's own default, so naming it is equivalent to its mutant.\n await writeFile(temporary, content, 'utf8');\n await rename(temporary, file);\n}\n\n/** A JSON object from a file: `null` when there is no file, `'unreadable'` when it holds anything else. */\nexport async function readJsonObject(file: string): Promise<Record<string, unknown> | null | 'unreadable'> {\n if (!existsSync(file)) return null;\n try {\n const value = JSON.parse(await readFile(file, 'utf8')) as unknown;\n return typeof value === 'object' && value !== null && !Array.isArray(value) ? (value as Record<string, unknown>) : 'unreadable';\n } catch {\n return 'unreadable';\n }\n}\n\nexport async function readText(file: string): Promise<string | null> {\n try {\n return await readFile(file, 'utf8');\n } catch {\n return null;\n }\n}\n"]}
|
package/dist/git.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface Run {
|
|
|
13
13
|
readonly stderr: string;
|
|
14
14
|
}
|
|
15
15
|
/** Runs git and answers whatever it answered; a missing git is a code of -1. */
|
|
16
|
-
export declare function git(args: readonly string[], cwd: string
|
|
16
|
+
export declare function git(args: readonly string[], cwd: string): Promise<Run>;
|
|
17
17
|
export declare class GitError extends Error {
|
|
18
18
|
constructor(message: string);
|
|
19
19
|
}
|
|
@@ -43,18 +43,24 @@ export declare function changes(from: string, to: string, cwd: string): Promise<
|
|
|
43
43
|
export declare function stagedChanges(cwd: string): Promise<FileChange[]>;
|
|
44
44
|
/** A file's content at a revision, or `null` when it does not exist there. */
|
|
45
45
|
export declare function show(ref: string, path: string, cwd: string): Promise<string | null>;
|
|
46
|
+
/** Whether a commit id from {@link blameLine} is the all-zero one of a line nobody committed. */
|
|
47
|
+
export declare function isUncommitted(sha: string): boolean;
|
|
46
48
|
/**
|
|
47
49
|
* The commit that last changed one line of a file, as of a revision, or of
|
|
48
50
|
* the working tree when `ref` is `null` - where a line nobody committed yet
|
|
49
51
|
* belongs to the all-zero commit.
|
|
50
52
|
*/
|
|
51
53
|
export declare function blameLine(ref: string | null, path: string, line: number, cwd: string): Promise<string | null>;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
readonly principal: string
|
|
54
|
+
/** A good signature names its allowed signer's principal; any other names none. */
|
|
55
|
+
export type Signature = {
|
|
56
|
+
readonly good: true;
|
|
57
|
+
readonly principal: string;
|
|
56
58
|
readonly detail: string;
|
|
57
|
-
}
|
|
59
|
+
} | {
|
|
60
|
+
readonly good: false;
|
|
61
|
+
readonly principal: null;
|
|
62
|
+
readonly detail: string;
|
|
63
|
+
};
|
|
58
64
|
/**
|
|
59
65
|
* Whether a commit carries a good SSH signature by a principal in the given
|
|
60
66
|
* allowed-signers file. The file is passed in, never read from the commit's
|
package/dist/git.js
CHANGED
|
@@ -8,14 +8,16 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { execFile } from 'node:child_process';
|
|
10
10
|
/** Runs git and answers whatever it answered; a missing git is a code of -1. */
|
|
11
|
-
export function git(args, cwd
|
|
11
|
+
export function git(args, cwd) {
|
|
12
12
|
return new Promise((resolve) => {
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
// windowsHide only keeps a console window from flashing up on Windows,
|
|
14
|
+
// and the sweep runs on Linux, where its mutant is equivalent.
|
|
15
|
+
execFile('git', [...args], { cwd, maxBuffer: 256 * 1024 * 1024, encoding: 'utf8', windowsHide: true }, (error, stdout, stderr) => {
|
|
16
|
+
// Every caller tells 0 from any other code, never one failure from
|
|
17
|
+
// another, so which code a failure gets is equivalent to its mutants.
|
|
18
|
+
const code = error === null ? 0 : typeof error.code === 'number' ? error.code : -1;
|
|
15
19
|
resolve({ code, stdout, stderr });
|
|
16
20
|
});
|
|
17
|
-
if (input !== undefined)
|
|
18
|
-
child.stdin?.end(input);
|
|
19
21
|
});
|
|
20
22
|
}
|
|
21
23
|
async function value(args, cwd) {
|
|
@@ -55,7 +57,9 @@ export async function remoteDefault(cwd) {
|
|
|
55
57
|
/** Every local branch, by its short name. */
|
|
56
58
|
export async function localBranches(cwd) {
|
|
57
59
|
const listed = await value(['for-each-ref', '--format=%(refname:short)', 'refs/heads/'], cwd);
|
|
58
|
-
|
|
60
|
+
// for-each-ref fails only where git itself does, outside a repository,
|
|
61
|
+
// where init never runs: the null check is there for the type.
|
|
62
|
+
return listed === null || listed === '' ? [] : listed.split('\n');
|
|
59
63
|
}
|
|
60
64
|
export async function revision(ref, cwd) {
|
|
61
65
|
return value(['rev-parse', '--verify', '--quiet', `${ref}^{commit}`], cwd);
|
|
@@ -68,7 +72,7 @@ export function parseNameStatus(output) {
|
|
|
68
72
|
const fields = output.split('\0');
|
|
69
73
|
const out = [];
|
|
70
74
|
for (let i = 0; i < fields.length;) {
|
|
71
|
-
const code = fields[i]
|
|
75
|
+
const code = fields[i];
|
|
72
76
|
if (code === '') {
|
|
73
77
|
i += 1;
|
|
74
78
|
continue;
|
|
@@ -104,6 +108,10 @@ export async function show(ref, path, cwd) {
|
|
|
104
108
|
const run = await git(['show', `${ref}:${path}`], cwd);
|
|
105
109
|
return run.code === 0 ? run.stdout : null;
|
|
106
110
|
}
|
|
111
|
+
/** Whether a commit id from {@link blameLine} is the all-zero one of a line nobody committed. */
|
|
112
|
+
export function isUncommitted(sha) {
|
|
113
|
+
return /^0+$/.test(sha);
|
|
114
|
+
}
|
|
107
115
|
/**
|
|
108
116
|
* The commit that last changed one line of a file, as of a revision, or of
|
|
109
117
|
* the working tree when `ref` is `null` - where a line nobody committed yet
|
|
@@ -111,8 +119,8 @@ export async function show(ref, path, cwd) {
|
|
|
111
119
|
*/
|
|
112
120
|
export async function blameLine(ref, path, line, cwd) {
|
|
113
121
|
const run = await git(['blame', '--porcelain', '-L', `${line},${line}`, ...(ref === null ? [] : [ref]), '--', path], cwd);
|
|
114
|
-
|
|
115
|
-
|
|
122
|
+
// A blame that fails prints nothing on stdout, which names no commit. Its
|
|
123
|
+
// output opens with the commit, so the anchor is equivalent to its mutant.
|
|
116
124
|
const sha = /^([0-9a-f]{40,64}) /.exec(run.stdout)?.[1];
|
|
117
125
|
return sha ?? null;
|
|
118
126
|
}
|
|
@@ -124,8 +132,14 @@ export async function blameLine(ref, path, line, cwd) {
|
|
|
124
132
|
export async function verifyCommit(sha, allowedSignersFile, cwd) {
|
|
125
133
|
const run = await git(['-c', `gpg.ssh.allowedSignersFile=${allowedSignersFile}`, '-c', 'gpg.format=ssh', 'verify-commit', '--raw', sha], cwd);
|
|
126
134
|
const text = `${run.stderr}\n${run.stdout}`;
|
|
127
|
-
const principal = /Good "git" signature for (\S+)/.exec(text)?.[1]
|
|
128
|
-
|
|
135
|
+
const principal = /Good "git" signature for (\S+)/.exec(text)?.[1];
|
|
136
|
+
// git writes the check to stderr, so the trim changes the first line only
|
|
137
|
+
// for output on stdout alone, which verify-commit does not print; and split
|
|
138
|
+
// always answers a first part.
|
|
139
|
+
const detail = text.trim().split('\n')[0];
|
|
140
|
+
// git exits 0 only for a signature by a listed principal, which it names:
|
|
141
|
+
// either condition alone is equivalent to both, and both trust neither.
|
|
142
|
+
return run.code === 0 && principal !== undefined ? { good: true, principal, detail } : { good: false, principal: null, detail };
|
|
129
143
|
}
|
|
130
144
|
export async function addWorktree(path, rev, cwd) {
|
|
131
145
|
const run = await git(['worktree', 'add', '--detach', '--force', path, rev], cwd);
|
package/dist/git.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The edge where the harness meets git.\n *\n * Reads, except for two things ADR-0003 allows and bounds: a temporary\n * worktree, added and always removed by the sandbox, and nothing else. Every\n * call is an argument vector, never a shell string, so a path or a ref cannot\n * become a command.\n */\n\nimport { execFile } from 'node:child_process';\n\nimport type { FileChange } from './types.js';\n\nexport interface Run {\n readonly code: number;\n readonly stdout: string;\n readonly stderr: string;\n}\n\n/** Runs git and answers whatever it answered; a missing git is a code of -1. */\nexport function git(args: readonly string[], cwd: string, input?: string): Promise<Run> {\n return new Promise((resolve) => {\n const child = execFile(\n 'git',\n [...args],\n { cwd, maxBuffer: 256 * 1024 * 1024, encoding: 'utf8', windowsHide: true },\n (error, stdout, stderr) => {\n const code = error === null ? 0 : typeof (error as { code?: unknown }).code === 'number' ? ((error as { code: number }).code) : -1;\n resolve({ code, stdout, stderr });\n },\n );\n if (input !== undefined) child.stdin?.end(input);\n });\n}\n\nasync function value(args: readonly string[], cwd: string): Promise<string | null> {\n const run = await git(args, cwd);\n return run.code === 0 ? run.stdout.trim() : null;\n}\n\nexport class GitError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'GitError';\n }\n}\n\n/** The top of the work tree holding `cwd`, POSIX-separated, or `null` outside one. */\nexport async function workTreeRoot(cwd: string): Promise<string | null> {\n return value(['rev-parse', '--show-toplevel'], cwd);\n}\n\n/** The git directory every worktree of this repository shares. */\nexport async function commonDirectory(cwd: string): Promise<string | null> {\n return value(['rev-parse', '--path-format=absolute', '--git-common-dir'], cwd);\n}\n\n/**\n * Where git runs the hook `name` from, absolute: `core.hooksPath` read as git\n * reads it - relative to the work tree, absolute, or under `~` - and a linked\n * worktree's shared hooks. `null` when git cannot say, before 2.31.\n */\nexport async function hookPath(name: string, cwd: string): Promise<string | null> {\n return value(['rev-parse', '--path-format=absolute', '--git-path', `hooks/${name}`], cwd);\n}\n\n/** The checked-out branch's short name, or `null` on a detached head. */\nexport async function currentBranch(cwd: string): Promise<string | null> {\n return value(['symbolic-ref', '--quiet', '--short', 'HEAD'], cwd);\n}\n\n/** The remote's default branch as a local ref, such as `origin/main`, or `null`. */\nexport async function remoteDefault(cwd: string): Promise<string | null> {\n return value(['symbolic-ref', '--quiet', '--short', 'refs/remotes/origin/HEAD'], cwd);\n}\n\n/** Every local branch, by its short name. */\nexport async function localBranches(cwd: string): Promise<string[]> {\n const listed = await value(['for-each-ref', '--format=%(refname:short)', 'refs/heads/'], cwd);\n return listed === null || listed === '' ? [] : listed.split('\\n').map((name) => name.trim());\n}\n\nexport async function revision(ref: string, cwd: string): Promise<string | null> {\n return value(['rev-parse', '--verify', '--quiet', `${ref}^{commit}`], cwd);\n}\n\nexport async function mergeBase(a: string, b: string, cwd: string): Promise<string | null> {\n return value(['merge-base', a, b], cwd);\n}\n\n/** Parses `git diff --name-status -z` output. */\nexport function parseNameStatus(output: string): FileChange[] {\n const fields = output.split('\\0');\n const out: FileChange[] = [];\n for (let i = 0; i < fields.length; ) {\n const code = fields[i] ?? '';\n if (code === '') {\n i += 1;\n continue;\n }\n const status = code.charAt(0) as FileChange['status'];\n if (status === 'R' || status === 'C') {\n out.push({ status, from: fields[i + 1] ?? '', path: fields[i + 2] ?? '' });\n i += 3;\n } else {\n out.push({ status, path: fields[i + 1] ?? '' });\n i += 2;\n }\n }\n return out;\n}\n\n/** The files changed from one revision to another, renames found. */\nexport async function changes(from: string, to: string, cwd: string): Promise<FileChange[]> {\n const run = await git(['diff', '--name-status', '-z', '--find-renames', '--no-color', from, to, '--'], cwd);\n if (run.code !== 0) throw new GitError(`git diff ${from} ${to} failed: ${run.stderr.trim()}`);\n return parseNameStatus(run.stdout);\n}\n\n/** The files a commit would change: what is staged. */\nexport async function stagedChanges(cwd: string): Promise<FileChange[]> {\n const run = await git(['diff', '--cached', '--name-status', '-z', '--find-renames', '--no-color', '--'], cwd);\n if (run.code !== 0) throw new GitError(`git diff --cached failed: ${run.stderr.trim()}`);\n return parseNameStatus(run.stdout);\n}\n\n/** A file's content at a revision, or `null` when it does not exist there. */\nexport async function show(ref: string, path: string, cwd: string): Promise<string | null> {\n const run = await git(['show', `${ref}:${path}`], cwd);\n return run.code === 0 ? run.stdout : null;\n}\n\n/**\n * The commit that last changed one line of a file, as of a revision, or of\n * the working tree when `ref` is `null` - where a line nobody committed yet\n * belongs to the all-zero commit.\n */\nexport async function blameLine(ref: string | null, path: string, line: number, cwd: string): Promise<string | null> {\n const run = await git(['blame', '--porcelain', '-L', `${line},${line}`, ...(ref === null ? [] : [ref]), '--', path], cwd);\n if (run.code !== 0) return null;\n const sha = /^([0-9a-f]{40,64}) /.exec(run.stdout)?.[1];\n return sha ?? null;\n}\n\nexport interface Signature {\n readonly good: boolean;\n /** The allowed signer's principal, for a good signature. */\n readonly principal: string | null;\n readonly detail: string;\n}\n\n/**\n * Whether a commit carries a good SSH signature by a principal in the given\n * allowed-signers file. The file is passed in, never read from the commit's\n * own tree: a round cannot vouch for itself (ADR-0006).\n */\nexport async function verifyCommit(sha: string, allowedSignersFile: string, cwd: string): Promise<Signature> {\n const run = await git(\n ['-c', `gpg.ssh.allowedSignersFile=${allowedSignersFile}`, '-c', 'gpg.format=ssh', 'verify-commit', '--raw', sha],\n cwd,\n );\n const text = `${run.stderr}\\n${run.stdout}`;\n const principal = /Good \"git\" signature for (\\S+)/.exec(text)?.[1] ?? null;\n return { good: run.code === 0 && principal !== null, principal, detail: text.trim().split('\\n')[0] ?? '' };\n}\n\nexport async function addWorktree(path: string, rev: string, cwd: string): Promise<void> {\n const run = await git(['worktree', 'add', '--detach', '--force', path, rev], cwd);\n if (run.code !== 0) throw new GitError(`git worktree add failed: ${run.stderr.trim()}`);\n}\n\n/**\n * Forgets the worktree at `path`, and no other, once its directory is\n * deleted. Git refuses to remove a worktree whose directory has lost its\n * `.git` file, and removes its record of one whose directory is gone, found by\n * the path it was added at: `git worktree prune` would also forget every other\n * worktree git has lost track of, the person's among them.\n */\nexport async function removeWorktree(path: string, cwd: string): Promise<void> {\n await git(['worktree', 'remove', '--force', path], cwd);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The edge where the harness meets git.\n *\n * Reads, except for two things ADR-0003 allows and bounds: a temporary\n * worktree, added and always removed by the sandbox, and nothing else. Every\n * call is an argument vector, never a shell string, so a path or a ref cannot\n * become a command.\n */\n\nimport { execFile } from 'node:child_process';\n\nimport type { FileChange } from './types.js';\n\nexport interface Run {\n readonly code: number;\n readonly stdout: string;\n readonly stderr: string;\n}\n\n/** Runs git and answers whatever it answered; a missing git is a code of -1. */\nexport function git(args: readonly string[], cwd: string): Promise<Run> {\n return new Promise((resolve) => {\n // windowsHide only keeps a console window from flashing up on Windows,\n // and the sweep runs on Linux, where its mutant is equivalent.\n execFile('git', [...args], { cwd, maxBuffer: 256 * 1024 * 1024, encoding: 'utf8', windowsHide: true }, (error, stdout, stderr) => {\n // Every caller tells 0 from any other code, never one failure from\n // another, so which code a failure gets is equivalent to its mutants.\n const code = error === null ? 0 : typeof (error as { code?: unknown }).code === 'number' ? (error as { code: number }).code : -1;\n resolve({ code, stdout, stderr });\n });\n });\n}\n\nasync function value(args: readonly string[], cwd: string): Promise<string | null> {\n const run = await git(args, cwd);\n return run.code === 0 ? run.stdout.trim() : null;\n}\n\nexport class GitError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'GitError';\n }\n}\n\n/** The top of the work tree holding `cwd`, POSIX-separated, or `null` outside one. */\nexport async function workTreeRoot(cwd: string): Promise<string | null> {\n return value(['rev-parse', '--show-toplevel'], cwd);\n}\n\n/** The git directory every worktree of this repository shares. */\nexport async function commonDirectory(cwd: string): Promise<string | null> {\n return value(['rev-parse', '--path-format=absolute', '--git-common-dir'], cwd);\n}\n\n/**\n * Where git runs the hook `name` from, absolute: `core.hooksPath` read as git\n * reads it - relative to the work tree, absolute, or under `~` - and a linked\n * worktree's shared hooks. `null` when git cannot say, before 2.31.\n */\nexport async function hookPath(name: string, cwd: string): Promise<string | null> {\n return value(['rev-parse', '--path-format=absolute', '--git-path', `hooks/${name}`], cwd);\n}\n\n/** The checked-out branch's short name, or `null` on a detached head. */\nexport async function currentBranch(cwd: string): Promise<string | null> {\n return value(['symbolic-ref', '--quiet', '--short', 'HEAD'], cwd);\n}\n\n/** The remote's default branch as a local ref, such as `origin/main`, or `null`. */\nexport async function remoteDefault(cwd: string): Promise<string | null> {\n return value(['symbolic-ref', '--quiet', '--short', 'refs/remotes/origin/HEAD'], cwd);\n}\n\n/** Every local branch, by its short name. */\nexport async function localBranches(cwd: string): Promise<string[]> {\n const listed = await value(['for-each-ref', '--format=%(refname:short)', 'refs/heads/'], cwd);\n // for-each-ref fails only where git itself does, outside a repository,\n // where init never runs: the null check is there for the type.\n return listed === null || listed === '' ? [] : listed.split('\\n');\n}\n\nexport async function revision(ref: string, cwd: string): Promise<string | null> {\n return value(['rev-parse', '--verify', '--quiet', `${ref}^{commit}`], cwd);\n}\n\nexport async function mergeBase(a: string, b: string, cwd: string): Promise<string | null> {\n return value(['merge-base', a, b], cwd);\n}\n\n/** Parses `git diff --name-status -z` output. */\nexport function parseNameStatus(output: string): FileChange[] {\n const fields = output.split('\\0');\n const out: FileChange[] = [];\n for (let i = 0; i < fields.length; ) {\n const code = fields[i] as string;\n if (code === '') {\n i += 1;\n continue;\n }\n const status = code.charAt(0) as FileChange['status'];\n if (status === 'R' || status === 'C') {\n out.push({ status, from: fields[i + 1] ?? '', path: fields[i + 2] ?? '' });\n i += 3;\n } else {\n out.push({ status, path: fields[i + 1] ?? '' });\n i += 2;\n }\n }\n return out;\n}\n\n/** The files changed from one revision to another, renames found. */\nexport async function changes(from: string, to: string, cwd: string): Promise<FileChange[]> {\n const run = await git(['diff', '--name-status', '-z', '--find-renames', '--no-color', from, to, '--'], cwd);\n if (run.code !== 0) throw new GitError(`git diff ${from} ${to} failed: ${run.stderr.trim()}`);\n return parseNameStatus(run.stdout);\n}\n\n/** The files a commit would change: what is staged. */\nexport async function stagedChanges(cwd: string): Promise<FileChange[]> {\n const run = await git(['diff', '--cached', '--name-status', '-z', '--find-renames', '--no-color', '--'], cwd);\n if (run.code !== 0) throw new GitError(`git diff --cached failed: ${run.stderr.trim()}`);\n return parseNameStatus(run.stdout);\n}\n\n/** A file's content at a revision, or `null` when it does not exist there. */\nexport async function show(ref: string, path: string, cwd: string): Promise<string | null> {\n const run = await git(['show', `${ref}:${path}`], cwd);\n return run.code === 0 ? run.stdout : null;\n}\n\n/** Whether a commit id from {@link blameLine} is the all-zero one of a line nobody committed. */\nexport function isUncommitted(sha: string): boolean {\n return /^0+$/.test(sha);\n}\n\n/**\n * The commit that last changed one line of a file, as of a revision, or of\n * the working tree when `ref` is `null` - where a line nobody committed yet\n * belongs to the all-zero commit.\n */\nexport async function blameLine(ref: string | null, path: string, line: number, cwd: string): Promise<string | null> {\n const run = await git(['blame', '--porcelain', '-L', `${line},${line}`, ...(ref === null ? [] : [ref]), '--', path], cwd);\n // A blame that fails prints nothing on stdout, which names no commit. Its\n // output opens with the commit, so the anchor is equivalent to its mutant.\n const sha = /^([0-9a-f]{40,64}) /.exec(run.stdout)?.[1];\n return sha ?? null;\n}\n\n/** A good signature names its allowed signer's principal; any other names none. */\nexport type Signature =\n | { readonly good: true; readonly principal: string; readonly detail: string }\n | { readonly good: false; readonly principal: null; readonly detail: string };\n\n/**\n * Whether a commit carries a good SSH signature by a principal in the given\n * allowed-signers file. The file is passed in, never read from the commit's\n * own tree: a round cannot vouch for itself (ADR-0006).\n */\nexport async function verifyCommit(sha: string, allowedSignersFile: string, cwd: string): Promise<Signature> {\n const run = await git(\n ['-c', `gpg.ssh.allowedSignersFile=${allowedSignersFile}`, '-c', 'gpg.format=ssh', 'verify-commit', '--raw', sha],\n cwd,\n );\n const text = `${run.stderr}\\n${run.stdout}`;\n const principal = /Good \"git\" signature for (\\S+)/.exec(text)?.[1];\n // git writes the check to stderr, so the trim changes the first line only\n // for output on stdout alone, which verify-commit does not print; and split\n // always answers a first part.\n const detail = text.trim().split('\\n')[0] as string;\n // git exits 0 only for a signature by a listed principal, which it names:\n // either condition alone is equivalent to both, and both trust neither.\n return run.code === 0 && principal !== undefined ? { good: true, principal, detail } : { good: false, principal: null, detail };\n}\n\nexport async function addWorktree(path: string, rev: string, cwd: string): Promise<void> {\n const run = await git(['worktree', 'add', '--detach', '--force', path, rev], cwd);\n if (run.code !== 0) throw new GitError(`git worktree add failed: ${run.stderr.trim()}`);\n}\n\n/**\n * Forgets the worktree at `path`, and no other, once its directory is\n * deleted. Git refuses to remove a worktree whose directory has lost its\n * `.git` file, and removes its record of one whose directory is gone, found by\n * the path it was added at: `git worktree prune` would also forget every other\n * worktree git has lost track of, the person's among them.\n */\nexport async function removeWorktree(path: string, cwd: string): Promise<void> {\n await git(['worktree', 'remove', '--force', path], cwd);\n}\n"]}
|
package/dist/junit.js
CHANGED
|
@@ -10,7 +10,18 @@
|
|
|
10
10
|
* attributes, entity-aware, that skips everything else, and it refuses text
|
|
11
11
|
* it cannot read rather than guessing at it.
|
|
12
12
|
*/
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* A map, because the name comes from a report and an object answers to more
|
|
15
|
+
* names than it was given: `&constructor;` in a test's name was replaced with
|
|
16
|
+
* the source of a function.
|
|
17
|
+
*/
|
|
18
|
+
const ENTITIES = new Map([
|
|
19
|
+
['amp', '&'],
|
|
20
|
+
['lt', '<'],
|
|
21
|
+
['gt', '>'],
|
|
22
|
+
['quot', '"'],
|
|
23
|
+
['apos', "'"],
|
|
24
|
+
]);
|
|
14
25
|
/** Replaces the five named entities and numeric references. An unknown entity stays as written. */
|
|
15
26
|
export function decodeEntities(text) {
|
|
16
27
|
return text.replace(/&(#x[0-9a-fA-F]+|#[0-9]+|[a-zA-Z]+);/g, (whole, body) => {
|
|
@@ -22,7 +33,7 @@ export function decodeEntities(text) {
|
|
|
22
33
|
const point = Number.parseInt(body.slice(1), 10);
|
|
23
34
|
return point <= 0x10ffff ? String.fromCodePoint(point) : whole;
|
|
24
35
|
}
|
|
25
|
-
return ENTITIES
|
|
36
|
+
return ENTITIES.get(body) ?? whole;
|
|
26
37
|
});
|
|
27
38
|
}
|
|
28
39
|
function attributes(text) {
|
package/dist/junit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"junit.js","sourceRoot":"","sources":["../src/junit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The failures a JUnit XML report records.\n *\n * JUnit XML is the one result format nearly every test runner writes, natively\n * or through a reporter - Vitest, Jest, pytest, `go test` through\n * go-junit-report, `cargo nextest`, `dotnet test` through its logger - so a\n * probe that reads it reads them all (ADR-0007). This reads the part of the\n * format a probe needs: which test cases failed or errored, and with what\n * message. It is not an XML parser; it is a tokenizer for elements and\n * attributes, entity-aware, that skips everything else, and it refuses text\n * it cannot read rather than guessing at it.\n */\n\nexport interface JUnitCase {\n readonly name: string;\n readonly classname: string;\n readonly outcome: 'passed' | 'failed' | 'errored' | 'skipped';\n /** The failure's or error's message and body, joined. */\n readonly message: string;\n}\n\nexport type JUnitRead = { readonly ok: true; readonly cases: readonly JUnitCase[] } | { readonly ok: false; readonly error: string };\n\nconst ENTITIES: Readonly<Record<string, string>> = { amp: '&', lt: '<', gt: '>', quot: '\"', apos: \"'\" };\n\n/** Replaces the five named entities and numeric references. An unknown entity stays as written. */\nexport function decodeEntities(text: string): string {\n return text.replace(/&(#x[0-9a-fA-F]+|#[0-9]+|[a-zA-Z]+);/g, (whole, body: string) => {\n if (body.startsWith('#x')) {\n const point = Number.parseInt(body.slice(2), 16);\n return point <= 0x10ffff ? String.fromCodePoint(point) : whole;\n }\n if (body.startsWith('#')) {\n const point = Number.parseInt(body.slice(1), 10);\n return point <= 0x10ffff ? String.fromCodePoint(point) : whole;\n }\n return ENTITIES[body] ?? whole;\n });\n}\n\nfunction attributes(text: string): Record<string, string> {\n const out: Record<string, string> = {};\n const pattern = /([A-Za-z_:][\\w:.-]*)\\s*=\\s*(\"([^\"]*)\"|'([^']*)')/g;\n for (let match = pattern.exec(text); match !== null; match = pattern.exec(text)) {\n out[match[1] as string] = decodeEntities(match[3] ?? match[4] ?? '');\n }\n return out;\n}\n\ninterface Tag {\n readonly kind: 'open' | 'close' | 'self';\n readonly name: string;\n readonly attributes: Record<string, string>;\n readonly start: number;\n readonly end: number;\n}\n\n/** The tags of a document, comments, CDATA and declarations skipped, in one pass. */\nfunction tags(xml: string): Tag[] | string {\n const out: Tag[] = [];\n let i = 0;\n while (i < xml.length) {\n const open = xml.indexOf('<', i);\n if (open < 0) break;\n if (xml.startsWith('<!--', open)) {\n const close = xml.indexOf('-->', open + 4);\n if (close < 0) return 'a comment is never closed';\n i = close + 3;\n continue;\n }\n if (xml.startsWith('<![CDATA[', open)) {\n const close = xml.indexOf(']]>', open + 9);\n if (close < 0) return 'a CDATA section is never closed';\n i = close + 3;\n continue;\n }\n if (xml.startsWith('<?', open) || xml.startsWith('<!', open)) {\n const close = xml.indexOf('>', open);\n if (close < 0) return 'a declaration is never closed';\n i = close + 1;\n continue;\n }\n let close = open + 1;\n let quote: string | null = null;\n for (; close < xml.length; close += 1) {\n const ch = xml.charAt(close);\n if (quote !== null) {\n if (ch === quote) quote = null;\n } else if (ch === '\"' || ch === \"'\") quote = ch;\n else if (ch === '>') break;\n }\n if (close >= xml.length) return 'a tag is never closed';\n const body = xml.slice(open + 1, close);\n const closing = body.startsWith('/');\n const self = body.endsWith('/');\n const name = /^\\/?\\s*([A-Za-z_:][\\w:.-]*)/.exec(body)?.[1];\n if (name === undefined) return `\"<${body.slice(0, 20)}\" is not a tag`;\n out.push({\n kind: closing ? 'close' : self ? 'self' : 'open',\n name,\n attributes: closing ? {} : attributes(body.slice(name.length + 1)),\n start: open,\n end: close + 1,\n });\n i = close + 1;\n }\n return out;\n}\n\n/**\n * The text between two offsets, tags dropped and entities decoded outside\n * CDATA, and CDATA kept as written: a stack trace's `Object.<anonymous>` or\n * `a < b` inside CDATA is text, not a tag.\n */\nfunction innerText(xml: string, from: number, to: number): string {\n const slice = xml.slice(from, to);\n const markup = (text: string): string => decodeEntities(text.replace(/<[^>]*>/g, ''));\n let out = '';\n let last = 0;\n for (const match of slice.matchAll(/<!\\[CDATA\\[([\\s\\S]*?)\\]\\]>/g)) {\n out += markup(slice.slice(last, match.index)) + (match[1] as string);\n last = match.index + match[0].length;\n }\n return (out + markup(slice.slice(last))).trim();\n}\n\n/** Reads the test cases of a JUnit XML report, nested suites included. */\nexport function readJUnit(xml: string): JUnitRead {\n const list = tags(xml);\n if (typeof list === 'string') return { ok: false, error: list };\n if (!list.some((tag) => tag.name === 'testsuite' || tag.name === 'testsuites')) {\n return { ok: false, error: 'no <testsuite> element: this is not a JUnit report' };\n }\n const cases: JUnitCase[] = [];\n for (let i = 0; i < list.length; i += 1) {\n const tag = list[i] as Tag;\n if (tag.name !== 'testcase' || tag.kind === 'close') continue;\n const name = tag.attributes['name'] ?? '';\n const classname = tag.attributes['classname'] ?? '';\n if (tag.kind === 'self') {\n cases.push({ name, classname, outcome: 'passed', message: '' });\n continue;\n }\n let outcome: JUnitCase['outcome'] = 'passed';\n const messages: string[] = [];\n let j = i + 1;\n for (; j < list.length; j += 1) {\n const inner = list[j] as Tag;\n if (inner.name === 'testcase' && inner.kind === 'close') break;\n if (inner.kind === 'close') continue;\n if (inner.name === 'failure' || inner.name === 'error') {\n outcome = inner.name === 'failure' ? 'failed' : outcome === 'failed' ? 'failed' : 'errored';\n const message = inner.attributes['message'];\n if (message !== undefined && message !== '') messages.push(message);\n if (inner.kind === 'open') {\n const end = list.findIndex((candidate, k) => k > j && candidate.name === inner.name && candidate.kind === 'close');\n if (end > j) {\n const body = innerText(xml, inner.end, (list[end] as Tag).start);\n if (body !== '') messages.push(body);\n }\n }\n } else if (inner.name === 'skipped' && outcome === 'passed') {\n outcome = 'skipped';\n }\n }\n if (j >= list.length) return { ok: false, error: `test case \"${name}\" is never closed` };\n cases.push({ name, classname, outcome, message: messages.join('\\n') });\n i = j;\n }\n return { ok: true, cases };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"junit.js","sourceRoot":"","sources":["../src/junit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The failures a JUnit XML report records.\n *\n * JUnit XML is the one result format nearly every test runner writes, natively\n * or through a reporter - Vitest, Jest, pytest, `go test` through\n * go-junit-report, `cargo nextest`, `dotnet test` through its logger - so a\n * probe that reads it reads them all (ADR-0007). This reads the part of the\n * format a probe needs: which test cases failed or errored, and with what\n * message. It is not an XML parser; it is a tokenizer for elements and\n * attributes, entity-aware, that skips everything else, and it refuses text\n * it cannot read rather than guessing at it.\n */\n\nexport interface JUnitCase {\n readonly name: string;\n readonly classname: string;\n readonly outcome: 'passed' | 'failed' | 'errored' | 'skipped';\n /** The failure's or error's message and body, joined. */\n readonly message: string;\n}\n\nexport type JUnitRead = { readonly ok: true; readonly cases: readonly JUnitCase[] } | { readonly ok: false; readonly error: string };\n\n/**\n * A map, because the name comes from a report and an object answers to more\n * names than it was given: `&constructor;` in a test's name was replaced with\n * the source of a function.\n */\nconst ENTITIES: ReadonlyMap<string, string> = new Map([\n ['amp', '&'],\n ['lt', '<'],\n ['gt', '>'],\n ['quot', '\"'],\n ['apos', \"'\"],\n]);\n\n/** Replaces the five named entities and numeric references. An unknown entity stays as written. */\nexport function decodeEntities(text: string): string {\n return text.replace(/&(#x[0-9a-fA-F]+|#[0-9]+|[a-zA-Z]+);/g, (whole, body: string) => {\n if (body.startsWith('#x')) {\n const point = Number.parseInt(body.slice(2), 16);\n return point <= 0x10ffff ? String.fromCodePoint(point) : whole;\n }\n if (body.startsWith('#')) {\n const point = Number.parseInt(body.slice(1), 10);\n return point <= 0x10ffff ? String.fromCodePoint(point) : whole;\n }\n return ENTITIES.get(body) ?? whole;\n });\n}\n\nfunction attributes(text: string): Record<string, string> {\n const out: Record<string, string> = {};\n const pattern = /([A-Za-z_:][\\w:.-]*)\\s*=\\s*(\"([^\"]*)\"|'([^']*)')/g;\n for (let match = pattern.exec(text); match !== null; match = pattern.exec(text)) {\n out[match[1] as string] = decodeEntities(match[3] ?? match[4] ?? '');\n }\n return out;\n}\n\ninterface Tag {\n readonly kind: 'open' | 'close' | 'self';\n readonly name: string;\n readonly attributes: Record<string, string>;\n readonly start: number;\n readonly end: number;\n}\n\n/** The tags of a document, comments, CDATA and declarations skipped, in one pass. */\nfunction tags(xml: string): Tag[] | string {\n const out: Tag[] = [];\n let i = 0;\n while (i < xml.length) {\n const open = xml.indexOf('<', i);\n if (open < 0) break;\n if (xml.startsWith('<!--', open)) {\n const close = xml.indexOf('-->', open + 4);\n if (close < 0) return 'a comment is never closed';\n i = close + 3;\n continue;\n }\n if (xml.startsWith('<![CDATA[', open)) {\n const close = xml.indexOf(']]>', open + 9);\n if (close < 0) return 'a CDATA section is never closed';\n i = close + 3;\n continue;\n }\n if (xml.startsWith('<?', open) || xml.startsWith('<!', open)) {\n const close = xml.indexOf('>', open);\n if (close < 0) return 'a declaration is never closed';\n i = close + 1;\n continue;\n }\n let close = open + 1;\n let quote: string | null = null;\n for (; close < xml.length; close += 1) {\n const ch = xml.charAt(close);\n if (quote !== null) {\n if (ch === quote) quote = null;\n } else if (ch === '\"' || ch === \"'\") quote = ch;\n else if (ch === '>') break;\n }\n if (close >= xml.length) return 'a tag is never closed';\n const body = xml.slice(open + 1, close);\n const closing = body.startsWith('/');\n const self = body.endsWith('/');\n const name = /^\\/?\\s*([A-Za-z_:][\\w:.-]*)/.exec(body)?.[1];\n if (name === undefined) return `\"<${body.slice(0, 20)}\" is not a tag`;\n out.push({\n kind: closing ? 'close' : self ? 'self' : 'open',\n name,\n attributes: closing ? {} : attributes(body.slice(name.length + 1)),\n start: open,\n end: close + 1,\n });\n i = close + 1;\n }\n return out;\n}\n\n/**\n * The text between two offsets, tags dropped and entities decoded outside\n * CDATA, and CDATA kept as written: a stack trace's `Object.<anonymous>` or\n * `a < b` inside CDATA is text, not a tag.\n */\nfunction innerText(xml: string, from: number, to: number): string {\n const slice = xml.slice(from, to);\n const markup = (text: string): string => decodeEntities(text.replace(/<[^>]*>/g, ''));\n let out = '';\n let last = 0;\n for (const match of slice.matchAll(/<!\\[CDATA\\[([\\s\\S]*?)\\]\\]>/g)) {\n out += markup(slice.slice(last, match.index)) + (match[1] as string);\n last = match.index + match[0].length;\n }\n return (out + markup(slice.slice(last))).trim();\n}\n\n/** Reads the test cases of a JUnit XML report, nested suites included. */\nexport function readJUnit(xml: string): JUnitRead {\n const list = tags(xml);\n if (typeof list === 'string') return { ok: false, error: list };\n if (!list.some((tag) => tag.name === 'testsuite' || tag.name === 'testsuites')) {\n return { ok: false, error: 'no <testsuite> element: this is not a JUnit report' };\n }\n const cases: JUnitCase[] = [];\n for (let i = 0; i < list.length; i += 1) {\n const tag = list[i] as Tag;\n if (tag.name !== 'testcase' || tag.kind === 'close') continue;\n const name = tag.attributes['name'] ?? '';\n const classname = tag.attributes['classname'] ?? '';\n if (tag.kind === 'self') {\n cases.push({ name, classname, outcome: 'passed', message: '' });\n continue;\n }\n let outcome: JUnitCase['outcome'] = 'passed';\n const messages: string[] = [];\n let j = i + 1;\n for (; j < list.length; j += 1) {\n const inner = list[j] as Tag;\n if (inner.name === 'testcase' && inner.kind === 'close') break;\n if (inner.kind === 'close') continue;\n if (inner.name === 'failure' || inner.name === 'error') {\n outcome = inner.name === 'failure' ? 'failed' : outcome === 'failed' ? 'failed' : 'errored';\n const message = inner.attributes['message'];\n if (message !== undefined && message !== '') messages.push(message);\n if (inner.kind === 'open') {\n const end = list.findIndex((candidate, k) => k > j && candidate.name === inner.name && candidate.kind === 'close');\n if (end > j) {\n const body = innerText(xml, inner.end, (list[end] as Tag).start);\n if (body !== '') messages.push(body);\n }\n }\n } else if (inner.name === 'skipped' && outcome === 'passed') {\n outcome = 'skipped';\n }\n }\n if (j >= list.length) return { ok: false, error: `test case \"${name}\" is never closed` };\n cases.push({ name, classname, outcome, message: messages.join('\\n') });\n i = j;\n }\n return { ok: true, cases };\n}\n"]}
|
package/dist/plugin.js
CHANGED
|
@@ -22,7 +22,11 @@ import { rulingFor } from './guard.js';
|
|
|
22
22
|
import { createReader } from './reader.js';
|
|
23
23
|
import { checkRulings, resolveBase } from './round.js';
|
|
24
24
|
import { createSiblings } from './siblings.js';
|
|
25
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* Paths a finding names, in either of the shapes spec-brief reports them. A
|
|
27
|
+
* finding with neither names none, and a path no ruling names is waived for
|
|
28
|
+
* none, so the empty list is equivalent to its mutant.
|
|
29
|
+
*/
|
|
26
30
|
function pathsOf(finding) {
|
|
27
31
|
if (finding.path !== undefined)
|
|
28
32
|
return [finding.path];
|
|
@@ -30,11 +34,15 @@ function pathsOf(finding) {
|
|
|
30
34
|
}
|
|
31
35
|
export async function waive(context) {
|
|
32
36
|
const protectedPaths = context.findings.filter((finding) => finding.rule === 'protected-file').flatMap(pathsOf);
|
|
37
|
+
// Asking git about nothing waives nothing: this saves the asking, and its
|
|
38
|
+
// mutant is equivalent.
|
|
33
39
|
if (protectedPaths.length === 0)
|
|
34
40
|
return [];
|
|
35
41
|
const { config } = await loadConfig(context.root);
|
|
36
42
|
const workspace = {
|
|
37
43
|
root: context.root,
|
|
44
|
+
// spec-brief runs in a work tree, where git always names the common
|
|
45
|
+
// directory; the fallback is there for the type.
|
|
38
46
|
commonDir: (await commonDirectory(context.root)) ?? `${context.root}/.git`,
|
|
39
47
|
config,
|
|
40
48
|
siblings: createSiblings(context.root, config),
|
|
@@ -42,22 +50,7 @@ export async function waive(context) {
|
|
|
42
50
|
cwd: context.root,
|
|
43
51
|
};
|
|
44
52
|
const base = await resolveBase(workspace, context.base ?? undefined);
|
|
45
|
-
const
|
|
46
|
-
id: context.brief.id ?? context.brief.file,
|
|
47
|
-
file: context.brief.file,
|
|
48
|
-
title: null,
|
|
49
|
-
phase: 'live',
|
|
50
|
-
status: null,
|
|
51
|
-
type: null,
|
|
52
|
-
wave: null,
|
|
53
|
-
dependsOn: [],
|
|
54
|
-
affectedFiles: [],
|
|
55
|
-
protectedFiles: [],
|
|
56
|
-
tasks: { total: 0, checked: 0 },
|
|
57
|
-
ready: false,
|
|
58
|
-
waitingOn: [],
|
|
59
|
-
};
|
|
60
|
-
const { verified } = await checkRulings(workspace, brief, context.brief.text, createReader(), base);
|
|
53
|
+
const { verified } = await checkRulings(workspace, { file: context.brief.file }, context.brief.text, createReader(), base);
|
|
61
54
|
const waivers = [];
|
|
62
55
|
for (const path of protectedPaths) {
|
|
63
56
|
const ruling = rulingFor(verified, path);
|
package/dist/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAkB/C;;;;GAIG;AACH,SAAS,OAAO,CAAC,OAAyC;IACxD,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,OAAqB;IAC/C,MAAM,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,gBAAgB,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAChH,0EAA0E;IAC1E,wBAAwB;IACxB,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAC3C,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,MAAM,SAAS,GAAc;QAC3B,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,oEAAoE;QACpE,iDAAiD;QACjD,SAAS,EAAE,CAAC,MAAM,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,OAAO;QAC1E,MAAM;QACN,QAAQ,EAAE,cAAc,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;QAC9C,MAAM,EAAE,MAAM,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC;QACzC,GAAG,EAAE,OAAO,CAAC,IAAI;KAClB,CAAC;IACF,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,SAAS,EAAE,OAAO,CAAC,IAAI,IAAI,SAAS,CAAC,CAAC;IACrE,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,YAAY,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,YAAY,EAAE,EAAE,IAAI,CAAC,CAAC;IAC3H,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACzC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,MAAM,CAAC,EAAE,eAAe,MAAM,CAAC,MAAM,aAAa,EAAE,CAAC,CAAC;QACvH,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,wGAAwG;AACxG,MAAM,CAAC,OAAO,UAAU,iBAAiB;IACvC,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;AACpD,CAAC","sourcesContent":["/**\n * The spec-brief plugin: signed rulings, read where spec-brief decides.\n *\n * spec-brief's archive refuses a round that changed a file its brief protects.\n * When a person has ruled that the round may change it, and signed the ruling\n * (ADR-0006), the refusal is lifted - but spec-brief does not read signatures,\n * and should not: the tool that defines a format ships the check for it\n * (spec-brief ADR-0007). So the check ships here, and spec-brief asks it\n * through its plugin `waive` hook, once its configuration loads the plugin,\n * as `spec-harness init` writes it:\n *\n * ```json\n * { \"plugins\": [\"@descent-vtt/spec-harness/spec-brief-plugin\"] }\n * ```\n *\n * It waives a `protected-file` refusal for a path a verified `allow` ruling\n * names, and nothing else. A ruling that does not verify waives nothing.\n */\n\nimport { loadConfig } from './fs.js';\nimport { commonDirectory, currentBranch } from './git.js';\nimport { rulingFor } from './guard.js';\nimport { createReader } from './reader.js';\nimport { checkRulings, resolveBase } from './round.js';\nimport { createSiblings } from './siblings.js';\nimport type { Workspace } from './workspace.js';\n\nexport interface WaiveContext {\n readonly root: string;\n /** spec-brief passes a `null` id for a brief it could not name; its file still identifies it. */\n readonly brief: { readonly id: string | null; readonly file: string; readonly text: string };\n readonly findings: readonly { readonly rule: string; readonly path?: string | undefined; readonly paths?: readonly string[] | undefined }[];\n readonly base: string | null;\n readonly commit: string | null;\n}\n\nexport interface Waiver {\n readonly rule: string;\n readonly path: string;\n readonly reason: string;\n}\n\n/**\n * Paths a finding names, in either of the shapes spec-brief reports them. A\n * finding with neither names none, and a path no ruling names is waived for\n * none, so the empty list is equivalent to its mutant.\n */\nfunction pathsOf(finding: WaiveContext['findings'][number]): string[] {\n if (finding.path !== undefined) return [finding.path];\n return [...(finding.paths ?? [])];\n}\n\nexport async function waive(context: WaiveContext): Promise<Waiver[]> {\n const protectedPaths = context.findings.filter((finding) => finding.rule === 'protected-file').flatMap(pathsOf);\n // Asking git about nothing waives nothing: this saves the asking, and its\n // mutant is equivalent.\n if (protectedPaths.length === 0) return [];\n const { config } = await loadConfig(context.root);\n const workspace: Workspace = {\n root: context.root,\n // spec-brief runs in a work tree, where git always names the common\n // directory; the fallback is there for the type.\n commonDir: (await commonDirectory(context.root)) ?? `${context.root}/.git`,\n config,\n siblings: createSiblings(context.root, config),\n branch: await currentBranch(context.root),\n cwd: context.root,\n };\n const base = await resolveBase(workspace, context.base ?? undefined);\n const { verified } = await checkRulings(workspace, { file: context.brief.file }, context.brief.text, createReader(), base);\n const waivers: Waiver[] = [];\n for (const path of protectedPaths) {\n const ruling = rulingFor(verified, path);\n if (ruling !== undefined) {\n waivers.push({ rule: 'protected-file', path, reason: `ruling ${ruling.id}, signed by ${ruling.signer}, allows it` });\n }\n }\n return waivers;\n}\n\n/** The plugin, as spec-brief loads it: a function of its options returning `{ name, rules, waive }`. */\nexport default function specHarnessPlugin(): { name: string; rules: readonly never[]; waive: typeof waive } {\n return { name: 'spec-harness', rules: [], waive };\n}\n"]}
|
package/dist/premises.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* A premise spec-guard cannot read is checked by nothing, and is a warning,
|
|
20
20
|
* as it is in the audit: `--strict` fails the run on it.
|
|
21
21
|
*/
|
|
22
|
+
import { readGuardRun } from './answers.js';
|
|
22
23
|
import { isPremise, premiseFinding, unreadablePremiseFinding } from './audit.js';
|
|
23
24
|
import { briefIdFromBranch } from './branch.js';
|
|
24
25
|
import { findActive } from './briefs.js';
|
|
@@ -32,7 +33,7 @@ export async function premisesCommand(options, io) {
|
|
|
32
33
|
const reader = createReader();
|
|
33
34
|
const briefs = await workspace.siblings.briefs();
|
|
34
35
|
const live = briefs.filter((brief) => brief.phase === 'live');
|
|
35
|
-
const fromBranch =
|
|
36
|
+
const fromBranch = briefIdFromBranch(workspace.config.branches, workspace.branch);
|
|
36
37
|
const active = findActive(briefs, { flag: options.brief, environment: io.env['SPEC_BRIEF'], branch: fromBranch });
|
|
37
38
|
const round = active.kind === 'found' ? active.brief.file : null;
|
|
38
39
|
if (live.length === 0) {
|
|
@@ -53,14 +54,9 @@ export async function premisesCommand(options, io) {
|
|
|
53
54
|
io.stderr.write('spec-harness: spec-guard could not run the briefs\' assertions\n');
|
|
54
55
|
return EXIT_ERROR;
|
|
55
56
|
}
|
|
56
|
-
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
// it is equivalent; so is the one for errors.
|
|
60
|
-
const results = document.results ?? [];
|
|
61
|
-
// A directive spec-guard cannot read is listed apart from the results; one
|
|
62
|
-
// in a premise section is a premise nothing checks.
|
|
63
|
-
const errors = document.errors ?? [];
|
|
57
|
+
// A directive spec-guard cannot read is listed apart from the results, as
|
|
58
|
+
// errors; one in a premise section is a premise nothing checks.
|
|
59
|
+
const { results, errors } = readGuardRun(answer.document);
|
|
64
60
|
// A directive is a brief's when spec-guard places it in the brief's file;
|
|
65
61
|
// one it places nowhere is no brief's.
|
|
66
62
|
const inBrief = (item, file) => item.spec?.file.replace(/\\/g, '/') === file;
|
|
@@ -86,7 +82,7 @@ export async function premisesCommand(options, io) {
|
|
|
86
82
|
for (const error of unread) {
|
|
87
83
|
const { line } = error.spec;
|
|
88
84
|
if (premise(line))
|
|
89
|
-
findings.push(unreadablePremiseFinding(brief, { message: error.message, line, raw: error.raw
|
|
85
|
+
findings.push(unreadablePremiseFinding(brief, { message: error.message, line, raw: error.raw }));
|
|
90
86
|
}
|
|
91
87
|
}
|
|
92
88
|
const stale = findings.filter((finding) => finding.severity === 'error').length;
|
package/dist/premises.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"premises.js","sourceRoot":"","sources":["../src/premises.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `spec-harness premises`: is every live brief still about something true?\n *\n * A brief written against a defect states the defect as a premise - an\n * assertion under a section such as \"The Defect, Measured\" that holds while\n * the defect does. When the premise stops holding before the round has run,\n * the brief is stale: someone fixed the defect another way, or the code it\n * describes is gone. A repository that keeps its premises executable can say\n * so on every build, and ask for the brief to be archived or rewritten before\n * an agent is sent to fix what is already fixed.\n *\n * Goals are the audit's (`audit`), not this: a goal fails until its round is\n * done, and a build that failed for that would fail for every brief in flight.\n * For the same reason the premise of the brief a round is working on - the\n * one the flag, SPEC_BRIEF or the branch names - is reported as the audit\n * reports it, retired as the round intends, and fails nothing: on the round's\n * branch, a premise that no longer holds is the work being done.\n *\n * A premise spec-guard cannot read is checked by nothing, and is a warning,\n * as it is in the audit: `--strict` fails the run on it.\n */\n\nimport { isPremise, premiseFinding, unreadablePremiseFinding } from './audit.js';\nimport { briefIdFromBranch } from './branch.js';\nimport { findActive } from './briefs.js';\nimport { CONFIG_FILE } from './config.js';\nimport { formatFindings } from './formats.js';\nimport { createReader } from './reader.js';\nimport { briefText } from './round.js';\nimport type { Finding } from './types.js';\nimport { EXIT_ERROR, EXIT_FAILED, EXIT_OK, json, openWorkspace, version, type CliIO, type Options } from './workspace.js';\n\n/** Where spec-guard places a directive. */\ninterface Spec {\n readonly file: string;\n readonly line: number;\n}\n\ninterface GuardResult {\n readonly ok: boolean;\n readonly description: string;\n readonly message: string;\n readonly spec?: Spec;\n}\n\ninterface GuardError {\n readonly message: string;\n readonly raw?: string;\n readonly spec?: Spec;\n}\n\nexport async function premisesCommand(options: Options, io: CliIO): Promise<number> {\n const workspace = await openWorkspace(options, io);\n const reader = createReader();\n const briefs = await workspace.siblings.briefs();\n const live = briefs.filter((brief) => brief.phase === 'live');\n const fromBranch = workspace.branch === null ? null : briefIdFromBranch(workspace.config.branches, workspace.branch);\n const active = findActive(briefs, { flag: options.brief, environment: io.env['SPEC_BRIEF'], branch: fromBranch });\n const round = active.kind === 'found' ? active.brief.file : null;\n if (live.length === 0) {\n if (options.format === 'json') io.stdout.write(json('premises', { ok: true, checked: 0, findings: [] }));\n else if (options.format === 'pretty') io.stdout.write('no live brief\\n');\n else io.stdout.write(formatFindings(options.format, [], { file: CONFIG_FILE, version: version(), summary: 'no live brief' }));\n return EXIT_OK;\n }\n const answer = await workspace.siblings.json('spec-guard', [...live.map((brief) => brief.file), '--ignore-status', '--json']);\n if ('absent' in answer) {\n io.stderr.write(`spec-harness: premises cannot be checked: ${answer.absent}\\n`);\n return EXIT_ERROR;\n }\n if (answer.code === 2) {\n io.stderr.write('spec-harness: spec-guard could not run the briefs\\' assertions\\n');\n return EXIT_ERROR;\n }\n const document = answer.document as { results?: GuardResult[]; errors?: GuardError[] };\n // A missing list is an empty one. A list that holds a string instead, the\n // mutant, sorts nothing to a brief either, since a string has no spec, so\n // it is equivalent; so is the one for errors.\n const results = document.results ?? [];\n // A directive spec-guard cannot read is listed apart from the results; one\n // in a premise section is a premise nothing checks.\n const errors = document.errors ?? [];\n // A directive is a brief's when spec-guard places it in the brief's file;\n // one it places nowhere is no brief's.\n const inBrief = <T extends { readonly spec?: Spec }>(item: T, file: string): item is T & { readonly spec: Spec } =>\n item.spec?.file.replace(/\\\\/g, '/') === file;\n const findings: Finding[] = [];\n let premises = 0;\n for (const brief of live) {\n const own = results.filter((result) => inBrief(result, brief.file));\n const unread = errors.filter((error) => inBrief(error, brief.file));\n // Only saves reading a brief with nothing to judge: going on finds nothing, so that mutant is equivalent.\n if (own.length === 0 && unread.length === 0) continue;\n const text = await briefText(workspace, brief);\n const premise = (line: number): boolean => isPremise(reader.sectionsAt(text, line), workspace.config.assertions.premises);\n for (const result of own) {\n const { line } = result.spec;\n if (!premise(line)) continue;\n premises += 1;\n if (result.ok) continue;\n findings.push(premiseFinding(brief, { description: result.description, message: result.message, line }, brief.file === round));\n }\n for (const error of unread) {\n const { line } = error.spec;\n if (premise(line)) findings.push(unreadablePremiseFinding(brief, { message: error.message, line, raw: error.raw ?? '' }));\n }\n }\n const stale = findings.filter((finding) => finding.severity === 'error').length;\n const retired = findings.filter((finding) => finding.severity === 'note').length;\n const unreadable = findings.filter((finding) => finding.severity === 'warning').length;\n // Only the round on the active brief retires a premise, so with one retired\n // there is an active brief, and dropping the second test is equivalent.\n const intended = retired === 0 || active.kind !== 'found' ? '' : `, and ${retired} retired by the round on brief ${active.brief.id}, as it intends`;\n const unchecked = unreadable === 0 ? '' : `; ${unreadable} premise(s) spec-guard cannot read, so not checked`;\n const summary = `${premises} premise(s) in ${live.length} live brief(s), ${stale} no longer hold${intended}${unchecked}`;\n // As the audit does: a warning fails the run under --strict.\n const failed = stale > 0 || (options.strict && unreadable > 0);\n if (options.format === 'json') {\n io.stdout.write(json('premises', { ok: !failed, briefs: live.length, premises, unreadable, findings }));\n } else if (options.format === 'pretty') {\n for (const finding of findings) io.stdout.write(`${finding.severity.padEnd(8)} ${finding.file}:${finding.line} ${finding.message}\\n ${finding.hint}\\n`);\n io.stdout.write(`${summary}\\n`);\n } else {\n io.stdout.write(formatFindings(options.format, findings, { file: CONFIG_FILE, version: version(), summary }));\n }\n return failed ? EXIT_FAILED : EXIT_OK;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"premises.js","sourceRoot":"","sources":["../src/premises.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `spec-harness premises`: is every live brief still about something true?\n *\n * A brief written against a defect states the defect as a premise - an\n * assertion under a section such as \"The Defect, Measured\" that holds while\n * the defect does. When the premise stops holding before the round has run,\n * the brief is stale: someone fixed the defect another way, or the code it\n * describes is gone. A repository that keeps its premises executable can say\n * so on every build, and ask for the brief to be archived or rewritten before\n * an agent is sent to fix what is already fixed.\n *\n * Goals are the audit's (`audit`), not this: a goal fails until its round is\n * done, and a build that failed for that would fail for every brief in flight.\n * For the same reason the premise of the brief a round is working on - the\n * one the flag, SPEC_BRIEF or the branch names - is reported as the audit\n * reports it, retired as the round intends, and fails nothing: on the round's\n * branch, a premise that no longer holds is the work being done.\n *\n * A premise spec-guard cannot read is checked by nothing, and is a warning,\n * as it is in the audit: `--strict` fails the run on it.\n */\n\nimport { readGuardRun, type Spec } from './answers.js';\nimport { isPremise, premiseFinding, unreadablePremiseFinding } from './audit.js';\nimport { briefIdFromBranch } from './branch.js';\nimport { findActive } from './briefs.js';\nimport { CONFIG_FILE } from './config.js';\nimport { formatFindings } from './formats.js';\nimport { createReader } from './reader.js';\nimport { briefText } from './round.js';\nimport type { Finding } from './types.js';\nimport { EXIT_ERROR, EXIT_FAILED, EXIT_OK, json, openWorkspace, version, type CliIO, type Options } from './workspace.js';\n\nexport async function premisesCommand(options: Options, io: CliIO): Promise<number> {\n const workspace = await openWorkspace(options, io);\n const reader = createReader();\n const briefs = await workspace.siblings.briefs();\n const live = briefs.filter((brief) => brief.phase === 'live');\n const fromBranch = briefIdFromBranch(workspace.config.branches, workspace.branch);\n const active = findActive(briefs, { flag: options.brief, environment: io.env['SPEC_BRIEF'], branch: fromBranch });\n const round = active.kind === 'found' ? active.brief.file : null;\n if (live.length === 0) {\n if (options.format === 'json') io.stdout.write(json('premises', { ok: true, checked: 0, findings: [] }));\n else if (options.format === 'pretty') io.stdout.write('no live brief\\n');\n else io.stdout.write(formatFindings(options.format, [], { file: CONFIG_FILE, version: version(), summary: 'no live brief' }));\n return EXIT_OK;\n }\n const answer = await workspace.siblings.json('spec-guard', [...live.map((brief) => brief.file), '--ignore-status', '--json']);\n if ('absent' in answer) {\n io.stderr.write(`spec-harness: premises cannot be checked: ${answer.absent}\\n`);\n return EXIT_ERROR;\n }\n if (answer.code === 2) {\n io.stderr.write('spec-harness: spec-guard could not run the briefs\\' assertions\\n');\n return EXIT_ERROR;\n }\n // A directive spec-guard cannot read is listed apart from the results, as\n // errors; one in a premise section is a premise nothing checks.\n const { results, errors } = readGuardRun(answer.document);\n // A directive is a brief's when spec-guard places it in the brief's file;\n // one it places nowhere is no brief's.\n const inBrief = <T extends { readonly spec?: Spec | undefined }>(item: T, file: string): item is T & { readonly spec: Spec } =>\n item.spec?.file.replace(/\\\\/g, '/') === file;\n const findings: Finding[] = [];\n let premises = 0;\n for (const brief of live) {\n const own = results.filter((result) => inBrief(result, brief.file));\n const unread = errors.filter((error) => inBrief(error, brief.file));\n // Only saves reading a brief with nothing to judge: going on finds nothing, so that mutant is equivalent.\n if (own.length === 0 && unread.length === 0) continue;\n const text = await briefText(workspace, brief);\n const premise = (line: number): boolean => isPremise(reader.sectionsAt(text, line), workspace.config.assertions.premises);\n for (const result of own) {\n const { line } = result.spec;\n if (!premise(line)) continue;\n premises += 1;\n if (result.ok) continue;\n findings.push(premiseFinding(brief, { description: result.description, message: result.message, line }, brief.file === round));\n }\n for (const error of unread) {\n const { line } = error.spec;\n if (premise(line)) findings.push(unreadablePremiseFinding(brief, { message: error.message, line, raw: error.raw }));\n }\n }\n const stale = findings.filter((finding) => finding.severity === 'error').length;\n const retired = findings.filter((finding) => finding.severity === 'note').length;\n const unreadable = findings.filter((finding) => finding.severity === 'warning').length;\n // Only the round on the active brief retires a premise, so with one retired\n // there is an active brief, and dropping the second test is equivalent.\n const intended = retired === 0 || active.kind !== 'found' ? '' : `, and ${retired} retired by the round on brief ${active.brief.id}, as it intends`;\n const unchecked = unreadable === 0 ? '' : `; ${unreadable} premise(s) spec-guard cannot read, so not checked`;\n const summary = `${premises} premise(s) in ${live.length} live brief(s), ${stale} no longer hold${intended}${unchecked}`;\n // As the audit does: a warning fails the run under --strict.\n const failed = stale > 0 || (options.strict && unreadable > 0);\n if (options.format === 'json') {\n io.stdout.write(json('premises', { ok: !failed, briefs: live.length, premises, unreadable, findings }));\n } else if (options.format === 'pretty') {\n for (const finding of findings) io.stdout.write(`${finding.severity.padEnd(8)} ${finding.file}:${finding.line} ${finding.message}\\n ${finding.hint}\\n`);\n io.stdout.write(`${summary}\\n`);\n } else {\n io.stdout.write(formatFindings(options.format, findings, { file: CONFIG_FILE, version: version(), summary }));\n }\n return failed ? EXIT_FAILED : EXIT_OK;\n}\n"]}
|
package/dist/round.d.ts
CHANGED
|
@@ -40,7 +40,7 @@ export interface RulingCheck {
|
|
|
40
40
|
* last changed its row must be signed by a principal the base branch's
|
|
41
41
|
* allowed-signers file lists (ADR-0006).
|
|
42
42
|
*/
|
|
43
|
-
export declare function checkRulings(workspace: Workspace, brief: BriefRow, text: string, reader: DocumentReader, base: Base): Promise<RulingCheck>;
|
|
43
|
+
export declare function checkRulings(workspace: Workspace, brief: Pick<BriefRow, 'file'>, text: string, reader: DocumentReader, base: Base): Promise<RulingCheck>;
|
|
44
44
|
export declare function briefText(workspace: Workspace, brief: BriefRow): Promise<string>;
|
|
45
45
|
/** Decides paths against the brief, with the rulings verified against the base `baseFlag` names, or the configured one. */
|
|
46
46
|
export declare function checkPaths(workspace: Workspace, brief: BriefRow | null, noBrief: string | undefined, given: readonly string[], cwd: string, reader: DocumentReader, baseFlag: string | undefined): Promise<Decision[]>;
|