@xmoon76/dsh-pi-tui 0.1.4 → 0.1.6

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmoon76/dsh-pi-tui",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
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",
@@ -119,7 +118,10 @@
119
118
  "@deepseek-ai/dsh-jobs": "^0.1.0-rc.6",
120
119
  "@deepseek-ai/dsh-shell": "^0.1.0-rc.6",
121
120
  "@deepseek-ai/dsh-credentials": "^0.1.0-rc.6",
122
- "@deepseek-ai/dsh-agent-presets": "^0.1.0-rc.6"
121
+ "@deepseek-ai/dsh-agent-presets": "^0.1.0-rc.6",
122
+ "@deepseek-ai/dsh-agent-loop": "^0.1.0-rc.6",
123
+ "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.6",
124
+ "@deepseek-ai/dsh-scope": "^0.1.0-rc.6"
123
125
  },
124
126
  "engines": {
125
127
  "node": ">=22.19.0"
@@ -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}):`)
@@ -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