@xmoon76/dsh-pi-tui 0.1.5 → 0.1.7
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/index.d.mts +112 -1
- package/dist/index.mjs +1463 -159
- package/package.json +1 -2
- package/scripts/naming-gate.mjs +43 -1
- package/scripts/repair-core.mjs +35 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmoon76/dsh-pi-tui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "The dsh-pi-tui bundle: a TUI surface for DeepSeek Harness profiles (dsh --profile pi-tui)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "XMoon <xmoonox@gmail.com>",
|
|
@@ -76,7 +76,6 @@
|
|
|
76
76
|
"@deepseek-ai/dsh-sandbox-policy": "^0.1.0-rc.6",
|
|
77
77
|
"@deepseek-ai/dsh-session": "^0.1.0-rc.6",
|
|
78
78
|
"@deepseek-ai/dsh-session-persistence": "^0.1.0-rc.6",
|
|
79
|
-
"@deepseek-ai/dsh-session-query": "^0.1.0-rc.6",
|
|
80
79
|
"@deepseek-ai/dsh-session-title": "^0.1.0-rc.6",
|
|
81
80
|
"@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
|
|
82
81
|
"@deepseek-ai/dsh-shell": "^0.1.0-rc.6",
|
package/scripts/naming-gate.mjs
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* @module naming-gate
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { existsSync, readFileSync } from 'node:fs'
|
|
12
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
|
13
13
|
import { spawnSync } from 'node:child_process'
|
|
14
14
|
import { join, dirname } from 'node:path'
|
|
15
15
|
import { fileURLToPath } from 'node:url'
|
|
@@ -93,6 +93,48 @@ for (const workflow of ['ci.yml', 'release.yml']) {
|
|
|
93
93
|
!text.includes('@xmoon76-dsh-pi-tui-*.tgz') && text.includes('xmoon76-dsh-pi-tui-*.tgz'))
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
// 8. The bundle's dsh dependency contract is declared in ONE place and the
|
|
97
|
+
// gate enforces it against the actual source (AGENTS.md rule 7): every
|
|
98
|
+
// `@deepseek-ai/*` package imported by `src/` (value OR type) must be a
|
|
99
|
+
// peerDependency, and every peerDependency must be imported by `src/`.
|
|
100
|
+
// A peer that only appears in a comment or a test is not a runtime
|
|
101
|
+
// contract; an import with no peer breaks the in-box resolution rule and
|
|
102
|
+
// can duplicate `@deepseek-ai` copies in the profile (first tool call
|
|
103
|
+
// crashes). `dsh-session-query` was removed by this rule: the picker
|
|
104
|
+
// types it structurally (src/sessions.ts) and reads the service off the
|
|
105
|
+
// live context, so it never needed the package.
|
|
106
|
+
const bundleSrc = join(ROOT, 'packages', 'dsh-pi-tui', 'src')
|
|
107
|
+
const srcFiles = (() => {
|
|
108
|
+
const out = []
|
|
109
|
+
const walk = (dir) => {
|
|
110
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
111
|
+
const path = join(dir, entry.name)
|
|
112
|
+
if (entry.isDirectory()) walk(path)
|
|
113
|
+
else if (entry.name.endsWith('.ts') && !entry.name.endsWith('.d.ts')) out.push(path)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
walk(bundleSrc)
|
|
117
|
+
return out
|
|
118
|
+
})()
|
|
119
|
+
const srcImports = new Set()
|
|
120
|
+
for (const file of srcFiles) {
|
|
121
|
+
const text = readFileSync(file, 'utf8')
|
|
122
|
+
for (const match of text.matchAll(/from\s+['"]@deepseek-ai\/([^'"\/]+)/g)) {
|
|
123
|
+
srcImports.add(`@deepseek-ai/${match[1]}`)
|
|
124
|
+
}
|
|
125
|
+
// Runtime service access via ctx.get('name') must stay structural: a
|
|
126
|
+
// package that is ONLY read this way (never imported) has no place in
|
|
127
|
+
// the peer list — same rule as dsh-session-query.
|
|
128
|
+
}
|
|
129
|
+
const peers = new Set(Object.keys(bundlePkg.peerDependencies ?? {}))
|
|
130
|
+
for (const imported of [...srcImports].sort()) {
|
|
131
|
+
check(`peer declared for src import ${imported}`, peers.has(imported), peers.has(imported) ? '' : 'missing from peerDependencies')
|
|
132
|
+
}
|
|
133
|
+
for (const peer of [...peers].sort()) {
|
|
134
|
+
if (!peer.startsWith('@deepseek-ai/')) continue // chalk etc. are regular deps
|
|
135
|
+
check(`peer ${peer} is imported by src/`, srcImports.has(peer), srcImports.has(peer) ? '' : 'no import in src/ (dead peer)')
|
|
136
|
+
}
|
|
137
|
+
|
|
96
138
|
for (const line of checks) console.log(line)
|
|
97
139
|
if (failures.length > 0) {
|
|
98
140
|
console.error(`\nnaming-gate FAILED (${failures.length}):`)
|
package/scripts/repair-core.mjs
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* @module repair-core
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
|
-
import { constants } from 'node:zlib'
|
|
36
|
+
import { constants, zstdDecompressSync } from 'node:zlib'
|
|
37
37
|
|
|
38
38
|
/** Zstandard frame magic, little-endian (`\x28\xB5\x2F\xFD`). */
|
|
39
39
|
export const ZSTD_MAGIC = 0xfd2fb528
|
|
@@ -247,6 +247,40 @@ export function compressLog(text, zstdCompressSync) {
|
|
|
247
247
|
if (chunkBytes >= 16 * 1024) flushChunk()
|
|
248
248
|
}
|
|
249
249
|
flushChunk()
|
|
250
|
+
// Node's zstdCompressSync can emit a trailing EMPTY frame after the content
|
|
251
|
+
// frame (observed when the compressed output lands on a 16 KiB boundary,
|
|
252
|
+
// e.g. exactly 16384 bytes): the returned buffer is then two concatenated
|
|
253
|
+
// frames, the second a valid zero-content frame. The dsh reader tolerates
|
|
254
|
+
// it, but every non-first frame must end on a JSONL record boundary, so
|
|
255
|
+
// the layout gate flags it. The harness writer uses the async compressor,
|
|
256
|
+
// which never emits it. Strip such empty frames from each compressed
|
|
257
|
+
// output so a repaired artifact always matches the harness layout exactly.
|
|
258
|
+
if (frames.length > 0) {
|
|
259
|
+
const stripped = []
|
|
260
|
+
for (const frame of frames) {
|
|
261
|
+
const { frames: inner } = scanZstdFrames(frame)
|
|
262
|
+
if (inner.length === 1) {
|
|
263
|
+
stripped.push(frame)
|
|
264
|
+
continue
|
|
265
|
+
}
|
|
266
|
+
for (let i = 0; i < inner.length; i += 1) {
|
|
267
|
+
const segment = frame.subarray(inner[i].start, inner[i].end)
|
|
268
|
+
try {
|
|
269
|
+
if (zstdDecompressSync(segment).length === 0) continue // empty frame
|
|
270
|
+
} catch {
|
|
271
|
+
// A frame that fails to decompress is content — keep it so the
|
|
272
|
+
// post-write verify can report it instead of silently dropping it.
|
|
273
|
+
}
|
|
274
|
+
stripped.push(segment)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// Always use the stripped result: stripping is identity for single-frame
|
|
278
|
+
// chunks, and a chunk that expands to N segments with an empty frame
|
|
279
|
+
// dropped can end up with the same element count as before (e.g. one
|
|
280
|
+
// two-frame chunk -> one kept segment), so a length comparison would
|
|
281
|
+
// wrongly fall through to the unstripped concat below.
|
|
282
|
+
return Buffer.concat(stripped)
|
|
283
|
+
}
|
|
250
284
|
return frames.length > 0 ? Buffer.concat(frames) : zstdCompressSync(Buffer.alloc(0), CHECKSUMMED_OPTIONS)
|
|
251
285
|
}
|
|
252
286
|
|