@descent-vtt/spec-harness 0.0.0 → 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/.claude-plugin/marketplace.json +16 -0
- package/.claude-plugin/plugin.json +17 -0
- package/.mcp.json +12 -0
- package/CHANGELOG.md +46 -0
- package/README.md +127 -9
- package/bin/spec-harness.js +19 -0
- package/dist/audit.d.ts +69 -0
- package/dist/audit.js +93 -0
- package/dist/audit.js.map +1 -0
- package/dist/branch.d.ts +26 -0
- package/dist/branch.js +100 -0
- package/dist/branch.js.map +1 -0
- package/dist/briefs.d.ts +49 -0
- package/dist/briefs.js +111 -0
- package/dist/briefs.js.map +1 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.js +245 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands.d.ts +18 -0
- package/dist/commands.js +224 -0
- package/dist/commands.js.map +1 -0
- package/dist/config.d.ts +57 -0
- package/dist/config.js +167 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +56 -0
- package/dist/context.js +125 -0
- package/dist/context.js.map +1 -0
- package/dist/document.d.ts +33 -0
- package/dist/document.js +9 -0
- package/dist/document.js.map +1 -0
- package/dist/fs.d.ts +29 -0
- package/dist/fs.js +85 -0
- package/dist/fs.js.map +1 -0
- package/dist/git.d.ts +57 -0
- package/dist/git.js +129 -0
- package/dist/git.js.map +1 -0
- package/dist/guard.d.ts +43 -0
- package/dist/guard.js +120 -0
- package/dist/guard.js.map +1 -0
- package/dist/hooks.d.ts +50 -0
- package/dist/hooks.js +109 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/junit.d.ts +30 -0
- package/dist/junit.js +166 -0
- package/dist/junit.js.map +1 -0
- package/dist/manifests.d.ts +52 -0
- package/dist/manifests.js +346 -0
- package/dist/manifests.js.map +1 -0
- package/dist/plugin.d.ts +45 -0
- package/dist/plugin.js +79 -0
- package/dist/plugin.js.map +1 -0
- package/dist/premises.d.ts +16 -0
- package/dist/premises.js +71 -0
- package/dist/premises.js.map +1 -0
- package/dist/probe.d.ts +108 -0
- package/dist/probe.js +203 -0
- package/dist/probe.js.map +1 -0
- package/dist/reader.d.ts +8 -0
- package/dist/reader.js +105 -0
- package/dist/reader.js.map +1 -0
- package/dist/round.d.ts +68 -0
- package/dist/round.js +339 -0
- package/dist/round.js.map +1 -0
- package/dist/rulings.d.ts +92 -0
- package/dist/rulings.js +160 -0
- package/dist/rulings.js.map +1 -0
- package/dist/sandbox.d.ts +28 -0
- package/dist/sandbox.js +110 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.js +212 -0
- package/dist/server.js.map +1 -0
- package/dist/setup.d.ts +32 -0
- package/dist/setup.js +218 -0
- package/dist/setup.js.map +1 -0
- package/dist/siblings.d.ts +66 -0
- package/dist/siblings.js +134 -0
- package/dist/siblings.js.map +1 -0
- package/dist/types.d.ts +38 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/vendor/spec-core/jsonrpc/index.d.ts +2 -0
- package/dist/vendor/spec-core/jsonrpc/index.js +2 -0
- package/dist/vendor/spec-core/jsonrpc/index.js.map +1 -0
- package/dist/vendor/spec-core/jsonrpc/mcp.d.ts +157 -0
- package/dist/vendor/spec-core/jsonrpc/mcp.js +353 -0
- package/dist/vendor/spec-core/jsonrpc/mcp.js.map +1 -0
- package/dist/vendor/spec-core/markdown/frontmatter.d.ts +103 -0
- package/dist/vendor/spec-core/markdown/frontmatter.js +459 -0
- package/dist/vendor/spec-core/markdown/frontmatter.js.map +1 -0
- package/dist/vendor/spec-core/markdown/headings.d.ts +45 -0
- package/dist/vendor/spec-core/markdown/headings.js +199 -0
- package/dist/vendor/spec-core/markdown/headings.js.map +1 -0
- package/dist/vendor/spec-core/markdown/index.d.ts +5 -0
- package/dist/vendor/spec-core/markdown/index.js +4 -0
- package/dist/vendor/spec-core/markdown/index.js.map +1 -0
- package/dist/vendor/spec-core/markdown/layout.d.ts +39 -0
- package/dist/vendor/spec-core/markdown/layout.js +28 -0
- package/dist/vendor/spec-core/markdown/layout.js.map +1 -0
- package/dist/vendor/spec-core/markdown/links.d.ts +22 -0
- package/dist/vendor/spec-core/markdown/links.js +364 -0
- package/dist/vendor/spec-core/markdown/links.js.map +1 -0
- package/dist/vendor/spec-core/markdown/lists.d.ts +22 -0
- package/dist/vendor/spec-core/markdown/lists.js +127 -0
- package/dist/vendor/spec-core/markdown/lists.js.map +1 -0
- package/dist/vendor/spec-core/markdown/scan.d.ts +36 -0
- package/dist/vendor/spec-core/markdown/scan.js +439 -0
- package/dist/vendor/spec-core/markdown/scan.js.map +1 -0
- package/dist/vendor/spec-core/markdown/syntax.d.ts +75 -0
- package/dist/vendor/spec-core/markdown/syntax.js +181 -0
- package/dist/vendor/spec-core/markdown/syntax.js.map +1 -0
- package/dist/vendor/spec-core/markdown/tables.d.ts +18 -0
- package/dist/vendor/spec-core/markdown/tables.js +86 -0
- package/dist/vendor/spec-core/markdown/tables.js.map +1 -0
- package/dist/vendor/spec-core/markdown/types.d.ts +287 -0
- package/dist/vendor/spec-core/markdown/types.js +9 -0
- package/dist/vendor/spec-core/markdown/types.js.map +1 -0
- package/dist/vendor/spec-core/path/index.d.ts +1 -0
- package/dist/vendor/spec-core/path/index.js +2 -0
- package/dist/vendor/spec-core/path/index.js.map +1 -0
- package/dist/vendor/spec-core/path/posix.d.ts +83 -0
- package/dist/vendor/spec-core/path/posix.js +150 -0
- package/dist/vendor/spec-core/path/posix.js.map +1 -0
- package/dist/vendor/spec-core/pattern/automaton.d.ts +129 -0
- package/dist/vendor/spec-core/pattern/automaton.js +340 -0
- package/dist/vendor/spec-core/pattern/automaton.js.map +1 -0
- package/dist/vendor/spec-core/pattern/charset.d.ts +58 -0
- package/dist/vendor/spec-core/pattern/charset.js +116 -0
- package/dist/vendor/spec-core/pattern/charset.js.map +1 -0
- package/dist/vendor/spec-core/pattern/glob.d.ts +121 -0
- package/dist/vendor/spec-core/pattern/glob.js +481 -0
- package/dist/vendor/spec-core/pattern/glob.js.map +1 -0
- package/dist/vendor/spec-core/pattern/index.d.ts +6 -0
- package/dist/vendor/spec-core/pattern/index.js +4 -0
- package/dist/vendor/spec-core/pattern/index.js.map +1 -0
- package/dist/vendor/spec-core/pattern/regex.d.ts +67 -0
- package/dist/vendor/spec-core/pattern/regex.js +813 -0
- package/dist/vendor/spec-core/pattern/regex.js.map +1 -0
- package/dist/vendor/spec-core/text/index.d.ts +2 -0
- package/dist/vendor/spec-core/text/index.js +2 -0
- package/dist/vendor/spec-core/text/index.js.map +1 -0
- package/dist/vendor/spec-core/text/lines.d.ts +72 -0
- package/dist/vendor/spec-core/text/lines.js +156 -0
- package/dist/vendor/spec-core/text/lines.js.map +1 -0
- package/dist/versions.d.ts +42 -0
- package/dist/versions.js +57 -0
- package/dist/versions.js.map +1 -0
- package/dist/workspace.d.ts +77 -0
- package/dist/workspace.js +138 -0
- package/dist/workspace.js.map +1 -0
- package/hooks/hooks.json +28 -0
- package/package.json +86 -6
- package/skills/close-round/SKILL.md +38 -0
- package/skills/draft-brief/SKILL.md +64 -0
- package/skills/run-round/SKILL.md +46 -0
- package/skills/split-goal/SKILL.md +42 -0
- package/src/vendor/spec-core/LICENSE +21 -0
- package/src/vendor/spec-core/README.md +8 -0
package/dist/round.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A round, measured: the operations the command line and the MCP server
|
|
3
|
+
* share. Each gathers what a pure module needs - from git, the disk and the
|
|
4
|
+
* siblings - and hands it over; the decisions are made in `context.ts`,
|
|
5
|
+
* `audit.ts`, `guard.ts` and `rulings.ts`.
|
|
6
|
+
*/
|
|
7
|
+
import { type AuditReport } from './audit.js';
|
|
8
|
+
import { type ContextPacket } from './context.js';
|
|
9
|
+
import type { DocumentReader } from './document.js';
|
|
10
|
+
import { type Decision, type VerifiedRuling } from './guard.js';
|
|
11
|
+
import { type DependencyChange } from './manifests.js';
|
|
12
|
+
import { type EscalationOption, type EscalationRequest, type RulingRow } from './rulings.js';
|
|
13
|
+
import type { BriefRow, Finding } from './types.js';
|
|
14
|
+
import { type Workspace } from './workspace.js';
|
|
15
|
+
export type Base = {
|
|
16
|
+
readonly kind: 'resolved';
|
|
17
|
+
readonly ref: string;
|
|
18
|
+
readonly sha: string;
|
|
19
|
+
readonly mergeBase: string;
|
|
20
|
+
readonly head: string;
|
|
21
|
+
} | {
|
|
22
|
+
readonly kind: 'unresolved';
|
|
23
|
+
readonly reason: string;
|
|
24
|
+
};
|
|
25
|
+
/** What the round is measured from: the flag, the configuration, then the remote's default branch. */
|
|
26
|
+
export declare function resolveBase(workspace: Workspace, flag: string | undefined): Promise<Base>;
|
|
27
|
+
export interface RulingCheck {
|
|
28
|
+
readonly rows: readonly RulingRow[];
|
|
29
|
+
readonly verified: readonly VerifiedRuling[];
|
|
30
|
+
readonly unverified: readonly {
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly reason: string;
|
|
33
|
+
}[];
|
|
34
|
+
readonly problems: readonly Finding[];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The brief's rulings, each checked against its signature: the commit that
|
|
38
|
+
* last changed its row must be signed by a principal the base branch's
|
|
39
|
+
* allowed-signers file lists (ADR-0006).
|
|
40
|
+
*/
|
|
41
|
+
export declare function checkRulings(workspace: Workspace, brief: BriefRow, text: string, reader: DocumentReader, base: Base): Promise<RulingCheck>;
|
|
42
|
+
export declare function briefText(workspace: Workspace, brief: BriefRow): Promise<string>;
|
|
43
|
+
/** Decides paths against the brief, with its verified rulings. */
|
|
44
|
+
export declare function checkPaths(workspace: Workspace, brief: BriefRow | null, noBrief: string | undefined, given: readonly string[], cwd: string, reader: DocumentReader): Promise<Decision[]>;
|
|
45
|
+
export declare function buildContext(workspace: Workspace, brief: BriefRow, briefs: readonly BriefRow[], reader: DocumentReader): Promise<ContextPacket>;
|
|
46
|
+
export interface AuditResult {
|
|
47
|
+
readonly brief: BriefRow;
|
|
48
|
+
readonly base: Base;
|
|
49
|
+
readonly report: AuditReport;
|
|
50
|
+
readonly dependencies: readonly DependencyChange[];
|
|
51
|
+
}
|
|
52
|
+
export declare function runAudit(workspace: Workspace, brief: BriefRow, reader: DocumentReader, baseFlag: string | undefined): Promise<AuditResult>;
|
|
53
|
+
export declare function listEscalations(workspace: Workspace): Promise<EscalationRequest[]>;
|
|
54
|
+
export declare function raiseEscalation(workspace: Workspace, brief: BriefRow, request: {
|
|
55
|
+
paths: readonly string[];
|
|
56
|
+
reason: string;
|
|
57
|
+
options: readonly EscalationOption[];
|
|
58
|
+
recommendation: string | null;
|
|
59
|
+
}): Promise<{
|
|
60
|
+
request: EscalationRequest;
|
|
61
|
+
memo: string;
|
|
62
|
+
}>;
|
|
63
|
+
/** Writes a person's ruling into the brief and retires the request. Never commits: the signature is the person's. */
|
|
64
|
+
export declare function recordRuling(workspace: Workspace, escalationId: string, decision: 'allow' | 'deny', note: string, reader: DocumentReader): Promise<{
|
|
65
|
+
id: string;
|
|
66
|
+
file: string;
|
|
67
|
+
row: string;
|
|
68
|
+
}>;
|
package/dist/round.js
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A round, measured: the operations the command line and the MCP server
|
|
3
|
+
* share. Each gathers what a pure module needs - from git, the disk and the
|
|
4
|
+
* siblings - and hands it over; the decisions are made in `context.ts`,
|
|
5
|
+
* `audit.ts`, `guard.ts` and `rulings.ts`.
|
|
6
|
+
*/
|
|
7
|
+
import { createHash } from 'node:crypto';
|
|
8
|
+
import { existsSync } from 'node:fs';
|
|
9
|
+
import { readdir, readFile, rm } from 'node:fs/promises';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { audit } from './audit.js';
|
|
12
|
+
import { sameId } from './branch.js';
|
|
13
|
+
import { renderContext } from './context.js';
|
|
14
|
+
import { readText, repositoryPath, stateDirectory, writeAtomic } from './fs.js';
|
|
15
|
+
import { blameLine, changes, mergeBase, remoteDefault, revision, show, verifyCommit } from './git.js';
|
|
16
|
+
import { decide } from './guard.js';
|
|
17
|
+
import { diffManifest, ecosystemOf, manifestMatcher } from './manifests.js';
|
|
18
|
+
import { addRulingRow, nextId, readRulings, renderMemo, renderRow, } from './rulings.js';
|
|
19
|
+
import { compileGlob } from './vendor/spec-core/pattern/index.js';
|
|
20
|
+
import { dirname, isRelativeReference, resolveInside, splitReference } from './vendor/spec-core/path/index.js';
|
|
21
|
+
import { UsageError } from './workspace.js';
|
|
22
|
+
/** What the round is measured from: the flag, the configuration, then the remote's default branch. */
|
|
23
|
+
export async function resolveBase(workspace, flag) {
|
|
24
|
+
const ref = flag ?? workspace.config.base ?? (await remoteDefault(workspace.root));
|
|
25
|
+
if (ref === null) {
|
|
26
|
+
return { kind: 'unresolved', reason: 'no base is named and the remote has no default branch; pass --base <ref> or set "base"' };
|
|
27
|
+
}
|
|
28
|
+
const sha = await revision(ref, workspace.root);
|
|
29
|
+
const head = await revision('HEAD', workspace.root);
|
|
30
|
+
if (sha === null || head === null)
|
|
31
|
+
return { kind: 'unresolved', reason: `"${ref}" names no commit` };
|
|
32
|
+
const common = await mergeBase(sha, head, workspace.root);
|
|
33
|
+
if (common === null)
|
|
34
|
+
return { kind: 'unresolved', reason: `"${ref}" and HEAD share no history` };
|
|
35
|
+
return { kind: 'resolved', ref, sha, mergeBase: common, head };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The brief's rulings, each checked against its signature: the commit that
|
|
39
|
+
* last changed its row must be signed by a principal the base branch's
|
|
40
|
+
* allowed-signers file lists (ADR-0006).
|
|
41
|
+
*/
|
|
42
|
+
export async function checkRulings(workspace, brief, text, reader, base) {
|
|
43
|
+
const { rulings: rows, problems } = readRulings(reader.sectionTables(text, workspace.config.rulings.section).tables);
|
|
44
|
+
const findings = problems.map((problem) => ({
|
|
45
|
+
rule: 'ruling-unreadable',
|
|
46
|
+
severity: 'warning',
|
|
47
|
+
message: problem.message,
|
|
48
|
+
hint: 'a ruling row is | id | `paths` | allow or deny | note |',
|
|
49
|
+
file: brief.file,
|
|
50
|
+
line: problem.line,
|
|
51
|
+
}));
|
|
52
|
+
const allows = rows.filter((row) => row.decision === 'allow');
|
|
53
|
+
if (allows.length === 0)
|
|
54
|
+
return { rows, verified: [], unverified: [], problems: findings };
|
|
55
|
+
if (base.kind === 'unresolved') {
|
|
56
|
+
return { rows, verified: [], unverified: allows.map((row) => ({ id: row.id, reason: `no base to read the allowed signers from: ${base.reason}` })), problems: findings };
|
|
57
|
+
}
|
|
58
|
+
const signers = await show(base.sha, workspace.config.rulings.allowedSigners, workspace.root);
|
|
59
|
+
if (signers === null) {
|
|
60
|
+
const reason = `${base.ref} has no ${workspace.config.rulings.allowedSigners}, so no signature can count`;
|
|
61
|
+
return { rows, verified: [], unverified: allows.map((row) => ({ id: row.id, reason })), problems: findings };
|
|
62
|
+
}
|
|
63
|
+
const directory = await stateDirectory(workspace.commonDir, 'signers');
|
|
64
|
+
const file = join(directory, `${createHash('sha256').update(signers).digest('hex').slice(0, 16)}`);
|
|
65
|
+
await writeAtomic(file, signers);
|
|
66
|
+
const verified = [];
|
|
67
|
+
const unverified = [];
|
|
68
|
+
for (const row of allows) {
|
|
69
|
+
// The row's line is a line of the text on disk, so it is blamed there: at
|
|
70
|
+
// HEAD the same number can be another line, one a signed commit wrote,
|
|
71
|
+
// and an uncommitted row would borrow that commit's signature.
|
|
72
|
+
const commit = await blameLine(null, brief.file, row.line, workspace.root);
|
|
73
|
+
if (commit === null || /^0+$/.test(commit)) {
|
|
74
|
+
unverified.push({ id: row.id, reason: 'its row is not committed' });
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const signature = await verifyCommit(commit, file, workspace.root);
|
|
78
|
+
if (signature.good && signature.principal !== null)
|
|
79
|
+
verified.push({ id: row.id, paths: row.paths, signer: signature.principal });
|
|
80
|
+
else
|
|
81
|
+
unverified.push({ id: row.id, reason: `commit ${commit.slice(0, 12)} last changed its row, and ${signature.detail || 'it is not signed'}` });
|
|
82
|
+
}
|
|
83
|
+
return { rows, verified, unverified, problems: findings };
|
|
84
|
+
}
|
|
85
|
+
/* ------------------------------------------------------------------- guard */
|
|
86
|
+
export async function briefText(workspace, brief) {
|
|
87
|
+
const text = await readText(join(workspace.root, brief.file));
|
|
88
|
+
if (text === null)
|
|
89
|
+
throw new UsageError(`${brief.file} cannot be read`);
|
|
90
|
+
return text;
|
|
91
|
+
}
|
|
92
|
+
/** Decides paths against the brief, with its verified rulings. */
|
|
93
|
+
export async function checkPaths(workspace, brief, noBrief, given, cwd, reader) {
|
|
94
|
+
let rulings = [];
|
|
95
|
+
const resolved = given.map((path) => ({ given: path, path: repositoryPath(path, workspace.root, cwd) }));
|
|
96
|
+
if (brief !== null) {
|
|
97
|
+
// Rulings cost a blame and a signature check each, so they are read only
|
|
98
|
+
// when a write reaches a protected file.
|
|
99
|
+
const first = resolved.map(({ path, given: g }) => decide({ path, given: g, brief, noBrief, rulings: [], outOfScope: workspace.config.outOfScope }));
|
|
100
|
+
if (first.some((decision) => decision.reason === 'protected')) {
|
|
101
|
+
const base = await resolveBase(workspace, undefined);
|
|
102
|
+
rulings = (await checkRulings(workspace, brief, await briefText(workspace, brief), reader, base)).verified;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
return first;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return resolved.map(({ path, given: g }) => decide({ path, given: g, brief, noBrief, rulings, outOfScope: workspace.config.outOfScope }));
|
|
109
|
+
}
|
|
110
|
+
/* ----------------------------------------------------------------- context */
|
|
111
|
+
function scopeBases(patterns) {
|
|
112
|
+
const bases = new Set();
|
|
113
|
+
for (const pattern of patterns) {
|
|
114
|
+
try {
|
|
115
|
+
for (const base of compileGlob(pattern, { dialect: 'path', caseSensitive: true }).bases)
|
|
116
|
+
bases.add(base === '' ? '.' : base);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// spec-brief lint reports a pattern it cannot read; the rules for the rest still count.
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return [...bases].sort();
|
|
123
|
+
}
|
|
124
|
+
async function rulesFor(workspace, brief) {
|
|
125
|
+
const paths = scopeBases(brief.affectedFiles);
|
|
126
|
+
if (paths.length === 0)
|
|
127
|
+
return [];
|
|
128
|
+
const answer = await workspace.siblings.json('spec-guard', ['query', ...paths, '--json']);
|
|
129
|
+
if ('absent' in answer)
|
|
130
|
+
return { unavailable: answer.absent };
|
|
131
|
+
if (answer.code === 2)
|
|
132
|
+
return { unavailable: 'spec-guard could not read its specs' };
|
|
133
|
+
const document = answer.document;
|
|
134
|
+
const seen = new Set();
|
|
135
|
+
const out = [];
|
|
136
|
+
for (const result of document.results ?? []) {
|
|
137
|
+
for (const rule of result.rules ?? []) {
|
|
138
|
+
const key = `${rule.document}:${rule.line}`;
|
|
139
|
+
if (rule.inForce === false || seen.has(key))
|
|
140
|
+
continue;
|
|
141
|
+
seen.add(key);
|
|
142
|
+
out.push({ document: rule.document, line: rule.line, kind: rule.kind, description: rule.description, reason: rule.reason ?? null });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
async function citedDocuments(workspace, brief, text, reader) {
|
|
148
|
+
const seen = new Set();
|
|
149
|
+
const out = [];
|
|
150
|
+
for (const citation of reader.citations(text)) {
|
|
151
|
+
if (!isRelativeReference(citation.target))
|
|
152
|
+
continue;
|
|
153
|
+
let target = splitReference(citation.target).path;
|
|
154
|
+
try {
|
|
155
|
+
target = decodeURIComponent(target);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
// A destination that is not valid percent-encoding is read as written.
|
|
159
|
+
}
|
|
160
|
+
const path = resolveInside(dirname(brief.file), target);
|
|
161
|
+
if (path === null || path === '' || path === brief.file || seen.has(path))
|
|
162
|
+
continue;
|
|
163
|
+
seen.add(path);
|
|
164
|
+
const document = /\.(?:md|markdown|mdx|txt)$/i.test(path);
|
|
165
|
+
// A link to code or a directory that exists is not a document to include,
|
|
166
|
+
// and must not be reported as a link that resolves to nothing.
|
|
167
|
+
if (!document && existsSync(join(workspace.root, path)))
|
|
168
|
+
continue;
|
|
169
|
+
const content = document ? await readText(join(workspace.root, path)) : null;
|
|
170
|
+
const meta = content === null ? { title: null, status: null } : reader.titleAndStatus(content);
|
|
171
|
+
out.push({ path, title: meta.title, status: meta.status, text: content });
|
|
172
|
+
}
|
|
173
|
+
return out;
|
|
174
|
+
}
|
|
175
|
+
export async function buildContext(workspace, brief, briefs, reader) {
|
|
176
|
+
const text = await briefText(workspace, brief);
|
|
177
|
+
const base = await resolveBase(workspace, undefined);
|
|
178
|
+
const [rules, cited, rulings] = await Promise.all([
|
|
179
|
+
rulesFor(workspace, brief),
|
|
180
|
+
citedDocuments(workspace, brief, text, reader),
|
|
181
|
+
checkRulings(workspace, brief, text, reader, base),
|
|
182
|
+
]);
|
|
183
|
+
return renderContext({
|
|
184
|
+
brief,
|
|
185
|
+
briefText: text,
|
|
186
|
+
dependencies: brief.dependsOn.map((id) =>
|
|
187
|
+
// spec-brief reports a dependency as its front matter spells it, `7` for brief 007.
|
|
188
|
+
briefs.find((candidate) => sameId(candidate.id, id)) ?? {
|
|
189
|
+
...brief,
|
|
190
|
+
id,
|
|
191
|
+
title: '(no such brief)',
|
|
192
|
+
file: '',
|
|
193
|
+
phase: 'live',
|
|
194
|
+
status: 'unknown',
|
|
195
|
+
}),
|
|
196
|
+
cited,
|
|
197
|
+
rules,
|
|
198
|
+
rulings: rulings.verified,
|
|
199
|
+
branch: workspace.branch,
|
|
200
|
+
base: base.kind === 'resolved' ? base.ref : null,
|
|
201
|
+
budget: workspace.config.context.budget,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
/* ------------------------------------------------------------------- audit */
|
|
205
|
+
async function dependencyChanges(workspace, base) {
|
|
206
|
+
if (base.kind === 'unresolved')
|
|
207
|
+
return { changes: [], unread: [] };
|
|
208
|
+
const isManifest = manifestMatcher(workspace.config.dependencies.manifests);
|
|
209
|
+
const out = [];
|
|
210
|
+
const unread = [];
|
|
211
|
+
for (const change of await changes(base.mergeBase, base.head, workspace.root)) {
|
|
212
|
+
if (!isManifest(change.path) && !(change.from !== undefined && isManifest(change.from)))
|
|
213
|
+
continue;
|
|
214
|
+
const ecosystem = ecosystemOf(change.path);
|
|
215
|
+
if (ecosystem === null) {
|
|
216
|
+
unread.push(change.path);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
const before = change.status === 'A' ? null : await show(base.mergeBase, change.from ?? change.path, workspace.root);
|
|
220
|
+
const after = change.status === 'D' ? null : await show(base.head, change.path, workspace.root);
|
|
221
|
+
const diff = diffManifest(change.path, ecosystem, before, after);
|
|
222
|
+
if ('error' in diff)
|
|
223
|
+
unread.push(change.path);
|
|
224
|
+
else
|
|
225
|
+
out.push(...diff.changes);
|
|
226
|
+
}
|
|
227
|
+
return { changes: out, unread };
|
|
228
|
+
}
|
|
229
|
+
async function archiveReasons(workspace, brief, base) {
|
|
230
|
+
const args = ['archive', brief.id, '--dry-run', '--format', 'json', '--no-color'];
|
|
231
|
+
if (base.kind === 'resolved')
|
|
232
|
+
args.push('--base', base.ref);
|
|
233
|
+
const answer = await workspace.siblings.json('spec-brief', args);
|
|
234
|
+
if ('absent' in answer)
|
|
235
|
+
return { unavailable: answer.absent };
|
|
236
|
+
if (answer.code === 2)
|
|
237
|
+
return { unavailable: 'spec-brief could not plan the archive' };
|
|
238
|
+
const plan = answer.document.plan;
|
|
239
|
+
return { blocking: plan?.blocking ?? [], warnings: plan?.warnings ?? [] };
|
|
240
|
+
}
|
|
241
|
+
async function briefAssertions(workspace, brief, text, reader) {
|
|
242
|
+
const answer = await workspace.siblings.json('spec-guard', [brief.file, '--ignore-status', '--json']);
|
|
243
|
+
if ('absent' in answer)
|
|
244
|
+
return { unavailable: answer.absent };
|
|
245
|
+
if (answer.code === 2)
|
|
246
|
+
return { unavailable: 'spec-guard could not run the brief\'s assertions' };
|
|
247
|
+
const report = answer.document;
|
|
248
|
+
return (report.results ?? [])
|
|
249
|
+
.filter((result) => result.spec === undefined || result.spec.file.replace(/\\/g, '/') === brief.file)
|
|
250
|
+
.map((result) => {
|
|
251
|
+
const line = result.spec?.line ?? 0;
|
|
252
|
+
return { ok: result.ok, description: result.description, message: result.message, line, section: reader.sectionAt(text, line), enclosing: reader.sectionsAt(text, line) };
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
export async function runAudit(workspace, brief, reader, baseFlag) {
|
|
256
|
+
const base = await resolveBase(workspace, baseFlag);
|
|
257
|
+
const text = await briefText(workspace, brief);
|
|
258
|
+
const [dependencies, archive, assertions, rulings] = await Promise.all([
|
|
259
|
+
dependencyChanges(workspace, base),
|
|
260
|
+
archiveReasons(workspace, brief, base),
|
|
261
|
+
briefAssertions(workspace, brief, text, reader),
|
|
262
|
+
checkRulings(workspace, brief, text, reader, base),
|
|
263
|
+
]);
|
|
264
|
+
const unmeasured = base.kind === 'unresolved' ? base.reason : base.mergeBase === base.head ? `${base.ref} and HEAD are the same commit: there is nothing to measure` : null;
|
|
265
|
+
const report = audit({
|
|
266
|
+
brief,
|
|
267
|
+
unmeasured,
|
|
268
|
+
dependencies,
|
|
269
|
+
archive,
|
|
270
|
+
assertions,
|
|
271
|
+
premiseSections: workspace.config.assertions.premises,
|
|
272
|
+
unverifiedRulings: rulings.unverified,
|
|
273
|
+
});
|
|
274
|
+
return {
|
|
275
|
+
brief,
|
|
276
|
+
base,
|
|
277
|
+
dependencies: dependencies.changes,
|
|
278
|
+
report: { findings: [...rulings.problems, ...report.findings], counts: tally([...rulings.problems, ...report.findings]) },
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function tally(findings) {
|
|
282
|
+
const counts = { error: 0, warning: 0, note: 0 };
|
|
283
|
+
for (const finding of findings)
|
|
284
|
+
counts[finding.severity] += 1;
|
|
285
|
+
return counts;
|
|
286
|
+
}
|
|
287
|
+
/* -------------------------------------------------------------- escalation */
|
|
288
|
+
async function escalationDirectory(workspace) {
|
|
289
|
+
return stateDirectory(workspace.commonDir, 'escalations');
|
|
290
|
+
}
|
|
291
|
+
export async function listEscalations(workspace) {
|
|
292
|
+
const directory = await escalationDirectory(workspace);
|
|
293
|
+
const out = [];
|
|
294
|
+
for (const name of (await readdir(directory)).filter((n) => n.endsWith('.json')).sort()) {
|
|
295
|
+
try {
|
|
296
|
+
out.push(JSON.parse(await readFile(join(directory, name), 'utf8')));
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
// A request that cannot be read is skipped; listing it would list nothing true.
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return out;
|
|
303
|
+
}
|
|
304
|
+
export async function raiseEscalation(workspace, brief, request) {
|
|
305
|
+
const existing = await listEscalations(workspace);
|
|
306
|
+
const id = nextId('E', brief.id, existing.map((e) => e.id));
|
|
307
|
+
const full = {
|
|
308
|
+
id,
|
|
309
|
+
brief: brief.id,
|
|
310
|
+
briefFile: brief.file,
|
|
311
|
+
paths: request.paths,
|
|
312
|
+
reason: request.reason,
|
|
313
|
+
options: request.options,
|
|
314
|
+
recommendation: request.recommendation,
|
|
315
|
+
created: new Date().toISOString(),
|
|
316
|
+
branch: workspace.branch,
|
|
317
|
+
head: await revision('HEAD', workspace.root),
|
|
318
|
+
};
|
|
319
|
+
await writeAtomic(join(await escalationDirectory(workspace), `${id}.json`), `${JSON.stringify(full, null, 2)}\n`);
|
|
320
|
+
return { request: full, memo: renderMemo(full) };
|
|
321
|
+
}
|
|
322
|
+
/** Writes a person's ruling into the brief and retires the request. Never commits: the signature is the person's. */
|
|
323
|
+
export async function recordRuling(workspace, escalationId, decision, note, reader) {
|
|
324
|
+
const request = (await listEscalations(workspace)).find((candidate) => candidate.id === escalationId.toUpperCase() || candidate.id === escalationId);
|
|
325
|
+
if (request === undefined)
|
|
326
|
+
throw new UsageError(`no escalation "${escalationId}" is waiting; spec-harness escalate --list shows those that are`);
|
|
327
|
+
const path = join(workspace.root, request.briefFile);
|
|
328
|
+
const text = await readText(path);
|
|
329
|
+
if (text === null)
|
|
330
|
+
throw new UsageError(`${request.briefFile} cannot be read`);
|
|
331
|
+
const where = reader.sectionTables(text, workspace.config.rulings.section);
|
|
332
|
+
const taken = readRulings(where.tables).rulings.map((row) => row.id);
|
|
333
|
+
const id = nextId('R', request.brief, taken);
|
|
334
|
+
const row = renderRow(id, request.paths, decision, note);
|
|
335
|
+
await writeAtomic(path, addRulingRow(text, workspace.config.rulings.section, row, where));
|
|
336
|
+
await rm(join(await escalationDirectory(workspace), `${request.id}.json`), { force: true });
|
|
337
|
+
return { id, file: request.briefFile, row };
|
|
338
|
+
}
|
|
339
|
+
//# sourceMappingURL=round.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"round.js","sourceRoot":"","sources":["../src/round.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A round, measured: the operations the command line and the MCP server\n * share. Each gathers what a pure module needs - from git, the disk and the\n * siblings - and hands it over; the decisions are made in `context.ts`,\n * `audit.ts`, `guard.ts` and `rulings.ts`.\n */\n\nimport { createHash } from 'node:crypto';\nimport { existsSync } from 'node:fs';\nimport { readdir, readFile, rm } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport { audit, type AssertionOutcome, type AuditReport } from './audit.js';\nimport { sameId } from './branch.js';\nimport { renderContext, type CitedDocument, type ContextPacket, type RuleInForce } from './context.js';\nimport type { DocumentReader } from './document.js';\nimport { readText, repositoryPath, stateDirectory, writeAtomic } from './fs.js';\nimport { blameLine, changes, mergeBase, remoteDefault, revision, show, verifyCommit } from './git.js';\nimport { decide, type Decision, type VerifiedRuling } from './guard.js';\nimport { diffManifest, ecosystemOf, manifestMatcher, type DependencyChange } from './manifests.js';\nimport {\n addRulingRow,\n nextId,\n readRulings,\n renderMemo,\n renderRow,\n type EscalationOption,\n type EscalationRequest,\n type RulingRow,\n} from './rulings.js';\nimport type { BriefRow, Finding } from './types.js';\nimport { compileGlob } from './vendor/spec-core/pattern/index.js';\nimport { dirname, isRelativeReference, resolveInside, splitReference } from './vendor/spec-core/path/index.js';\nimport { UsageError, type Workspace } from './workspace.js';\n\n/* -------------------------------------------------------------------- base */\n\nexport type Base =\n | { readonly kind: 'resolved'; readonly ref: string; readonly sha: string; readonly mergeBase: string; readonly head: string }\n | { readonly kind: 'unresolved'; readonly reason: string };\n\n/** What the round is measured from: the flag, the configuration, then the remote's default branch. */\nexport async function resolveBase(workspace: Workspace, flag: string | undefined): Promise<Base> {\n const ref = flag ?? workspace.config.base ?? (await remoteDefault(workspace.root));\n if (ref === null) {\n return { kind: 'unresolved', reason: 'no base is named and the remote has no default branch; pass --base <ref> or set \"base\"' };\n }\n const sha = await revision(ref, workspace.root);\n const head = await revision('HEAD', workspace.root);\n if (sha === null || head === null) return { kind: 'unresolved', reason: `\"${ref}\" names no commit` };\n const common = await mergeBase(sha, head, workspace.root);\n if (common === null) return { kind: 'unresolved', reason: `\"${ref}\" and HEAD share no history` };\n return { kind: 'resolved', ref, sha, mergeBase: common, head };\n}\n\n/* ----------------------------------------------------------------- rulings */\n\nexport interface RulingCheck {\n readonly rows: readonly RulingRow[];\n readonly verified: readonly VerifiedRuling[];\n readonly unverified: readonly { readonly id: string; readonly reason: string }[];\n readonly problems: readonly Finding[];\n}\n\n/**\n * The brief's rulings, each checked against its signature: the commit that\n * last changed its row must be signed by a principal the base branch's\n * allowed-signers file lists (ADR-0006).\n */\nexport async function checkRulings(workspace: Workspace, brief: BriefRow, text: string, reader: DocumentReader, base: Base): Promise<RulingCheck> {\n const { rulings: rows, problems } = readRulings(reader.sectionTables(text, workspace.config.rulings.section).tables);\n const findings: Finding[] = problems.map((problem) => ({\n rule: 'ruling-unreadable',\n severity: 'warning',\n message: problem.message,\n hint: 'a ruling row is | id | `paths` | allow or deny | note |',\n file: brief.file,\n line: problem.line,\n }));\n const allows = rows.filter((row) => row.decision === 'allow');\n if (allows.length === 0) return { rows, verified: [], unverified: [], problems: findings };\n if (base.kind === 'unresolved') {\n return { rows, verified: [], unverified: allows.map((row) => ({ id: row.id, reason: `no base to read the allowed signers from: ${base.reason}` })), problems: findings };\n }\n const signers = await show(base.sha, workspace.config.rulings.allowedSigners, workspace.root);\n if (signers === null) {\n const reason = `${base.ref} has no ${workspace.config.rulings.allowedSigners}, so no signature can count`;\n return { rows, verified: [], unverified: allows.map((row) => ({ id: row.id, reason })), problems: findings };\n }\n const directory = await stateDirectory(workspace.commonDir, 'signers');\n const file = join(directory, `${createHash('sha256').update(signers).digest('hex').slice(0, 16)}`);\n await writeAtomic(file, signers);\n const verified: VerifiedRuling[] = [];\n const unverified: { id: string; reason: string }[] = [];\n for (const row of allows) {\n // The row's line is a line of the text on disk, so it is blamed there: at\n // HEAD the same number can be another line, one a signed commit wrote,\n // and an uncommitted row would borrow that commit's signature.\n const commit = await blameLine(null, brief.file, row.line, workspace.root);\n if (commit === null || /^0+$/.test(commit)) {\n unverified.push({ id: row.id, reason: 'its row is not committed' });\n continue;\n }\n const signature = await verifyCommit(commit, file, workspace.root);\n if (signature.good && signature.principal !== null) verified.push({ id: row.id, paths: row.paths, signer: signature.principal });\n else unverified.push({ id: row.id, reason: `commit ${commit.slice(0, 12)} last changed its row, and ${signature.detail || 'it is not signed'}` });\n }\n return { rows, verified, unverified, problems: findings };\n}\n\n/* ------------------------------------------------------------------- guard */\n\nexport async function briefText(workspace: Workspace, brief: BriefRow): Promise<string> {\n const text = await readText(join(workspace.root, brief.file));\n if (text === null) throw new UsageError(`${brief.file} cannot be read`);\n return text;\n}\n\n/** Decides paths against the brief, with its verified rulings. */\nexport async function checkPaths(\n workspace: Workspace,\n brief: BriefRow | null,\n noBrief: string | undefined,\n given: readonly string[],\n cwd: string,\n reader: DocumentReader,\n): Promise<Decision[]> {\n let rulings: readonly VerifiedRuling[] = [];\n const resolved = given.map((path) => ({ given: path, path: repositoryPath(path, workspace.root, cwd) }));\n if (brief !== null) {\n // Rulings cost a blame and a signature check each, so they are read only\n // when a write reaches a protected file.\n const first = resolved.map(({ path, given: g }) =>\n decide({ path, given: g, brief, noBrief, rulings: [], outOfScope: workspace.config.outOfScope }),\n );\n if (first.some((decision) => decision.reason === 'protected')) {\n const base = await resolveBase(workspace, undefined);\n rulings = (await checkRulings(workspace, brief, await briefText(workspace, brief), reader, base)).verified;\n } else {\n return first;\n }\n }\n return resolved.map(({ path, given: g }) => decide({ path, given: g, brief, noBrief, rulings, outOfScope: workspace.config.outOfScope }));\n}\n\n/* ----------------------------------------------------------------- context */\n\nfunction scopeBases(patterns: readonly string[]): string[] {\n const bases = new Set<string>();\n for (const pattern of patterns) {\n try {\n for (const base of compileGlob(pattern, { dialect: 'path', caseSensitive: true }).bases) bases.add(base === '' ? '.' : base);\n } catch {\n // spec-brief lint reports a pattern it cannot read; the rules for the rest still count.\n }\n }\n return [...bases].sort();\n}\n\nasync function rulesFor(workspace: Workspace, brief: BriefRow): Promise<readonly RuleInForce[] | { unavailable: string }> {\n const paths = scopeBases(brief.affectedFiles);\n if (paths.length === 0) return [];\n const answer = await workspace.siblings.json('spec-guard', ['query', ...paths, '--json']);\n if ('absent' in answer) return { unavailable: answer.absent };\n if (answer.code === 2) return { unavailable: 'spec-guard could not read its specs' };\n const document = answer.document as { results?: { rules?: { document: string; line: number; kind: string; description: string; reason?: string | null; inForce?: boolean }[] }[] };\n const seen = new Set<string>();\n const out: RuleInForce[] = [];\n for (const result of document.results ?? []) {\n for (const rule of result.rules ?? []) {\n const key = `${rule.document}:${rule.line}`;\n if (rule.inForce === false || seen.has(key)) continue;\n seen.add(key);\n out.push({ document: rule.document, line: rule.line, kind: rule.kind, description: rule.description, reason: rule.reason ?? null });\n }\n }\n return out;\n}\n\nasync function citedDocuments(workspace: Workspace, brief: BriefRow, text: string, reader: DocumentReader): Promise<CitedDocument[]> {\n const seen = new Set<string>();\n const out: CitedDocument[] = [];\n for (const citation of reader.citations(text)) {\n if (!isRelativeReference(citation.target)) continue;\n let target = splitReference(citation.target).path;\n try {\n target = decodeURIComponent(target);\n } catch {\n // A destination that is not valid percent-encoding is read as written.\n }\n const path = resolveInside(dirname(brief.file), target);\n if (path === null || path === '' || path === brief.file || seen.has(path)) continue;\n seen.add(path);\n const document = /\\.(?:md|markdown|mdx|txt)$/i.test(path);\n // A link to code or a directory that exists is not a document to include,\n // and must not be reported as a link that resolves to nothing.\n if (!document && existsSync(join(workspace.root, path))) continue;\n const content = document ? await readText(join(workspace.root, path)) : null;\n const meta = content === null ? { title: null, status: null } : reader.titleAndStatus(content);\n out.push({ path, title: meta.title, status: meta.status, text: content });\n }\n return out;\n}\n\nexport async function buildContext(\n workspace: Workspace,\n brief: BriefRow,\n briefs: readonly BriefRow[],\n reader: DocumentReader,\n): Promise<ContextPacket> {\n const text = await briefText(workspace, brief);\n const base = await resolveBase(workspace, undefined);\n const [rules, cited, rulings] = await Promise.all([\n rulesFor(workspace, brief),\n citedDocuments(workspace, brief, text, reader),\n checkRulings(workspace, brief, text, reader, base),\n ]);\n return renderContext({\n brief,\n briefText: text,\n dependencies: brief.dependsOn.map(\n (id) =>\n // spec-brief reports a dependency as its front matter spells it, `7` for brief 007.\n briefs.find((candidate) => sameId(candidate.id, id)) ?? {\n ...brief,\n id,\n title: '(no such brief)',\n file: '',\n phase: 'live',\n status: 'unknown',\n },\n ),\n cited,\n rules,\n rulings: rulings.verified,\n branch: workspace.branch,\n base: base.kind === 'resolved' ? base.ref : null,\n budget: workspace.config.context.budget,\n });\n}\n\n/* ------------------------------------------------------------------- audit */\n\nasync function dependencyChanges(workspace: Workspace, base: Base): Promise<{ changes: DependencyChange[]; unread: string[] }> {\n if (base.kind === 'unresolved') return { changes: [], unread: [] };\n const isManifest = manifestMatcher(workspace.config.dependencies.manifests);\n const out: DependencyChange[] = [];\n const unread: string[] = [];\n for (const change of await changes(base.mergeBase, base.head, workspace.root)) {\n if (!isManifest(change.path) && !(change.from !== undefined && isManifest(change.from))) continue;\n const ecosystem = ecosystemOf(change.path);\n if (ecosystem === null) {\n unread.push(change.path);\n continue;\n }\n const before = change.status === 'A' ? null : await show(base.mergeBase, change.from ?? change.path, workspace.root);\n const after = change.status === 'D' ? null : await show(base.head, change.path, workspace.root);\n const diff = diffManifest(change.path, ecosystem, before, after);\n if ('error' in diff) unread.push(change.path);\n else out.push(...diff.changes);\n }\n return { changes: out, unread };\n}\n\nasync function archiveReasons(workspace: Workspace, brief: BriefRow, base: Base): Promise<{ blocking: Finding[]; warnings: Finding[] } | { unavailable: string }> {\n const args = ['archive', brief.id, '--dry-run', '--format', 'json', '--no-color'];\n if (base.kind === 'resolved') args.push('--base', base.ref);\n const answer = await workspace.siblings.json('spec-brief', args);\n if ('absent' in answer) return { unavailable: answer.absent };\n if (answer.code === 2) return { unavailable: 'spec-brief could not plan the archive' };\n const plan = (answer.document as { plan?: { blocking?: Finding[]; warnings?: Finding[] } }).plan;\n return { blocking: plan?.blocking ?? [], warnings: plan?.warnings ?? [] };\n}\n\nasync function briefAssertions(workspace: Workspace, brief: BriefRow, text: string, reader: DocumentReader): Promise<AssertionOutcome[] | { unavailable: string }> {\n const answer = await workspace.siblings.json('spec-guard', [brief.file, '--ignore-status', '--json']);\n if ('absent' in answer) return { unavailable: answer.absent };\n if (answer.code === 2) return { unavailable: 'spec-guard could not run the brief\\'s assertions' };\n const report = answer.document as { results?: { ok: boolean; description: string; message: string; spec?: { file: string; line: number } }[] };\n return (report.results ?? [])\n .filter((result) => result.spec === undefined || result.spec.file.replace(/\\\\/g, '/') === brief.file)\n .map((result) => {\n const line = result.spec?.line ?? 0;\n return { ok: result.ok, description: result.description, message: result.message, line, section: reader.sectionAt(text, line), enclosing: reader.sectionsAt(text, line) };\n });\n}\n\nexport interface AuditResult {\n readonly brief: BriefRow;\n readonly base: Base;\n readonly report: AuditReport;\n readonly dependencies: readonly DependencyChange[];\n}\n\nexport async function runAudit(workspace: Workspace, brief: BriefRow, reader: DocumentReader, baseFlag: string | undefined): Promise<AuditResult> {\n const base = await resolveBase(workspace, baseFlag);\n const text = await briefText(workspace, brief);\n const [dependencies, archive, assertions, rulings] = await Promise.all([\n dependencyChanges(workspace, base),\n archiveReasons(workspace, brief, base),\n briefAssertions(workspace, brief, text, reader),\n checkRulings(workspace, brief, text, reader, base),\n ]);\n const unmeasured =\n base.kind === 'unresolved' ? base.reason : base.mergeBase === base.head ? `${base.ref} and HEAD are the same commit: there is nothing to measure` : null;\n const report = audit({\n brief,\n unmeasured,\n dependencies,\n archive,\n assertions,\n premiseSections: workspace.config.assertions.premises,\n unverifiedRulings: rulings.unverified,\n });\n return {\n brief,\n base,\n dependencies: dependencies.changes,\n report: { findings: [...rulings.problems, ...report.findings], counts: tally([...rulings.problems, ...report.findings]) },\n };\n}\n\nfunction tally(findings: readonly Finding[]): AuditReport['counts'] {\n const counts = { error: 0, warning: 0, note: 0 };\n for (const finding of findings) counts[finding.severity] += 1;\n return counts;\n}\n\n/* -------------------------------------------------------------- escalation */\n\nasync function escalationDirectory(workspace: Workspace): Promise<string> {\n return stateDirectory(workspace.commonDir, 'escalations');\n}\n\nexport async function listEscalations(workspace: Workspace): Promise<EscalationRequest[]> {\n const directory = await escalationDirectory(workspace);\n const out: EscalationRequest[] = [];\n for (const name of (await readdir(directory)).filter((n) => n.endsWith('.json')).sort()) {\n try {\n out.push(JSON.parse(await readFile(join(directory, name), 'utf8')) as EscalationRequest);\n } catch {\n // A request that cannot be read is skipped; listing it would list nothing true.\n }\n }\n return out;\n}\n\nexport async function raiseEscalation(\n workspace: Workspace,\n brief: BriefRow,\n request: { paths: readonly string[]; reason: string; options: readonly EscalationOption[]; recommendation: string | null },\n): Promise<{ request: EscalationRequest; memo: string }> {\n const existing = await listEscalations(workspace);\n const id = nextId('E', brief.id, existing.map((e) => e.id));\n const full: EscalationRequest = {\n id,\n brief: brief.id,\n briefFile: brief.file,\n paths: request.paths,\n reason: request.reason,\n options: request.options,\n recommendation: request.recommendation,\n created: new Date().toISOString(),\n branch: workspace.branch,\n head: await revision('HEAD', workspace.root),\n };\n await writeAtomic(join(await escalationDirectory(workspace), `${id}.json`), `${JSON.stringify(full, null, 2)}\\n`);\n return { request: full, memo: renderMemo(full) };\n}\n\n/** Writes a person's ruling into the brief and retires the request. Never commits: the signature is the person's. */\nexport async function recordRuling(\n workspace: Workspace,\n escalationId: string,\n decision: 'allow' | 'deny',\n note: string,\n reader: DocumentReader,\n): Promise<{ id: string; file: string; row: string }> {\n const request = (await listEscalations(workspace)).find((candidate) => candidate.id === escalationId.toUpperCase() || candidate.id === escalationId);\n if (request === undefined) throw new UsageError(`no escalation \"${escalationId}\" is waiting; spec-harness escalate --list shows those that are`);\n const path = join(workspace.root, request.briefFile);\n const text = await readText(path);\n if (text === null) throw new UsageError(`${request.briefFile} cannot be read`);\n const where = reader.sectionTables(text, workspace.config.rulings.section);\n const taken = readRulings(where.tables).rulings.map((row) => row.id);\n const id = nextId('R', request.brief, taken);\n const row = renderRow(id, request.paths, decision, note);\n await writeAtomic(path, addRulingRow(text, workspace.config.rulings.section, row, where));\n await rm(join(await escalationDirectory(workspace), `${request.id}.json`), { force: true });\n return { id, file: request.briefFile, row };\n}\n"]}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escalations an agent raises and the rulings a person signs.
|
|
3
|
+
*
|
|
4
|
+
* A brief says what a round may not touch. When the round cannot be done
|
|
5
|
+
* without touching it, the agent stops and asks; a person decides; and the
|
|
6
|
+
* decision becomes a row in the brief's rulings table:
|
|
7
|
+
*
|
|
8
|
+
* ```markdown
|
|
9
|
+
* ## Rulings
|
|
10
|
+
*
|
|
11
|
+
* | Ruling | Paths | Decision | Note |
|
|
12
|
+
* | --- | --- | --- | --- |
|
|
13
|
+
* | R-012-1 | `src/db/schema.ts` | allow | Add rotated_at; no other schema change. |
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* A row is text, and an agent can write text. What makes a row a ruling is
|
|
17
|
+
* the commit that last changed it: signed, with a key listed in the allowed
|
|
18
|
+
* signers file as the base branch has it (ADR-0006). The row states the
|
|
19
|
+
* decision; the signature says a person made it; the base branch says whose
|
|
20
|
+
* signatures count. A hash of the row, which the agent could compute as well
|
|
21
|
+
* as anyone, would say none of that.
|
|
22
|
+
*
|
|
23
|
+
* The request itself lives outside the work tree while it waits
|
|
24
|
+
* (`<git-common-dir>/spec-harness/escalations/`): it is in-flight state, and
|
|
25
|
+
* only its outcome belongs in the record (spec-core ADR-0005).
|
|
26
|
+
*/
|
|
27
|
+
export interface EscalationOption {
|
|
28
|
+
readonly label: string;
|
|
29
|
+
readonly consequence: string;
|
|
30
|
+
}
|
|
31
|
+
export interface EscalationRequest {
|
|
32
|
+
readonly id: string;
|
|
33
|
+
readonly brief: string;
|
|
34
|
+
readonly briefFile: string;
|
|
35
|
+
readonly paths: readonly string[];
|
|
36
|
+
readonly reason: string;
|
|
37
|
+
readonly options: readonly EscalationOption[];
|
|
38
|
+
readonly recommendation: string | null;
|
|
39
|
+
readonly created: string;
|
|
40
|
+
readonly branch: string | null;
|
|
41
|
+
readonly head: string | null;
|
|
42
|
+
}
|
|
43
|
+
export interface RulingRow {
|
|
44
|
+
readonly id: string;
|
|
45
|
+
readonly paths: readonly string[];
|
|
46
|
+
readonly decision: 'allow' | 'deny';
|
|
47
|
+
readonly note: string;
|
|
48
|
+
/** 1-based line of the row in the brief. */
|
|
49
|
+
readonly line: number;
|
|
50
|
+
}
|
|
51
|
+
/** A table as the Markdown scanner reads it: header cells, and rows with their lines. */
|
|
52
|
+
export interface TableView {
|
|
53
|
+
readonly headers: readonly string[];
|
|
54
|
+
readonly rows: readonly {
|
|
55
|
+
readonly cells: readonly string[];
|
|
56
|
+
readonly line: number;
|
|
57
|
+
}[];
|
|
58
|
+
}
|
|
59
|
+
export type RulingProblem = {
|
|
60
|
+
readonly line: number;
|
|
61
|
+
readonly message: string;
|
|
62
|
+
};
|
|
63
|
+
/** The globs a paths cell names: backtick-quoted, or comma-separated. */
|
|
64
|
+
export declare function pathsOf(cell: string): string[];
|
|
65
|
+
/**
|
|
66
|
+
* The rulings in a brief's rulings section, from its tables. A table whose
|
|
67
|
+
* header does not name the columns is not a rulings table; a row that cannot
|
|
68
|
+
* be read is a problem to report, never a ruling.
|
|
69
|
+
*/
|
|
70
|
+
export declare function readRulings(tables: readonly TableView[]): {
|
|
71
|
+
rulings: RulingRow[];
|
|
72
|
+
problems: RulingProblem[];
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* The next free escalation id for a brief, given the ids already taken. The
|
|
76
|
+
* stem compares without case: `readRulings` reads ids in capitals, so a brief
|
|
77
|
+
* named `auth` has `R-AUTH-1` taken when this writes `R-auth-2`.
|
|
78
|
+
*/
|
|
79
|
+
export declare function nextId(prefix: 'E' | 'R', brief: string, taken: readonly string[]): string;
|
|
80
|
+
/** The memo a person reads to rule: what, why, the choices, and what each costs. */
|
|
81
|
+
export declare function renderMemo(request: EscalationRequest): string;
|
|
82
|
+
/** The table row a ruling writes. */
|
|
83
|
+
export declare function renderRow(id: string, paths: readonly string[], decision: 'allow' | 'deny', note: string): string;
|
|
84
|
+
/**
|
|
85
|
+
* The brief with a ruling row added: under the rulings section's table when
|
|
86
|
+
* there is one, or in a new section at the end. Every other line is left as
|
|
87
|
+
* it was; line endings are the brief's own.
|
|
88
|
+
*/
|
|
89
|
+
export declare function addRulingRow(text: string, section: string, row: string, where: {
|
|
90
|
+
readonly headingLine: number | null;
|
|
91
|
+
readonly tableEndLine: number | null;
|
|
92
|
+
}): string;
|