@descent-vtt/spec-harness 0.0.0 → 0.1.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 +16 -0
- package/.claude-plugin/plugin.json +17 -0
- package/.mcp.json +12 -0
- package/CHANGELOG.md +56 -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 +85 -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
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What every command shares: exit codes, options, the repository a command
|
|
3
|
+
* runs in, and which brief the round is working on.
|
|
4
|
+
*/
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { parseArgs } from 'node:util';
|
|
7
|
+
import { findActive } from './briefs.js';
|
|
8
|
+
import { briefIdFromBranch } from './branch.js';
|
|
9
|
+
import { loadConfig } from './fs.js';
|
|
10
|
+
import { commonDirectory, currentBranch, workTreeRoot } from './git.js';
|
|
11
|
+
import { createSiblings } from './siblings.js';
|
|
12
|
+
export const EXIT_OK = 0;
|
|
13
|
+
export const EXIT_FAILED = 1;
|
|
14
|
+
export const EXIT_ERROR = 2;
|
|
15
|
+
/** The version of the JSON documents this tool prints. Bumped when a field changes meaning. */
|
|
16
|
+
export const JSON_SCHEMA_VERSION = 1;
|
|
17
|
+
export class UsageError extends Error {
|
|
18
|
+
constructor(message) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = 'UsageError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function version() {
|
|
24
|
+
const manifest = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
25
|
+
return manifest.version;
|
|
26
|
+
}
|
|
27
|
+
export function parseOptions(argv) {
|
|
28
|
+
let parsed;
|
|
29
|
+
try {
|
|
30
|
+
parsed = parseArgs({
|
|
31
|
+
args: [...argv],
|
|
32
|
+
allowPositionals: true,
|
|
33
|
+
strict: true,
|
|
34
|
+
options: {
|
|
35
|
+
brief: { type: 'string' },
|
|
36
|
+
base: { type: 'string' },
|
|
37
|
+
root: { type: 'string' },
|
|
38
|
+
format: { type: 'string' },
|
|
39
|
+
strict: { type: 'boolean' },
|
|
40
|
+
write: { type: 'boolean' },
|
|
41
|
+
'git-hook': { type: 'boolean' },
|
|
42
|
+
path: { type: 'string', multiple: true },
|
|
43
|
+
reason: { type: 'string' },
|
|
44
|
+
option: { type: 'string', multiple: true },
|
|
45
|
+
recommend: { type: 'string' },
|
|
46
|
+
list: { type: 'boolean' },
|
|
47
|
+
show: { type: 'string' },
|
|
48
|
+
allow: { type: 'boolean' },
|
|
49
|
+
deny: { type: 'boolean' },
|
|
50
|
+
note: { type: 'string' },
|
|
51
|
+
id: { type: 'string' },
|
|
52
|
+
at: { type: 'string' },
|
|
53
|
+
help: { type: 'boolean', short: 'h' },
|
|
54
|
+
version: { type: 'boolean', short: 'v' },
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
throw new UsageError(error.message);
|
|
60
|
+
}
|
|
61
|
+
const { values, positionals } = parsed;
|
|
62
|
+
const format = values.format ?? 'pretty';
|
|
63
|
+
if (format !== 'pretty' && format !== 'json')
|
|
64
|
+
throw new UsageError(`--format must be pretty or json, not "${format}"`);
|
|
65
|
+
const at = values.at;
|
|
66
|
+
if (at !== undefined && at !== 'base' && at !== 'head' && at !== 'both')
|
|
67
|
+
throw new UsageError(`--at must be base, head or both, not "${at}"`);
|
|
68
|
+
return {
|
|
69
|
+
command: positionals[0],
|
|
70
|
+
positionals: positionals.slice(1),
|
|
71
|
+
brief: values.brief,
|
|
72
|
+
base: values.base,
|
|
73
|
+
root: values.root,
|
|
74
|
+
format,
|
|
75
|
+
strict: values.strict === true,
|
|
76
|
+
help: values.help === true,
|
|
77
|
+
version: values.version === true,
|
|
78
|
+
write: values.write === true,
|
|
79
|
+
gitHook: values['git-hook'] === true,
|
|
80
|
+
paths: values.path ?? [],
|
|
81
|
+
reason: values.reason,
|
|
82
|
+
options: values.option ?? [],
|
|
83
|
+
recommend: values.recommend,
|
|
84
|
+
list: values.list === true,
|
|
85
|
+
show: values.show,
|
|
86
|
+
allow: values.allow === true,
|
|
87
|
+
deny: values.deny === true,
|
|
88
|
+
note: values.note,
|
|
89
|
+
id: values.id,
|
|
90
|
+
at,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export async function openWorkspace(options, io) {
|
|
94
|
+
const cwd = options.root ?? io.cwd;
|
|
95
|
+
const root = await workTreeRoot(cwd);
|
|
96
|
+
if (root === null)
|
|
97
|
+
throw new UsageError(`${cwd} is not inside a git work tree; spec-harness measures rounds by their commits`);
|
|
98
|
+
const commonDir = (await commonDirectory(root)) ?? `${root}/.git`;
|
|
99
|
+
const { config } = await loadConfig(root);
|
|
100
|
+
return { root, commonDir, config, siblings: createSiblings(root, config), branch: await currentBranch(root), cwd: io.cwd };
|
|
101
|
+
}
|
|
102
|
+
/** The id the flag, the environment or the branch names, without asking spec-brief anything. */
|
|
103
|
+
export function namedId(workspace, options, env) {
|
|
104
|
+
if (options.brief !== undefined && options.brief.trim() !== '')
|
|
105
|
+
return options.brief.trim();
|
|
106
|
+
const environment = env['SPEC_BRIEF'];
|
|
107
|
+
if (environment !== undefined && environment.trim() !== '')
|
|
108
|
+
return environment.trim();
|
|
109
|
+
return workspace.branch === null ? null : briefIdFromBranch(workspace.config.branches, workspace.branch);
|
|
110
|
+
}
|
|
111
|
+
export async function activeBrief(workspace, options, env) {
|
|
112
|
+
if (namedId(workspace, options, env) === null) {
|
|
113
|
+
return findActive([], {});
|
|
114
|
+
}
|
|
115
|
+
const briefs = await workspace.siblings.briefs();
|
|
116
|
+
const fromBranch = workspace.branch === null ? null : briefIdFromBranch(workspace.config.branches, workspace.branch);
|
|
117
|
+
return findActive(briefs, { flag: options.brief, environment: env['SPEC_BRIEF'], branch: fromBranch });
|
|
118
|
+
}
|
|
119
|
+
export function describeActive(active) {
|
|
120
|
+
switch (active.kind) {
|
|
121
|
+
case 'found':
|
|
122
|
+
return { brief: active.brief, note: undefined, problem: null };
|
|
123
|
+
case 'none':
|
|
124
|
+
return { brief: null, note: active.reason, problem: null };
|
|
125
|
+
case 'unknown':
|
|
126
|
+
return { brief: null, note: undefined, problem: `the ${active.source} names brief ${active.id}, and spec-brief knows no such brief` };
|
|
127
|
+
case 'archived':
|
|
128
|
+
return {
|
|
129
|
+
brief: null,
|
|
130
|
+
note: undefined,
|
|
131
|
+
problem: `the ${active.source} names brief ${active.brief.id}, which is archived; a closed round writes nothing`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export function json(command, body) {
|
|
136
|
+
return `${JSON.stringify({ tool: 'spec-harness', version: version(), schemaVersion: JSON_SCHEMA_VERSION, command, ...body }, null, 2)}\n`;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=workspace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace.js","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What every command shares: exit codes, options, the repository a command\n * runs in, and which brief the round is working on.\n */\n\nimport { readFileSync } from 'node:fs';\nimport { parseArgs } from 'node:util';\n\nimport { findActive, type ActiveBrief } from './briefs.js';\nimport { briefIdFromBranch } from './branch.js';\nimport type { HarnessConfig } from './config.js';\nimport { loadConfig } from './fs.js';\nimport { commonDirectory, currentBranch, workTreeRoot } from './git.js';\nimport { createSiblings, type Siblings } from './siblings.js';\nimport type { BriefRow } from './types.js';\n\nexport const EXIT_OK = 0;\nexport const EXIT_FAILED = 1;\nexport const EXIT_ERROR = 2;\n\n/** The version of the JSON documents this tool prints. Bumped when a field changes meaning. */\nexport const JSON_SCHEMA_VERSION = 1;\n\nexport class UsageError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'UsageError';\n }\n}\n\nexport interface CliIO {\n readonly stdout: { write(text: string): unknown };\n readonly stderr: { write(text: string): unknown };\n readonly stdin?: () => Promise<string>;\n readonly cwd: string;\n readonly env: Readonly<Record<string, string | undefined>>;\n}\n\nexport function version(): string {\n const manifest = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as { version: string };\n return manifest.version;\n}\n\nexport interface Options {\n readonly command: string | undefined;\n readonly positionals: readonly string[];\n readonly brief: string | undefined;\n readonly base: string | undefined;\n readonly root: string | undefined;\n readonly format: 'pretty' | 'json';\n readonly strict: boolean;\n readonly help: boolean;\n readonly version: boolean;\n /** init: apply the plan instead of printing it. */\n readonly write: boolean;\n /** init: also install git's pre-commit hook. */\n readonly gitHook: boolean;\n /** escalate: the files the round needs, the reason, the options and the recommendation. */\n readonly paths: readonly string[];\n readonly reason: string | undefined;\n readonly options: readonly string[];\n readonly recommend: string | undefined;\n readonly list: boolean;\n readonly show: string | undefined;\n /** rule: the decision and what it allows. */\n readonly allow: boolean;\n readonly deny: boolean;\n readonly note: string | undefined;\n /** probe: which probe, and at which commit. */\n readonly id: string | undefined;\n readonly at: 'base' | 'head' | 'both' | undefined;\n}\n\nexport function parseOptions(argv: readonly string[]): Options {\n let parsed;\n try {\n parsed = parseArgs({\n args: [...argv],\n allowPositionals: true,\n strict: true,\n options: {\n brief: { type: 'string' },\n base: { type: 'string' },\n root: { type: 'string' },\n format: { type: 'string' },\n strict: { type: 'boolean' },\n write: { type: 'boolean' },\n 'git-hook': { type: 'boolean' },\n path: { type: 'string', multiple: true },\n reason: { type: 'string' },\n option: { type: 'string', multiple: true },\n recommend: { type: 'string' },\n list: { type: 'boolean' },\n show: { type: 'string' },\n allow: { type: 'boolean' },\n deny: { type: 'boolean' },\n note: { type: 'string' },\n id: { type: 'string' },\n at: { type: 'string' },\n help: { type: 'boolean', short: 'h' },\n version: { type: 'boolean', short: 'v' },\n },\n });\n } catch (error) {\n throw new UsageError((error as Error).message);\n }\n const { values, positionals } = parsed;\n const format = values.format ?? 'pretty';\n if (format !== 'pretty' && format !== 'json') throw new UsageError(`--format must be pretty or json, not \"${format}\"`);\n const at = values.at;\n if (at !== undefined && at !== 'base' && at !== 'head' && at !== 'both') throw new UsageError(`--at must be base, head or both, not \"${at}\"`);\n return {\n command: positionals[0],\n positionals: positionals.slice(1),\n brief: values.brief,\n base: values.base,\n root: values.root,\n format,\n strict: values.strict === true,\n help: values.help === true,\n version: values.version === true,\n write: values.write === true,\n gitHook: values['git-hook'] === true,\n paths: values.path ?? [],\n reason: values.reason,\n options: values.option ?? [],\n recommend: values.recommend,\n list: values.list === true,\n show: values.show,\n allow: values.allow === true,\n deny: values.deny === true,\n note: values.note,\n id: values.id,\n at,\n };\n}\n\n/** Everything a command needs about the repository it runs in. */\nexport interface Workspace {\n readonly root: string;\n readonly commonDir: string;\n readonly config: HarnessConfig;\n readonly siblings: Siblings;\n readonly branch: string | null;\n readonly cwd: string;\n}\n\nexport async function openWorkspace(options: Options, io: CliIO): Promise<Workspace> {\n const cwd = options.root ?? io.cwd;\n const root = await workTreeRoot(cwd);\n if (root === null) throw new UsageError(`${cwd} is not inside a git work tree; spec-harness measures rounds by their commits`);\n const commonDir = (await commonDirectory(root)) ?? `${root}/.git`;\n const { config } = await loadConfig(root);\n return { root, commonDir, config, siblings: createSiblings(root, config), branch: await currentBranch(root), cwd: io.cwd };\n}\n\n/** The id the flag, the environment or the branch names, without asking spec-brief anything. */\nexport function namedId(workspace: Workspace, options: Options, env: CliIO['env']): string | null {\n if (options.brief !== undefined && options.brief.trim() !== '') return options.brief.trim();\n const environment = env['SPEC_BRIEF'];\n if (environment !== undefined && environment.trim() !== '') return environment.trim();\n return workspace.branch === null ? null : briefIdFromBranch(workspace.config.branches, workspace.branch);\n}\n\nexport async function activeBrief(workspace: Workspace, options: Options, env: CliIO['env']): Promise<ActiveBrief> {\n if (namedId(workspace, options, env) === null) {\n return findActive([], {});\n }\n const briefs = await workspace.siblings.briefs();\n const fromBranch = workspace.branch === null ? null : briefIdFromBranch(workspace.config.branches, workspace.branch);\n return findActive(briefs, { flag: options.brief, environment: env['SPEC_BRIEF'], branch: fromBranch });\n}\n\nexport function describeActive(active: ActiveBrief): { brief: BriefRow | null; note: string | undefined; problem: string | null } {\n switch (active.kind) {\n case 'found':\n return { brief: active.brief, note: undefined, problem: null };\n case 'none':\n return { brief: null, note: active.reason, problem: null };\n case 'unknown':\n return { brief: null, note: undefined, problem: `the ${active.source} names brief ${active.id}, and spec-brief knows no such brief` };\n case 'archived':\n return {\n brief: null,\n note: undefined,\n problem: `the ${active.source} names brief ${active.brief.id}, which is archived; a closed round writes nothing`,\n };\n }\n}\n\nexport function json(command: string, body: Record<string, unknown>): string {\n return `${JSON.stringify({ tool: 'spec-harness', version: version(), schemaVersion: JSON_SCHEMA_VERSION, command, ...body }, null, 2)}\\n`;\n}\n\n"]}
|
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PreToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "Edit|Write|MultiEdit|NotebookEdit",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "npx --no-install spec-harness hook claude",
|
|
10
|
+
"timeout": 60
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"PostToolUse": [
|
|
16
|
+
{
|
|
17
|
+
"matcher": "Edit|Write|MultiEdit|NotebookEdit",
|
|
18
|
+
"hooks": [
|
|
19
|
+
{
|
|
20
|
+
"type": "command",
|
|
21
|
+
"command": "npx --no-install spec-harness hook claude",
|
|
22
|
+
"timeout": 60
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,93 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@descent-vtt/spec-harness",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "The agent-facing member of the spec-* tools: context for a round, a guard on every write, an audit when it ends, probes that prove a defect before it is filed, and rulings a person signs.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agents",
|
|
7
|
+
"brief",
|
|
8
|
+
"claude-code",
|
|
9
|
+
"guardrails",
|
|
10
|
+
"harness",
|
|
11
|
+
"hooks",
|
|
12
|
+
"mcp",
|
|
13
|
+
"spec"
|
|
14
|
+
],
|
|
5
15
|
"license": "MIT",
|
|
6
|
-
"
|
|
16
|
+
"type": "module",
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=22"
|
|
19
|
+
},
|
|
20
|
+
"bin": {
|
|
21
|
+
"spec-harness": "bin/spec-harness.js"
|
|
22
|
+
},
|
|
23
|
+
"main": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./spec-brief-plugin": {
|
|
31
|
+
"types": "./dist/plugin.d.ts",
|
|
32
|
+
"default": "./dist/plugin.js"
|
|
33
|
+
},
|
|
34
|
+
"./package.json": "./package.json"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"CHANGELOG.md",
|
|
38
|
+
"LICENSE",
|
|
39
|
+
"README.md",
|
|
40
|
+
"bin",
|
|
41
|
+
"dist",
|
|
42
|
+
"skills",
|
|
43
|
+
"hooks",
|
|
44
|
+
".claude-plugin",
|
|
45
|
+
".mcp.json",
|
|
46
|
+
"src/vendor/spec-core/LICENSE"
|
|
47
|
+
],
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsc -p tsconfig.build.json",
|
|
53
|
+
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
54
|
+
"lint": "tsc -p tsconfig.json --noEmit",
|
|
55
|
+
"test": "vitest run",
|
|
56
|
+
"test:watch": "vitest",
|
|
57
|
+
"test:coverage": "vitest run --coverage",
|
|
58
|
+
"test:mutation": "stryker run stryker.core.config.mjs",
|
|
59
|
+
"test:mutation:full": "stryker run",
|
|
60
|
+
"prepublishOnly": "node -e \"if (process.env.GITHUB_ACTIONS !== 'true') { console.error('npm publish is refused outside GitHub Actions: a release is a v* tag, staged by CI with provenance.'); process.exit(1) }\" && npm run clean && npm run build"
|
|
61
|
+
},
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"@descent-vtt/spec-brief": ">=0.2.0",
|
|
64
|
+
"@descent-vtt/spec-graph": ">=0.9.0",
|
|
65
|
+
"@descent-vtt/spec-guard": ">=0.12.0"
|
|
66
|
+
},
|
|
67
|
+
"peerDependenciesMeta": {
|
|
68
|
+
"@descent-vtt/spec-graph": {
|
|
69
|
+
"optional": true
|
|
70
|
+
},
|
|
71
|
+
"@descent-vtt/spec-guard": {
|
|
72
|
+
"optional": true
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"devDependencies": {
|
|
76
|
+
"@descent-vtt/spec-brief": "^0.2.0",
|
|
77
|
+
"@descent-vtt/spec-guard": "^0.12.0",
|
|
78
|
+
"@stryker-mutator/core": "^10.0.0",
|
|
79
|
+
"@stryker-mutator/vitest-runner": "^10.0.0",
|
|
80
|
+
"@types/node": "^22.20.4",
|
|
81
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
82
|
+
"typescript": "^7.0.2",
|
|
83
|
+
"vitest": "^4.1.11"
|
|
84
|
+
},
|
|
7
85
|
"repository": {
|
|
8
86
|
"type": "git",
|
|
9
87
|
"url": "git+https://github.com/DescentVTT/spec-harness.git"
|
|
10
88
|
},
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
}
|
|
89
|
+
"bugs": {
|
|
90
|
+
"url": "https://github.com/DescentVTT/spec-harness/issues"
|
|
91
|
+
},
|
|
92
|
+
"homepage": "https://github.com/DescentVTT/spec-harness#readme"
|
|
14
93
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: close-round
|
|
3
|
+
description: Check that a round is really done - scope kept, protections held, goals met, the defect's probe green, no unexplained dependency - and hand the archive decision to a person. Use when a brief's work looks finished, before opening or merging its pull request.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Close a round
|
|
7
|
+
|
|
8
|
+
## 1. Audit
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npx spec-harness audit --format json
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
It reports, as one list: what spec-brief's archive would refuse or warn
|
|
15
|
+
about (open boxes, protected files changed, files outside the scope, work not
|
|
16
|
+
committed), the brief's own assertions (a goal that fails; a premise that
|
|
17
|
+
still holds), rulings whose signatures do not verify, and every dependency the
|
|
18
|
+
round added. Fix what it reports, then run it again. `unmeasured` means it
|
|
19
|
+
could not see the round's commits: pass `--base <branch>`.
|
|
20
|
+
|
|
21
|
+
## 2. For a defect, show it is gone
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx spec-harness probe <id> --at head
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`fixed` is the only verdict that closes a defect. Add the evidence table to
|
|
28
|
+
the brief next to the one that measured it.
|
|
29
|
+
|
|
30
|
+
## 3. Hand over
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx spec-brief archive <id> --dry-run --base <branch>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Show the person the audit, the dry run and a short summary of what the round
|
|
37
|
+
did and did not do. Archiving is theirs: they run `spec-brief archive <id>`
|
|
38
|
+
and commit the result. Do not archive a brief yourself.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: draft-brief
|
|
3
|
+
description: Draft the brief for one round of work - the contract an agent works under - from a request, with its scope, its protections and, for a defect, a probe that proves the defect exists. Use when asked to plan, specify or write up a change before implementing it, or when a request is too loose to start on.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Draft a brief
|
|
7
|
+
|
|
8
|
+
A brief is the contract for one round: what done looks like, what the round
|
|
9
|
+
must not do, which files it may write and which it may not touch. You write
|
|
10
|
+
it; `spec-brief` checks it; a person approves it before anyone starts. Never
|
|
11
|
+
start implementing from a brief the person has not approved.
|
|
12
|
+
|
|
13
|
+
## 1. Scaffold from the repository's own rules
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx spec-brief new "<short title>" --type <feature|defect|refactor|chore> --wave <n>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The scaffold holds every section this repository requires, each with a hint
|
|
20
|
+
in a comment saying what it must answer. Those hints are your interview:
|
|
21
|
+
ask the person what you cannot find out yourself, one section at a time.
|
|
22
|
+
Press hardest on the two that keep a round from wandering:
|
|
23
|
+
|
|
24
|
+
- **What the round does not do.** Name the tempting adjacent work and rule it
|
|
25
|
+
out.
|
|
26
|
+
- **What it is not empowered to change.** Public interfaces, schemas, other
|
|
27
|
+
teams' code, the rules files themselves.
|
|
28
|
+
|
|
29
|
+
## 2. Measure the scope, do not guess it
|
|
30
|
+
|
|
31
|
+
- Find the files the change touches: search the code, read `git log` for
|
|
32
|
+
files that change together, and ask spec-guard which rules govern them:
|
|
33
|
+
`npx spec-guard query <path> --json`.
|
|
34
|
+
- Write `affectedFiles` as narrow globs (`src/auth/**`, not `src/**`). A scope
|
|
35
|
+
too wide makes rounds that could run in parallel wait for each other; one
|
|
36
|
+
too narrow stops you mid-round. A new directory ends with `/`.
|
|
37
|
+
- Write `protectedFiles` for what lies inside or beside the scope and must not
|
|
38
|
+
change.
|
|
39
|
+
|
|
40
|
+
## 3. A defect is measured before it is filed
|
|
41
|
+
|
|
42
|
+
For a `defect` brief, write a probe: a test that fails now, for the reason
|
|
43
|
+
the brief states, declared in the brief as a `probe` block (with the test in
|
|
44
|
+
a `probe-file` block if it is new). Give it a `signature` - text the failure
|
|
45
|
+
must contain - or a JUnit `test` name, so a compile error is never taken for
|
|
46
|
+
the defect. Then:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx spec-harness probe <id> --at base
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`measured` means the defect is real; paste the evidence table into the
|
|
53
|
+
brief. `vacuous` means it is not there: say so to the person and stop.
|
|
54
|
+
`invalid` or `flaky` means the probe proves nothing yet: fix the probe.
|
|
55
|
+
|
|
56
|
+
## 4. Check, then hand over
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npx spec-brief lint <id> --format json # until it reports nothing
|
|
60
|
+
npx spec-brief matrix # no collision with its wave
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Show the person the brief, the scope and the protections, and wait for their
|
|
64
|
+
approval. The approval is theirs to give; a clean lint is not one.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: run-round
|
|
3
|
+
description: Carry out one approved brief as a contract - load its context, stay inside its scope, escalate instead of working around a protection, and keep its checklist honest. Use when asked to implement, execute or work on a brief, or when on a branch named like brief/<id>-<topic>.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Run a round
|
|
7
|
+
|
|
8
|
+
## 1. Start from the contract
|
|
9
|
+
|
|
10
|
+
Work on a branch named after the brief, `brief/<id>-<topic>`, or pass
|
|
11
|
+
`--brief <id>`. Then:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx spec-harness context <id>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
It gives you the brief in full, the scope as the guard reads it, the rulings
|
|
18
|
+
already signed, the briefs this one waited on, the rules spec-guard holds the
|
|
19
|
+
scope's code to, and the documents the brief cites. Read it before the code.
|
|
20
|
+
|
|
21
|
+
## 2. Stay inside the lines
|
|
22
|
+
|
|
23
|
+
- Write the files in `affectedFiles`. If the hook is installed, every edit is
|
|
24
|
+
checked; otherwise ask first: `npx spec-harness guard <path>`.
|
|
25
|
+
- A protected file is not yours to change. If the round cannot be finished
|
|
26
|
+
without it, stop and escalate - do not work around it, do not edit through
|
|
27
|
+
a shell to avoid the hook:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx spec-harness escalate --path <file> --reason "<why the round needs it>" \
|
|
31
|
+
--option "<option>: <what it costs>" --option "<other option>: <cost>" \
|
|
32
|
+
--recommend "<which, and why>"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Then wait. A person rules, and signs the ruling; only a signed ruling lets
|
|
36
|
+
the guard pass the file.
|
|
37
|
+
- A file outside the scope that the round genuinely needs: say so in the
|
|
38
|
+
brief and tell the person. The archive reports every such file.
|
|
39
|
+
- Adding a dependency is a decision: name it in the brief and say why.
|
|
40
|
+
|
|
41
|
+
## 3. Keep the checklist true
|
|
42
|
+
|
|
43
|
+
Tick a box when its work is done and verified, not before. A box you will
|
|
44
|
+
not do gets a note under it that starts with a disposition the repository
|
|
45
|
+
accepts - `**Delegated`, `**Accepted debt`, `**Rejected` by default - and
|
|
46
|
+
says why. Commit as you go; the audit measures commits.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: split-goal
|
|
3
|
+
description: Split a goal too large for one round into briefs - each a small, separately reviewable round - ordered by dependency and grouped into waves that provably cannot write the same file. Use when asked to plan a multi-step feature, an epic, or work for several agents in parallel.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Split a goal into rounds
|
|
7
|
+
|
|
8
|
+
You decide what the pieces are; the tools decide whether the plan holds.
|
|
9
|
+
|
|
10
|
+
## 1. Cut along files and decisions
|
|
11
|
+
|
|
12
|
+
- One brief, one outcome a reviewer can judge on its own. Prefer rounds that
|
|
13
|
+
touch few files; a round whose scope is `src/**` collides with everything.
|
|
14
|
+
- What must exist before something else can start is a `dependsOn`.
|
|
15
|
+
- Split off what can wait - scaling, polish, the second backend - as its own
|
|
16
|
+
brief, and give each deferral an **observable trigger** in its text ("when
|
|
17
|
+
the second tenant signs", "when p95 exceeds 200 ms"), never a date.
|
|
18
|
+
- Write each piece as a draft with the `draft-brief` steps. Drafts need not be
|
|
19
|
+
complete to be scheduled; their scopes must be.
|
|
20
|
+
|
|
21
|
+
## 2. Let the tools place the waves
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx spec-brief schedule --format json # if this version has it
|
|
25
|
+
npx spec-brief matrix --all-waves # otherwise: which pairs collide
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
A wave may run in parallel only if no two of its briefs can name the same
|
|
29
|
+
file. `schedule` computes waves from `dependsOn` and from collisions; `matrix`
|
|
30
|
+
names a file both briefs would write when two collide. Resolve a collision by
|
|
31
|
+
narrowing a scope, by adding a `dependsOn`, or by moving a brief to a later
|
|
32
|
+
wave - never by leaving it for merge time.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx spec-brief lint --format json # no cycles, waves in dependency order
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 3. Present the plan
|
|
39
|
+
|
|
40
|
+
Give the person the list: each brief's title, wave, dependencies and scope,
|
|
41
|
+
and the deferrals with their triggers. The person approves the plan and each
|
|
42
|
+
brief before its round starts.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 spec-core contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Vendored from spec-core
|
|
2
|
+
|
|
3
|
+
These files are copies of modules from
|
|
4
|
+
[spec-core](https://github.com/DescentVTT/spec-core), byte for byte. Do not edit them here: change
|
|
5
|
+
spec-core, then run `node scripts/vendor.mjs --into <this tool>` there.
|
|
6
|
+
`VENDOR.json` records the commit and the SHA-256 of every file, and this
|
|
7
|
+
tool's tests fail when a file no longer matches its hash. `LICENSE` is
|
|
8
|
+
spec-core's, and ships in this tool's package with the compiled copies.
|