@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.
Files changed (161) hide show
  1. package/.claude-plugin/marketplace.json +16 -0
  2. package/.claude-plugin/plugin.json +17 -0
  3. package/.mcp.json +12 -0
  4. package/CHANGELOG.md +56 -0
  5. package/README.md +127 -9
  6. package/bin/spec-harness.js +19 -0
  7. package/dist/audit.d.ts +69 -0
  8. package/dist/audit.js +93 -0
  9. package/dist/audit.js.map +1 -0
  10. package/dist/branch.d.ts +26 -0
  11. package/dist/branch.js +100 -0
  12. package/dist/branch.js.map +1 -0
  13. package/dist/briefs.d.ts +49 -0
  14. package/dist/briefs.js +111 -0
  15. package/dist/briefs.js.map +1 -0
  16. package/dist/cli.d.ts +14 -0
  17. package/dist/cli.js +245 -0
  18. package/dist/cli.js.map +1 -0
  19. package/dist/commands.d.ts +18 -0
  20. package/dist/commands.js +224 -0
  21. package/dist/commands.js.map +1 -0
  22. package/dist/config.d.ts +57 -0
  23. package/dist/config.js +167 -0
  24. package/dist/config.js.map +1 -0
  25. package/dist/context.d.ts +56 -0
  26. package/dist/context.js +125 -0
  27. package/dist/context.js.map +1 -0
  28. package/dist/document.d.ts +33 -0
  29. package/dist/document.js +9 -0
  30. package/dist/document.js.map +1 -0
  31. package/dist/fs.d.ts +29 -0
  32. package/dist/fs.js +85 -0
  33. package/dist/fs.js.map +1 -0
  34. package/dist/git.d.ts +57 -0
  35. package/dist/git.js +129 -0
  36. package/dist/git.js.map +1 -0
  37. package/dist/guard.d.ts +43 -0
  38. package/dist/guard.js +120 -0
  39. package/dist/guard.js.map +1 -0
  40. package/dist/hooks.d.ts +50 -0
  41. package/dist/hooks.js +109 -0
  42. package/dist/hooks.js.map +1 -0
  43. package/dist/index.d.ts +31 -0
  44. package/dist/index.js +20 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/junit.d.ts +30 -0
  47. package/dist/junit.js +166 -0
  48. package/dist/junit.js.map +1 -0
  49. package/dist/manifests.d.ts +52 -0
  50. package/dist/manifests.js +346 -0
  51. package/dist/manifests.js.map +1 -0
  52. package/dist/plugin.d.ts +45 -0
  53. package/dist/plugin.js +79 -0
  54. package/dist/plugin.js.map +1 -0
  55. package/dist/premises.d.ts +16 -0
  56. package/dist/premises.js +71 -0
  57. package/dist/premises.js.map +1 -0
  58. package/dist/probe.d.ts +108 -0
  59. package/dist/probe.js +203 -0
  60. package/dist/probe.js.map +1 -0
  61. package/dist/reader.d.ts +8 -0
  62. package/dist/reader.js +105 -0
  63. package/dist/reader.js.map +1 -0
  64. package/dist/round.d.ts +68 -0
  65. package/dist/round.js +339 -0
  66. package/dist/round.js.map +1 -0
  67. package/dist/rulings.d.ts +92 -0
  68. package/dist/rulings.js +160 -0
  69. package/dist/rulings.js.map +1 -0
  70. package/dist/sandbox.d.ts +28 -0
  71. package/dist/sandbox.js +110 -0
  72. package/dist/sandbox.js.map +1 -0
  73. package/dist/server.d.ts +13 -0
  74. package/dist/server.js +212 -0
  75. package/dist/server.js.map +1 -0
  76. package/dist/setup.d.ts +32 -0
  77. package/dist/setup.js +218 -0
  78. package/dist/setup.js.map +1 -0
  79. package/dist/siblings.d.ts +66 -0
  80. package/dist/siblings.js +134 -0
  81. package/dist/siblings.js.map +1 -0
  82. package/dist/types.d.ts +38 -0
  83. package/dist/types.js +3 -0
  84. package/dist/types.js.map +1 -0
  85. package/dist/vendor/spec-core/jsonrpc/index.d.ts +2 -0
  86. package/dist/vendor/spec-core/jsonrpc/index.js +2 -0
  87. package/dist/vendor/spec-core/jsonrpc/index.js.map +1 -0
  88. package/dist/vendor/spec-core/jsonrpc/mcp.d.ts +157 -0
  89. package/dist/vendor/spec-core/jsonrpc/mcp.js +353 -0
  90. package/dist/vendor/spec-core/jsonrpc/mcp.js.map +1 -0
  91. package/dist/vendor/spec-core/markdown/frontmatter.d.ts +103 -0
  92. package/dist/vendor/spec-core/markdown/frontmatter.js +459 -0
  93. package/dist/vendor/spec-core/markdown/frontmatter.js.map +1 -0
  94. package/dist/vendor/spec-core/markdown/headings.d.ts +45 -0
  95. package/dist/vendor/spec-core/markdown/headings.js +199 -0
  96. package/dist/vendor/spec-core/markdown/headings.js.map +1 -0
  97. package/dist/vendor/spec-core/markdown/index.d.ts +5 -0
  98. package/dist/vendor/spec-core/markdown/index.js +4 -0
  99. package/dist/vendor/spec-core/markdown/index.js.map +1 -0
  100. package/dist/vendor/spec-core/markdown/layout.d.ts +39 -0
  101. package/dist/vendor/spec-core/markdown/layout.js +28 -0
  102. package/dist/vendor/spec-core/markdown/layout.js.map +1 -0
  103. package/dist/vendor/spec-core/markdown/links.d.ts +22 -0
  104. package/dist/vendor/spec-core/markdown/links.js +364 -0
  105. package/dist/vendor/spec-core/markdown/links.js.map +1 -0
  106. package/dist/vendor/spec-core/markdown/lists.d.ts +22 -0
  107. package/dist/vendor/spec-core/markdown/lists.js +127 -0
  108. package/dist/vendor/spec-core/markdown/lists.js.map +1 -0
  109. package/dist/vendor/spec-core/markdown/scan.d.ts +36 -0
  110. package/dist/vendor/spec-core/markdown/scan.js +439 -0
  111. package/dist/vendor/spec-core/markdown/scan.js.map +1 -0
  112. package/dist/vendor/spec-core/markdown/syntax.d.ts +75 -0
  113. package/dist/vendor/spec-core/markdown/syntax.js +181 -0
  114. package/dist/vendor/spec-core/markdown/syntax.js.map +1 -0
  115. package/dist/vendor/spec-core/markdown/tables.d.ts +18 -0
  116. package/dist/vendor/spec-core/markdown/tables.js +86 -0
  117. package/dist/vendor/spec-core/markdown/tables.js.map +1 -0
  118. package/dist/vendor/spec-core/markdown/types.d.ts +287 -0
  119. package/dist/vendor/spec-core/markdown/types.js +9 -0
  120. package/dist/vendor/spec-core/markdown/types.js.map +1 -0
  121. package/dist/vendor/spec-core/path/index.d.ts +1 -0
  122. package/dist/vendor/spec-core/path/index.js +2 -0
  123. package/dist/vendor/spec-core/path/index.js.map +1 -0
  124. package/dist/vendor/spec-core/path/posix.d.ts +83 -0
  125. package/dist/vendor/spec-core/path/posix.js +150 -0
  126. package/dist/vendor/spec-core/path/posix.js.map +1 -0
  127. package/dist/vendor/spec-core/pattern/automaton.d.ts +129 -0
  128. package/dist/vendor/spec-core/pattern/automaton.js +340 -0
  129. package/dist/vendor/spec-core/pattern/automaton.js.map +1 -0
  130. package/dist/vendor/spec-core/pattern/charset.d.ts +58 -0
  131. package/dist/vendor/spec-core/pattern/charset.js +116 -0
  132. package/dist/vendor/spec-core/pattern/charset.js.map +1 -0
  133. package/dist/vendor/spec-core/pattern/glob.d.ts +121 -0
  134. package/dist/vendor/spec-core/pattern/glob.js +481 -0
  135. package/dist/vendor/spec-core/pattern/glob.js.map +1 -0
  136. package/dist/vendor/spec-core/pattern/index.d.ts +6 -0
  137. package/dist/vendor/spec-core/pattern/index.js +4 -0
  138. package/dist/vendor/spec-core/pattern/index.js.map +1 -0
  139. package/dist/vendor/spec-core/pattern/regex.d.ts +67 -0
  140. package/dist/vendor/spec-core/pattern/regex.js +813 -0
  141. package/dist/vendor/spec-core/pattern/regex.js.map +1 -0
  142. package/dist/vendor/spec-core/text/index.d.ts +2 -0
  143. package/dist/vendor/spec-core/text/index.js +2 -0
  144. package/dist/vendor/spec-core/text/index.js.map +1 -0
  145. package/dist/vendor/spec-core/text/lines.d.ts +72 -0
  146. package/dist/vendor/spec-core/text/lines.js +156 -0
  147. package/dist/vendor/spec-core/text/lines.js.map +1 -0
  148. package/dist/versions.d.ts +42 -0
  149. package/dist/versions.js +57 -0
  150. package/dist/versions.js.map +1 -0
  151. package/dist/workspace.d.ts +77 -0
  152. package/dist/workspace.js +138 -0
  153. package/dist/workspace.js.map +1 -0
  154. package/hooks/hooks.json +28 -0
  155. package/package.json +85 -6
  156. package/skills/close-round/SKILL.md +38 -0
  157. package/skills/draft-brief/SKILL.md +64 -0
  158. package/skills/run-round/SKILL.md +46 -0
  159. package/skills/split-goal/SKILL.md +42 -0
  160. package/src/vendor/spec-core/LICENSE +21 -0
  161. 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"]}
@@ -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.0.0",
4
- "description": "Placeholder that holds the name until 0.1.0, which CI publishes with provenance. It contains no code.",
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
- "homepage": "https://github.com/DescentVTT/spec-harness",
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
- "publishConfig": {
12
- "access": "public"
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.