@deepwatch/dsh-contracts 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Whether a role can actually run, kept as four separate facts.
3
+ *
4
+ * This module exists because they were one. A provider row showed a green dot
5
+ * the moment a credential was saved, and a person reasonably read that as "the
6
+ * product is ready" — so they typed a prompt, and the runtime routed it to a
7
+ * provider they had never configured, failed on a missing environment
8
+ * variable, and left a failed turn in their session. Every step of that was a
9
+ * consequence of one indicator standing for four different questions:
10
+ *
11
+ * 1. is there a credential? {@link ProviderCredentialStatus}
12
+ * 2. does the provider answer? {@link ProviderReachability}
13
+ * 3. is a model chosen? {@link ModelSelectionStatus}
14
+ * 4. is a role bound and runnable? {@link RoleBindingStatus}
15
+ *
16
+ * A credential is the *first* of those and implies none of the others.
17
+ * {@link roleReadiness} is the only thing in this product allowed to answer
18
+ * "ready", and it requires all four to line up, plus a route that supports the
19
+ * role and a policy state that permits the request.
20
+ *
21
+ * **Nothing here ever carries secret material.** Not a value, not a prefix, a
22
+ * suffix, a length or a hash. A credential appears in these types only as a
23
+ * *reference* — an opaque handle the Host can resolve — and as a status word.
24
+ * `configured_unverified` is the honest state after a save: something is
25
+ * stored, and nobody has asked the provider whether it works.
26
+ *
27
+ * Verification is deliberately not automatic. Saving a credential must not
28
+ * quietly spend a request, and discovering models must be distinguishable from
29
+ * a billable completion — so both are user actions with their own results, and
30
+ * {@link ProviderReachability} stays `unknown` until one of them runs.
31
+ *
32
+ * @module @deepwatch/dsh-contracts/readiness
33
+ */
34
+ /**
35
+ * The order blockers are reported in.
36
+ *
37
+ * Not alphabetical and not arbitrary: it is the order a person has to fix them
38
+ * in. Telling somebody their model is unavailable when they have not chosen a
39
+ * provider sends them to the wrong screen.
40
+ */
41
+ const BLOCKER_ORDER = [
42
+ 'no_binding',
43
+ 'provider_unknown',
44
+ 'credential_absent',
45
+ 'credential_inaccessible',
46
+ 'credential_rejected',
47
+ 'model_unset',
48
+ 'model_invalid',
49
+ 'model_unavailable',
50
+ 'provider_untested',
51
+ 'provider_unreachable',
52
+ 'provider_rate_limited',
53
+ 'route_lacks_role',
54
+ 'modality_unsupported',
55
+ 'contract_mismatch',
56
+ 'consent_required',
57
+ 'policy_forbids',
58
+ ];
59
+ /**
60
+ * Whether a role can run, and if not, exactly what is missing.
61
+ *
62
+ * The single gate. A caller may not assemble "ready" from parts: every surface
63
+ * that shows readiness, and both halves of preflight, ask this function, so
64
+ * there is one definition of executable and it is testable on its own.
65
+ *
66
+ * @param inputs - every fact that bears on the decision.
67
+ * @param role - the role being asked about.
68
+ * @returns the status and the blockers, in the order they must be fixed.
69
+ */
70
+ export function roleReadiness(role, inputs) {
71
+ const found = new Set();
72
+ const { binding, route } = inputs;
73
+ if (binding === null) {
74
+ found.add('no_binding');
75
+ }
76
+ else {
77
+ if (route === null)
78
+ found.add('provider_unknown');
79
+ if (inputs.credential === 'absent')
80
+ found.add('credential_absent');
81
+ if (inputs.credential === 'inaccessible')
82
+ found.add('credential_inaccessible');
83
+ if (inputs.credential === 'rejected')
84
+ found.add('credential_rejected');
85
+ if (inputs.credential === 'configured_unverified')
86
+ found.add('provider_untested');
87
+ // A rejected credential is also what an `unauthorized` probe reports; both
88
+ // are the same missing step for a person, so they collapse to one blocker.
89
+ if (inputs.reachability === 'unauthorized')
90
+ found.add('credential_rejected');
91
+ if (inputs.reachability === 'unknown')
92
+ found.add('provider_untested');
93
+ if (inputs.reachability === 'unreachable')
94
+ found.add('provider_unreachable');
95
+ if (inputs.reachability === 'rate_limited')
96
+ found.add('provider_rate_limited');
97
+ if (inputs.model === 'none' || binding.model === '')
98
+ found.add('model_unset');
99
+ if (inputs.model === 'invalid')
100
+ found.add('model_invalid');
101
+ if (inputs.model === 'unavailable')
102
+ found.add('model_unavailable');
103
+ if (route !== null) {
104
+ if (!route.roles.includes(role))
105
+ found.add('route_lacks_role');
106
+ const supported = new Set(route.modalities);
107
+ if (!binding.modalities.every(modality => supported.has(modality))) {
108
+ found.add('modality_unsupported');
109
+ }
110
+ }
111
+ if (!inputs.contractMatches)
112
+ found.add('contract_mismatch');
113
+ if (!inputs.consentGranted)
114
+ found.add('consent_required');
115
+ if (!inputs.policyPermits)
116
+ found.add('policy_forbids');
117
+ }
118
+ const blockers = BLOCKER_ORDER.filter(blocker => found.has(blocker));
119
+ if (blockers.length === 0) {
120
+ return { role, status: 'executable', blockers: [], primaryBlocker: null };
121
+ }
122
+ // `unbound` and `blocked` are different answers to "what do I do now?": one
123
+ // needs a first choice, the other needs something repaired.
124
+ const status = binding === null
125
+ ? 'unbound'
126
+ : blockers.every(blocker => blocker === 'provider_untested')
127
+ ? 'bound_unverified'
128
+ : 'blocked';
129
+ return { role, status, blockers, primaryBlocker: blockers[0] ?? null };
130
+ }
131
+ /**
132
+ * Whether a role may be described to a person as ready.
133
+ *
134
+ * A helper rather than a comparison at each call site, because "ready" is
135
+ * exactly the word this whole module exists to stop being used loosely.
136
+ */
137
+ export function isExecutable(readiness) {
138
+ return readiness.status === 'executable';
139
+ }
140
+ /** What a person should be told to do about one blocker, in their words. */
141
+ const BLOCKER_COPY = {
142
+ no_binding: 'Choose a provider and model for this capability.',
143
+ provider_unknown: 'The bound provider is no longer available. Choose another.',
144
+ credential_absent: 'Add a credential for this provider.',
145
+ credential_inaccessible: 'The saved credential could not be read. Save it again.',
146
+ credential_rejected: 'The provider rejected the saved credential. Update it.',
147
+ provider_untested: 'Run the provider test before using this capability.',
148
+ provider_unreachable: 'The provider did not answer. Check the network and try again.',
149
+ provider_rate_limited: 'The provider rate-limited the test. Wait, then try again.',
150
+ model_unset: 'Choose a model for this capability.',
151
+ model_invalid: 'The chosen model is not valid for this provider. Choose another.',
152
+ model_unavailable: 'The provider no longer offers the chosen model. Choose another.',
153
+ route_lacks_role: 'This provider cannot serve this capability. Choose another.',
154
+ modality_unsupported: 'This model does not support what this capability needs.',
155
+ contract_mismatch: 'This version of DeepWatch cannot talk to that provider.',
156
+ consent_required: 'This capability needs a consent that has not been granted.',
157
+ policy_forbids: 'Policy on this machine forbids this request.',
158
+ };
159
+ /**
160
+ * One sentence a person can act on, for the blocker that matters most.
161
+ *
162
+ * Deliberately free of route ids, package names and environment variables:
163
+ * those belong in Diagnostics, and a person reading their first error should
164
+ * be told what to do rather than what broke internally.
165
+ */
166
+ export function blockerMessage(readiness) {
167
+ const blocker = readiness.primaryBlocker;
168
+ return blocker === null ? null : BLOCKER_COPY[blocker];
169
+ }
170
+ /**
171
+ * Whether a status word may be drawn with the affordance that reads as "good".
172
+ *
173
+ * Only a proved binding earns it. `bound_unverified` deliberately does not:
174
+ * that is the state a green dot used to claim, and claiming it is what sent a
175
+ * prompt to an unconfigured provider.
176
+ */
177
+ export function isPositiveBindingStatus(status) {
178
+ return status === 'executable';
179
+ }
180
+ /**
181
+ * The accessible label for a status, so colour is never the only signal.
182
+ *
183
+ * Every surface uses these words, so a screen reader and a sighted reader are
184
+ * told the same thing, and a red or green dot is decoration on top of text
185
+ * rather than the fact itself.
186
+ */
187
+ export const BINDING_STATUS_LABEL = {
188
+ unbound: 'Not configured',
189
+ bound_unverified: 'Configured · not tested',
190
+ executable: 'Ready',
191
+ blocked: 'Blocked',
192
+ };
193
+ /** The accessible label for a credential status. */
194
+ export const CREDENTIAL_STATUS_LABEL = {
195
+ absent: 'No credential',
196
+ configured_unverified: 'Credential saved · not yet assigned',
197
+ verified: 'Credential accepted',
198
+ rejected: 'Credential rejected',
199
+ inaccessible: 'Credential unreadable',
200
+ };
201
+ /** The accessible label for provider reachability. */
202
+ export const REACHABILITY_LABEL = {
203
+ unknown: 'Not tested',
204
+ reachable: 'Answered',
205
+ unreachable: 'No answer',
206
+ rate_limited: 'Rate limited',
207
+ unauthorized: 'Rejected the credential',
208
+ };
209
+ //# sourceMappingURL=readiness.js.map
@@ -0,0 +1,141 @@
1
+ /**
2
+ * The one resolved workspace every layer agrees on.
3
+ *
4
+ * A real owner session created `owner-test/totals.json` correctly and then
5
+ * could not be verified, because three layers each answered "which directory
6
+ * is this relative path in?" from a different place:
7
+ *
8
+ * - the agent's filesystem tools resolved against the Harness session
9
+ * workspace, which the Harness derives from *its own process cwd*;
10
+ * - Watch Core was spawned with an empty `cwd`, so it inherited whatever the
11
+ * Host happened to be started from;
12
+ * - the verifier, given no `workingDir`, fell back to `Path(".")` — the Core
13
+ * process's cwd, which was neither of the above.
14
+ *
15
+ * Every one of those defaults is individually reasonable and the combination
16
+ * is a product that writes a file it cannot then find. The file landed outside
17
+ * every root the verifier would look in, so the honest answer was
18
+ * `INCONCLUSIVE` — correct, and useless.
19
+ *
20
+ * The fix is not to widen the verifier until it can see the file. It is to
21
+ * make the question have one answer. A {@link WorkspaceContext} is that
22
+ * answer: one absolute, traversal-resolved root, established once at launch
23
+ * and handed to the filesystem tools, the shell, Watch policy, the verifier,
24
+ * the evidence resolver, receipts, the Library projection and the session UI.
25
+ *
26
+ * **Fail closed.** {@link requireWorkspace} throws rather than inventing a
27
+ * root. A layer that cannot say where it is must stop and say so, because the
28
+ * alternative is what shipped: a silent default that looks like it worked.
29
+ *
30
+ * @module @deepwatch/dsh-contracts/workspace
31
+ */
32
+ import { type PathRoots } from './paths.js';
33
+ /** Environment variable naming the canonical workspace for a composed run. */
34
+ export declare const WORKSPACE_ENV = "DEEPWATCH_WORKSPACE";
35
+ /**
36
+ * One workspace, resolved once.
37
+ *
38
+ * `root` is absolute, separator-normalised and traversal-resolved, so two
39
+ * layers comparing it are comparing the same string rather than two spellings
40
+ * of one directory.
41
+ */
42
+ export interface WorkspaceContext {
43
+ /** The canonical absolute root. Never rendered to a person or a model. */
44
+ readonly root: string;
45
+ /** How this root was chosen, for a diagnostic that can be acted on. */
46
+ readonly origin: WorkspaceOrigin;
47
+ /** Redaction roots, with the workspace already registered. */
48
+ readonly roots: PathRoots;
49
+ }
50
+ /**
51
+ * Where a canonical workspace came from.
52
+ *
53
+ * Recorded because "the workspace is wrong" and "there was no workspace and
54
+ * something guessed" are different bugs with different fixes, and the failed
55
+ * owner session could not tell them apart.
56
+ */
57
+ export type WorkspaceOrigin =
58
+ /** A person named it: `deepwatch web --workspace <dir>`. */
59
+ 'flag'
60
+ /** Inherited from a composed profile or launcher environment. */
61
+ | 'environment'
62
+ /** The directory the command was invoked from, adopted deliberately. */
63
+ | 'invocation';
64
+ /** Raised when a layer needs the canonical workspace and none was established. */
65
+ export declare class WorkspaceNotEstablished extends Error {
66
+ readonly where: string;
67
+ constructor(where: string);
68
+ }
69
+ /** Raised when a relative path would resolve outside the canonical workspace. */
70
+ export declare class WorkspaceEscape extends Error {
71
+ readonly attempted: string;
72
+ constructor(attempted: string);
73
+ }
74
+ /**
75
+ * Establish the canonical workspace from an absolute path.
76
+ *
77
+ * Rejects a relative candidate rather than resolving it against the current
78
+ * process cwd. Resolving here is what created the original defect: each layer
79
+ * had a different cwd, so "resolve it against cwd" produced three roots. A
80
+ * caller that only has a relative path has not yet decided which directory it
81
+ * means, and must decide before calling.
82
+ *
83
+ * @param candidate - an absolute local path.
84
+ * @param origin - how it was chosen, for diagnostics.
85
+ * @param extra - further roots to register for redaction (profile, dsh-home).
86
+ */
87
+ export declare function establishWorkspace(candidate: string, origin: WorkspaceOrigin, extra?: PathRoots): WorkspaceContext;
88
+ /**
89
+ * The established workspace, or a failure naming what to do about it.
90
+ *
91
+ * The fail-closed half of the contract. Call it from any layer that is about
92
+ * to resolve a relative path, so an unestablished workspace is a stop rather
93
+ * than a guess.
94
+ */
95
+ export declare function requireWorkspace(context: WorkspaceContext | null | undefined, where: string): WorkspaceContext;
96
+ /**
97
+ * Read the canonical workspace out of an environment.
98
+ *
99
+ * Returns null rather than throwing, so a caller can distinguish "nobody said"
100
+ * from "somebody said something unusable" and report the right one.
101
+ */
102
+ export declare function workspaceFromEnvironment(env: Readonly<Record<string, string | undefined>>, extra?: PathRoots): WorkspaceContext | null;
103
+ /**
104
+ * An absolute path for a workspace-relative one, or a refusal.
105
+ *
106
+ * The single resolution every layer routes through. `..` is resolved *before*
107
+ * the containment test, so `../elsewhere/notes.md` is refused rather than
108
+ * quietly landing a directory up — the check that a literal prefix comparison
109
+ * gets wrong.
110
+ *
111
+ * An already-absolute input is accepted only when it is inside the workspace,
112
+ * which keeps a caller from smuggling an outside path through a parameter
113
+ * documented as relative.
114
+ */
115
+ export declare function resolveInWorkspace(context: WorkspaceContext, relative: string): string;
116
+ /**
117
+ * Whether a path lands inside the canonical workspace.
118
+ *
119
+ * The containment question for a boundary that must answer before the side
120
+ * effect, not after it.
121
+ */
122
+ export declare function insideWorkspace(context: WorkspaceContext, candidate: string): boolean;
123
+ /**
124
+ * The workspace-relative, forward-slashed spelling of a path.
125
+ *
126
+ * What a receipt, a Library row and a verifier request should carry, so the
127
+ * same file is one string everywhere and no machine's directory names ride
128
+ * along. Returns null when the path is outside, so a caller cannot treat a
129
+ * failed conversion as a success and emit an absolute path.
130
+ */
131
+ export declare function workspaceRelative(context: WorkspaceContext, candidate: string): string | null;
132
+ /**
133
+ * Whether two layers agree about the workspace.
134
+ *
135
+ * The assertion a composed run makes at startup. Comparing normalised
136
+ * spellings rather than raw strings is the point: `D:\Ws` and `d:/Ws/` are one
137
+ * directory, and a mismatch reported between those two would send somebody
138
+ * looking for a bug that is not there.
139
+ */
140
+ export declare function sameWorkspace(left: string, right: string): boolean;
141
+ //# sourceMappingURL=workspace.d.ts.map
@@ -0,0 +1,158 @@
1
+ /**
2
+ * The one resolved workspace every layer agrees on.
3
+ *
4
+ * A real owner session created `owner-test/totals.json` correctly and then
5
+ * could not be verified, because three layers each answered "which directory
6
+ * is this relative path in?" from a different place:
7
+ *
8
+ * - the agent's filesystem tools resolved against the Harness session
9
+ * workspace, which the Harness derives from *its own process cwd*;
10
+ * - Watch Core was spawned with an empty `cwd`, so it inherited whatever the
11
+ * Host happened to be started from;
12
+ * - the verifier, given no `workingDir`, fell back to `Path(".")` — the Core
13
+ * process's cwd, which was neither of the above.
14
+ *
15
+ * Every one of those defaults is individually reasonable and the combination
16
+ * is a product that writes a file it cannot then find. The file landed outside
17
+ * every root the verifier would look in, so the honest answer was
18
+ * `INCONCLUSIVE` — correct, and useless.
19
+ *
20
+ * The fix is not to widen the verifier until it can see the file. It is to
21
+ * make the question have one answer. A {@link WorkspaceContext} is that
22
+ * answer: one absolute, traversal-resolved root, established once at launch
23
+ * and handed to the filesystem tools, the shell, Watch policy, the verifier,
24
+ * the evidence resolver, receipts, the Library projection and the session UI.
25
+ *
26
+ * **Fail closed.** {@link requireWorkspace} throws rather than inventing a
27
+ * root. A layer that cannot say where it is must stop and say so, because the
28
+ * alternative is what shipped: a silent default that looks like it worked.
29
+ *
30
+ * @module @deepwatch/dsh-contracts/workspace
31
+ */
32
+ import { containsPath, isAbsoluteLocalPath, normalisePath, relativeToRoot, resolveTraversal, } from './paths.js';
33
+ /** Environment variable naming the canonical workspace for a composed run. */
34
+ export const WORKSPACE_ENV = 'DEEPWATCH_WORKSPACE';
35
+ /** Raised when a layer needs the canonical workspace and none was established. */
36
+ export class WorkspaceNotEstablished extends Error {
37
+ where;
38
+ constructor(where) {
39
+ super(`${where} needs the canonical workspace and none was established. `
40
+ + 'Start DeepWatch with `deepwatch web --workspace <dir>`, or set '
41
+ + `${WORKSPACE_ENV} to an absolute path, so the agent's tools, Watch `
42
+ + 'containment and the verifier all resolve relative paths in one place.');
43
+ this.name = 'WorkspaceNotEstablished';
44
+ this.where = where;
45
+ }
46
+ }
47
+ /** Raised when a relative path would resolve outside the canonical workspace. */
48
+ export class WorkspaceEscape extends Error {
49
+ attempted;
50
+ constructor(attempted) {
51
+ super(`"${attempted}" resolves outside the workspace. `
52
+ + 'Paths handed to the agent, the shell and the verifier are workspace-relative '
53
+ + 'by contract; nothing outside it is reachable by relative path.');
54
+ this.name = 'WorkspaceEscape';
55
+ this.attempted = attempted;
56
+ }
57
+ }
58
+ /**
59
+ * Establish the canonical workspace from an absolute path.
60
+ *
61
+ * Rejects a relative candidate rather than resolving it against the current
62
+ * process cwd. Resolving here is what created the original defect: each layer
63
+ * had a different cwd, so "resolve it against cwd" produced three roots. A
64
+ * caller that only has a relative path has not yet decided which directory it
65
+ * means, and must decide before calling.
66
+ *
67
+ * @param candidate - an absolute local path.
68
+ * @param origin - how it was chosen, for diagnostics.
69
+ * @param extra - further roots to register for redaction (profile, dsh-home).
70
+ */
71
+ export function establishWorkspace(candidate, origin, extra = []) {
72
+ if (candidate === '' || !isAbsoluteLocalPath(candidate)) {
73
+ throw new Error(`A canonical workspace must be an absolute path; got "${candidate}". `
74
+ + 'Resolve it where the directory is actually known, not here — this module '
75
+ + 'has no cwd of its own on purpose.');
76
+ }
77
+ const root = resolveTraversal(candidate);
78
+ const workspaceRoot = { kind: 'workspace', path: root };
79
+ return { root, origin, roots: [workspaceRoot, ...extra] };
80
+ }
81
+ /**
82
+ * The established workspace, or a failure naming what to do about it.
83
+ *
84
+ * The fail-closed half of the contract. Call it from any layer that is about
85
+ * to resolve a relative path, so an unestablished workspace is a stop rather
86
+ * than a guess.
87
+ */
88
+ export function requireWorkspace(context, where) {
89
+ if (context === null || context === undefined)
90
+ throw new WorkspaceNotEstablished(where);
91
+ return context;
92
+ }
93
+ /**
94
+ * Read the canonical workspace out of an environment.
95
+ *
96
+ * Returns null rather than throwing, so a caller can distinguish "nobody said"
97
+ * from "somebody said something unusable" and report the right one.
98
+ */
99
+ export function workspaceFromEnvironment(env, extra = []) {
100
+ const named = env[WORKSPACE_ENV];
101
+ if (typeof named !== 'string' || named === '')
102
+ return null;
103
+ if (!isAbsoluteLocalPath(named))
104
+ return null;
105
+ return establishWorkspace(named, 'environment', extra);
106
+ }
107
+ /**
108
+ * An absolute path for a workspace-relative one, or a refusal.
109
+ *
110
+ * The single resolution every layer routes through. `..` is resolved *before*
111
+ * the containment test, so `../elsewhere/notes.md` is refused rather than
112
+ * quietly landing a directory up — the check that a literal prefix comparison
113
+ * gets wrong.
114
+ *
115
+ * An already-absolute input is accepted only when it is inside the workspace,
116
+ * which keeps a caller from smuggling an outside path through a parameter
117
+ * documented as relative.
118
+ */
119
+ export function resolveInWorkspace(context, relative) {
120
+ const joined = isAbsoluteLocalPath(relative)
121
+ ? resolveTraversal(relative)
122
+ : resolveTraversal(`${context.root}/${relative}`);
123
+ if (!containsPath(context.root, joined))
124
+ throw new WorkspaceEscape(relative);
125
+ return joined;
126
+ }
127
+ /**
128
+ * Whether a path lands inside the canonical workspace.
129
+ *
130
+ * The containment question for a boundary that must answer before the side
131
+ * effect, not after it.
132
+ */
133
+ export function insideWorkspace(context, candidate) {
134
+ return containsPath(context.root, candidate);
135
+ }
136
+ /**
137
+ * The workspace-relative, forward-slashed spelling of a path.
138
+ *
139
+ * What a receipt, a Library row and a verifier request should carry, so the
140
+ * same file is one string everywhere and no machine's directory names ride
141
+ * along. Returns null when the path is outside, so a caller cannot treat a
142
+ * failed conversion as a success and emit an absolute path.
143
+ */
144
+ export function workspaceRelative(context, candidate) {
145
+ return relativeToRoot(resolveTraversal(candidate), context.root);
146
+ }
147
+ /**
148
+ * Whether two layers agree about the workspace.
149
+ *
150
+ * The assertion a composed run makes at startup. Comparing normalised
151
+ * spellings rather than raw strings is the point: `D:\Ws` and `d:/Ws/` are one
152
+ * directory, and a mismatch reported between those two would send somebody
153
+ * looking for a bug that is not there.
154
+ */
155
+ export function sameWorkspace(left, right) {
156
+ return normalisePath(resolveTraversal(left)) === normalisePath(resolveTraversal(right));
157
+ }
158
+ //# sourceMappingURL=workspace.js.map
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@deepwatch/dsh-contracts",
3
+ "version": "0.1.0",
4
+ "description": "Watch Bridge wire contracts shared by the Host plugins and the browser halves",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Sayed Allam",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/oxbshw/watch-skill.git",
11
+ "directory": "workspace/packages/watch/contracts"
12
+ },
13
+ "homepage": "https://github.com/oxbshw/watch-skill/tree/main/workspace#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/oxbshw/watch-skill/issues"
16
+ },
17
+ "keywords": [
18
+ "deepwatch",
19
+ "deepseek-harness",
20
+ "watch-skill",
21
+ "contracts",
22
+ "protocol"
23
+ ],
24
+ "main": "lib/index.js",
25
+ "types": "lib/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./lib/index.d.ts",
29
+ "default": "./lib/index.js"
30
+ },
31
+ "./identity": {
32
+ "types": "./lib/identity.d.ts",
33
+ "default": "./lib/identity.js"
34
+ },
35
+ "./query": {
36
+ "types": "./lib/query.d.ts",
37
+ "default": "./lib/query.js"
38
+ },
39
+ "./query/wire": {
40
+ "types": "./lib/query/wire.d.ts",
41
+ "default": "./lib/query/wire.js"
42
+ },
43
+ "./query/validate": {
44
+ "types": "./lib/query/validate.d.ts",
45
+ "default": "./lib/query/validate.js"
46
+ },
47
+ "./package.json": "./package.json"
48
+ },
49
+ "files": [
50
+ "lib/**/*.js",
51
+ "lib/**/*.d.ts"
52
+ ],
53
+ "sideEffects": false,
54
+ "engines": {
55
+ "node": "^22.19.0 || >=24.0.0"
56
+ },
57
+ "publishConfig": {
58
+ "access": "public"
59
+ }
60
+ }