dsh-sa-workbench 0.1.30 → 0.1.32
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 +1 -1
- package/client.js +1761 -697
- package/modules/dsh-pm-core/delivery-library.mjs +279 -0
- package/modules/dsh-pm-core/engine.js +1 -1
- package/modules/dsh-pm-core/index.js +41 -1
- package/modules/dsh-pm-core/knowledge-model.mjs +36 -0
- package/modules/dsh-pm-core/knowledge-workflow.mjs +291 -0
- package/modules/dsh-pm-core/product-context.mjs +74 -0
- package/modules/dsh-pm-core/requirement-deletion.mjs +276 -0
- package/modules/dsh-pm-ui/client.js +1761 -697
- package/modules/dsh-pm-ui/index.js +23 -3
- package/modules/dsh-pm-ui/knowledge-model.mjs +36 -0
- package/modules/dsh-pm-ui/package.json +1 -1
- package/package.json +2 -2
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {knowledgeCatalog} from './knowledge-model.mjs'
|
|
1
2
|
/**
|
|
2
3
|
* dsh-pm-ui — host half.
|
|
3
4
|
*
|
|
@@ -110,6 +111,7 @@ function statusFor(code) {
|
|
|
110
111
|
return 400
|
|
111
112
|
case 'stage_incomplete':
|
|
112
113
|
case 'stale_artifact':
|
|
114
|
+
case 'conflict':
|
|
113
115
|
case 'stale_requirement':
|
|
114
116
|
case 'archived':
|
|
115
117
|
case 'corrupt_state':
|
|
@@ -211,6 +213,21 @@ export function apply(ctx) {
|
|
|
211
213
|
|
|
212
214
|
const setup = pm.getSettings()
|
|
213
215
|
if ((setup.setupRequired || setup.restartRequired) && req.method !== 'GET' && route !== '/settings') return fail(res,409,'请先保存目录配置并完整重启 DSH。','setup_required')
|
|
216
|
+
if (route === '/knowledge-search' && req.method === 'GET') return json(res,200,{ok:true,...await pm.knowledgeWorkflow.search(url.searchParams.get('query') || '')})
|
|
217
|
+
if (route === '/knowledge-candidates' && req.method === 'GET') return json(res,200,{ok:true,...await pm.knowledgeWorkflow.load(url.searchParams.get('id') || '')})
|
|
218
|
+
if (route === '/knowledge-candidate-preview' && req.method === 'POST') return json(res,200,{ok:true,...await pm.knowledgeWorkflow.preview(await readBody(req))})
|
|
219
|
+
if (route === '/knowledge-candidate-approve' && req.method === 'POST') return json(res,200,{ok:true,receipt:await pm.knowledgeWorkflow.approve(await readBody(req))})
|
|
220
|
+
if (route === '/deliveries' && req.method === 'GET') return json(res,200,{ok:true,...await pm.deliveryLibrary.list(url.searchParams.get('query') || '')})
|
|
221
|
+
if (route === '/delivery' && req.method === 'GET') return json(res,200,{ok:true,...await pm.deliveryLibrary.detail(url.searchParams.get('id') || '')})
|
|
222
|
+
if (route === '/delivery-file' && req.method === 'GET') return json(res,200,{ok:true,...await pm.deliveryLibrary.read(url.searchParams.get('id') || '',url.searchParams.get('path') || '')})
|
|
223
|
+
if (route === '/delivery-capture' && req.method === 'POST') return json(res,200,{ok:true,entry:await pm.deliveryLibrary.capture(await readBody(req))})
|
|
224
|
+
if (route === '/delivery-assistant' && req.method === 'POST') return json(res,200,{ok:true,...await pm.deliveryLibrary.assistant((await readBody(req)).id)})
|
|
225
|
+
if (route === '/delivery-knowledge-candidates' && req.method === 'GET') return json(res,200,{ok:true,...await pm.deliveryLibrary.workflow.load(url.searchParams.get('id') || '')})
|
|
226
|
+
if (route === '/delivery-knowledge-candidate-preview' && req.method === 'POST') return json(res,200,{ok:true,...await pm.deliveryLibrary.workflow.preview(await readBody(req))})
|
|
227
|
+
if (route === '/delivery-knowledge-candidate-approve' && req.method === 'POST') return json(res,200,{ok:true,receipt:await pm.deliveryLibrary.workflow.approve(await readBody(req))})
|
|
228
|
+
if (route === '/product-context' && req.method === 'GET') return json(res,200,{ok:true,...await pm.productContext.read()})
|
|
229
|
+
if (route === '/product-context' && req.method === 'POST') return json(res,200,{ok:true,...await pm.productContext.save(await readBody(req))})
|
|
230
|
+
if (route === '/product-document' && req.method === 'GET') return json(res,200,{ok:true,...await pm.productContext.document(url.searchParams.get('path') || '')})
|
|
214
231
|
if (route === '/directories' && req.method === 'GET') {
|
|
215
232
|
return json(res, 200, { ok: true, ...await listDirectories(url.searchParams.get('path') ?? '') })
|
|
216
233
|
}
|
|
@@ -254,7 +271,8 @@ export function apply(ctx) {
|
|
|
254
271
|
}
|
|
255
272
|
|
|
256
273
|
if (route === '/state' && req.method === 'GET') {
|
|
257
|
-
const
|
|
274
|
+
const view = await pm.viewAll()
|
|
275
|
+
const requirements = await pm.deliveryLibrary.withDeliveryFacts(view.requirements)
|
|
258
276
|
return json(res, 200, {
|
|
259
277
|
ok: true,
|
|
260
278
|
workspace: {
|
|
@@ -367,9 +385,11 @@ export function apply(ctx) {
|
|
|
367
385
|
return json(res, 200, { ok: true, id: record.id, title: record.title })
|
|
368
386
|
}
|
|
369
387
|
|
|
388
|
+
if (route === '/requirement-delete-preview' && req.method === 'GET') return json(res,200,{ok:true,...await pm.requirementDeletion.preview(String(url.searchParams.get('id') || ''))})
|
|
389
|
+
|
|
370
390
|
if (route === '/requirement' && req.method === 'DELETE') {
|
|
371
391
|
const body = await readBody(req)
|
|
372
|
-
const result = await pm.deleteRequirement(String(body.id ?? ''))
|
|
392
|
+
const result = await pm.deleteRequirement(String(body.id ?? ''), body)
|
|
373
393
|
return json(res, 200, { ok: true, ...result })
|
|
374
394
|
}
|
|
375
395
|
|
|
@@ -479,7 +499,7 @@ export function apply(ctx) {
|
|
|
479
499
|
|
|
480
500
|
// 全局知识库:列表 + 单文件读取(只读;修改走会话或直接编辑文件)
|
|
481
501
|
if (route === '/knowledge' && req.method === 'GET') {
|
|
482
|
-
const files = await pm
|
|
502
|
+
const files = await knowledgeCatalog(pm)
|
|
483
503
|
return json(res, 200, { ok: true, root: pm.knowledgeDir, files })
|
|
484
504
|
}
|
|
485
505
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Deliberately restricted front matter: plain scalar fields and string lists only.
|
|
2
|
+
// Never infer business confirmation or domains from prose or filenames.
|
|
3
|
+
export function knowledgeRecord(file, text='') {
|
|
4
|
+
const fields={}, match=String(text).match(/^\uFEFF?---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)
|
|
5
|
+
const scalar=s=>{
|
|
6
|
+
const value=String(s).trim()
|
|
7
|
+
if(value.startsWith('"')){try{return String(JSON.parse(value))}catch{}}
|
|
8
|
+
return value.replace(/^(['"])(.*)\1$/,'$2')
|
|
9
|
+
}
|
|
10
|
+
if(match){let key='';for(const line of match[1].split(/\r?\n/)){
|
|
11
|
+
const pair=line.match(/^([a-z_]+):\s*(.*?)\s*$/)
|
|
12
|
+
if(pair){key=pair[1];fields[key]=pair[2] ? scalar(pair[2]) : []}
|
|
13
|
+
else {const item=line.match(/^\s+-\s+(.+)$/);if(item && Array.isArray(fields[key]))fields[key].push(scalar(item[1]))}
|
|
14
|
+
}}
|
|
15
|
+
const list=v=>Array.isArray(v)?v.map(scalar):typeof v==='string'?v.replace(/^\[|\]$/g,'').split(/[,,]/).map(scalar).filter(Boolean):[]
|
|
16
|
+
const body=match?text.slice(match[0].length):String(text)
|
|
17
|
+
return {...file,title:scalar(fields.title || body.match(/^#\s+(.+)$/m)?.[1] || file.path.replace(/.*\//,'').replace(/\.[^.]+$/,'')),
|
|
18
|
+
kind:scalar(fields.type || '未分类'),domains:list(fields.domains),status:scalar(fields.status || '未标注'),
|
|
19
|
+
summary:scalar(fields.summary || body.replace(/^#+\s+.*$/gm,'').replace(/[`*_>#\[\]]/g,'').trim().slice(0,160)),
|
|
20
|
+
source:scalar(fields.source || ''),owner:scalar(fields.owner || ''),version:scalar(fields.version || ''),
|
|
21
|
+
scope:scalar(fields.scope || ''),reviewedBy:scalar(fields.reviewed_by || ''),
|
|
22
|
+
sourceDeliveryId:scalar(fields.source_delivery_id || ''),sourceRequirementId:scalar(fields.source_requirement_id || ''),
|
|
23
|
+
related:list(fields.related),searchText:body.slice(0,262144),metadata:!!match}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function knowledgeCatalog(pm){
|
|
27
|
+
const files=await pm.listKnowledge(), records=[]
|
|
28
|
+
let budget=4*1024*1024
|
|
29
|
+
// Sequential, bounded reads avoid unbounded I/O on a large knowledge directory.
|
|
30
|
+
for(const file of files){
|
|
31
|
+
if(file.bytes>262144 || file.bytes>budget){records.push({...knowledgeRecord(file),previewLimited:true});continue}
|
|
32
|
+
try{const content=await pm.readKnowledge(file.path) || '';budget-=Math.max(file.bytes,content.length);records.push(knowledgeRecord(file,content))}
|
|
33
|
+
catch{records.push({...knowledgeRecord(file),metadataError:true})}
|
|
34
|
+
}
|
|
35
|
+
return records
|
|
36
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"type":"module","version":"0.1.
|
|
1
|
+
{"type":"module","version":"0.1.32"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-sa-workbench",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.32",
|
|
4
4
|
"description": "SA 工作台:需求整理、方案、PRD、原型和交付归档,一个插件统一安装与更新。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -71,4 +71,4 @@
|
|
|
71
71
|
"registry": "https://registry.npmjs.org/",
|
|
72
72
|
"tag": "latest"
|
|
73
73
|
}
|
|
74
|
-
}
|
|
74
|
+
}
|