@eatosin/dshify-plugin 0.0.0-stage → 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.
- package/LICENSE +21 -0
- package/README.md +51 -2
- package/cordis.patch.yml +4 -0
- package/icon.svg +6 -0
- package/index.d.ts +5 -0
- package/index.js +360 -0
- package/locale/en.json +6 -0
- package/package.json +59 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Owadokun Tosin Tobi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,52 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @eatosin/dshify-plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
DeepSeek Harness (DSH) plugin for [dshify](https://github.com/Eatosin/dshify). It ports an OpenCode repository to a DSH surface from inside a session.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
In DSH, open the plugin installation page and enter the package name:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
@eatosin/dshify-plugin
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Compatibility: DSH `0.2.2-rc.1` and later. The plugin requires Node.js 22.12 or later. It depends on `@eatosin/dshify`, which contains the migration engine and the offline core.
|
|
14
|
+
|
|
15
|
+
## What it adds
|
|
16
|
+
|
|
17
|
+
The plugin adds one model-facing tool and one human command.
|
|
18
|
+
|
|
19
|
+
| Name | Purpose |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `migrate_harness` tool | Run `scan`, `plan`, `apply`, or `check` on a repository. |
|
|
22
|
+
| `/migrate` command | The same actions from the composer. Default action is `plan`. |
|
|
23
|
+
|
|
24
|
+
The tool input:
|
|
25
|
+
|
|
26
|
+
| Field | Type | Default | Notes |
|
|
27
|
+
|---|---|---|---|
|
|
28
|
+
| `action` | `scan` \| `plan` \| `apply` \| `check` | required | `apply` writes files. |
|
|
29
|
+
| `path` | string | `.` | Repository path. Relative paths use the session working directory. |
|
|
30
|
+
| `dest` | string | `.dsh` | Destination tree inside the repository. |
|
|
31
|
+
| `presets` | boolean | `false` | Also emit the `.dsh/harness/` preset bundle. |
|
|
32
|
+
| `force` | boolean | `false` | Replace human-edited artifacts during `apply`. |
|
|
33
|
+
|
|
34
|
+
`apply` writes only inside `dest` and the lockfile. It never deletes user files. If a file is not owned by dshify, `apply` reports a conflict.
|
|
35
|
+
|
|
36
|
+
## Command
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
/migrate plan # show the conversion plan
|
|
40
|
+
/migrate apply . --presets # write the surface and the preset bundle
|
|
41
|
+
/migrate check # verify files, lockfile and DSH discovery
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Security
|
|
45
|
+
|
|
46
|
+
- The migration engine is offline. `scan`, `plan`, and `check` never make network calls.
|
|
47
|
+
- `apply` writes files. Use a test branch first.
|
|
48
|
+
- No secret value from the source repository enters the tool output.
|
|
49
|
+
|
|
50
|
+
## License
|
|
51
|
+
|
|
52
|
+
MIT. See [LICENSE](LICENSE).
|
package/cordis.patch.yml
ADDED
package/icon.svg
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="dshify migration">
|
|
2
|
+
<rect x="4" y="10" width="56" height="44" rx="7" fill="#111827"/>
|
|
3
|
+
<path d="M14 24l9 8-9 8" fill="none" stroke="#34d399" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
|
4
|
+
<path d="M28 40h22" stroke="#e5e7eb" stroke-width="4" stroke-linecap="round"/>
|
|
5
|
+
<circle cx="52" cy="18" r="6" fill="#34d399"/>
|
|
6
|
+
</svg>
|
package/index.d.ts
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dshify migration plugin for DeepSeek Harness.
|
|
3
|
+
*
|
|
4
|
+
* Host half: registers the model-facing `migrate_harness` tool and the
|
|
5
|
+
* human-facing `/migrate` command. All work happens in @eatosin/dshify, the
|
|
6
|
+
* offline CLI engine. The plugin adds no network access.
|
|
7
|
+
*
|
|
8
|
+
* Loader contract: `apply(ctx)` registers resources through `ctx.effect`.
|
|
9
|
+
* The tool definition is plain JSON Schema data; no harness package import is
|
|
10
|
+
* needed, so the bundle has no peer dependencies.
|
|
11
|
+
*/
|
|
12
|
+
import { readFile, realpath, stat } from 'node:fs/promises';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import {
|
|
15
|
+
DEFAULT_SCAN_LIMITS,
|
|
16
|
+
VERSION,
|
|
17
|
+
applyRepo,
|
|
18
|
+
checkRepo,
|
|
19
|
+
planRepo,
|
|
20
|
+
scanRepo,
|
|
21
|
+
} from '@eatosin/dshify';
|
|
22
|
+
|
|
23
|
+
export const name = 'dshify';
|
|
24
|
+
export const inject = ['tools'];
|
|
25
|
+
|
|
26
|
+
const ACTIONS = ['scan', 'plan', 'apply', 'check'];
|
|
27
|
+
const LOCKFILE = '.dshify.lock.json';
|
|
28
|
+
|
|
29
|
+
const TOOL_DESCRIPTION =
|
|
30
|
+
'Port an OpenCode repository to a DeepSeek Harness (DSH) surface. ' +
|
|
31
|
+
'Actions: "scan" lists the source skills, commands, personas and MCP servers (read-only); ' +
|
|
32
|
+
'"plan" shows the DSH artifacts, findings and losses (read-only); ' +
|
|
33
|
+
'"apply" writes .dsh/ and .dshify.lock.json (writes files; use a test branch); ' +
|
|
34
|
+
'"check" verifies the files, the lockfile and DSH discovery (read-only). ' +
|
|
35
|
+
'apply never deletes user files and refuses to overwrite files it does not own.';
|
|
36
|
+
|
|
37
|
+
const SUMMARY_SCHEMA = {
|
|
38
|
+
type: 'object',
|
|
39
|
+
additionalProperties: true,
|
|
40
|
+
description: 'Counts for the completed action.',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const OUTPUT_SCHEMA = {
|
|
44
|
+
type: 'object',
|
|
45
|
+
additionalProperties: false,
|
|
46
|
+
properties: {
|
|
47
|
+
action: { type: 'string', enum: ACTIONS, description: 'Action that ran.' },
|
|
48
|
+
ok: { type: 'boolean', description: 'True when the action found no blocking problem.' },
|
|
49
|
+
root: { type: 'string', description: 'Absolute repository root.' },
|
|
50
|
+
exitCode: { type: 'integer', description: '0, 3 (drift or conflict) or 4 (verification failed).' },
|
|
51
|
+
summary: SUMMARY_SCHEMA,
|
|
52
|
+
findings: { type: 'array', items: { type: 'object', additionalProperties: true } },
|
|
53
|
+
artifacts: { type: 'array', items: { type: 'object', additionalProperties: true } },
|
|
54
|
+
verification: { type: 'object', additionalProperties: true },
|
|
55
|
+
},
|
|
56
|
+
required: ['action', 'ok', 'root'],
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const TOOL = {
|
|
60
|
+
name: 'migrate_harness',
|
|
61
|
+
description: TOOL_DESCRIPTION,
|
|
62
|
+
parameters: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
additionalProperties: false,
|
|
65
|
+
properties: {
|
|
66
|
+
action: {
|
|
67
|
+
type: 'string',
|
|
68
|
+
enum: ACTIONS,
|
|
69
|
+
description: 'scan, plan, apply or check.',
|
|
70
|
+
},
|
|
71
|
+
path: {
|
|
72
|
+
type: 'string',
|
|
73
|
+
description: 'Repository path. A relative path resolves from the working directory. Default: ".".',
|
|
74
|
+
},
|
|
75
|
+
dest: {
|
|
76
|
+
type: 'string',
|
|
77
|
+
description: 'Destination tree inside the repository. Default: ".dsh".',
|
|
78
|
+
},
|
|
79
|
+
presets: {
|
|
80
|
+
type: 'boolean',
|
|
81
|
+
description: 'Also emit the .dsh/harness/ preset bundle (persona and MCP rows).',
|
|
82
|
+
},
|
|
83
|
+
force: {
|
|
84
|
+
type: 'boolean',
|
|
85
|
+
description: 'Replace human-edited artifacts during apply. Use with care.',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
required: ['action'],
|
|
89
|
+
},
|
|
90
|
+
output: {
|
|
91
|
+
schema: OUTPUT_SCHEMA,
|
|
92
|
+
render(_args, value) {
|
|
93
|
+
return [{ type: 'text', text: formatResult(value) }];
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
presentCall(args) {
|
|
97
|
+
const action = typeof args?.action === 'string' ? args.action : 'migration';
|
|
98
|
+
return { card: 'generic', title: `dshify ${action}`, kind: 'other', rawInput: args };
|
|
99
|
+
},
|
|
100
|
+
async execute(rawArgs) {
|
|
101
|
+
const input = validateInput(rawArgs);
|
|
102
|
+
return await runAction(input);
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const COMMAND = {
|
|
107
|
+
name: 'migrate',
|
|
108
|
+
description: 'Port an OpenCode repository to DSH (scan, plan, apply, check).',
|
|
109
|
+
handler: async (invocation) => {
|
|
110
|
+
try {
|
|
111
|
+
const input = parseCommand(invocation.rawInput);
|
|
112
|
+
return { kind: 'success', text: formatResult(await runAction(input)) };
|
|
113
|
+
} catch (error) {
|
|
114
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
115
|
+
return { kind: 'error', text: message };
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Register the tool and, when the profile has the command service, /migrate.
|
|
122
|
+
* @param {object} ctx - Cordis context.
|
|
123
|
+
*/
|
|
124
|
+
export function apply(ctx) {
|
|
125
|
+
ctx.effect(() => ctx.tools.register(TOOL));
|
|
126
|
+
|
|
127
|
+
const commands = typeof ctx.get === 'function' ? ctx.get('commands') : undefined;
|
|
128
|
+
if (commands !== undefined && typeof commands.register === 'function') {
|
|
129
|
+
ctx.effect(() => commands.register(COMMAND));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Validate model input. The registry enforces the JSON Schema; this adds the
|
|
135
|
+
* path and enum guarantees the action bodies rely on.
|
|
136
|
+
* @param {unknown} value
|
|
137
|
+
*/
|
|
138
|
+
function validateInput(value) {
|
|
139
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
140
|
+
throw new Error('migrate_harness: arguments must be an object');
|
|
141
|
+
}
|
|
142
|
+
const input = /** @type {Record<string, unknown>} */ (value);
|
|
143
|
+
const action = input['action'];
|
|
144
|
+
if (typeof action !== 'string' || !ACTIONS.includes(action)) {
|
|
145
|
+
throw new Error(`migrate_harness: action must be one of ${ACTIONS.join(', ')}`);
|
|
146
|
+
}
|
|
147
|
+
const readString = (key) => {
|
|
148
|
+
const field = input[key];
|
|
149
|
+
if (field === undefined) return undefined;
|
|
150
|
+
if (typeof field !== 'string' || field.length === 0) {
|
|
151
|
+
throw new Error(`migrate_harness: ${key} must be a non-empty string`);
|
|
152
|
+
}
|
|
153
|
+
return field;
|
|
154
|
+
};
|
|
155
|
+
const readBoolean = (key) => {
|
|
156
|
+
const field = input[key];
|
|
157
|
+
if (field === undefined) return false;
|
|
158
|
+
if (typeof field !== 'boolean') throw new Error(`migrate_harness: ${key} must be a boolean`);
|
|
159
|
+
return field;
|
|
160
|
+
};
|
|
161
|
+
for (const key of Object.keys(input)) {
|
|
162
|
+
if (!['action', 'path', 'dest', 'presets', 'force'].includes(key)) {
|
|
163
|
+
throw new Error(`migrate_harness: unknown argument ${key}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
action,
|
|
168
|
+
path: readString('path'),
|
|
169
|
+
dest: readString('dest') ?? '.dsh',
|
|
170
|
+
presets: readBoolean('presets'),
|
|
171
|
+
force: readBoolean('force'),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Resolve the scan root and its repository root without importing CLI internals.
|
|
177
|
+
* @param {string} target
|
|
178
|
+
*/
|
|
179
|
+
async function resolveRoots(target) {
|
|
180
|
+
const requested = path.resolve(process.cwd(), target);
|
|
181
|
+
let scanRoot;
|
|
182
|
+
try {
|
|
183
|
+
scanRoot = await realpath(requested);
|
|
184
|
+
} catch {
|
|
185
|
+
throw new Error(`path does not exist: ${target}`);
|
|
186
|
+
}
|
|
187
|
+
const info = await stat(scanRoot);
|
|
188
|
+
if (!info.isDirectory()) throw new Error(`path is not a directory: ${target}`);
|
|
189
|
+
|
|
190
|
+
let current = scanRoot;
|
|
191
|
+
for (;;) {
|
|
192
|
+
try {
|
|
193
|
+
await stat(path.join(current, '.git'));
|
|
194
|
+
return { scanRoot, repoRoot: current };
|
|
195
|
+
} catch {
|
|
196
|
+
const parent = path.dirname(current);
|
|
197
|
+
if (parent === current) return { scanRoot, repoRoot: scanRoot };
|
|
198
|
+
current = parent;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function readResource(absolute) {
|
|
204
|
+
return new Uint8Array(await readFile(absolute));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Run one action through the dshify engine.
|
|
209
|
+
* @param {{action: string, path?: string, dest: string, presets: boolean, force: boolean}} input
|
|
210
|
+
*/
|
|
211
|
+
async function runAction(input) {
|
|
212
|
+
const { scanRoot, repoRoot } = await resolveRoots(input.path ?? '.');
|
|
213
|
+
const ir = await scanRepo({ root: scanRoot, extraRoots: [], limits: DEFAULT_SCAN_LIMITS });
|
|
214
|
+
const base = {
|
|
215
|
+
repoRoot,
|
|
216
|
+
dest: input.dest,
|
|
217
|
+
presets: input.presets,
|
|
218
|
+
strategy: 'copy',
|
|
219
|
+
version: VERSION,
|
|
220
|
+
readResource,
|
|
221
|
+
};
|
|
222
|
+
const findings = [...ir.warnings];
|
|
223
|
+
|
|
224
|
+
if (input.action === 'scan') {
|
|
225
|
+
return {
|
|
226
|
+
action: 'scan',
|
|
227
|
+
ok: true,
|
|
228
|
+
root: repoRoot,
|
|
229
|
+
summary: {
|
|
230
|
+
skills: ir.skills.length,
|
|
231
|
+
commands: ir.commands.length,
|
|
232
|
+
personas: ir.personas.length,
|
|
233
|
+
mcpServers: ir.mcpServers.length,
|
|
234
|
+
findings: findings.length,
|
|
235
|
+
},
|
|
236
|
+
findings,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (input.action === 'plan') {
|
|
241
|
+
const planned = await planRepo(ir, base);
|
|
242
|
+
return {
|
|
243
|
+
action: 'plan',
|
|
244
|
+
ok: planned.conflicts.length === 0,
|
|
245
|
+
root: repoRoot,
|
|
246
|
+
summary: {
|
|
247
|
+
artifacts: planned.artifacts.length,
|
|
248
|
+
losses: planned.losses.length,
|
|
249
|
+
conflicts: planned.conflicts.length,
|
|
250
|
+
},
|
|
251
|
+
findings: [...findings, ...planned.losses],
|
|
252
|
+
artifacts: planned.artifacts.map((artifact) => ({
|
|
253
|
+
path: artifact.path,
|
|
254
|
+
kind: artifact.kind,
|
|
255
|
+
capability: artifact.capability,
|
|
256
|
+
})),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (input.action === 'apply') {
|
|
261
|
+
const applied = await applyRepo(ir, { ...base, lockfilePath: LOCKFILE, force: input.force });
|
|
262
|
+
const counts = {};
|
|
263
|
+
for (const result of applied.results) {
|
|
264
|
+
counts[result.status] = (counts[result.status] ?? 0) + 1;
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
action: 'apply',
|
|
268
|
+
ok: applied.ok,
|
|
269
|
+
root: repoRoot,
|
|
270
|
+
exitCode: applied.ok ? 0 : 3,
|
|
271
|
+
summary: { ...counts, conflicts: applied.conflicts.length, lockfile: applied.lockfilePath },
|
|
272
|
+
findings: [...findings, ...applied.findings],
|
|
273
|
+
artifacts: applied.results.map((result) => ({
|
|
274
|
+
path: result.path,
|
|
275
|
+
kind: result.kind,
|
|
276
|
+
capability: result.capability,
|
|
277
|
+
status: result.status,
|
|
278
|
+
})),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const checked = await checkRepo(ir, { ...base, lockfilePath: LOCKFILE, limits: DEFAULT_SCAN_LIMITS });
|
|
283
|
+
return {
|
|
284
|
+
action: 'check',
|
|
285
|
+
ok: checked.ok,
|
|
286
|
+
root: repoRoot,
|
|
287
|
+
exitCode: checked.exitCode,
|
|
288
|
+
summary: {
|
|
289
|
+
planned: checked.planned.artifacts.length,
|
|
290
|
+
visible: checked.verification.visible.length,
|
|
291
|
+
skipped: checked.verification.skipped.length,
|
|
292
|
+
findings: checked.findings.length,
|
|
293
|
+
},
|
|
294
|
+
findings: [...findings, ...checked.findings],
|
|
295
|
+
verification: {
|
|
296
|
+
roots: checked.verification.roots,
|
|
297
|
+
visible: checked.verification.visible.map((skill) => skill.path),
|
|
298
|
+
skipped: checked.verification.skipped,
|
|
299
|
+
complete: checked.verification.complete,
|
|
300
|
+
},
|
|
301
|
+
artifacts: checked.results.map((result) => ({
|
|
302
|
+
path: result.path,
|
|
303
|
+
kind: result.kind,
|
|
304
|
+
capability: result.capability,
|
|
305
|
+
status: result.status,
|
|
306
|
+
})),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Parse `/migrate [action] [path] [--presets] [--force]`.
|
|
312
|
+
* @param {string} rawInput
|
|
313
|
+
*/
|
|
314
|
+
function parseCommand(rawInput) {
|
|
315
|
+
const tokens = rawInput.trim().split(/\s+/).filter(Boolean);
|
|
316
|
+
let action = 'plan';
|
|
317
|
+
if (tokens.length > 0 && ACTIONS.includes(tokens[0])) action = tokens.shift();
|
|
318
|
+
let presets = false;
|
|
319
|
+
let force = false;
|
|
320
|
+
const positional = [];
|
|
321
|
+
for (const token of tokens) {
|
|
322
|
+
if (token === '--presets') presets = true;
|
|
323
|
+
else if (token === '--force') force = true;
|
|
324
|
+
else positional.push(token);
|
|
325
|
+
}
|
|
326
|
+
const target = positional[0];
|
|
327
|
+
return { action, path: target, dest: '.dsh', presets, force };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Model-facing text for one result.
|
|
332
|
+
* @param {object} value
|
|
333
|
+
*/
|
|
334
|
+
function formatResult(value) {
|
|
335
|
+
const lines = [`dshify ${value.action}: ${value.ok ? 'ok' : 'needs attention'}`];
|
|
336
|
+
if (value.exitCode !== undefined) lines.push(`exit code: ${value.exitCode}`);
|
|
337
|
+
lines.push(`root: ${value.root}`);
|
|
338
|
+
if (value.summary !== undefined) {
|
|
339
|
+
const summary = Object.entries(value.summary)
|
|
340
|
+
.map(([key, count]) => `${key}=${count}`)
|
|
341
|
+
.join(' ');
|
|
342
|
+
if (summary !== '') lines.push(`summary: ${summary}`);
|
|
343
|
+
}
|
|
344
|
+
if (Array.isArray(value.findings) && value.findings.length > 0) {
|
|
345
|
+
lines.push('findings:');
|
|
346
|
+
for (const finding of value.findings) {
|
|
347
|
+
const where = finding.path === undefined ? '' : ` ${finding.path}`;
|
|
348
|
+
lines.push(`- ${finding.code ?? finding.code} (${finding.severity ?? 'info'})${where}`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (Array.isArray(value.artifacts) && value.artifacts.length > 0) {
|
|
352
|
+
lines.push('artifacts:');
|
|
353
|
+
for (const artifact of value.artifacts.slice(0, 50)) {
|
|
354
|
+
const status = artifact.status === undefined ? '' : ` [${artifact.status}]`;
|
|
355
|
+
lines.push(`- ${artifact.path} (${artifact.capability})${status}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
lines.push('', JSON.stringify(value, null, 2));
|
|
359
|
+
return lines.join('\n');
|
|
360
|
+
}
|
package/locale/en.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,61 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eatosin/dshify-plugin",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Port an OpenCode repository to DeepSeek Harness from inside DSH: scan, plan, apply and check, with owned writes and discovery verification.",
|
|
5
|
+
"icon": "icon.svg",
|
|
6
|
+
"author": "Owadokun Tosin Tobi",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"main": "index.js",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./index.js",
|
|
12
|
+
"./package.json": "./package.json",
|
|
13
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
14
|
+
"./locale/*.json": "./locale/*.json",
|
|
15
|
+
"./icon": "./icon.svg"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"index.js",
|
|
19
|
+
"index.d.ts",
|
|
20
|
+
"cordis.patch.yml",
|
|
21
|
+
"locale/*.json",
|
|
22
|
+
"icon.svg",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=22.12.0",
|
|
28
|
+
"dsh": ">=0.2.2-rc.1"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"deepseek-harness",
|
|
32
|
+
"dsh",
|
|
33
|
+
"cordis",
|
|
34
|
+
"plugin",
|
|
35
|
+
"migration",
|
|
36
|
+
"opencode"
|
|
37
|
+
],
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/Eatosin/dshify.git",
|
|
41
|
+
"directory": "plugin"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://github.com/Eatosin/dshify#readme",
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/Eatosin/dshify/issues"
|
|
46
|
+
},
|
|
47
|
+
"dsh": {
|
|
48
|
+
"bundle": {
|
|
49
|
+
"patch": "./cordis.patch.yml"
|
|
50
|
+
},
|
|
51
|
+
"compatibility": {
|
|
52
|
+
"dshReleases": {
|
|
53
|
+
"0.2.2-rc.1+": "compatible"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"@eatosin/dshify": "^0.1.2"
|
|
59
|
+
},
|
|
60
|
+
"types": "index.d.ts"
|
|
61
|
+
}
|