@foggy-projects/deepseek-harness-plugin 0.4.0-beta.5 → 0.4.0-beta.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/README.md CHANGED
@@ -8,7 +8,7 @@ only when the user selects **Initialize Foggy**.
8
8
  ## Local beta installation
9
9
 
10
10
  ```powershell
11
- dsh plugin --profile web add --workspace-root ./foggy-projects-deepseek-harness-plugin-0.4.0-beta.5.tgz
11
+ dsh plugin --profile web add --workspace-root ./foggy-projects-deepseek-harness-plugin-0.4.0-beta.7.tgz
12
12
  ```
13
13
 
14
14
  Restart `dsh web`, open Settings → Plugins → Foggy Data Analysis, and initialize
@@ -23,8 +23,9 @@ dsh plugin --profile web add --workspace-root @foggy-projects/deepseek-harness-p
23
23
  For development, `FOGGY_ASSET_CACHE_DIRS` can contain platform-delimited verified
24
24
  asset-cache directories. It is not required for a normal install.
25
25
 
26
- Database credentials are deliberately outside the ordinary DSH settings
27
- document. The database and semantic-layer wizard is the next Bundle milestone.
26
+ Database credentials remain outside the ordinary DSH settings document. The
27
+ bundled onboarding Skill drives datasource discovery, semantic drafting,
28
+ checkpointed publication, and bounded read-only query verification.
28
29
 
29
30
  ## Native Skill and workspace contract
30
31
 
@@ -34,6 +35,15 @@ are available in every DSH workspace without copying or symlinking `.agents`.
34
35
  The current session `cwd` remains the workspace boundary for semantic drafts and
35
36
  evidence.
36
37
 
38
+ Opaque CLI profiles default to the private persistent
39
+ `<dataRoot>/cli-profiles` directory. Composite onboarding commands are
40
+ idempotent: unchanged completed phases are resumed rather than re-adding a
41
+ datasource or re-registering a published bundle. Settings detects legacy
42
+ temporary profiles and offers an explicit, validated migration into the
43
+ persistent store. A completed profile can also be bound non-destructively to an
44
+ additional DSH workspace when its reviewed connection contract and published
45
+ semantic digest are unchanged.
46
+
37
47
  CLI, Launcher, the analysis Skill, install state, and Runtime state live in the
38
48
  user-level Foggy component directories. The managed CLI is intentionally isolated
39
49
  and does not need to be on `PATH`. **Re-download / Repair** verifies the global
@@ -41,6 +51,19 @@ analysis Skill, backs up modified or outdated managed content, restores it, and
41
51
  invalidates DSH's Skill catalog. The onboarding Skill is bundled with the plugin
42
52
  and is restored by reinstalling or upgrading the plugin package.
43
53
 
54
+ The Foggy settings tab shows the persisted database/semantic onboarding stages,
55
+ offers pinned checks and repair for CLI, Launcher, and the managed analysis
56
+ Skill, and exports a private redacted diagnostics report. Runtime start is
57
+ idempotent: an already-recorded process is verified with `wait-ready` and
58
+ `capabilities` instead of being treated as a failed second start.
59
+
60
+ This beta remains a local dev/test integration. The bundled Runtime reports
61
+ `securityMode=none-dev-test-only` and must not be exposed to a network or used as
62
+ a production service.
63
+
64
+ See [`docs/PUBLIC-BETA-READINESS.md`](./docs/PUBLIC-BETA-READINESS.md) for the
65
+ tested public Beta scope, release gates, and stable-release blockers.
66
+
44
67
  ## Linux and WSL2 experience
45
68
 
46
69
  Ubuntu and WSL2 users can use the checked-in preflighted installer under
package/lib/client.js CHANGED
@@ -10,7 +10,10 @@ window.__ModuleLoader__.load({
10
10
  const passthroughSchema = { parse: (value) => value }
11
11
  const TYPERT_REMOTE = {
12
12
  package: '@foggy-projects/deepseek-harness-plugin',
13
- descriptors: ['status', 'plan', 'initialize', 'repair', 'runtimeStart', 'runtimeStop'].map((method) => ({
13
+ descriptors: [
14
+ 'status', 'plan', 'initialize', 'repair', 'repairCli', 'repairLauncher', 'repairAnalysisSkill',
15
+ 'migrateProfiles', 'diagnostics', 'runtimeStart', 'runtimeStop',
16
+ ].map((method) => ({
14
17
  id: `@foggy-projects/deepseek-harness-plugin#foggyIntegration/${method}`,
15
18
  service: 'foggyIntegration',
16
19
  namespace: 'foggyIntegration',
@@ -51,6 +54,12 @@ window.__ModuleLoader__.load({
51
54
  unavailable: '不可用',
52
55
  initialize: '初始化 Foggy',
53
56
  repair: '重新下载 / 修复',
57
+ repairTitle: '分组件检查与修复',
58
+ repairCli: '检查 / 修复 CLI',
59
+ repairLauncher: '检查 / 修复 Launcher',
60
+ repairAnalysisSkill: '检查 / 修复分析 Skill',
61
+ diagnostics: '导出诊断报告',
62
+ diagnosticsSaved: '诊断报告已保存',
54
63
  start: '启动 Runtime',
55
64
  stop: '停止 Runtime',
56
65
  working: '操作进行中…',
@@ -68,6 +77,23 @@ window.__ModuleLoader__.load({
68
77
  roots: '本地目录',
69
78
  installRoot: '组件目录',
70
79
  dataRoot: '数据目录',
80
+ profileStore: 'CLI Profile',
81
+ profileMigrationTitle: '旧 Profile 迁移',
82
+ profileMigrationPending: '检测到旧版临时 Profile。迁移只转移连接元数据和密码环境变量引用,不复制密码值。',
83
+ profileMigrationConflict: '旧 Profile 存在冲突或格式问题,请先导出诊断报告。',
84
+ migrateProfiles: '迁移到持久目录',
85
+ onboardingProgress: '数据库与语义层进度',
86
+ noOnboarding: '尚无数据库引导记录;请在对话中调用 Foggy onboarding Skill。',
87
+ projectRoot: '工作区',
88
+ resumeInChat: '未完成步骤应回到对应工作区,在对话中继续。',
89
+ stepPlanned: '规划',
90
+ stepDatasourceConfigured: '数据库连接',
91
+ stepDatasourceVerified: '连接验证',
92
+ stepSchemaDiscovered: 'Schema 发现',
93
+ stepSemanticDrafted: '语义层草拟',
94
+ stepSemanticValidated: '模型校验',
95
+ stepSemanticPublished: '发布',
96
+ stepSemanticVerified: '首次查询',
71
97
  runtimeUrl: 'Runtime 地址',
72
98
  nextTitle: '后续配置',
73
99
  nextCopy: 'Skills 已通过 DeepSeek Harness 原生注册表提供给所有工作区;每个会话直接使用自己的工作目录。Runtime 启动成功后,将继续进入数据库连接与语义层向导。',
@@ -99,6 +125,12 @@ window.__ModuleLoader__.load({
99
125
  unavailable: 'Unavailable',
100
126
  initialize: 'Initialize Foggy',
101
127
  repair: 'Re-download / Repair',
128
+ repairTitle: 'Component checks and repair',
129
+ repairCli: 'Check / repair CLI',
130
+ repairLauncher: 'Check / repair Launcher',
131
+ repairAnalysisSkill: 'Check / repair analysis Skill',
132
+ diagnostics: 'Export diagnostics',
133
+ diagnosticsSaved: 'Diagnostics saved',
102
134
  start: 'Start Runtime',
103
135
  stop: 'Stop Runtime',
104
136
  working: 'Operation in progress…',
@@ -116,6 +148,23 @@ window.__ModuleLoader__.load({
116
148
  roots: 'Local directories',
117
149
  installRoot: 'Components',
118
150
  dataRoot: 'Data',
151
+ profileStore: 'CLI profiles',
152
+ profileMigrationTitle: 'Legacy profile migration',
153
+ profileMigrationPending: 'Legacy temporary profiles were found. Migration transfers connection metadata and password environment-variable references, never password values.',
154
+ profileMigrationConflict: 'A legacy profile has a conflict or invalid format. Export diagnostics before continuing.',
155
+ migrateProfiles: 'Move to persistent store',
156
+ onboardingProgress: 'Database and semantic-layer progress',
157
+ noOnboarding: 'No database onboarding record yet. Invoke the Foggy onboarding Skill in a conversation.',
158
+ projectRoot: 'Workspace',
159
+ resumeInChat: 'Resume incomplete steps from a conversation in the matching workspace.',
160
+ stepPlanned: 'Plan',
161
+ stepDatasourceConfigured: 'Database connection',
162
+ stepDatasourceVerified: 'Connection test',
163
+ stepSchemaDiscovered: 'Schema discovery',
164
+ stepSemanticDrafted: 'Semantic draft',
165
+ stepSemanticValidated: 'Model validation',
166
+ stepSemanticPublished: 'Publish',
167
+ stepSemanticVerified: 'First query',
119
168
  runtimeUrl: 'Runtime URL',
120
169
  nextTitle: 'Next configuration',
121
170
  nextCopy: 'Skills are provided to every workspace through the native DeepSeek Harness registry, and each session uses its own working directory. After Runtime starts, the database connection and semantic-layer wizard comes next.',
@@ -131,7 +180,7 @@ window.__ModuleLoader__.load({
131
180
  .foggy-actions{display:flex;gap:8px;flex-wrap:wrap}.foggy-button{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;border-radius:8px;padding:7px 12px;cursor:pointer}.foggy-button:hover{background:var(--dsw-alias-interactive-bg-hover)}.foggy-button:disabled{opacity:.55;cursor:not-allowed}.foggy-button-primary{border-color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-state-business-primary);color:white}
132
181
  .foggy-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.foggy-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;padding:13px 14px}.foggy-card-head{display:flex;justify-content:space-between;gap:8px}.foggy-card strong{font-size:13px}.foggy-badge{font-size:11px;color:var(--dsw-alias-label-secondary)}.foggy-card code{display:block;margin-top:8px;color:var(--dsw-alias-label-tertiary);font-size:11px;overflow-wrap:anywhere}
133
182
  .foggy-progress{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;padding:13px 14px}.foggy-progress-head{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:13px}.foggy-progress-head strong{font-weight:600}.foggy-progress-percent{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums}.foggy-progress-track{height:8px;margin-top:10px;border-radius:999px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}.foggy-progress-fill{height:100%;border-radius:inherit;background:var(--dsw-alias-state-business-primary);transition:width .25s ease}.foggy-progress-meta{display:flex;justify-content:space-between;gap:12px;margin-top:8px;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}.foggy-progress-file{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
134
- .foggy-message{margin:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}.foggy-message[data-error=true]{color:var(--dsw-alias-state-error-primary)}.foggy-paths,.foggy-next{border-top:1px solid var(--dsw-alias-border-l2);padding-top:14px}.foggy-paths h4,.foggy-next h4{margin:0 0 8px;font-size:13px}.foggy-paths dl{display:grid;grid-template-columns:100px minmax(0,1fr);gap:6px 10px;margin:0}.foggy-paths dt{color:var(--dsw-alias-label-tertiary);font-size:12px}.foggy-paths dd{margin:0;min-width:0;overflow-wrap:anywhere;font-family:var(--ds-font-family-code);font-size:11px}.foggy-next p{margin:0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}
183
+ .foggy-message{margin:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}.foggy-message[data-error=true]{color:var(--dsw-alias-state-error-primary)}.foggy-paths,.foggy-next,.foggy-section{border-top:1px solid var(--dsw-alias-border-l2);padding-top:14px}.foggy-paths h4,.foggy-next h4,.foggy-section h4{margin:0 0 8px;font-size:13px}.foggy-paths dl{display:grid;grid-template-columns:100px minmax(0,1fr);gap:6px 10px;margin:0}.foggy-paths dt{color:var(--dsw-alias-label-tertiary);font-size:12px}.foggy-paths dd{margin:0;min-width:0;overflow-wrap:anywhere;font-family:var(--ds-font-family-code);font-size:11px}.foggy-next p,.foggy-section p{margin:0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}.foggy-section-actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}.foggy-onboarding-head{display:flex;justify-content:space-between;gap:12px;align-items:start}.foggy-onboarding-head code{font-size:11px;color:var(--dsw-alias-label-tertiary);overflow-wrap:anywhere}.foggy-step-list{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:7px;margin-top:10px}.foggy-step{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:8px;font-size:11px;color:var(--dsw-alias-label-tertiary)}.foggy-step[data-state=completed]{border-color:color-mix(in srgb,var(--dsw-alias-state-success-primary) 45%,var(--dsw-alias-border-l2));color:var(--dsw-alias-state-success-primary)}.foggy-step[data-state=failed],.foggy-step[data-state=invalid]{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.foggy-warning{border:1px solid color-mix(in srgb,var(--dsw-alias-state-warning-primary) 55%,var(--dsw-alias-border-l2));background:color-mix(in srgb,var(--dsw-alias-state-warning-primary) 8%,transparent);border-radius:10px;padding:12px}
135
184
  @media (width<=680px){.foggy-grid{grid-template-columns:minmax(0,1fr)}}
136
185
  `
137
186
 
@@ -213,6 +262,41 @@ window.__ModuleLoader__.load({
213
262
  })
214
263
  }
215
264
 
265
+ function onboardingPanel(onboarding, t) {
266
+ const profile = onboarding?.profiles?.[0]
267
+ if (!profile) return jsxs('div', { className: 'foggy-section', children: [
268
+ jsx('h4', { children: t('onboardingProgress') }),
269
+ jsx('p', { children: t('noOnboarding') }),
270
+ ] })
271
+ const steps = [
272
+ ['planned', 'stepPlanned'],
273
+ ['datasourceConfigured', 'stepDatasourceConfigured'],
274
+ ['datasourceVerified', 'stepDatasourceVerified'],
275
+ ['schemaDiscovered', 'stepSchemaDiscovered'],
276
+ ['semanticDrafted', 'stepSemanticDrafted'],
277
+ ['semanticValidated', 'stepSemanticValidated'],
278
+ ['semanticPublished', 'stepSemanticPublished'],
279
+ ['semanticVerified', 'stepSemanticVerified'],
280
+ ]
281
+ const percent = Math.round(((profile.completedSteps || 0) / Math.max(profile.totalSteps || 8, 1)) * 100)
282
+ return jsxs('div', { className: 'foggy-section', children: [
283
+ jsxs('div', { className: 'foggy-onboarding-head', children: [
284
+ jsxs('div', { children: [
285
+ jsx('h4', { children: `${t('onboardingProgress')} · ${profile.profile}` }),
286
+ jsx('code', { children: `${t('projectRoot')}: ${profile.projectRoot || '—'}` }),
287
+ ] }),
288
+ jsx('span', { className: 'foggy-progress-percent', children: `${percent}%` }),
289
+ ] }),
290
+ jsx('div', { className: 'foggy-step-list', children: steps.map(([name, label]) => jsx('div', {
291
+ className: 'foggy-step',
292
+ 'data-state': profile.steps?.[name]?.status || 'pending',
293
+ title: profile.steps?.[name]?.status || 'pending',
294
+ children: t(label),
295
+ }, name)) }),
296
+ profile.next?.status !== 'completed' ? jsx('p', { style: { marginTop: '9px' }, children: t('resumeInChat') }) : null,
297
+ ] })
298
+ }
299
+
216
300
  function FoggySettingsTab({ api, t }) {
217
301
  const [view, setView] = useState({ phase: 'loading', status: null, message: '', error: false })
218
302
 
@@ -237,7 +321,12 @@ window.__ModuleLoader__.load({
237
321
  setView((current) => ({ ...current, message: t('working'), error: false }))
238
322
  try {
239
323
  const result = unwrap(await api[method](), method)
240
- setView((current) => ({ ...current, message: result.accepted ? t('accepted') : '', error: result.success === false }))
324
+ const message = result.accepted
325
+ ? t('accepted')
326
+ : result.path
327
+ ? `${t('diagnosticsSaved')}: ${result.path}`
328
+ : ''
329
+ setView((current) => ({ ...current, message, error: result.success === false }))
241
330
  await refresh()
242
331
  } catch (error) {
243
332
  setView((current) => ({ ...current, message: `${t('actionFailed')}: ${String(error.message || error)}`, error: true }))
@@ -266,6 +355,7 @@ window.__ModuleLoader__.load({
266
355
  jsxs('div', { className: 'foggy-state', children: [jsx('span', { className: 'foggy-dot', 'data-state': status.state }), jsx('span', { children: t(stateLabels[status.state] || 'notInstalled') })] }),
267
356
  jsxs('div', { className: 'foggy-actions', children: [
268
357
  jsx('button', { className: 'foggy-button', type: 'button', disabled: busy, onClick: refresh, children: t('refresh') }),
358
+ jsx('button', { className: 'foggy-button', type: 'button', disabled: busy, onClick: () => run('diagnostics'), children: t('diagnostics') }),
269
359
  status.state === 'not-installed' ? jsx('button', { className: 'foggy-button foggy-button-primary', type: 'button', disabled: busy, onClick: () => run('initialize'), children: t('initialize') }) : null,
270
360
  status.state !== 'not-installed' ? jsx('button', { className: 'foggy-button', type: 'button', disabled: busy, onClick: () => run('repair'), children: t('repair') }) : null,
271
361
  status.installed && !status.running && status.components.java.available ? jsx('button', { className: 'foggy-button foggy-button-primary', type: 'button', disabled: busy, onClick: () => run('runtimeStart'), children: t('start') }) : null,
@@ -283,11 +373,29 @@ window.__ModuleLoader__.load({
283
373
  componentCard(t('onboardingSkill'), status.components.onboardingSkill, 'runtime', t),
284
374
  componentCard(t('skillRegistry'), { installed: status.components.onboardingSkill?.provider === 'foggy-managed-skills', version: 'native' }, 'runtime', t),
285
375
  ] }),
376
+ jsxs('div', { className: 'foggy-section', children: [
377
+ jsx('h4', { children: t('repairTitle') }),
378
+ jsx('div', { className: 'foggy-section-actions', children: [
379
+ jsx('button', { className: 'foggy-button', type: 'button', disabled: busy, onClick: () => run('repairCli'), children: t('repairCli') }),
380
+ jsx('button', { className: 'foggy-button', type: 'button', disabled: busy, onClick: () => run('repairLauncher'), children: t('repairLauncher') }),
381
+ jsx('button', { className: 'foggy-button', type: 'button', disabled: busy, onClick: () => run('repairAnalysisSkill'), children: t('repairAnalysisSkill') }),
382
+ ] }),
383
+ ] }),
384
+ status.profileMigration?.pendingCount || status.profileMigration?.conflictCount ? jsxs('div', { className: 'foggy-warning', children: [
385
+ jsx('h4', { children: t('profileMigrationTitle') }),
386
+ jsx('p', { children: status.profileMigration.conflictCount ? t('profileMigrationConflict') : t('profileMigrationPending') }),
387
+ !status.profileMigration.conflictCount ? jsx('div', { className: 'foggy-section-actions', children: jsx('button', {
388
+ className: 'foggy-button foggy-button-primary', type: 'button', disabled: busy,
389
+ onClick: () => run('migrateProfiles'), children: t('migrateProfiles'),
390
+ }) }) : null,
391
+ ] }) : null,
392
+ onboardingPanel(status.onboarding, t),
286
393
  jsxs('div', { className: 'foggy-paths', children: [
287
394
  jsx('h4', { children: t('roots') }),
288
395
  jsxs('dl', { children: [
289
396
  jsx('dt', { children: t('installRoot') }), jsx('dd', { children: status.roots.installRoot }),
290
397
  jsx('dt', { children: t('dataRoot') }), jsx('dd', { children: status.roots.dataRoot }),
398
+ jsx('dt', { children: t('profileStore') }), jsx('dd', { children: status.roots.profileStore }),
291
399
  status.runtimeUrl ? jsx('dt', { children: t('runtimeUrl') }) : null,
292
400
  status.runtimeUrl ? jsx('dd', { children: status.runtimeUrl }) : null,
293
401
  ] }),
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { execFile } from 'node:child_process'
2
- import { access, readFile } from 'node:fs/promises'
2
+ import { access, mkdir, readFile, writeFile } from 'node:fs/promises'
3
3
  import { constants as fsConstants } from 'node:fs'
4
4
  import { dirname, join, delimiter } from 'node:path'
5
5
  import { fileURLToPath } from 'node:url'
@@ -20,17 +20,20 @@ function defaultRoots() {
20
20
  return {
21
21
  installRoot: join(base, 'Foggy', 'DeepSeekHarness'),
22
22
  dataRoot: join(base, 'Foggy', 'DeepSeekHarnessData'),
23
+ profileStore: process.env.FOGGY_RUNTIME_PROFILE_STORE || join(base, 'Foggy', 'DeepSeekHarnessData', 'cli-profiles'),
23
24
  }
24
25
  }
25
26
  const home = process.env.HOME
26
27
  if (!home) throw new Error('HOME is not set')
28
+ const dataRoot = process.env.XDG_STATE_HOME
29
+ ? join(process.env.XDG_STATE_HOME, 'foggy', 'deepseek-harness')
30
+ : join(home, '.local', 'state', 'foggy', 'deepseek-harness')
27
31
  return {
28
32
  installRoot: process.env.XDG_DATA_HOME
29
33
  ? join(process.env.XDG_DATA_HOME, 'foggy', 'deepseek-harness')
30
34
  : join(home, '.local', 'share', 'foggy', 'deepseek-harness'),
31
- dataRoot: process.env.XDG_STATE_HOME
32
- ? join(process.env.XDG_STATE_HOME, 'foggy', 'deepseek-harness')
33
- : join(home, '.local', 'state', 'foggy', 'deepseek-harness'),
35
+ dataRoot,
36
+ profileStore: process.env.FOGGY_RUNTIME_PROFILE_STORE || join(dataRoot, 'cli-profiles'),
34
37
  }
35
38
  }
36
39
 
@@ -154,6 +157,16 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
154
157
  let runtime = null
155
158
  try { state = await readJson(statePath) } catch {}
156
159
  try { runtime = await readJson(runtimeStatePath) } catch {}
160
+ let onboarding = { success: true, profiles: [], profileCount: 0 }
161
+ let profileMigration = { success: true, entries: [], pendingCount: 0, conflictCount: 0, profileStore: roots.profileStore }
162
+ if (state) {
163
+ try { onboarding = await runOnboarding(['onboard-list']) } catch (error) {
164
+ onboarding = { success: false, profiles: [], profileCount: 0, error: String(error.message ?? error) }
165
+ }
166
+ try { profileMigration = await runOnboarding(['profile-migration-status']) } catch (error) {
167
+ profileMigration = { success: false, entries: [], pendingCount: 0, conflictCount: 0, profileStore: roots.profileStore, error: String(error.message ?? error) }
168
+ }
169
+ }
157
170
  const cliPath = state?.cli?.command
158
171
  const launcherPath = state?.launcher?.path
159
172
  const analysisSkillPath = state?.skills?.analysis?.path || join(roots.installRoot, 'skills', 'foggy-ai-analysis')
@@ -215,6 +228,8 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
215
228
  roots,
216
229
  components,
217
230
  operation,
231
+ onboarding,
232
+ profileMigration,
218
233
  next: installed ? (running ? 'configure-database' : 'start-runtime') : 'initialize',
219
234
  }
220
235
  }
@@ -246,6 +261,49 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
246
261
  return this.startOperation('repair', true)
247
262
  }
248
263
 
264
+ async repairCli() {
265
+ return this.startOperation('repair-cli', false, ['install', '--repair-component', 'cli'])
266
+ }
267
+
268
+ async repairLauncher() {
269
+ return this.startOperation('repair-launcher', false, ['install', '--repair-component', 'launcher'])
270
+ }
271
+
272
+ async repairAnalysisSkill() {
273
+ return this.startOperation('repair-analysis-skill', true, ['install', '--repair-component', 'analysis-skill'])
274
+ }
275
+
276
+ async migrateProfiles() {
277
+ return this.startOperation('profile-migration', false, ['profile-migrate', '--approve'])
278
+ }
279
+
280
+ async diagnostics() {
281
+ const roots = defaultRoots()
282
+ const [status, doctor] = await Promise.all([
283
+ this.status(),
284
+ runOnboarding(['doctor', '--no-fail']),
285
+ ])
286
+ const report = {
287
+ schemaVersion: 'foggy-deepseek-diagnostics/v1',
288
+ generatedAt: new Date().toISOString(),
289
+ packageVersion: status.packageVersion,
290
+ state: status.state,
291
+ installed: status.installed,
292
+ running: status.running,
293
+ runtimeUrl: status.runtimeUrl,
294
+ roots: status.roots,
295
+ components: status.components,
296
+ onboarding: status.onboarding,
297
+ profileMigration: status.profileMigration,
298
+ doctor,
299
+ }
300
+ const directory = join(roots.dataRoot, 'diagnostics')
301
+ await mkdir(directory, { recursive: true })
302
+ const path = join(directory, `diagnostics-${Date.now()}.json`)
303
+ await writeFile(path, `${JSON.stringify(report, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 })
304
+ return { success: true, path, report }
305
+ }
306
+
249
307
  async runtimeStart() {
250
308
  return this.startOperation('runtime-start', false, ['runtime-start'])
251
309
  }
@@ -260,7 +318,8 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
260
318
  }
261
319
  const id = `${Date.now()}-${Math.random().toString(16).slice(2, 10)}`
262
320
  const args = explicitArgs ?? ['install']
263
- if (!explicitArgs) {
321
+ const reportsProgress = args[0] === 'install'
322
+ if (reportsProgress) {
264
323
  const roots = defaultRoots()
265
324
  args.push('--progress-file', join(roots.dataRoot, 'operation-progress.json'), '--operation-id', id, '--operation-kind', kind)
266
325
  }
@@ -276,7 +335,7 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
276
335
  finishedAt: null,
277
336
  result: null,
278
337
  error: null,
279
- progress: !explicitArgs ? {
338
+ progress: reportsProgress ? {
280
339
  schemaVersion: 'foggy-deepseek-onboarding-progress/v1',
281
340
  operationId: id,
282
341
  kind,
@@ -324,7 +383,10 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
324
383
  }
325
384
 
326
385
  const markerInitializers = []
327
- for (const method of ['status', 'plan', 'initialize', 'repair', 'runtimeStart', 'runtimeStop']) {
386
+ for (const method of [
387
+ 'status', 'plan', 'initialize', 'repair', 'repairCli', 'repairLauncher', 'repairAnalysisSkill',
388
+ 'migrateProfiles', 'diagnostics', 'runtimeStart', 'runtimeStop',
389
+ ]) {
328
390
  Remote(method)(FoggyIntegrationGateway.prototype[method], {
329
391
  kind: 'method',
330
392
  name: method,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foggy-projects/deepseek-harness-plugin",
3
- "version": "0.4.0-beta.5",
3
+ "version": "0.4.0-beta.7",
4
4
  "description": "Foggy Java data analysis engine integration for DeepSeek Harness",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -55,6 +55,6 @@
55
55
  },
56
56
  "scripts": {
57
57
  "check": "node --check lib/index.js && node --check lib/skill-provider.js && node --check lib/client.js && node --check lib/typert.js && node --check lib/remote.js",
58
- "test": "node --test test/package.test.js"
58
+ "test": "node --test test/package.test.js && python test/onboarding_unit.py"
59
59
  }
60
60
  }
@@ -22,8 +22,14 @@ copy this Skill into the current workspace.
22
22
  its absolute command in the global install state.
23
23
  - Do not independently download or reinstall the CLI. If the global install state, managed marker, or
24
24
  analysis Skill is missing or invalid, ask the user to open the Foggy plugin settings and use
25
- **Re-download / Repair**. Repair restores the global managed analysis Skill and invalidates the
25
+ the matching component repair action. Repair restores managed components and invalidates the
26
26
  native DSH Skill catalog. Reinstall or upgrade the plugin itself to restore this bundled Skill.
27
+ - Treat the current DSH session workspace as the authoritative `projectRoot` for the whole onboarding
28
+ run. Do not redirect semantic drafts or contracts to a different repository merely because another
29
+ Skill or example was found there. Place non-secret contracts below
30
+ `<projectRoot>/.foggy/onboarding-contracts/<profile>/`, drafts below
31
+ `<projectRoot>/.foggy/onboarding-drafts/<profile>/`, and published files below the approved
32
+ project-relative `modelsDir`.
27
33
 
28
34
  ## Mandatory orchestration boundary
29
35
 
@@ -46,6 +52,9 @@ For every new-database onboarding session, this Skill is the orchestration autho
46
52
  - Never read query-execution evidence back into the conversation. Report only validation state,
47
53
  execution state, row count, and evidence path; do not report row values or generated SQL containing
48
54
  business literals.
55
+ - Prefer one composite command per approval boundary. Do not inspect this Skill's Python implementation
56
+ or the CLI package source during a normal run; use the documented command contract and inspect code
57
+ only after a structured wrapper error requires troubleshooting.
49
58
 
50
59
  ## Boundaries
51
60
 
@@ -66,14 +75,21 @@ For every new-database onboarding session, this Skill is the orchestration autho
66
75
  plugin's Repair action. Use the matching install script only when the plugin UI is unavailable;
67
76
  use `--dry-run` first when paths or permissions are uncertain.
68
77
  3. Run `runtime-start` and require successful `wait-ready` plus `capabilities`. Record engine,
69
- Runtime API version, schema version, security mode, URL, namespace, PID, and evidence path.
78
+ Runtime API version, schema version, security mode, URL, namespace, PID, and evidence path. If the
79
+ recorded Runtime is already running, `runtime-start` verifies and reuses it instead of starting a
80
+ second process.
70
81
  4. Load `foggy-ai-analysis` from the native DSH Skill registry. Do not require a workspace copy.
71
82
  5. For a new business database, read [references/onboarding-workflow.md](references/onboarding-workflow.md)
72
83
  and prefer its two composite `onboard-datasource-run` / `onboard-semantic-run` commands. Require the
73
- trusted operator to create the private CLI profile outside Harness. Accept only the opaque profile
84
+ trusted operator to create the private CLI profile outside Harness. Unless the operator explicitly
85
+ overrides it, use the persistent profile store reported by the wrapper under the Foggy data root
86
+ (`<dataRoot>/cli-profiles`), never `/tmp`. Accept only the opaque profile
74
87
  ID, exact revision, datasource name/type, and namespace; never request JDBC URL, username,
75
88
  password, or password environment-variable name in Harness.
76
- 6. After schema discovery, use `foggy-ai-analysis` only to author TM/QM files in a separate project-local
89
+ If plugin settings report a legacy temporary profile, use the explicit migration action before
90
+ onboarding. It moves only validated connection metadata and environment-variable references; it
91
+ never copies a password value and leaves a recoverable private backup below the Foggy data root.
92
+ 6. After schema discovery, use `foggy-ai-analysis` only to author TM/QM files in the standard project-local
77
93
  draft directory. Register, validate, publish, and verify them through this Skill's wrapper using the deterministic commands in
78
94
  [references/onboarding-workflow.md](references/onboarding-workflow.md). Do not publish, prune, replace
79
95
  a bundle, or execute a business-data query without the matching explicit flag and user approval.
@@ -98,5 +114,18 @@ In DeepSeek Harness, do not expand the composite onboarding commands back into t
98
114
  operations. This order documents what the wrapper enforces internally and becomes a direct CLI workflow
99
115
  only after onboarding is complete.
100
116
 
117
+ Composite commands are checkpointed and idempotent: a retry skips completed datasource, validation,
118
+ publication, and verification phases when the approved contract and draft digest are unchanged. Fix a
119
+ query payload in place and rerun the same semantic composite command; do not remove a successfully
120
+ published bundle merely to recover from a later query-validation failure.
121
+
122
+ An already-completed profile may be reused from another DSH workspace when the approved connection
123
+ contract is byte-for-byte equivalent. Run the datasource composite in the new workspace first; it adds
124
+ that workspace as a non-destructive binding and reuses datasource/schema checkpoints. Then pass the
125
+ current workspace explicitly as `--project-root` to the semantic composite. A secondary workspace may
126
+ reuse an identical published semantic digest and run its own bounded verification query, but it cannot
127
+ replace the published semantic layer; changes must be published from the original workspace or a new
128
+ profile.
129
+
101
130
  Keep user business data separate from the sales-drop SQLite demo. Prefer a read-only database account,
102
131
  opaque CLI profile references, and bounded query limits.
@@ -13,6 +13,26 @@
13
13
  "installRoot": {"type": "string"},
14
14
  "dataRoot": {"type": "string"},
15
15
  "projectRoot": {"type": "string"},
16
+ "workspaceBindings": {
17
+ "type": "array",
18
+ "items": {"type": "string"},
19
+ "uniqueItems": true
20
+ },
21
+ "workspaceVerifications": {
22
+ "type": "array",
23
+ "items": {
24
+ "type": "object",
25
+ "required": ["projectRoot", "queryModel", "queryPayloadDigest", "verifiedAt"],
26
+ "properties": {
27
+ "projectRoot": {"type": "string"},
28
+ "queryModel": {"type": "string"},
29
+ "queryPayloadDigest": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
30
+ "rowCount": {"type": ["integer", "null"], "minimum": 0},
31
+ "verifiedAt": {"type": "string", "format": "date-time"}
32
+ },
33
+ "additionalProperties": false
34
+ }
35
+ },
16
36
  "runtime": {"type": "object"},
17
37
  "connection": {"$ref": "connection.schema.json"},
18
38
  "semantic": {"type": "object"},
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": "foggy-deepseek-onboarding-versions/v1",
3
- "packageVersion": "0.4.0-beta.5",
3
+ "packageVersion": "0.4.0-beta.7",
4
4
  "validatedAt": "2026-09-01",
5
5
  "components": {
6
6
  "deepseekHarness": {
@@ -23,6 +23,22 @@ to paste a password and do not put a JDBC URL, username, password environment-va
23
23
  line, logs, or evidence. For non-SQLite databases, ask the user to set the named environment variable
24
24
  before Runtime starts so the Java process inherits it. Recommend a read-only database account.
25
25
 
26
+ Use the current DSH session workspace as `projectRoot`. Store approved non-secret contracts at
27
+ `.foggy/onboarding-contracts/<profile>/`, semantic drafts at `.foggy/onboarding-drafts/<profile>/`, and
28
+ command evidence at `.foggy/onboarding-command-evidence/<profile>/`. Do not split these files across the
29
+ session workspace and a separate example repository. The wrapper rejects a query payload outside the
30
+ recorded project root before any semantic validation or publication mutation occurs.
31
+
32
+ The wrapper defaults `FOGGY_RUNTIME_PROFILE_STORE` to the private persistent directory
33
+ `<dataRoot>/cli-profiles`. An explicit operator-provided value still wins. Do not use `/tmp` for a profile
34
+ that must survive a WSL or Harness restart.
35
+
36
+ If plugin settings detect profiles in the legacy temporary store, use **Move to persistent store**.
37
+ Migration validates the opaque profile schema, rejects embedded passwords and conflicts, writes the
38
+ destination with private permissions, verifies it, and moves the legacy JSON to a recoverable private
39
+ backup below the Foggy data root.
40
+ Do not manually copy or edit opaque profile JSON.
41
+
26
42
  Treat every user-supplied identifier and bound as immutable input: profile, datasource, namespace,
27
43
  models directory, bundle name, TM/QM name, query fields, and limit. Do not swap in demo names, add
28
44
  fields, raise the limit, or introduce replacement flags. If one of these inputs is missing, pause for
@@ -42,17 +58,32 @@ For Harness-driven onboarding, use the two composite commands below. Each writes
42
58
  evidence internally and refuses to cross an unapproved mutation gate:
43
59
 
44
60
  ```text
45
- onboard-datasource-run --connection-file <approved-json> \
61
+ onboard-datasource-run --project-root <current-session-workspace> \
62
+ --connection-file <approved-json> \
46
63
  --approve-configure --approve-bind --include-indexes
47
64
 
48
65
  # After authoring the registered TM/QM draft from schema metadata:
49
- onboard-semantic-run --semantic-plan <approved-json> --query-payload <approved-json> \
66
+ onboard-semantic-run --project-root <current-session-workspace> \
67
+ --semantic-plan <approved-json> --query-payload <approved-json> \
50
68
  --approve-validate --approve-publish --approve-execute
51
69
  ```
52
70
 
53
71
  Include an approval flag only after the user approves that exact action. Without it, the composite
54
72
  command stops after the corresponding dry-run and returns `phaseStatus=awaiting-...-approval`.
55
73
 
74
+ Both composite commands are resumable. When the approved contract is unchanged, they skip completed
75
+ checkpoints instead of re-adding an existing datasource, revalidating an already published draft, or
76
+ registering the same bundle twice. If query validation fails after publication, correct the project-local
77
+ payload and rerun `onboard-semantic-run`; it resumes at query verification and leaves the active bundle in
78
+ place. A same-name datasource is accepted idempotently only when its public name and database type match
79
+ the approved plan; otherwise replacement still requires explicit approval.
80
+
81
+ When a completed profile already belongs to another workspace, the datasource composite may add the
82
+ current workspace as a binding only if the approved connection contract is identical and the datasource,
83
+ schema, and semantic publication checkpoints are complete. The semantic composite then accepts the same
84
+ published draft digest from the bound workspace and performs a workspace-specific bounded verification
85
+ query. It refuses semantic replacement from the secondary workspace.
86
+
56
87
  The granular commands below remain available for manual troubleshooting and resumption. Do not expand
57
88
  the composite commands into this list during a normal Harness turn.
58
89
 
@@ -68,8 +99,8 @@ semantic-validate --profile <profile>
68
99
  semantic-validate --profile <profile> --apply
69
100
  semantic-publish --profile <profile>
70
101
  semantic-publish --profile <profile> --apply
71
- semantic-verify --profile <profile> --query-payload <json>
72
- semantic-verify --profile <profile> --query-payload <json> --execute
102
+ semantic-verify --profile <profile> --project-root <current-session-workspace> --query-payload <json>
103
+ semantic-verify --profile <profile> --project-root <current-session-workspace> --query-payload <json> --execute
73
104
  onboard-status --profile <profile>
74
105
  ```
75
106
 
@@ -111,6 +142,11 @@ requires an integer `limit` from 1 through 100. The first `semantic-verify` call
111
142
  validates the query; only `--execute` reads business data. Full results stay in evidence and the command
112
143
  returns only counts and paths.
113
144
 
145
+ Before the first semantic mutation, the composite command checks that the query payload is inside
146
+ `projectRoot`, has a bounded limit, and targets a query model declared in the approved semantic plan.
147
+ After publication, `semantic-verify` always describes the live model before validation. If a field is
148
+ rejected, update the payload from those described names and rerun the same composite command.
149
+
114
150
  Do not open, summarize, or quote `query-execute.json` or the Runtime's generated SQL after execution.
115
151
  The conversational result may contain only `queryValidated`, `queryExecuted`, `rowCount`, the model
116
152
  name, and the evidence path. A successful direct CLI command is not proof that onboarding completed;