@descent-vtt/spec-harness 0.18.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +160 -0
- package/README.md +57 -5
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +274 -51
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/configure.d.ts +88 -32
- package/dist/configure.js +187 -68
- package/dist/configure.js.map +1 -1
- package/dist/fs.d.ts +73 -3
- package/dist/fs.js +176 -13
- package/dist/fs.js.map +1 -1
- package/dist/git.d.ts +35 -7
- package/dist/git.js +42 -8
- package/dist/git.js.map +1 -1
- package/dist/hooks.d.ts +66 -0
- package/dist/hooks.js +112 -15
- package/dist/hooks.js.map +1 -1
- package/dist/round.js +11 -3
- package/dist/round.js.map +1 -1
- package/dist/setup.d.ts +6 -3
- package/dist/setup.js +39 -18
- package/dist/setup.js.map +1 -1
- package/dist/siblings.d.ts +18 -4
- package/dist/siblings.js +30 -17
- package/dist/siblings.js.map +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/types.js.map +1 -1
- package/dist/usage.d.ts +25 -6
- package/dist/usage.js +73 -19
- package/dist/usage.js.map +1 -1
- package/dist/workspace.d.ts +38 -1
- package/dist/workspace.js +68 -8
- package/dist/workspace.js.map +1 -1
- package/hooks/hooks.json +16 -0
- package/package.json +2 -2
package/dist/configure.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* nothing to add.
|
|
9
9
|
*/
|
|
10
10
|
import type { AllowedSigner } from './signers.js';
|
|
11
|
+
import type { LocalBranch } from './types.js';
|
|
11
12
|
import { type HookLine } from './usage.js';
|
|
12
13
|
import { type ClaudeCodeCheck } from './versions.js';
|
|
13
14
|
type Json = Record<string, unknown>;
|
|
@@ -34,25 +35,34 @@ export declare const GUARD_HOOK: Readonly<Json>;
|
|
|
34
35
|
*/
|
|
35
36
|
export declare const COMMA_SINCE = "2.1.191";
|
|
36
37
|
/**
|
|
37
|
-
*
|
|
38
|
-
* names
|
|
39
|
-
*
|
|
40
|
-
*
|
|
38
|
+
* The release of Claude Code from which a hyphenated name in a matcher of
|
|
39
|
+
* names is matched exactly. Its changelog
|
|
40
|
+
* (https://code.claude.com/docs/en/changelog, 2.1.195, read 2026-10-18) has,
|
|
41
|
+
* among its fixes, hook matchers with hyphenated identifiers that matched as
|
|
42
|
+
* substrings, and match exactly from this release on; it does not say how
|
|
43
|
+
* the rest of such a matcher was read.
|
|
41
44
|
*/
|
|
42
|
-
export
|
|
43
|
-
|
|
45
|
+
export declare const HYPHEN_SINCE = "2.1.195";
|
|
46
|
+
/**
|
|
47
|
+
* How the Claude Code that runs the hooks reads a matcher of names: as every
|
|
48
|
+
* release from {@link HYPHEN_SINCE} on, and the hooks reference today; as
|
|
49
|
+
* the release named, which is older; or it cannot be told, where the release
|
|
50
|
+
* cannot.
|
|
51
|
+
*/
|
|
52
|
+
export type Matching = {
|
|
53
|
+
readonly as: 'today';
|
|
44
54
|
} | {
|
|
45
|
-
readonly as: '
|
|
55
|
+
readonly as: 'older';
|
|
46
56
|
readonly release: string;
|
|
47
57
|
} | {
|
|
48
58
|
readonly as: 'untold';
|
|
49
59
|
};
|
|
50
60
|
/**
|
|
51
|
-
* How the Claude Code doctor asked its release reads a
|
|
52
|
-
* was not asked, or could not be told, leaves the reading
|
|
53
|
-
* not, nothing says which it is.
|
|
61
|
+
* How the Claude Code doctor asked its release reads a matcher of names. A
|
|
62
|
+
* release that was not asked, or could not be told, leaves the reading
|
|
63
|
+
* untold: asked or not, nothing says which it is.
|
|
54
64
|
*/
|
|
55
|
-
export declare function
|
|
65
|
+
export declare function matchingUnder(release: ClaudeCodeCheck | null): Matching;
|
|
56
66
|
/** What reads a matcher that is a pattern: the writing tools it matches, or `null` where that cannot be told. */
|
|
57
67
|
export type PatternReader = (source: string) => readonly string[] | null;
|
|
58
68
|
/**
|
|
@@ -86,12 +96,34 @@ export declare function patternReader(read?: PatternReader): PatternReader;
|
|
|
86
96
|
*
|
|
87
97
|
* `pattern` reads a matcher that is a pattern: the run's own reader, where
|
|
88
98
|
* the run has read the file's matchers already ({@link patternReader}).
|
|
89
|
-
* `
|
|
90
|
-
*
|
|
99
|
+
* `matching` is how the Claude Code here reads a matcher of names, where
|
|
100
|
+
* the run asked its release ({@link matchingUnder}): a line that
|
|
91
101
|
* release starts for no write is no guard, and init's own is written beside
|
|
92
102
|
* it.
|
|
93
103
|
*/
|
|
94
|
-
export declare function mergeClaudeSettings(current: Json, isHook?: (words: readonly string[]) => boolean, pattern?: PatternReader,
|
|
104
|
+
export declare function mergeClaudeSettings(current: Json, isHook?: (words: readonly string[]) => boolean, pattern?: PatternReader, matching?: Matching): Json | null;
|
|
105
|
+
/**
|
|
106
|
+
* Settings with init's entry under `SessionStart` added beside its two, or
|
|
107
|
+
* `null` where it is not to be: where init's own line does not stand
|
|
108
|
+
* under both `PreToolUse` and `PostToolUse`, a file init writes no hook
|
|
109
|
+
* into or one it has yet to complete; and where a line of the hook stands
|
|
110
|
+
* under `SessionStart` already, init's or one `isHook` knows, which would
|
|
111
|
+
* say the same twice (ADR-0012).
|
|
112
|
+
*/
|
|
113
|
+
export declare function withSessionStart(settings: Json, isHook: (words: readonly string[]) => boolean): Json | null;
|
|
114
|
+
/**
|
|
115
|
+
* Whether settings have Claude Code start the hook as a session starts: an
|
|
116
|
+
* entry under `SessionStart` whose line is init's, or one `isHook` knows
|
|
117
|
+
* as the hook's. Its matcher is not read: one that names some of the ways a
|
|
118
|
+
* session starts has the check made for those.
|
|
119
|
+
*/
|
|
120
|
+
export declare function startsSessions(settings: unknown, isHook: (words: readonly string[]) => boolean): boolean;
|
|
121
|
+
/**
|
|
122
|
+
* What doctor notes where settings hold the guard hooks and none under
|
|
123
|
+
* `SessionStart`: what would hold every write is said at the first write,
|
|
124
|
+
* which it holds, and not as the session starts.
|
|
125
|
+
*/
|
|
126
|
+
export declare const NO_SESSION_START = "note: no settings file has Claude Code start the hook as a session starts, so a configuration that does not load, or a brief named that spec-brief does not list, is said only at the first write, which it holds: spec-harness init --write adds the entry beside the two it wrote";
|
|
95
127
|
/** The server's entry, reading the project at `projectDir`: {@link PROJECT_DIR} for the plugin, {@link PROJECT_DIR_OR_HERE} for a project. */
|
|
96
128
|
export declare function mcpServer(projectDir: string): Json;
|
|
97
129
|
/**
|
|
@@ -210,17 +242,17 @@ export declare function handedLines(sources: readonly ClaudeSettings[], project:
|
|
|
210
242
|
*
|
|
211
243
|
* `pattern` reads a matcher that is a pattern, once for every entry under
|
|
212
244
|
* it in every file: the run's own reader, where something else in the run
|
|
213
|
-
* reads the same files ({@link patternReader}). `
|
|
214
|
-
* Code here reads a
|
|
215
|
-
*
|
|
216
|
-
*
|
|
245
|
+
* reads the same files ({@link patternReader}). `matching` is how the
|
|
246
|
+
* Claude Code here reads a matcher of names, where its release was asked
|
|
247
|
+
* ({@link matchingUnder}); a caller that asks none reads one as every
|
|
248
|
+
* release from {@link HYPHEN_SINCE} on does.
|
|
217
249
|
*
|
|
218
250
|
* A file that holds an array for a matcher under `PreToolUse` or
|
|
219
251
|
* `PermissionRequest` has none of its hooks loaded ({@link arrayMatcher}):
|
|
220
252
|
* each line of the hook there is started for no write, and one that stood
|
|
221
253
|
* to be started for a write is a guard that is off.
|
|
222
254
|
*/
|
|
223
|
-
export declare function reportHooks(sources: readonly ClaudeSettings[], project: string, read: (words: readonly string[]) => HookLine | null, pattern?: PatternReader,
|
|
255
|
+
export declare function reportHooks(sources: readonly ClaudeSettings[], project: string, read: (words: readonly string[]) => HookLine | null, pattern?: PatternReader, matching?: Matching): HookReport[];
|
|
224
256
|
/** Whether a project's `.mcp.json` registers a server under the harness's name, however it runs it. */
|
|
225
257
|
export declare function registersServer(mcp: unknown): boolean;
|
|
226
258
|
/** A Claude Code settings file, named as a person finds it, and what reading it gave: an object, or nothing usable. */
|
|
@@ -345,17 +377,26 @@ export interface ClaudeCodeWiring {
|
|
|
345
377
|
readonly lines: readonly HookReport[];
|
|
346
378
|
/** Whether `.mcp.json` registers the server. */
|
|
347
379
|
readonly server: boolean;
|
|
380
|
+
/** Whether a settings file has Claude Code start the hook as a session starts (startsSessions). */
|
|
381
|
+
readonly sessionStart: boolean;
|
|
348
382
|
/**
|
|
349
383
|
* The release of the Claude Code on `PATH`, where it was asked: a matcher
|
|
350
384
|
* that lists names with a comma is read as that release reads it
|
|
351
|
-
* ({@link
|
|
385
|
+
* ({@link matchingUnder}). `null` where it was not asked.
|
|
352
386
|
*/
|
|
353
387
|
readonly release: ClaudeCodeCheck | null;
|
|
388
|
+
/**
|
|
389
|
+
* An array for a matcher that keeps Claude Code from loading the hooks of
|
|
390
|
+
* the project's settings file, the file init writes its hooks into, or
|
|
391
|
+
* `null`: init writes none there while it stands ({@link arrayMatcher}).
|
|
392
|
+
*/
|
|
393
|
+
readonly unloaded: ArrayMatcher | null;
|
|
354
394
|
}
|
|
355
395
|
export type WiringState = 'plugin' | 'init' | 'both' | 'none';
|
|
356
|
-
/** The wiring as its descriptions read it: what a caller knows of the plugin's presence, which one that does not ask leaves out. */
|
|
396
|
+
/** The wiring as its descriptions read it: what a caller knows of the plugin's presence, and of an array in the project's settings, which one that does not ask leaves out. */
|
|
357
397
|
type Wired = Pick<ClaudeCodeWiring, 'plugin' | 'hooks' | 'server'> & {
|
|
358
398
|
readonly presence?: PluginPresence | null;
|
|
399
|
+
readonly unloaded?: ArrayMatcher | null;
|
|
359
400
|
};
|
|
360
401
|
/**
|
|
361
402
|
* Which of the four the wiring is. `both` is a double install, and doctor
|
|
@@ -388,8 +429,7 @@ export declare function describePresence(wiring: Wired): string | null;
|
|
|
388
429
|
export declare function describeClaudeCode(wiring: Wired): string;
|
|
389
430
|
/** The spec-brief plugin this package ships, as spec-brief's `plugins` names it. */
|
|
390
431
|
export declare const PLUGIN = "@descent-vtt/spec-harness/spec-brief-plugin";
|
|
391
|
-
|
|
392
|
-
export declare const SPEC_BRIEF_CONFIGS: readonly string[];
|
|
432
|
+
export { SPEC_BRIEF_CONFIGS } from './config.js';
|
|
393
433
|
/**
|
|
394
434
|
* The file a package's `exports` target names for an import, as Node's
|
|
395
435
|
* PACKAGE_TARGET_RESOLVE finds it, or `null` when it names none: a string
|
|
@@ -435,10 +475,10 @@ export declare function describePlugin(state: PluginState): string;
|
|
|
435
475
|
export interface BaseFacts {
|
|
436
476
|
/** The remote's default branch as a local ref, such as `origin/main`, or `null`. */
|
|
437
477
|
readonly remoteDefault: string | null;
|
|
438
|
-
/** The branch checked out, or `null` on a detached head. */
|
|
478
|
+
/** The name of the branch checked out, or `null` on a detached head. */
|
|
439
479
|
readonly branch: string | null;
|
|
440
|
-
/** Every local branch. */
|
|
441
|
-
readonly branches: readonly
|
|
480
|
+
/** Every local branch, by its name and the ref a base names it by. */
|
|
481
|
+
readonly branches: readonly LocalBranch[];
|
|
442
482
|
}
|
|
443
483
|
/** The base `init` names, and why; `base` is `null` when none can be told. */
|
|
444
484
|
export interface BaseChoice {
|
|
@@ -496,6 +536,19 @@ export type GitHookState = {
|
|
|
496
536
|
readonly state: 'stops' | 'ignored';
|
|
497
537
|
readonly file: string;
|
|
498
538
|
readonly what: string;
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* A file the account may not read, `reason` being why, and `runs` whether
|
|
542
|
+
* git may execute it. One git may not execute it skips, with a hint, and
|
|
543
|
+
* commits. One it may it runs, and a script the shell cannot read either
|
|
544
|
+
* stops every commit there (measured with git 2.47 as an ordinary account),
|
|
545
|
+
* where a program runs without being read: which it is cannot be read.
|
|
546
|
+
*/
|
|
547
|
+
| {
|
|
548
|
+
readonly state: 'unreadable';
|
|
549
|
+
readonly file: string;
|
|
550
|
+
readonly runs: boolean;
|
|
551
|
+
readonly reason: string;
|
|
499
552
|
} | {
|
|
500
553
|
readonly state: 'unknown';
|
|
501
554
|
readonly reason: string;
|
|
@@ -522,13 +575,17 @@ export declare const PROTECT_RULE_FILES: string;
|
|
|
522
575
|
export declare const GIT_HOOK_LINE = "node node_modules/@descent-vtt/spec-harness/bin/spec-harness.js hook git";
|
|
523
576
|
/**
|
|
524
577
|
* The hook init writes. It is a POSIX sh script on every platform, since Git
|
|
525
|
-
* for Windows runs a hook with the sh it ships.
|
|
526
|
-
*
|
|
578
|
+
* for Windows runs a hook with the sh it ships. git starts it at the top of
|
|
579
|
+
* the work tree it commits in, so the project's install is read from there,
|
|
580
|
+
* and where a linked worktree has none, from the repository's main work
|
|
581
|
+
* tree: the work tree whose `.git` the common directory is, where `git
|
|
582
|
+
* worktree add` was run and the project installed (ADR-0005). Where neither
|
|
583
|
+
* has the install - a worktree of a repository nobody installed into, a
|
|
527
584
|
* repository that shares its hooks with one that uses the harness - node
|
|
528
|
-
* would end on a stack trace, so the hook says what is missing and where,
|
|
529
|
-
* the commit waits, as it did when npx found nothing to run.
|
|
585
|
+
* would end on a stack trace, so the hook says what is missing and where,
|
|
586
|
+
* and the commit waits, as it did when npx found nothing to run.
|
|
530
587
|
*/
|
|
531
|
-
export declare const PRE_COMMIT = "#!/bin/sh\n# spec-harness: refuse a commit that changes what the active brief protects.\n# git runs this at the top of the work tree it commits in, a linked worktree's\n# own, wherever the hook is kept: the project's install is read from there.\nbin=node_modules/@descent-vtt/spec-harness/bin/spec-harness.js\nif [ ! -f \"$bin\" ]; then\n printf '%s\\n' \"spec-harness: $bin is not in $PWD, the work tree git commits in: install the project's dependencies there, as with npm ci, and commit again\" >&2\n
|
|
588
|
+
export declare const PRE_COMMIT = "#!/bin/sh\n# spec-harness: refuse a commit that changes what the active brief protects.\n# git runs this at the top of the work tree it commits in, a linked worktree's\n# own, wherever the hook is kept: the project's install is read from there,\n# or, where a linked worktree has none, from the repository's main work tree.\nbin=node_modules/@descent-vtt/spec-harness/bin/spec-harness.js\nif [ ! -f \"$bin\" ]; then\n common=$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)\n if [ \"${common##*/}\" = .git ] && [ -f \"${common%/.git}/$bin\" ]; then\n bin=${common%/.git}/$bin\n else\n printf '%s\\n' \"spec-harness: $bin is not in $PWD, the work tree git commits in, nor in its repository's main work tree: install the project's dependencies there, as with npm ci, and commit again\" >&2\n exit 2\n fi\nfi\nexec node \"$bin\" hook git\n";
|
|
532
589
|
/**
|
|
533
590
|
* What doctor and init add of a hook whose text runs the harness through
|
|
534
591
|
* npx: the hook init wrote through 0.9.1, or the line it advised for a hook
|
|
@@ -553,4 +610,3 @@ export declare function graphReadsBriefs(config: Json, briefs: string): boolean;
|
|
|
553
610
|
export declare function describeGraph(archiveGlob: string, briefs: string, reads: boolean, added: boolean): string;
|
|
554
611
|
/** spec-graph's configuration with the archive read as history, or `null` when it already is. */
|
|
555
612
|
export declare function mergeSpecGraph(current: Json, archiveGlob: string): Json | null;
|
|
556
|
-
export {};
|