@superdoc-dev/sdk 1.0.0-alpha.2 → 1.0.0-alpha.3

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/package.json CHANGED
@@ -1,20 +1,21 @@
1
1
  {
2
2
  "name": "@superdoc-dev/sdk",
3
- "version": "1.0.0-alpha.2",
3
+ "version": "1.0.0-alpha.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
7
7
  "module": "./src/index.ts",
8
8
  "files": [
9
9
  "src",
10
- "skills"
10
+ "skills",
11
+ "tools"
11
12
  ],
12
13
  "optionalDependencies": {
13
- "@superdoc-dev/sdk-darwin-arm64": "1.0.0-alpha.2",
14
- "@superdoc-dev/sdk-darwin-x64": "1.0.0-alpha.2",
15
- "@superdoc-dev/sdk-linux-arm64": "1.0.0-alpha.2",
16
- "@superdoc-dev/sdk-linux-x64": "1.0.0-alpha.2",
17
- "@superdoc-dev/sdk-windows-x64": "1.0.0-alpha.2"
14
+ "@superdoc-dev/sdk-darwin-arm64": "1.0.0-alpha.3",
15
+ "@superdoc-dev/sdk-darwin-x64": "1.0.0-alpha.3",
16
+ "@superdoc-dev/sdk-linux-arm64": "1.0.0-alpha.3",
17
+ "@superdoc-dev/sdk-linux-x64": "1.0.0-alpha.3",
18
+ "@superdoc-dev/sdk-windows-x64": "1.0.0-alpha.3"
18
19
  },
19
20
  "devDependencies": {
20
21
  "@types/bun": "^1.3.8",
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: editing-docx
2
+ name: superdoc
3
3
  description: Session-first SuperDoc CLI workflows for querying and editing DOCX files (find/inspect, comments lifecycle, text mutations, tracked-changes review, and close safely).
4
4
  ---
5
5
 
@@ -130,6 +130,10 @@ For stateless mutate commands, provide `--out <path>`.
130
130
  - `create paragraph` supports `--change-mode tracked` for tracked structural creation.
131
131
  - `replace`, `insert`, `delete`, and `format bold` support `--change-mode tracked`.
132
132
  - `track-changes *` commands are for review lifecycle (list/get/accept/reject), not content insertion.
133
+ - Deterministic tracked-change outcomes:
134
+ - unknown/stale ids -> `TRACK_CHANGE_NOT_FOUND`
135
+ - no applicable `accept-all`/`reject-all` change -> `NO_OP` receipt
136
+ - missing tracking capability -> `TRACK_CHANGE_COMMAND_UNAVAILABLE`
133
137
 
134
138
  ## Scenario Prompts (Copy/Paste)
135
139
 
@@ -1,6 +1,9 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import path from 'node:path';
2
5
  import { SuperDocCliError } from '../runtime/errors';
3
- import { getSkill, listSkills } from '../skills';
6
+ import { getSkill, installSkill, listSkills } from '../skills';
4
7
 
5
8
  describe('listSkills', () => {
6
9
  test('returns an array of skill names', () => {
@@ -91,3 +94,73 @@ describe('getSkill', () => {
91
94
  }
92
95
  });
93
96
  });
97
+
98
+ describe('installSkill', () => {
99
+ test('installs bundled skill to Claude project skills directory', async () => {
100
+ const workdir = await mkdtemp(path.join(tmpdir(), 'superdoc-sdk-skills-project-'));
101
+ try {
102
+ const result = installSkill('editing-docx', { cwd: workdir });
103
+ const expectedPath = path.join(workdir, '.claude', 'skills', 'editing-docx', 'SKILL.md');
104
+ const content = await readFile(expectedPath, 'utf8');
105
+
106
+ expect(result.path).toBe(expectedPath);
107
+ expect(result.scope).toBe('project');
108
+ expect(result.written).toBe(true);
109
+ expect(result.overwritten).toBe(false);
110
+ expect(content).toContain('superdoc open');
111
+ } finally {
112
+ await rm(workdir, { recursive: true, force: true });
113
+ }
114
+ });
115
+
116
+ test('installs bundled skill to Claude user skills directory', async () => {
117
+ const homeDir = await mkdtemp(path.join(tmpdir(), 'superdoc-sdk-skills-user-'));
118
+ try {
119
+ const result = installSkill('editing-docx', { scope: 'user', homeDir });
120
+ const expectedPath = path.join(homeDir, '.claude', 'skills', 'editing-docx', 'SKILL.md');
121
+ const content = await readFile(expectedPath, 'utf8');
122
+
123
+ expect(result.path).toBe(expectedPath);
124
+ expect(result.scope).toBe('user');
125
+ expect(result.written).toBe(true);
126
+ expect(result.overwritten).toBe(false);
127
+ expect(content).toContain('superdoc open');
128
+ } finally {
129
+ await rm(homeDir, { recursive: true, force: true });
130
+ }
131
+ });
132
+
133
+ test('returns without writing when overwrite is false and target exists', async () => {
134
+ const workdir = await mkdtemp(path.join(tmpdir(), 'superdoc-sdk-skills-no-overwrite-'));
135
+ const skillFile = path.join(workdir, '.claude', 'skills', 'editing-docx', 'SKILL.md');
136
+ try {
137
+ await mkdir(path.dirname(skillFile), { recursive: true });
138
+ await writeFile(skillFile, 'custom-content', 'utf8');
139
+ const result = installSkill('editing-docx', { cwd: workdir, overwrite: false });
140
+ const content = await readFile(skillFile, 'utf8');
141
+
142
+ expect(result.written).toBe(false);
143
+ expect(result.overwritten).toBe(false);
144
+ expect(content).toBe('custom-content');
145
+ } finally {
146
+ await rm(workdir, { recursive: true, force: true });
147
+ }
148
+ });
149
+
150
+ test('overwrites existing target by default', async () => {
151
+ const workdir = await mkdtemp(path.join(tmpdir(), 'superdoc-sdk-skills-overwrite-'));
152
+ const skillFile = path.join(workdir, '.claude', 'skills', 'editing-docx', 'SKILL.md');
153
+ try {
154
+ await mkdir(path.dirname(skillFile), { recursive: true });
155
+ await writeFile(skillFile, 'old-content', 'utf8');
156
+ const result = installSkill('editing-docx', { cwd: workdir });
157
+ const content = await readFile(skillFile, 'utf8');
158
+
159
+ expect(result.written).toBe(true);
160
+ expect(result.overwritten).toBe(true);
161
+ expect(content).toContain('superdoc open');
162
+ } finally {
163
+ await rm(workdir, { recursive: true, force: true });
164
+ }
165
+ });
166
+ });
@@ -0,0 +1,96 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { dispatchSuperDocTool, resolveToolOperation } from '../tools';
3
+ import { SuperDocCliError } from '../runtime/errors';
4
+
5
+ describe('tools dispatch constraints', () => {
6
+ test('intent tool name resolves to doc.find operation', async () => {
7
+ const operationId = await resolveToolOperation('find_content');
8
+ expect(operationId).toBe('doc.find');
9
+ });
10
+
11
+ test('enforces requiresOneOf for doc.find (type | query)', async () => {
12
+ const client = {
13
+ doc: {
14
+ find: async () => ({ ok: true }),
15
+ },
16
+ };
17
+
18
+ try {
19
+ await dispatchSuperDocTool(client, 'find_content', {});
20
+ throw new Error('Expected dispatch to fail.');
21
+ } catch (error) {
22
+ expect(error).toBeInstanceOf(SuperDocCliError);
23
+ const cliError = error as SuperDocCliError;
24
+ expect(cliError.code).toBe('INVALID_ARGUMENT');
25
+ expect(cliError.message).toContain('One of the following arguments is required');
26
+ }
27
+ });
28
+
29
+ test('enforces mutuallyExclusive constraints for doc.find query + flat flags', async () => {
30
+ let called = false;
31
+ const client = {
32
+ doc: {
33
+ find: async () => {
34
+ called = true;
35
+ return { ok: true };
36
+ },
37
+ },
38
+ };
39
+
40
+ try {
41
+ await dispatchSuperDocTool(client, 'find_content', {
42
+ query: { select: { type: 'text', pattern: 'Wilde' } },
43
+ type: 'text',
44
+ });
45
+ throw new Error('Expected dispatch to fail.');
46
+ } catch (error) {
47
+ expect(error).toBeInstanceOf(SuperDocCliError);
48
+ const cliError = error as SuperDocCliError;
49
+ expect(cliError.code).toBe('INVALID_ARGUMENT');
50
+ expect(cliError.message).toContain('mutually exclusive');
51
+ expect(called).toBe(false);
52
+ }
53
+ });
54
+
55
+ test('enforces requiredWhen for doc.find text selectors', async () => {
56
+ const client = {
57
+ doc: {
58
+ find: async () => ({ ok: true }),
59
+ },
60
+ };
61
+
62
+ try {
63
+ await dispatchSuperDocTool(client, 'find_content', {
64
+ type: 'text',
65
+ });
66
+ throw new Error('Expected dispatch to fail.');
67
+ } catch (error) {
68
+ expect(error).toBeInstanceOf(SuperDocCliError);
69
+ const cliError = error as SuperDocCliError;
70
+ expect(cliError.code).toBe('INVALID_ARGUMENT');
71
+ expect(cliError.message).toContain('required by constraints');
72
+ }
73
+ });
74
+
75
+ test('dispatches when constraints are satisfied', async () => {
76
+ const client = {
77
+ doc: {
78
+ find: async (args: Record<string, unknown>) => ({
79
+ ok: true,
80
+ query: args,
81
+ }),
82
+ },
83
+ };
84
+
85
+ const result = await dispatchSuperDocTool(client, 'find_content', {
86
+ query: {
87
+ select: { type: 'text', pattern: 'Wilde' },
88
+ },
89
+ });
90
+
91
+ expect(result).toMatchObject({
92
+ ok: true,
93
+ });
94
+ });
95
+ });
96
+