@descent-vtt/spec-harness 0.17.0 → 0.19.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/dist/setup.js CHANGED
@@ -11,11 +11,11 @@
11
11
  * that exist and never replaces what a person wrote; a git hook the
12
12
  * repository already has is left alone, with the line to add printed instead.
13
13
  */
14
- import { existsSync, statSync } from 'node:fs';
14
+ import { accessSync, constants, existsSync, statSync } from 'node:fs';
15
15
  import { chmod, mkdir, readFile } from 'node:fs/promises';
16
16
  import { dirname, isAbsolute, join, relative, sep } from 'node:path';
17
17
  import { CONFIG_FILE } from './config.js';
18
- import { chooseBase, describeAbsent, describeGitHook, describeGraph, describeHooksOff, describePresence, describeSkipped, GIT_HOOK_LINE, gitHookNote, graphReadsBriefs, GUARD_HOOK, guardSwitchedOff, isStarted, loadsPlugin, measuresArchive, mergeClaudeSettings, mergeMcp, mergeSpecBrief, mergeSpecGraph, missingGitHook, patternReader, PLUGIN, PRE_COMMIT, PROJECT_SETTINGS, PROTECT_RULE_FILES, registersServer, SPEC_BRIEF_CONFIGS, SPEC_GRAPH_CONFIGS, } from './configure.js';
18
+ import { arrayMatcher, chooseBase, describeAbsent, describeArrayMatcher, describeGitHook, describeGraph, describeHooksOff, describePresence, describeSkipped, GIT_HOOK_LINE, gitHookNote, graphReadsBriefs, GUARD_HOOK, guardSwitchedOff, isStarted, loadsPlugin, matchingUnder, measuresArchive, mergeClaudeSettings, withSessionStart, mergeMcp, mergeSpecBrief, mergeSpecGraph, missingGitHook, ONE_STRING, patternReader, PLUGIN, PRE_COMMIT, PROJECT_SETTINGS, PROTECT_RULE_FILES, registersServer, SPEC_BRIEF_CONFIGS, SPEC_GRAPH_CONFIGS, } from './configure.js';
19
19
  import { readJsonObject as readJson, writeAtomic } from './fs.js';
20
20
  import { hookPath, localBranches, remoteDefault } from './git.js';
21
21
  import { claudeCodeWiring, pluginFile } from './round.js';
@@ -39,27 +39,72 @@ function shown(root, path) {
39
39
  const inside = relative(root, path);
40
40
  return inside.startsWith('..') || isAbsolute(inside) ? path : inside.split(sep).join('/');
41
41
  }
42
+ /**
43
+ * Whether git takes what stands at a hook's path for a hook to run. git
44
+ * asks the system whether the path can be executed, and skips one that
45
+ * cannot, with no more than a hint: asked here the same way. On Windows the
46
+ * question is only whether the path is there (Node's fs page, File access
47
+ * constants, read 2026-10-14), and git there runs whatever is.
48
+ */
49
+ function executable(path) {
50
+ let may = true;
51
+ try {
52
+ accessSync(path, constants.X_OK);
53
+ }
54
+ catch {
55
+ may = false;
56
+ }
57
+ return may;
58
+ }
59
+ /** What stands at a path, links followed, or `null` where nothing does: a link to nothing is nothing, to git as well. */
60
+ function standing(path) {
61
+ try {
62
+ return statSync(path);
63
+ }
64
+ catch {
65
+ return null;
66
+ }
67
+ }
42
68
  /**
43
69
  * git's pre-commit hook, where git runs it from, and what is there: init and
44
70
  * doctor both read it. `path` is `null` when git cannot say where its hooks
45
- * are. `note` is what there is to say of how a hook that runs the harness
46
- * runs it, or `null`.
71
+ * are, which is said in git's own words: a `core.hooksPath` that names a
72
+ * file is refused by git as no directory, and git then commits with no hook
73
+ * (ADR-0005). `note` is what there is to say of how a hook that runs the
74
+ * harness runs it, or `null`.
75
+ *
76
+ * Only a file is read. A directory under the hook's name, which a
77
+ * repository whose `core.hooksPath` is in its work tree can hold, is not a
78
+ * hook and is one to git all the same: it tries to run what it can execute
79
+ * there, fails, and stops the commit (ADR-0005 has what was measured). Read
80
+ * as a file, it ended this on Node's EISDIR and a stack, and a named pipe
81
+ * would have kept it from ever coming back. A file the account may not read
82
+ * ended it on EACCES and a stack: it is said with what git does with it.
47
83
  */
48
84
  export async function gitHook(root) {
49
- const path = await hookPath('pre-commit', root);
50
- if (path === null)
51
- return { hook: { state: 'unknown', reason: 'git does not say where they are; spec-harness needs git 2.31 or later' }, path, note: null };
85
+ const where = await hookPath('pre-commit', root);
86
+ if ('refused' in where)
87
+ return { hook: { state: 'unknown', reason: `git rev-parse --git-path hooks/pre-commit failed: ${where.refused}` }, path: null, note: null };
88
+ const { path } = where;
52
89
  const file = shown(root, path);
53
- if (!existsSync(path))
90
+ const there = standing(path);
91
+ if (there === null)
54
92
  return { hook: { state: 'absent', file }, path, note: null };
55
- const text = await readFile(path, 'utf8');
93
+ const runs = executable(path);
94
+ if (!there.isFile()) {
95
+ const what = there.isDirectory() ? 'a directory' : 'neither a file nor a directory';
96
+ return { hook: { state: runs ? 'stops' : 'ignored', file, what }, path, note: null };
97
+ }
98
+ let text;
99
+ try {
100
+ text = await readFile(path, 'utf8');
101
+ }
102
+ catch (error) {
103
+ return { hook: { state: 'unreadable', file, runs, reason: error.message }, path, note: null };
104
+ }
56
105
  if (!text.includes('spec-harness'))
57
106
  return { hook: { state: 'other', file }, path, note: null };
58
- // git skips a hook it cannot execute, and says so only in a hint; Windows
59
- // has no executable bit, and runs it.
60
- // The Windows job holds the platform check; the sweep runs on Linux, where it is always true.
61
- const inert = process.platform !== 'win32' && (statSync(path).mode & 0o111) === 0;
62
- return { hook: { state: inert ? 'inert' : 'runs', file }, path, note: gitHookNote(text) };
107
+ return { hook: { state: runs ? 'runs' : 'inert', file }, path, note: gitHookNote(text) };
63
108
  }
64
109
  export async function plan(workspace, options, io) {
65
110
  const { root } = workspace;
@@ -77,11 +122,12 @@ export async function plan(workspace, options, io) {
77
122
  // processes side by side where each waited on the one before. Each is
78
123
  // read below where it was asked before, so a plan that fails still fails
79
124
  // on the first of them in that order. The matchers in the settings are
80
- // read with one reader, for the wiring and for what to write.
125
+ // read with one reader, for the wiring and for what to write. Claude
126
+ // Code's release is asked only where what is read of a line turns on it.
81
127
  const pattern = patternReader();
82
128
  const [factsAsked, wiringAsked, hookAsked] = await Promise.allSettled([
83
129
  namedBase === null ? Promise.all([remoteDefault(root), localBranches(root)]) : null,
84
- claudeCodeWiring(root, io, pattern),
130
+ claudeCodeWiring(root, io, pattern, false),
85
131
  gitHook(root),
86
132
  ]);
87
133
  const facts = answerOf(factsAsked);
@@ -212,6 +258,8 @@ export async function plan(workspace, options, io) {
212
258
  const absent = wiring.presence?.state === 'absent' ? wiring.presence.detail : null;
213
259
  const settingsFile = join(root, '.claude', 'settings.json');
214
260
  const settings = await readJson(settingsFile);
261
+ // An array for a matcher, under which Claude Code loads no hook of the file.
262
+ const array = arrayMatcher(settings);
215
263
  const lines = wiring.lines.filter((line) => line.file === SETTINGS);
216
264
  // What doctor says of a line this file holds is said here too, with why
217
265
  // init leaves the line: one a person changed is theirs, and a second line
@@ -237,21 +285,36 @@ export async function plan(workspace, options, io) {
237
285
  else if (settings === 'unreadable') {
238
286
  steps.push({ file: SETTINGS, action: 'advise', detail: `cannot be read as JSON; add the guard as a PreToolUse and a PostToolUse hook by hand: ${JSON.stringify(GUARD_HOOK)}` });
239
287
  }
288
+ else if (array !== null) {
289
+ // A hook written into such a file would be one more that is not loaded:
290
+ // the matcher is the person's to mend, and init is run again after.
291
+ steps.push({ file: SETTINGS, action: 'advise', detail: `${describeArrayMatcher(SETTINGS, array)}, those init writes among them, so it writes none: ${ONE_STRING}, and run init again` });
292
+ }
240
293
  else {
241
294
  // An entry is a line of the hook where doctor reports its words as one in this file.
242
295
  const reported = new Set(lines.map((line) => JSON.stringify(line.line)));
243
- const merged = mergeClaudeSettings(settings ?? {}, (words) => reported.has(JSON.stringify(words)), pattern);
296
+ const isHook = (words) => reported.has(JSON.stringify(words));
297
+ const guarded = mergeClaudeSettings(settings ?? {}, isHook, pattern, matchingUnder(wiring.release));
298
+ // The check as a session starts, beside init's two where they stand as
299
+ // init writes them and doctor has nothing more to say of a line: with
300
+ // them, or by itself in a file whose two an earlier release wrote. A file
301
+ // with a line of the person's is theirs to complete (ADR-0012).
302
+ const started = left === '' ? withSessionStart(guarded ?? settings ?? {}, isHook) : null;
303
+ const merged = started ?? guarded;
244
304
  // A line that turns the guard off or holds every write is one doctor
245
305
  // fails, and lines of which none is waited on before a write are no
246
306
  // guard between them: left as they are, they are for the person to mend.
247
307
  const broken = lines.some((line) => line.state === 'off' || line.state === 'held') || !lines.some((line) => isStarted(line.before));
308
+ const atStart = 'a SessionStart hook says, as a session starts, what would hold every write of it';
248
309
  if (merged === null)
249
310
  steps.push({ file: SETTINGS, action: broken ? 'advise' : 'keep', detail: left === '' ? 'the guard hooks are installed' : left });
250
311
  else
251
312
  steps.push({
252
313
  file: SETTINGS,
253
314
  action: settings === null ? 'create' : 'update',
254
- detail: `the guard hooks, run with node from the project's install: a PreToolUse hook refuses writes to protected files; a PostToolUse hook warns about writes outside the scope${left === '' ? '' : `; ${left}`}`,
315
+ detail: guarded === null
316
+ ? `beside the guard hooks, ${atStart}`
317
+ : `the guard hooks, run with node from the project's install: a PreToolUse hook refuses writes to protected files; a PostToolUse hook warns about writes outside the scope${started === null ? '' : `; ${atStart}`}${left === '' ? '' : `; ${left}`}`,
255
318
  apply: () => writeAtomic(settingsFile, stringify(merged)),
256
319
  });
257
320
  }
@@ -289,10 +352,9 @@ export async function plan(workspace, options, io) {
289
352
  // hooks never see, one through a shell, so it is advised when not asked
290
353
  // for; it is configuration outside the tree, so it is written only then.
291
354
  const { hook, path: hookFile, note: hookNote } = answerOf(hookAsked);
292
- // Only a git older than 2.31, which the README states as the harness's
293
- // floor, cannot name its hooks, and init runs only in a work tree, where
294
- // any later git names them: no test reaches this advice, by that decision.
295
- if (hook.state === 'unknown' || hookFile === null) {
355
+ // git cannot name its hook where core.hooksPath names a file, and says so,
356
+ // which is said here as doctor says it; git then commits with no hook.
357
+ if (hook.state === 'unknown') {
296
358
  steps.push({ file: 'pre-commit', action: 'advise', detail: describeGitHook(hook) });
297
359
  }
298
360
  else if (hook.state === 'runs') {
@@ -300,21 +362,25 @@ export async function plan(workspace, options, io) {
300
362
  // with the line that runs it as init writes the hook now.
301
363
  steps.push({ file: hook.file, action: 'keep', detail: hookNote === null ? 'already runs spec-harness' : `already runs spec-harness; ${hookNote}` });
302
364
  }
303
- else if (hook.state === 'inert') {
365
+ else if (hook.state === 'inert' || hook.state === 'stops' || hook.state === 'ignored' || hook.state === 'unreadable') {
366
+ // What is there is the person's to mend or to take away: nothing is
367
+ // written over it, and what doctor says of it is said.
304
368
  steps.push({ file: hook.file, action: 'advise', detail: describeGitHook(hook) });
305
369
  }
306
370
  else if (hook.state === 'other') {
307
371
  steps.push({ file: hook.file, action: 'advise', detail: `a pre-commit hook exists; add the line "${GIT_HOOK_LINE}" to it` });
308
372
  }
309
373
  else if (options.gitHook) {
374
+ // A path is named for every state but the one asked about first.
375
+ const target = hookFile;
310
376
  steps.push({
311
377
  file: hook.file,
312
378
  action: 'create',
313
379
  detail: 'refuse a commit that changes what the active brief protects',
314
380
  apply: async () => {
315
- await mkdir(dirname(hookFile), { recursive: true });
316
- await writeAtomic(hookFile, PRE_COMMIT);
317
- await chmod(hookFile, 0o755);
381
+ await mkdir(dirname(target), { recursive: true });
382
+ await writeAtomic(target, PRE_COMMIT);
383
+ await chmod(target, 0o755);
318
384
  },
319
385
  });
320
386
  }
package/dist/setup.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `spec-harness init`: the family configured to agree, from the first day.\n *\n * The integration faults found when the tools were first run together were all\n * configuration: spec-graph read an archived brief as a retired decision and\n * reported every brief that depended on one; spec-guard executed an archived\n * brief's assertions. Each tool can be told the truth; `init` tells each of\n * them the same truth, derived from spec-brief's own settings.\n *\n * It prints a plan and changes nothing until `--write`. It merges into files\n * that exist and never replaces what a person wrote; a git hook the\n * repository already has is left alone, with the line to add printed instead.\n */\n\nimport { existsSync, statSync } from 'node:fs';\nimport { chmod, mkdir, readFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, join, relative, sep } from 'node:path';\n\nimport { CONFIG_FILE } from './config.js';\nimport {\n chooseBase,\n describeAbsent,\n describeGitHook,\n describeGraph,\n describeHooksOff,\n describePresence,\n describeSkipped,\n GIT_HOOK_LINE,\n gitHookNote,\n type GitHookState,\n graphReadsBriefs,\n GUARD_HOOK,\n guardSwitchedOff,\n isStarted,\n loadsPlugin,\n measuresArchive,\n mergeClaudeSettings,\n mergeMcp,\n mergeSpecBrief,\n mergeSpecGraph,\n missingGitHook,\n patternReader,\n PLUGIN,\n PRE_COMMIT,\n PROJECT_SETTINGS,\n PROTECT_RULE_FILES,\n registersServer,\n SPEC_BRIEF_CONFIGS,\n SPEC_GRAPH_CONFIGS,\n} from './configure.js';\nimport { readJsonObject as readJson, writeAtomic } from './fs.js';\nimport { hookPath, localBranches, remoteDefault } from './git.js';\nimport { claudeCodeWiring, pluginFile } from './round.js';\nimport { runSibling } from './siblings.js';\nimport { answerOf, EXIT_ERROR, EXIT_OK, json, openWorkspace, type CliIO, type Options, type Workspace } from './workspace.js';\n\n/** `skip`: a file init would write, left alone because Claude Code's plugin brings what init would add. */\nexport type Action = 'create' | 'update' | 'keep' | 'skip' | 'run' | 'advise';\n\nexport interface Step {\n readonly file: string;\n readonly action: Action;\n readonly detail: string;\n apply?: () => Promise<void>;\n}\n\ntype Json = Record<string, unknown>;\n\n/** The project's settings file for Claude Code, as a person finds it and as doctor names it. */\nconst SETTINGS = PROJECT_SETTINGS;\n\nfunction stringify(value: unknown): string {\n return `${JSON.stringify(value, null, 2)}\\n`;\n}\n\nconst PLUGIN_DETAIL = `load spec-harness's plugin, \"${PLUGIN}\": spec-brief's archive asks it whether a signed ruling allows a protected file, and refuses the file without it`;\n\n/** What init adds to spec-brief's configuration, said once for the plan. */\nfunction specBriefDetail(plugin: boolean, base: string | null): string {\n const parts = plugin ? [PLUGIN_DETAIL] : [];\n if (base !== null) parts.push(`measure the archive from ${base}, as spec-harness does: without a base, spec-brief's archive warns that the scope went unmeasured and checks no protected file`);\n return parts.join('; ');\n}\n\n/** A path as a person finds it: from the root when it is inside it, POSIX-separated, and as it is otherwise. */\nfunction shown(root: string, path: string): string {\n const inside = relative(root, path);\n return inside.startsWith('..') || isAbsolute(inside) ? path : inside.split(sep).join('/');\n}\n\n/**\n * git's pre-commit hook, where git runs it from, and what is there: init and\n * doctor both read it. `path` is `null` when git cannot say where its hooks\n * are. `note` is what there is to say of how a hook that runs the harness\n * runs it, or `null`.\n */\nexport async function gitHook(root: string): Promise<{ readonly hook: GitHookState; readonly path: string | null; readonly note: string | null }> {\n const path = await hookPath('pre-commit', root);\n if (path === null) return { hook: { state: 'unknown', reason: 'git does not say where they are; spec-harness needs git 2.31 or later' }, path, note: null };\n const file = shown(root, path);\n if (!existsSync(path)) return { hook: { state: 'absent', file }, path, note: null };\n const text = await readFile(path, 'utf8');\n if (!text.includes('spec-harness')) return { hook: { state: 'other', file }, path, note: null };\n // git skips a hook it cannot execute, and says so only in a hint; Windows\n // has no executable bit, and runs it.\n // The Windows job holds the platform check; the sweep runs on Linux, where it is always true.\n const inert = process.platform !== 'win32' && (statSync(path).mode & 0o111) === 0;\n return { hook: { state: inert ? 'inert' : 'runs', file }, path, note: gitHookNote(text) };\n}\n\nexport async function plan(workspace: Workspace, options: Options, io: CliIO): Promise<Step[]> {\n const { root } = workspace;\n const steps: Step[] = [];\n\n // The base rounds merge into, decided once: spec-harness verifies rulings\n // against the allowed signers on it, and spec-brief's archive measures the\n // round from it. A base a person wrote, in either file, is kept.\n // openWorkspace loaded this file and refused one that is not a JSON object,\n // so here it is one, or there is none.\n const harnessFile = join(root, CONFIG_FILE);\n const harness = (await readJson(harnessFile)) as Json | null;\n const namedBase = harness !== null && typeof harness['base'] === 'string' ? harness['base'] : null;\n // What the plan asks of git and of Claude Code's settings waits on the\n // root and on nothing of the rest, so it is asked at once: three of git's\n // processes side by side where each waited on the one before. Each is\n // read below where it was asked before, so a plan that fails still fails\n // on the first of them in that order. The matchers in the settings are\n // read with one reader, for the wiring and for what to write.\n const pattern = patternReader();\n const [factsAsked, wiringAsked, hookAsked] = await Promise.allSettled([\n namedBase === null ? Promise.all([remoteDefault(root), localBranches(root)]) : null,\n claudeCodeWiring(root, io, pattern),\n gitHook(root),\n ]);\n const facts = answerOf(factsAsked);\n const choice = facts === null ? null : chooseBase({ remoteDefault: facts[0], branch: workspace.branch, branches: facts[1] });\n const base = choice === null ? namedBase : choice.base;\n\n // spec-brief first: every other setting is derived from its directories.\n const briefConfig = SPEC_BRIEF_CONFIGS.find((name) => existsSync(join(root, name)));\n const brief = workspace.siblings.locate('spec-brief');\n let briefs = 'briefs';\n let archive = 'briefs/archive';\n if (briefConfig === undefined) {\n if (brief.kind === 'found') {\n const command = brief.command;\n const created = join(root, '.spec-brief.json');\n steps.push({\n file: '.spec-brief.json',\n action: 'run',\n detail: 'spec-brief init: the brief and archive directories, every default spelled out',\n apply: async () => {\n const run = await runSibling(command, ['init'], root);\n if (run.code !== 0) throw new Error(`spec-brief init failed: ${run.stderr.trim()}`);\n },\n });\n steps.push({\n file: '.spec-brief.json',\n action: 'update',\n detail: specBriefDetail(true, base),\n apply: async () => {\n const written = await readJson(created);\n if (written === null || written === 'unreadable') throw new Error('spec-brief init wrote no configuration JSON can read');\n const merged = mergeSpecBrief(written, base, pluginFile(root));\n if (merged !== null) await writeAtomic(created, stringify(merged));\n },\n });\n } else {\n steps.push({ file: '.spec-brief.json', action: 'advise', detail: brief.reason });\n }\n } else {\n const file = join(root, briefConfig);\n const config = await readJson(file);\n // The file was found a moment ago, so null is one removed in between; and\n // a string has neither key, so these checks are there for the type.\n if (config !== null && config !== 'unreadable') {\n if (typeof config['briefs'] === 'string') briefs = config['briefs'];\n archive = typeof config['archive'] === 'string' ? config['archive'] : `${briefs}/archive`;\n }\n steps.push({ file: briefConfig, action: 'keep', detail: `briefs in ${briefs}/, the archive in ${archive}/` });\n // spec-brief's archive learns that a signed ruling allows a protected file\n // only from this package's plugin (ADR-0006).\n if (config === 'unreadable' || config === null) {\n const measure = base === null ? '' : `, and \"archiving\": { \"base\": \"${base}\" }`;\n steps.push({ file: briefConfig, action: 'advise', detail: `cannot be read as JSON; add \"plugins\": [\"${PLUGIN}\"]${measure} by hand` });\n } else {\n const isPluginFile = pluginFile(root);\n const merged = mergeSpecBrief(config, base, isPluginFile);\n const measured = measuresArchive(config);\n if (merged === null) {\n const archiving = config['archiving'];\n const from = measured ? `, and the archive is measured from ${String((archiving as Json)['base'])}` : '';\n steps.push({ file: briefConfig, action: 'keep', detail: `spec-harness's plugin is loaded${from}` });\n } else {\n const detail = specBriefDetail(!loadsPlugin(config, isPluginFile), measured ? null : base);\n steps.push({ file: briefConfig, action: 'update', detail, apply: () => writeAtomic(file, stringify(merged)) });\n }\n }\n }\n\n // spec-graph: an archived brief is a record, not a retired decision. Its\n // configuration is the first of its files, then a key in package.json; a\n // file written beside that key would shadow it.\n const graph = workspace.siblings.locate('spec-graph');\n if (graph.kind === 'outdated') steps.push({ file: '.spec-graph.json', action: 'advise', detail: graph.reason });\n if (graph.kind === 'found') {\n const archiveGlob = `${archive}/**`;\n const graphName = SPEC_GRAPH_CONFIGS.find((name) => existsSync(join(root, name)));\n const manifest = graphName === undefined ? await readJson(join(root, 'package.json')) : null;\n if (manifest !== null && manifest !== 'unreadable' && 'spec-graph' in manifest) {\n steps.push({ file: 'package.json', action: 'advise', detail: `spec-graph reads its configuration from the \"spec-graph\" key here, which init does not edit: add \"${archiveGlob}\" to its \"historyPatterns\" by hand` });\n } else {\n const file = graphName ?? '.spec-graph.json';\n const current = await readJson(join(root, file));\n if (current === 'unreadable') {\n steps.push({ file, action: 'advise', detail: `cannot be read as JSON; add \"historyPatterns\": [\"${archiveGlob}\"] by hand` });\n } else {\n const merged = mergeSpecGraph(current ?? {}, archiveGlob);\n const detail = describeGraph(archiveGlob, briefs, graphReadsBriefs(current ?? {}, briefs), merged !== null);\n if (merged === null) steps.push({ file, action: 'keep', detail });\n else steps.push({ file, action: current === null ? 'create' : 'update', detail, apply: () => writeAtomic(join(root, file), stringify(merged)) });\n }\n }\n }\n\n // spec-harness's own file, naming the base the rounds merge into: every\n // ruling is verified against the allowed signers on it. No choice was made\n // only where the file names a base.\n if (choice === null) {\n steps.push({ file: CONFIG_FILE, action: 'keep', detail: `rounds are measured from ${namedBase}` });\n } else if (choice.base === null) {\n steps.push({ file: CONFIG_FILE, action: 'advise', detail: choice.detail });\n } else {\n const content: Json = { ...harness, base: choice.base };\n steps.push({ file: CONFIG_FILE, action: harness === null ? 'create' : 'update', detail: choice.detail, apply: () => writeAtomic(harnessFile, stringify(content)) });\n }\n\n // The agent's hooks and server, unless the plugin brings them: Claude Code\n // runs a plugin's hook beside the same hook in settings, so both would\n // guard every write twice (ADR-0012). The hook's lines are known as doctor\n // knows them, by the hook's own reading and by the writes Claude Code\n // starts each for, so that the two say the same of every file (ADR-0005).\n const wiring = answerOf(wiringAsked);\n const { plugin } = wiring;\n // A plugin that is not on this machine brings neither. init writes none\n // on that reading alone, which a plugin that is there after all would\n // make a second guard: it says what doctor says, and how to have the\n // entries here instead.\n const absent = wiring.presence?.state === 'absent' ? wiring.presence.detail : null;\n const settingsFile = join(root, '.claude', 'settings.json');\n const settings = await readJson(settingsFile);\n const lines = wiring.lines.filter((line) => line.file === SETTINGS);\n // What doctor says of a line this file holds is said here too, with why\n // init leaves the line: one a person changed is theirs, and a second line\n // beside one that is started would run for every write as well.\n const said = lines.flatMap((line) => [line.detail, line.cover]).filter((note): note is string => note !== null);\n const left = said.length === 0 ? '' : `${said.join('; ')}; init rewrites no line of the hook it did not write, and writes none beside one Claude Code starts for a write`;\n if (plugin !== null) {\n // Doubled in this file or in one of the person's own, it is the double\n // install doctor fails: said here too, though init writes neither file.\n // This file's own entry is said first, so the others are named only\n // where it holds none.\n const present = wiring.hooks.includes(SETTINGS);\n if (absent !== null) steps.push({ file: SETTINGS, action: 'advise', detail: describeAbsent(plugin, absent, 'hooks', present) });\n else {\n // A plugin that cannot be told to be on this machine is no reason to\n // write either: said beside the reason, as doctor says it.\n const untold = describePresence(wiring);\n const detail = describeSkipped(plugin, 'hooks', present, wiring.hooks);\n steps.push({ file: SETTINGS, action: wiring.hooks.length > 0 ? 'advise' : 'skip', detail: untold === null ? detail : `${detail}. ${untold}` });\n }\n } else if (settings === 'unreadable') {\n steps.push({ file: SETTINGS, action: 'advise', detail: `cannot be read as JSON; add the guard as a PreToolUse and a PostToolUse hook by hand: ${JSON.stringify(GUARD_HOOK)}` });\n } else {\n // An entry is a line of the hook where doctor reports its words as one in this file.\n const reported = new Set(lines.map((line) => JSON.stringify(line.line)));\n const merged = mergeClaudeSettings(settings ?? {}, (words) => reported.has(JSON.stringify(words)), pattern);\n // A line that turns the guard off or holds every write is one doctor\n // fails, and lines of which none is waited on before a write are no\n // guard between them: left as they are, they are for the person to mend.\n const broken = lines.some((line) => line.state === 'off' || line.state === 'held') || !lines.some((line) => isStarted(line.before));\n if (merged === null) steps.push({ file: SETTINGS, action: broken ? 'advise' : 'keep', detail: left === '' ? 'the guard hooks are installed' : left });\n else\n steps.push({\n file: SETTINGS,\n action: settings === null ? 'create' : 'update',\n detail: `the guard hooks, run with node from the project's install: a PreToolUse hook refuses writes to protected files; a PostToolUse hook warns about writes outside the scope${left === '' ? '' : `; ${left}`}`,\n apply: () => writeAtomic(settingsFile, stringify(merged)),\n });\n }\n // disableAllHooks turns off whatever hooks there are, and those init\n // writes with them: said of the file that holds the key, which is the\n // person's to change. init writes what it would have written.\n if (wiring.hooksOff !== null) steps.push({ file: wiring.hooksOff, action: 'advise', detail: describeHooksOff(wiring.hooksOff, guardSwitchedOff(wiring)) });\n const mcpFile = join(root, '.mcp.json');\n const mcp = await readJson(mcpFile);\n if (plugin !== null) {\n const present = registersServer(mcp);\n if (absent !== null) steps.push({ file: '.mcp.json', action: 'advise', detail: describeAbsent(plugin, absent, 'server', present) });\n else steps.push({ file: '.mcp.json', action: present ? 'advise' : 'skip', detail: describeSkipped(plugin, 'server', present) });\n } else if (mcp === 'unreadable') {\n steps.push({ file: '.mcp.json', action: 'advise', detail: 'cannot be read as JSON; add the spec-harness server by hand' });\n } else {\n const merged = mergeMcp(mcp ?? {});\n if (merged === null) steps.push({ file: '.mcp.json', action: 'keep', detail: 'the spec-harness server is registered' });\n else\n steps.push({\n file: '.mcp.json',\n action: mcp === null ? 'create' : 'update',\n detail: 'the spec-harness MCP server, run with node from the project\\'s install: start_round, check_path, request_escalation, audit_round, list_rounds',\n apply: () => writeAtomic(mcpFile, stringify(merged)),\n });\n }\n\n // git's hook, where git runs it from: core.hooksPath as git reads it, and\n // a linked worktree's shared hooks. It is the guard for a write the agent's\n // hooks never see, one through a shell, so it is advised when not asked\n // for; it is configuration outside the tree, so it is written only then.\n const { hook, path: hookFile, note: hookNote } = answerOf(hookAsked);\n // Only a git older than 2.31, which the README states as the harness's\n // floor, cannot name its hooks, and init runs only in a work tree, where\n // any later git names them: no test reaches this advice, by that decision.\n if (hook.state === 'unknown' || hookFile === null) {\n steps.push({ file: 'pre-commit', action: 'advise', detail: describeGitHook(hook) });\n } else if (hook.state === 'runs') {\n // A hook that runs it through npx is kept, as any hook that exists is,\n // with the line that runs it as init writes the hook now.\n steps.push({ file: hook.file, action: 'keep', detail: hookNote === null ? 'already runs spec-harness' : `already runs spec-harness; ${hookNote}` });\n } else if (hook.state === 'inert') {\n steps.push({ file: hook.file, action: 'advise', detail: describeGitHook(hook) });\n } else if (hook.state === 'other') {\n steps.push({ file: hook.file, action: 'advise', detail: `a pre-commit hook exists; add the line \"${GIT_HOOK_LINE}\" to it` });\n } else if (options.gitHook) {\n steps.push({\n file: hook.file,\n action: 'create',\n detail: 'refuse a commit that changes what the active brief protects',\n apply: async () => {\n await mkdir(dirname(hookFile), { recursive: true });\n await writeAtomic(hookFile, PRE_COMMIT);\n await chmod(hookFile, 0o755);\n },\n });\n } else {\n steps.push({ file: hook.file, action: 'advise', detail: missingGitHook(null) });\n }\n\n // The file the configuration names, which a ruling's signature is checked\n // against on the base branch (ADR-0006).\n steps.push({\n file: workspace.config.rulings.allowedSigners,\n action: 'advise',\n detail: `rulings count only when signed by a key listed here on the base branch: one line per person, <email> namespaces=\"git\" <public key>. Where an agent runs as the person, make it a FIDO2 key, ssh-keygen -t ed25519-sk, whose signature needs a touch no process can supply. ${PROTECT_RULE_FILES}`,\n });\n return steps;\n}\n\nexport async function initCommand(options: Options, io: CliIO): Promise<number> {\n const workspace = await openWorkspace(options, io);\n const steps = await plan(workspace, options, io);\n if (options.write) {\n for (const step of steps) {\n if (step.apply !== undefined) {\n try {\n await step.apply();\n } catch (error) {\n io.stderr.write(`spec-harness: ${step.file}: ${(error as Error).message}\\n`);\n return EXIT_ERROR;\n }\n }\n }\n }\n if (options.format === 'json') {\n io.stdout.write(json('init', { written: options.write, steps: steps.map(({ file, action, detail }) => ({ file, action, detail })) }));\n } else {\n for (const step of steps) io.stdout.write(`${step.action.padEnd(6)} ${step.file}\\n ${step.detail}\\n`);\n if (!options.write && steps.some((step) => step.apply !== undefined)) io.stdout.write('\\nNothing was changed. Run again with --write to apply the plan.\\n');\n }\n return EXIT_OK;\n}\n"]}
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `spec-harness init`: the family configured to agree, from the first day.\n *\n * The integration faults found when the tools were first run together were all\n * configuration: spec-graph read an archived brief as a retired decision and\n * reported every brief that depended on one; spec-guard executed an archived\n * brief's assertions. Each tool can be told the truth; `init` tells each of\n * them the same truth, derived from spec-brief's own settings.\n *\n * It prints a plan and changes nothing until `--write`. It merges into files\n * that exist and never replaces what a person wrote; a git hook the\n * repository already has is left alone, with the line to add printed instead.\n */\n\nimport { accessSync, constants, existsSync, statSync, type Stats } from 'node:fs';\nimport { chmod, mkdir, readFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, join, relative, sep } from 'node:path';\n\nimport { CONFIG_FILE } from './config.js';\nimport {\n arrayMatcher,\n chooseBase,\n describeAbsent,\n describeArrayMatcher,\n describeGitHook,\n describeGraph,\n describeHooksOff,\n describePresence,\n describeSkipped,\n GIT_HOOK_LINE,\n gitHookNote,\n type GitHookState,\n graphReadsBriefs,\n GUARD_HOOK,\n guardSwitchedOff,\n isStarted,\n loadsPlugin,\n matchingUnder,\n measuresArchive,\n mergeClaudeSettings,\n withSessionStart,\n mergeMcp,\n mergeSpecBrief,\n mergeSpecGraph,\n missingGitHook,\n ONE_STRING,\n patternReader,\n PLUGIN,\n PRE_COMMIT,\n PROJECT_SETTINGS,\n PROTECT_RULE_FILES,\n registersServer,\n SPEC_BRIEF_CONFIGS,\n SPEC_GRAPH_CONFIGS,\n} from './configure.js';\nimport { readJsonObject as readJson, writeAtomic } from './fs.js';\nimport { hookPath, localBranches, remoteDefault } from './git.js';\nimport { claudeCodeWiring, pluginFile } from './round.js';\nimport { runSibling } from './siblings.js';\nimport { answerOf, EXIT_ERROR, EXIT_OK, json, openWorkspace, type CliIO, type Options, type Workspace } from './workspace.js';\n\n/** `skip`: a file init would write, left alone because Claude Code's plugin brings what init would add. */\nexport type Action = 'create' | 'update' | 'keep' | 'skip' | 'run' | 'advise';\n\nexport interface Step {\n readonly file: string;\n readonly action: Action;\n readonly detail: string;\n apply?: () => Promise<void>;\n}\n\ntype Json = Record<string, unknown>;\n\n/** The project's settings file for Claude Code, as a person finds it and as doctor names it. */\nconst SETTINGS = PROJECT_SETTINGS;\n\nfunction stringify(value: unknown): string {\n return `${JSON.stringify(value, null, 2)}\\n`;\n}\n\nconst PLUGIN_DETAIL = `load spec-harness's plugin, \"${PLUGIN}\": spec-brief's archive asks it whether a signed ruling allows a protected file, and refuses the file without it`;\n\n/** What init adds to spec-brief's configuration, said once for the plan. */\nfunction specBriefDetail(plugin: boolean, base: string | null): string {\n const parts = plugin ? [PLUGIN_DETAIL] : [];\n if (base !== null) parts.push(`measure the archive from ${base}, as spec-harness does: without a base, spec-brief's archive warns that the scope went unmeasured and checks no protected file`);\n return parts.join('; ');\n}\n\n/** A path as a person finds it: from the root when it is inside it, POSIX-separated, and as it is otherwise. */\nfunction shown(root: string, path: string): string {\n const inside = relative(root, path);\n return inside.startsWith('..') || isAbsolute(inside) ? path : inside.split(sep).join('/');\n}\n\n/**\n * Whether git takes what stands at a hook's path for a hook to run. git\n * asks the system whether the path can be executed, and skips one that\n * cannot, with no more than a hint: asked here the same way. On Windows the\n * question is only whether the path is there (Node's fs page, File access\n * constants, read 2026-10-14), and git there runs whatever is.\n */\nfunction executable(path: string): boolean {\n let may = true;\n try {\n accessSync(path, constants.X_OK);\n } catch {\n may = false;\n }\n return may;\n}\n\n/** What stands at a path, links followed, or `null` where nothing does: a link to nothing is nothing, to git as well. */\nfunction standing(path: string): Stats | null {\n try {\n return statSync(path);\n } catch {\n return null;\n }\n}\n\n/**\n * git's pre-commit hook, where git runs it from, and what is there: init and\n * doctor both read it. `path` is `null` when git cannot say where its hooks\n * are, which is said in git's own words: a `core.hooksPath` that names a\n * file is refused by git as no directory, and git then commits with no hook\n * (ADR-0005). `note` is what there is to say of how a hook that runs the\n * harness runs it, or `null`.\n *\n * Only a file is read. A directory under the hook's name, which a\n * repository whose `core.hooksPath` is in its work tree can hold, is not a\n * hook and is one to git all the same: it tries to run what it can execute\n * there, fails, and stops the commit (ADR-0005 has what was measured). Read\n * as a file, it ended this on Node's EISDIR and a stack, and a named pipe\n * would have kept it from ever coming back. A file the account may not read\n * ended it on EACCES and a stack: it is said with what git does with it.\n */\nexport async function gitHook(root: string): Promise<{ readonly hook: GitHookState; readonly path: string | null; readonly note: string | null }> {\n const where = await hookPath('pre-commit', root);\n if ('refused' in where) return { hook: { state: 'unknown', reason: `git rev-parse --git-path hooks/pre-commit failed: ${where.refused}` }, path: null, note: null };\n const { path } = where;\n const file = shown(root, path);\n const there = standing(path);\n if (there === null) return { hook: { state: 'absent', file }, path, note: null };\n const runs = executable(path);\n if (!there.isFile()) {\n const what = there.isDirectory() ? 'a directory' : 'neither a file nor a directory';\n return { hook: { state: runs ? 'stops' : 'ignored', file, what }, path, note: null };\n }\n let text: string;\n try {\n text = await readFile(path, 'utf8');\n } catch (error) {\n return { hook: { state: 'unreadable', file, runs, reason: (error as Error).message }, path, note: null };\n }\n if (!text.includes('spec-harness')) return { hook: { state: 'other', file }, path, note: null };\n return { hook: { state: runs ? 'runs' : 'inert', file }, path, note: gitHookNote(text) };\n}\n\nexport async function plan(workspace: Workspace, options: Options, io: CliIO): Promise<Step[]> {\n const { root } = workspace;\n const steps: Step[] = [];\n\n // The base rounds merge into, decided once: spec-harness verifies rulings\n // against the allowed signers on it, and spec-brief's archive measures the\n // round from it. A base a person wrote, in either file, is kept.\n // openWorkspace loaded this file and refused one that is not a JSON object,\n // so here it is one, or there is none.\n const harnessFile = join(root, CONFIG_FILE);\n const harness = (await readJson(harnessFile)) as Json | null;\n const namedBase = harness !== null && typeof harness['base'] === 'string' ? harness['base'] : null;\n // What the plan asks of git and of Claude Code's settings waits on the\n // root and on nothing of the rest, so it is asked at once: three of git's\n // processes side by side where each waited on the one before. Each is\n // read below where it was asked before, so a plan that fails still fails\n // on the first of them in that order. The matchers in the settings are\n // read with one reader, for the wiring and for what to write. Claude\n // Code's release is asked only where what is read of a line turns on it.\n const pattern = patternReader();\n const [factsAsked, wiringAsked, hookAsked] = await Promise.allSettled([\n namedBase === null ? Promise.all([remoteDefault(root), localBranches(root)]) : null,\n claudeCodeWiring(root, io, pattern, false),\n gitHook(root),\n ]);\n const facts = answerOf(factsAsked);\n const choice = facts === null ? null : chooseBase({ remoteDefault: facts[0], branch: workspace.branch, branches: facts[1] });\n const base = choice === null ? namedBase : choice.base;\n\n // spec-brief first: every other setting is derived from its directories.\n const briefConfig = SPEC_BRIEF_CONFIGS.find((name) => existsSync(join(root, name)));\n const brief = workspace.siblings.locate('spec-brief');\n let briefs = 'briefs';\n let archive = 'briefs/archive';\n if (briefConfig === undefined) {\n if (brief.kind === 'found') {\n const command = brief.command;\n const created = join(root, '.spec-brief.json');\n steps.push({\n file: '.spec-brief.json',\n action: 'run',\n detail: 'spec-brief init: the brief and archive directories, every default spelled out',\n apply: async () => {\n const run = await runSibling(command, ['init'], root);\n if (run.code !== 0) throw new Error(`spec-brief init failed: ${run.stderr.trim()}`);\n },\n });\n steps.push({\n file: '.spec-brief.json',\n action: 'update',\n detail: specBriefDetail(true, base),\n apply: async () => {\n const written = await readJson(created);\n if (written === null || written === 'unreadable') throw new Error('spec-brief init wrote no configuration JSON can read');\n const merged = mergeSpecBrief(written, base, pluginFile(root));\n if (merged !== null) await writeAtomic(created, stringify(merged));\n },\n });\n } else {\n steps.push({ file: '.spec-brief.json', action: 'advise', detail: brief.reason });\n }\n } else {\n const file = join(root, briefConfig);\n const config = await readJson(file);\n // The file was found a moment ago, so null is one removed in between; and\n // a string has neither key, so these checks are there for the type.\n if (config !== null && config !== 'unreadable') {\n if (typeof config['briefs'] === 'string') briefs = config['briefs'];\n archive = typeof config['archive'] === 'string' ? config['archive'] : `${briefs}/archive`;\n }\n steps.push({ file: briefConfig, action: 'keep', detail: `briefs in ${briefs}/, the archive in ${archive}/` });\n // spec-brief's archive learns that a signed ruling allows a protected file\n // only from this package's plugin (ADR-0006).\n if (config === 'unreadable' || config === null) {\n const measure = base === null ? '' : `, and \"archiving\": { \"base\": \"${base}\" }`;\n steps.push({ file: briefConfig, action: 'advise', detail: `cannot be read as JSON; add \"plugins\": [\"${PLUGIN}\"]${measure} by hand` });\n } else {\n const isPluginFile = pluginFile(root);\n const merged = mergeSpecBrief(config, base, isPluginFile);\n const measured = measuresArchive(config);\n if (merged === null) {\n const archiving = config['archiving'];\n const from = measured ? `, and the archive is measured from ${String((archiving as Json)['base'])}` : '';\n steps.push({ file: briefConfig, action: 'keep', detail: `spec-harness's plugin is loaded${from}` });\n } else {\n const detail = specBriefDetail(!loadsPlugin(config, isPluginFile), measured ? null : base);\n steps.push({ file: briefConfig, action: 'update', detail, apply: () => writeAtomic(file, stringify(merged)) });\n }\n }\n }\n\n // spec-graph: an archived brief is a record, not a retired decision. Its\n // configuration is the first of its files, then a key in package.json; a\n // file written beside that key would shadow it.\n const graph = workspace.siblings.locate('spec-graph');\n if (graph.kind === 'outdated') steps.push({ file: '.spec-graph.json', action: 'advise', detail: graph.reason });\n if (graph.kind === 'found') {\n const archiveGlob = `${archive}/**`;\n const graphName = SPEC_GRAPH_CONFIGS.find((name) => existsSync(join(root, name)));\n const manifest = graphName === undefined ? await readJson(join(root, 'package.json')) : null;\n if (manifest !== null && manifest !== 'unreadable' && 'spec-graph' in manifest) {\n steps.push({ file: 'package.json', action: 'advise', detail: `spec-graph reads its configuration from the \"spec-graph\" key here, which init does not edit: add \"${archiveGlob}\" to its \"historyPatterns\" by hand` });\n } else {\n const file = graphName ?? '.spec-graph.json';\n const current = await readJson(join(root, file));\n if (current === 'unreadable') {\n steps.push({ file, action: 'advise', detail: `cannot be read as JSON; add \"historyPatterns\": [\"${archiveGlob}\"] by hand` });\n } else {\n const merged = mergeSpecGraph(current ?? {}, archiveGlob);\n const detail = describeGraph(archiveGlob, briefs, graphReadsBriefs(current ?? {}, briefs), merged !== null);\n if (merged === null) steps.push({ file, action: 'keep', detail });\n else steps.push({ file, action: current === null ? 'create' : 'update', detail, apply: () => writeAtomic(join(root, file), stringify(merged)) });\n }\n }\n }\n\n // spec-harness's own file, naming the base the rounds merge into: every\n // ruling is verified against the allowed signers on it. No choice was made\n // only where the file names a base.\n if (choice === null) {\n steps.push({ file: CONFIG_FILE, action: 'keep', detail: `rounds are measured from ${namedBase}` });\n } else if (choice.base === null) {\n steps.push({ file: CONFIG_FILE, action: 'advise', detail: choice.detail });\n } else {\n const content: Json = { ...harness, base: choice.base };\n steps.push({ file: CONFIG_FILE, action: harness === null ? 'create' : 'update', detail: choice.detail, apply: () => writeAtomic(harnessFile, stringify(content)) });\n }\n\n // The agent's hooks and server, unless the plugin brings them: Claude Code\n // runs a plugin's hook beside the same hook in settings, so both would\n // guard every write twice (ADR-0012). The hook's lines are known as doctor\n // knows them, by the hook's own reading and by the writes Claude Code\n // starts each for, so that the two say the same of every file (ADR-0005).\n const wiring = answerOf(wiringAsked);\n const { plugin } = wiring;\n // A plugin that is not on this machine brings neither. init writes none\n // on that reading alone, which a plugin that is there after all would\n // make a second guard: it says what doctor says, and how to have the\n // entries here instead.\n const absent = wiring.presence?.state === 'absent' ? wiring.presence.detail : null;\n const settingsFile = join(root, '.claude', 'settings.json');\n const settings = await readJson(settingsFile);\n // An array for a matcher, under which Claude Code loads no hook of the file.\n const array = arrayMatcher(settings);\n const lines = wiring.lines.filter((line) => line.file === SETTINGS);\n // What doctor says of a line this file holds is said here too, with why\n // init leaves the line: one a person changed is theirs, and a second line\n // beside one that is started would run for every write as well.\n const said = lines.flatMap((line) => [line.detail, line.cover]).filter((note): note is string => note !== null);\n const left = said.length === 0 ? '' : `${said.join('; ')}; init rewrites no line of the hook it did not write, and writes none beside one Claude Code starts for a write`;\n if (plugin !== null) {\n // Doubled in this file or in one of the person's own, it is the double\n // install doctor fails: said here too, though init writes neither file.\n // This file's own entry is said first, so the others are named only\n // where it holds none.\n const present = wiring.hooks.includes(SETTINGS);\n if (absent !== null) steps.push({ file: SETTINGS, action: 'advise', detail: describeAbsent(plugin, absent, 'hooks', present) });\n else {\n // A plugin that cannot be told to be on this machine is no reason to\n // write either: said beside the reason, as doctor says it.\n const untold = describePresence(wiring);\n const detail = describeSkipped(plugin, 'hooks', present, wiring.hooks);\n steps.push({ file: SETTINGS, action: wiring.hooks.length > 0 ? 'advise' : 'skip', detail: untold === null ? detail : `${detail}. ${untold}` });\n }\n } else if (settings === 'unreadable') {\n steps.push({ file: SETTINGS, action: 'advise', detail: `cannot be read as JSON; add the guard as a PreToolUse and a PostToolUse hook by hand: ${JSON.stringify(GUARD_HOOK)}` });\n } else if (array !== null) {\n // A hook written into such a file would be one more that is not loaded:\n // the matcher is the person's to mend, and init is run again after.\n steps.push({ file: SETTINGS, action: 'advise', detail: `${describeArrayMatcher(SETTINGS, array)}, those init writes among them, so it writes none: ${ONE_STRING}, and run init again` });\n } else {\n // An entry is a line of the hook where doctor reports its words as one in this file.\n const reported = new Set(lines.map((line) => JSON.stringify(line.line)));\n const isHook = (words: readonly string[]): boolean => reported.has(JSON.stringify(words));\n const guarded = mergeClaudeSettings(settings ?? {}, isHook, pattern, matchingUnder(wiring.release));\n // The check as a session starts, beside init's two where they stand as\n // init writes them and doctor has nothing more to say of a line: with\n // them, or by itself in a file whose two an earlier release wrote. A file\n // with a line of the person's is theirs to complete (ADR-0012).\n const started = left === '' ? withSessionStart(guarded ?? settings ?? {}, isHook) : null;\n const merged = started ?? guarded;\n // A line that turns the guard off or holds every write is one doctor\n // fails, and lines of which none is waited on before a write are no\n // guard between them: left as they are, they are for the person to mend.\n const broken = lines.some((line) => line.state === 'off' || line.state === 'held') || !lines.some((line) => isStarted(line.before));\n const atStart = 'a SessionStart hook says, as a session starts, what would hold every write of it';\n if (merged === null) steps.push({ file: SETTINGS, action: broken ? 'advise' : 'keep', detail: left === '' ? 'the guard hooks are installed' : left });\n else\n steps.push({\n file: SETTINGS,\n action: settings === null ? 'create' : 'update',\n detail:\n guarded === null\n ? `beside the guard hooks, ${atStart}`\n : `the guard hooks, run with node from the project's install: a PreToolUse hook refuses writes to protected files; a PostToolUse hook warns about writes outside the scope${started === null ? '' : `; ${atStart}`}${left === '' ? '' : `; ${left}`}`,\n apply: () => writeAtomic(settingsFile, stringify(merged)),\n });\n }\n // disableAllHooks turns off whatever hooks there are, and those init\n // writes with them: said of the file that holds the key, which is the\n // person's to change. init writes what it would have written.\n if (wiring.hooksOff !== null) steps.push({ file: wiring.hooksOff, action: 'advise', detail: describeHooksOff(wiring.hooksOff, guardSwitchedOff(wiring)) });\n const mcpFile = join(root, '.mcp.json');\n const mcp = await readJson(mcpFile);\n if (plugin !== null) {\n const present = registersServer(mcp);\n if (absent !== null) steps.push({ file: '.mcp.json', action: 'advise', detail: describeAbsent(plugin, absent, 'server', present) });\n else steps.push({ file: '.mcp.json', action: present ? 'advise' : 'skip', detail: describeSkipped(plugin, 'server', present) });\n } else if (mcp === 'unreadable') {\n steps.push({ file: '.mcp.json', action: 'advise', detail: 'cannot be read as JSON; add the spec-harness server by hand' });\n } else {\n const merged = mergeMcp(mcp ?? {});\n if (merged === null) steps.push({ file: '.mcp.json', action: 'keep', detail: 'the spec-harness server is registered' });\n else\n steps.push({\n file: '.mcp.json',\n action: mcp === null ? 'create' : 'update',\n detail: 'the spec-harness MCP server, run with node from the project\\'s install: start_round, check_path, request_escalation, audit_round, list_rounds',\n apply: () => writeAtomic(mcpFile, stringify(merged)),\n });\n }\n\n // git's hook, where git runs it from: core.hooksPath as git reads it, and\n // a linked worktree's shared hooks. It is the guard for a write the agent's\n // hooks never see, one through a shell, so it is advised when not asked\n // for; it is configuration outside the tree, so it is written only then.\n const { hook, path: hookFile, note: hookNote } = answerOf(hookAsked);\n // git cannot name its hook where core.hooksPath names a file, and says so,\n // which is said here as doctor says it; git then commits with no hook.\n if (hook.state === 'unknown') {\n steps.push({ file: 'pre-commit', action: 'advise', detail: describeGitHook(hook) });\n } else if (hook.state === 'runs') {\n // A hook that runs it through npx is kept, as any hook that exists is,\n // with the line that runs it as init writes the hook now.\n steps.push({ file: hook.file, action: 'keep', detail: hookNote === null ? 'already runs spec-harness' : `already runs spec-harness; ${hookNote}` });\n } else if (hook.state === 'inert' || hook.state === 'stops' || hook.state === 'ignored' || hook.state === 'unreadable') {\n // What is there is the person's to mend or to take away: nothing is\n // written over it, and what doctor says of it is said.\n steps.push({ file: hook.file, action: 'advise', detail: describeGitHook(hook) });\n } else if (hook.state === 'other') {\n steps.push({ file: hook.file, action: 'advise', detail: `a pre-commit hook exists; add the line \"${GIT_HOOK_LINE}\" to it` });\n } else if (options.gitHook) {\n // A path is named for every state but the one asked about first.\n const target = hookFile!;\n steps.push({\n file: hook.file,\n action: 'create',\n detail: 'refuse a commit that changes what the active brief protects',\n apply: async () => {\n await mkdir(dirname(target), { recursive: true });\n await writeAtomic(target, PRE_COMMIT);\n await chmod(target, 0o755);\n },\n });\n } else {\n steps.push({ file: hook.file, action: 'advise', detail: missingGitHook(null) });\n }\n\n // The file the configuration names, which a ruling's signature is checked\n // against on the base branch (ADR-0006).\n steps.push({\n file: workspace.config.rulings.allowedSigners,\n action: 'advise',\n detail: `rulings count only when signed by a key listed here on the base branch: one line per person, <email> namespaces=\"git\" <public key>. Where an agent runs as the person, make it a FIDO2 key, ssh-keygen -t ed25519-sk, whose signature needs a touch no process can supply. ${PROTECT_RULE_FILES}`,\n });\n return steps;\n}\n\nexport async function initCommand(options: Options, io: CliIO): Promise<number> {\n const workspace = await openWorkspace(options, io);\n const steps = await plan(workspace, options, io);\n if (options.write) {\n for (const step of steps) {\n if (step.apply !== undefined) {\n try {\n await step.apply();\n } catch (error) {\n io.stderr.write(`spec-harness: ${step.file}: ${(error as Error).message}\\n`);\n return EXIT_ERROR;\n }\n }\n }\n }\n if (options.format === 'json') {\n io.stdout.write(json('init', { written: options.write, steps: steps.map(({ file, action, detail }) => ({ file, action, detail })) }));\n } else {\n for (const step of steps) io.stdout.write(`${step.action.padEnd(6)} ${step.file}\\n ${step.detail}\\n`);\n if (!options.write && steps.some((step) => step.apply !== undefined)) io.stdout.write('\\nNothing was changed. Run again with --write to apply the plan.\\n');\n }\n return EXIT_OK;\n}\n"]}
@@ -20,11 +20,16 @@ export interface SiblingRun {
20
20
  readonly stderr: string;
21
21
  }
22
22
  export type Sibling =
23
- /** `version` is `null` for a command `.spec-harness.json` names, whose version is not checked. */
23
+ /**
24
+ * `version` is `null` for a command `.spec-harness.json` names, whose
25
+ * version is not checked. `elsewhere` is the directory whose install it
26
+ * is, where that is not the work tree's own.
27
+ */
24
28
  {
25
29
  readonly kind: 'found';
26
30
  readonly command: readonly string[];
27
31
  readonly version: string | null;
32
+ readonly elsewhere?: string;
28
33
  } | {
29
34
  readonly kind: 'absent';
30
35
  readonly reason: string;
@@ -34,9 +39,17 @@ export type Sibling =
34
39
  readonly kind: 'outdated';
35
40
  readonly reason: string;
36
41
  readonly version: string | null;
42
+ readonly elsewhere?: string;
37
43
  };
38
- /** Where a sibling is, or why it cannot be run. */
39
- export declare function locate(name: SiblingName, root: string, config: HarnessConfig): Sibling;
44
+ /**
45
+ * Where a sibling is, or why it cannot be run. It is looked for in `root`'s
46
+ * install, then in each of `installs` in turn, and the first that has it
47
+ * decides, at this release's floor: a work tree with an install of its own
48
+ * runs it, outdated or not, and one with none is checked with the
49
+ * project's, as git's hook and Claude Code's are where a linked worktree has
50
+ * none (ADR-0005).
51
+ */
52
+ export declare function locate(name: SiblingName, root: string, config: HarnessConfig, installs?: readonly string[]): Sibling;
40
53
  /** The `version` a package.json declares, or `undefined` when there is no such file or it is not JSON. */
41
54
  export declare function declaredVersion(file: string): unknown;
42
55
  /**
@@ -66,4 +79,5 @@ export interface Siblings {
66
79
  readonly absent: string;
67
80
  }>;
68
81
  }
69
- export declare function createSiblings(root: string, config: HarnessConfig): Siblings;
82
+ /** The siblings of the work tree at `root`, looked for in its install and then in each of `installs` (locate). */
83
+ export declare function createSiblings(root: string, config: HarnessConfig, installs?: readonly string[]): Siblings;
package/dist/siblings.js CHANGED
@@ -17,25 +17,37 @@ import { existsSync, readFileSync } from 'node:fs';
17
17
  import { join } from 'node:path';
18
18
  import { parseBriefList, SiblingOutputError } from './briefs.js';
19
19
  import { checkVersion } from './versions.js';
20
- /** Where a sibling is, or why it cannot be run. */
21
- export function locate(name, root, config) {
20
+ /**
21
+ * Where a sibling is, or why it cannot be run. It is looked for in `root`'s
22
+ * install, then in each of `installs` in turn, and the first that has it
23
+ * decides, at this release's floor: a work tree with an install of its own
24
+ * runs it, outdated or not, and one with none is checked with the
25
+ * project's, as git's hook and Claude Code's are where a linked worktree has
26
+ * none (ADR-0005).
27
+ */
28
+ // A name a mutant puts on the default list is a directory relative to where
29
+ // the run is, which holds no install: equivalent, here and below.
30
+ export function locate(name, root, config, installs = []) {
22
31
  // A command the configuration names is run as named: which package it runs,
23
32
  // and so which version, is the configuration's to say.
24
33
  const configured = config.tools[name];
25
34
  if (configured !== null)
26
35
  return { kind: 'found', command: configured, version: null };
27
- const directory = join(root, 'node_modules', '@descent-vtt', name);
28
- const bin = join(directory, 'bin', `${name}.js`);
29
- if (!existsSync(bin)) {
30
- return {
31
- kind: 'absent',
32
- reason: `${name} is not installed here: npm install --save-dev @descent-vtt/${name}, or name its command under "tools" in .spec-harness.json`,
33
- };
36
+ for (const place of [root, ...installs]) {
37
+ const directory = join(place, 'node_modules', '@descent-vtt', name);
38
+ const bin = join(directory, 'bin', `${name}.js`);
39
+ if (!existsSync(bin))
40
+ continue;
41
+ const check = checkVersion(name, declaredVersion(join(directory, 'package.json')));
42
+ const where = place === root ? {} : { elsewhere: place };
43
+ if (!check.ok)
44
+ return { kind: 'outdated', reason: check.reason, version: check.version, ...where };
45
+ return { kind: 'found', command: [process.execPath, bin], version: check.version, ...where };
34
46
  }
35
- const check = checkVersion(name, declaredVersion(join(directory, 'package.json')));
36
- if (!check.ok)
37
- return { kind: 'outdated', reason: check.reason, version: check.version };
38
- return { kind: 'found', command: [process.execPath, bin], version: check.version };
47
+ return {
48
+ kind: 'absent',
49
+ reason: `${name} is not installed here: npm install --save-dev @descent-vtt/${name}, or name its command under "tools" in .spec-harness.json`,
50
+ };
39
51
  }
40
52
  /** The `version` a package.json declares, or `undefined` when there is no such file or it is not JSON. */
41
53
  export function declaredVersion(file) {
@@ -104,16 +116,17 @@ function parseJson(name, run) {
104
116
  throw new SiblingError(`${name} exited ${run.code} without JSON${said ? `: ${said}` : ''}`);
105
117
  }
106
118
  }
107
- export function createSiblings(root, config) {
119
+ /** The siblings of the work tree at `root`, looked for in its install and then in each of `installs` (locate). */
120
+ export function createSiblings(root, config, installs = []) {
108
121
  const found = (name) => {
109
- const sibling = locate(name, root, config);
122
+ const sibling = locate(name, root, config, installs);
110
123
  if (sibling.kind !== 'found')
111
124
  throw new SiblingError(sibling.reason);
112
125
  return sibling.command;
113
126
  };
114
127
  return {
115
128
  root,
116
- locate: (name) => locate(name, root, config),
129
+ locate: (name) => locate(name, root, config, installs),
117
130
  async briefs() {
118
131
  const run = await runSibling(found('spec-brief'), ['list', '--archived', '--format', 'json', '--no-color'], root);
119
132
  if (run.code === 2) {
@@ -132,7 +145,7 @@ export function createSiblings(root, config) {
132
145
  }
133
146
  },
134
147
  async json(name, args) {
135
- const sibling = locate(name, root, config);
148
+ const sibling = locate(name, root, config, installs);
136
149
  if (sibling.kind !== 'found')
137
150
  return { absent: sibling.reason };
138
151
  const run = await runSibling(sibling.command, args, root);
@@ -1 +1 @@
1
- {"version":3,"file":"siblings.js","sourceRoot":"","sources":["../src/siblings.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The edge where the harness meets spec-brief, spec-graph and spec-guard.\n *\n * Through their command lines and their versioned JSON, never by importing\n * their packages: each is released, pinned and dropped on its own, and none\n * becomes a dependency of this one (spec-core ADR-0005, ADR-0002 here).\n *\n * A sibling is found as the repository installed it -\n * `node_modules/@descent-vtt/<name>/bin/<name>.js`, run with this Node - or\n * as `.spec-harness.json` names it. One that is not there is `absent`, and\n * what needed it says so; nothing it would have checked is reported clean.\n * One installed below this release's minimum is `outdated`, and is reported\n * the same way rather than run (versions.ts).\n */\n\nimport { execFile } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { parseBriefList, SiblingOutputError } from './briefs.js';\nimport type { HarnessConfig, SiblingName } from './config.js';\nimport type { BriefRow } from './types.js';\nimport { checkVersion } from './versions.js';\n\nexport interface SiblingRun {\n readonly code: number;\n readonly stdout: string;\n readonly stderr: string;\n}\n\nexport type Sibling =\n /** `version` is `null` for a command `.spec-harness.json` names, whose version is not checked. */\n | { readonly kind: 'found'; readonly command: readonly string[]; readonly version: string | null }\n | { readonly kind: 'absent'; readonly reason: string }\n /** Installed, at a version below the minimum or one that cannot be read, which is `null`. */\n | { readonly kind: 'outdated'; readonly reason: string; readonly version: string | null };\n\n/** Where a sibling is, or why it cannot be run. */\nexport function locate(name: SiblingName, root: string, config: HarnessConfig): Sibling {\n // A command the configuration names is run as named: which package it runs,\n // and so which version, is the configuration's to say.\n const configured = config.tools[name];\n if (configured !== null) return { kind: 'found', command: configured, version: null };\n const directory = join(root, 'node_modules', '@descent-vtt', name);\n const bin = join(directory, 'bin', `${name}.js`);\n if (!existsSync(bin)) {\n return {\n kind: 'absent',\n reason: `${name} is not installed here: npm install --save-dev @descent-vtt/${name}, or name its command under \"tools\" in .spec-harness.json`,\n };\n }\n const check = checkVersion(name, declaredVersion(join(directory, 'package.json')));\n if (!check.ok) return { kind: 'outdated', reason: check.reason, version: check.version };\n return { kind: 'found', command: [process.execPath, bin], version: check.version };\n}\n\n/** The `version` a package.json declares, or `undefined` when there is no such file or it is not JSON. */\nexport function declaredVersion(file: string): unknown {\n // A manifest that holds null throws on the read, and any other value that\n // is not an object has no version, so the catch answers for both. Falling\n // out of it answers undefined as well, and a Buffer parses as its text:\n // the empty catch and the encoding are equivalent to their mutants.\n try {\n const manifest: { version?: unknown } = JSON.parse(readFileSync(file, 'utf8'));\n return manifest.version;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Runs a sibling with arguments, in the repository root.\n *\n * Never through a shell: an argument is a path or a brief id, and a shell\n * would read it as syntax. `node` as a command's first word is this Node, so\n * `[\"node\", \"path/to/bin.js\"]` works on every host; a Windows `.cmd` shim such\n * as `npx` cannot be started without a shell, and the error says to name the\n * script instead.\n */\nexport function runSibling(command: readonly string[], args: readonly string[], root: string, timeoutMs = 600_000): Promise<SiblingRun> {\n const [first, ...rest] = command;\n const program = first === 'node' ? process.execPath : (first as string);\n return new Promise((resolve) => {\n execFile(\n program,\n [...rest, ...args],\n {\n cwd: root,\n maxBuffer: 256 * 1024 * 1024,\n encoding: 'utf8',\n // Keeps a console window from opening on Windows; the sweep runs on\n // Linux, where the mutant is equivalent.\n windowsHide: true,\n timeout: timeoutMs,\n shell: false,\n env: { ...process.env, NO_COLOR: '1', FORCE_COLOR: '0' },\n },\n (error, stdout, stderr) => {\n const failure = error as (Error & { code?: unknown }) | null;\n if (failure !== null && failure.code === 'ENOENT') {\n resolve({\n code: -1,\n stdout: '',\n stderr: `cannot start \"${first}\": name the tool's script, such as [\"node\", \"node_modules/@descent-vtt/<tool>/bin/<tool>.js\"]`,\n });\n return;\n }\n const code = failure === null ? 0 : typeof failure.code === 'number' ? failure.code : -1;\n resolve({ code, stdout, stderr });\n },\n );\n });\n}\n\nexport class SiblingError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'SiblingError';\n }\n}\n\nfunction parseJson(name: string, run: SiblingRun): unknown {\n try {\n return JSON.parse(run.stdout);\n } catch {\n const said = (run.stderr.trim() || run.stdout.trim()).split('\\n').slice(0, 3).join(' ');\n throw new SiblingError(`${name} exited ${run.code} without JSON${said ? `: ${said}` : ''}`);\n }\n}\n\nexport interface Siblings {\n readonly root: string;\n locate(name: SiblingName): Sibling;\n /** Every brief, live and archived. Exit 2 from spec-brief is an error here too. */\n briefs(): Promise<BriefRow[]>;\n /** A sibling's JSON output for a command line, with its exit code and what it said on stderr; `absent` says why it cannot be run, missing or outdated. */\n json(name: SiblingName, args: readonly string[]): Promise<{ readonly code: number; readonly document: unknown; readonly stderr: string } | { readonly absent: string }>;\n}\n\nexport function createSiblings(root: string, config: HarnessConfig): Siblings {\n const found = (name: SiblingName): readonly string[] => {\n const sibling = locate(name, root, config);\n if (sibling.kind !== 'found') throw new SiblingError(sibling.reason);\n return sibling.command;\n };\n return {\n root,\n locate: (name) => locate(name, root, config),\n async briefs() {\n const run = await runSibling(found('spec-brief'), ['list', '--archived', '--format', 'json', '--no-color'], root);\n if (run.code === 2) {\n throw new SiblingError(`spec-brief could not list the briefs: ${run.stderr.trim().split('\\n')[0] || 'exit 2'}`);\n }\n try {\n return parseBriefList(parseJson('spec-brief', run));\n } catch (error) {\n // parseJson throws a SiblingError, which this would copy, and\n // parseBriefList nothing but its own: the check is equivalent to its\n // mutant, and keeps any other error the stack trace it needs.\n if (error instanceof SiblingOutputError) throw new SiblingError(error.message);\n throw error;\n }\n },\n async json(name, args) {\n const sibling = locate(name, root, config);\n if (sibling.kind !== 'found') return { absent: sibling.reason };\n const run = await runSibling(sibling.command, args, root);\n return { code: run.code, document: parseJson(name, run), stderr: run.stderr };\n },\n };\n}\n"]}
1
+ {"version":3,"file":"siblings.js","sourceRoot":"","sources":["../src/siblings.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The edge where the harness meets spec-brief, spec-graph and spec-guard.\n *\n * Through their command lines and their versioned JSON, never by importing\n * their packages: each is released, pinned and dropped on its own, and none\n * becomes a dependency of this one (spec-core ADR-0005, ADR-0002 here).\n *\n * A sibling is found as the repository installed it -\n * `node_modules/@descent-vtt/<name>/bin/<name>.js`, run with this Node - or\n * as `.spec-harness.json` names it. One that is not there is `absent`, and\n * what needed it says so; nothing it would have checked is reported clean.\n * One installed below this release's minimum is `outdated`, and is reported\n * the same way rather than run (versions.ts).\n */\n\nimport { execFile } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { parseBriefList, SiblingOutputError } from './briefs.js';\nimport type { HarnessConfig, SiblingName } from './config.js';\nimport type { BriefRow } from './types.js';\nimport { checkVersion } from './versions.js';\n\nexport interface SiblingRun {\n readonly code: number;\n readonly stdout: string;\n readonly stderr: string;\n}\n\nexport type Sibling =\n /**\n * `version` is `null` for a command `.spec-harness.json` names, whose\n * version is not checked. `elsewhere` is the directory whose install it\n * is, where that is not the work tree's own.\n */\n | { readonly kind: 'found'; readonly command: readonly string[]; readonly version: string | null; readonly elsewhere?: string }\n | { readonly kind: 'absent'; readonly reason: string }\n /** Installed, at a version below the minimum or one that cannot be read, which is `null`. */\n | { readonly kind: 'outdated'; readonly reason: string; readonly version: string | null; readonly elsewhere?: string };\n\n/**\n * Where a sibling is, or why it cannot be run. It is looked for in `root`'s\n * install, then in each of `installs` in turn, and the first that has it\n * decides, at this release's floor: a work tree with an install of its own\n * runs it, outdated or not, and one with none is checked with the\n * project's, as git's hook and Claude Code's are where a linked worktree has\n * none (ADR-0005).\n */\n// A name a mutant puts on the default list is a directory relative to where\n// the run is, which holds no install: equivalent, here and below.\nexport function locate(name: SiblingName, root: string, config: HarnessConfig, installs: readonly string[] = []): Sibling {\n // A command the configuration names is run as named: which package it runs,\n // and so which version, is the configuration's to say.\n const configured = config.tools[name];\n if (configured !== null) return { kind: 'found', command: configured, version: null };\n for (const place of [root, ...installs]) {\n const directory = join(place, 'node_modules', '@descent-vtt', name);\n const bin = join(directory, 'bin', `${name}.js`);\n if (!existsSync(bin)) continue;\n const check = checkVersion(name, declaredVersion(join(directory, 'package.json')));\n const where = place === root ? {} : { elsewhere: place };\n if (!check.ok) return { kind: 'outdated', reason: check.reason, version: check.version, ...where };\n return { kind: 'found', command: [process.execPath, bin], version: check.version, ...where };\n }\n return {\n kind: 'absent',\n reason: `${name} is not installed here: npm install --save-dev @descent-vtt/${name}, or name its command under \"tools\" in .spec-harness.json`,\n };\n}\n\n/** The `version` a package.json declares, or `undefined` when there is no such file or it is not JSON. */\nexport function declaredVersion(file: string): unknown {\n // A manifest that holds null throws on the read, and any other value that\n // is not an object has no version, so the catch answers for both. Falling\n // out of it answers undefined as well, and a Buffer parses as its text:\n // the empty catch and the encoding are equivalent to their mutants.\n try {\n const manifest: { version?: unknown } = JSON.parse(readFileSync(file, 'utf8'));\n return manifest.version;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Runs a sibling with arguments, in the repository root.\n *\n * Never through a shell: an argument is a path or a brief id, and a shell\n * would read it as syntax. `node` as a command's first word is this Node, so\n * `[\"node\", \"path/to/bin.js\"]` works on every host; a Windows `.cmd` shim such\n * as `npx` cannot be started without a shell, and the error says to name the\n * script instead.\n */\nexport function runSibling(command: readonly string[], args: readonly string[], root: string, timeoutMs = 600_000): Promise<SiblingRun> {\n const [first, ...rest] = command;\n const program = first === 'node' ? process.execPath : (first as string);\n return new Promise((resolve) => {\n execFile(\n program,\n [...rest, ...args],\n {\n cwd: root,\n maxBuffer: 256 * 1024 * 1024,\n encoding: 'utf8',\n // Keeps a console window from opening on Windows; the sweep runs on\n // Linux, where the mutant is equivalent.\n windowsHide: true,\n timeout: timeoutMs,\n shell: false,\n env: { ...process.env, NO_COLOR: '1', FORCE_COLOR: '0' },\n },\n (error, stdout, stderr) => {\n const failure = error as (Error & { code?: unknown }) | null;\n if (failure !== null && failure.code === 'ENOENT') {\n resolve({\n code: -1,\n stdout: '',\n stderr: `cannot start \"${first}\": name the tool's script, such as [\"node\", \"node_modules/@descent-vtt/<tool>/bin/<tool>.js\"]`,\n });\n return;\n }\n const code = failure === null ? 0 : typeof failure.code === 'number' ? failure.code : -1;\n resolve({ code, stdout, stderr });\n },\n );\n });\n}\n\nexport class SiblingError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'SiblingError';\n }\n}\n\nfunction parseJson(name: string, run: SiblingRun): unknown {\n try {\n return JSON.parse(run.stdout);\n } catch {\n const said = (run.stderr.trim() || run.stdout.trim()).split('\\n').slice(0, 3).join(' ');\n throw new SiblingError(`${name} exited ${run.code} without JSON${said ? `: ${said}` : ''}`);\n }\n}\n\nexport interface Siblings {\n readonly root: string;\n locate(name: SiblingName): Sibling;\n /** Every brief, live and archived. Exit 2 from spec-brief is an error here too. */\n briefs(): Promise<BriefRow[]>;\n /** A sibling's JSON output for a command line, with its exit code and what it said on stderr; `absent` says why it cannot be run, missing or outdated. */\n json(name: SiblingName, args: readonly string[]): Promise<{ readonly code: number; readonly document: unknown; readonly stderr: string } | { readonly absent: string }>;\n}\n\n/** The siblings of the work tree at `root`, looked for in its install and then in each of `installs` (locate). */\nexport function createSiblings(root: string, config: HarnessConfig, installs: readonly string[] = []): Siblings {\n const found = (name: SiblingName): readonly string[] => {\n const sibling = locate(name, root, config, installs);\n if (sibling.kind !== 'found') throw new SiblingError(sibling.reason);\n return sibling.command;\n };\n return {\n root,\n locate: (name) => locate(name, root, config, installs),\n async briefs() {\n const run = await runSibling(found('spec-brief'), ['list', '--archived', '--format', 'json', '--no-color'], root);\n if (run.code === 2) {\n throw new SiblingError(`spec-brief could not list the briefs: ${run.stderr.trim().split('\\n')[0] || 'exit 2'}`);\n }\n try {\n return parseBriefList(parseJson('spec-brief', run));\n } catch (error) {\n // parseJson throws a SiblingError, which this would copy, and\n // parseBriefList nothing but its own: the check is equivalent to its\n // mutant, and keeps any other error the stack trace it needs.\n if (error instanceof SiblingOutputError) throw new SiblingError(error.message);\n throw error;\n }\n },\n async json(name, args) {\n const sibling = locate(name, root, config, installs);\n if (sibling.kind !== 'found') return { absent: sibling.reason };\n const run = await runSibling(sibling.command, args, root);\n return { code: run.code, document: parseJson(name, run), stderr: run.stderr };\n },\n };\n}\n"]}
package/dist/types.d.ts CHANGED
@@ -36,6 +36,11 @@ export interface BriefRow {
36
36
  readonly ready: boolean;
37
37
  readonly waitingOn: readonly string[];
38
38
  }
39
+ /** A local branch: its name, and the short name git reads as it and nothing else, as a base is written. */
40
+ export interface LocalBranch {
41
+ readonly name: string;
42
+ readonly ref: string;
43
+ }
39
44
  /** A file a range of commits changed, as `git diff --name-status` reports it. */
40
45
  export interface FileChange {
41
46
  readonly status: 'A' | 'M' | 'D' | 'R' | 'C' | 'T';