@openspecui/core 1.2.0 → 1.5.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/dist/index.d.mts +263 -110
- package/dist/index.mjs +263 -172
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -3,10 +3,10 @@ import { dirname, join } from "path";
|
|
|
3
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
4
|
import { readFile as readFile$1, readdir, stat } from "node:fs/promises";
|
|
5
5
|
import { dirname as dirname$1, join as join$1, matchesGlob, relative, resolve, sep } from "node:path";
|
|
6
|
-
import { existsSync,
|
|
6
|
+
import { existsSync, lstatSync, realpathSync } from "node:fs";
|
|
7
7
|
import { z } from "zod";
|
|
8
|
-
import { watch } from "fs";
|
|
9
8
|
import { EventEmitter } from "events";
|
|
9
|
+
import { watch } from "fs";
|
|
10
10
|
import { exec, spawn } from "child_process";
|
|
11
11
|
import { promisify } from "util";
|
|
12
12
|
import { parse } from "yaml";
|
|
@@ -220,14 +220,14 @@ var MarkdownParser = class {
|
|
|
220
220
|
if (currentOperation === "RENAMED") {
|
|
221
221
|
const fromMatch = line.match(/FROM:\s*`?###\s*Requirement:\s*(.+?)`?$/i);
|
|
222
222
|
const toMatch = line.match(/TO:\s*`?###\s*Requirement:\s*(.+?)`?$/i);
|
|
223
|
-
if (fromMatch)
|
|
224
|
-
|
|
225
|
-
from
|
|
226
|
-
}
|
|
227
|
-
if (toMatch)
|
|
228
|
-
|
|
229
|
-
to
|
|
230
|
-
}
|
|
223
|
+
if (fromMatch) {
|
|
224
|
+
if (!renameBuffer) renameBuffer = {};
|
|
225
|
+
renameBuffer.from = fromMatch[1].trim();
|
|
226
|
+
}
|
|
227
|
+
if (toMatch) {
|
|
228
|
+
if (!renameBuffer) renameBuffer = {};
|
|
229
|
+
renameBuffer.to = toMatch[1].trim();
|
|
230
|
+
}
|
|
231
231
|
if (renameBuffer?.from && renameBuffer?.to) {
|
|
232
232
|
deltas.push({
|
|
233
233
|
spec: deltaSpec.specId,
|
|
@@ -312,86 +312,6 @@ var MarkdownParser = class {
|
|
|
312
312
|
}
|
|
313
313
|
};
|
|
314
314
|
|
|
315
|
-
//#endregion
|
|
316
|
-
//#region src/validator.ts
|
|
317
|
-
/**
|
|
318
|
-
* Validator for OpenSpec documents
|
|
319
|
-
*/
|
|
320
|
-
var Validator = class {
|
|
321
|
-
/**
|
|
322
|
-
* Validate a spec document
|
|
323
|
-
*/
|
|
324
|
-
validateSpec(spec) {
|
|
325
|
-
const issues = [];
|
|
326
|
-
if (!spec.overview || spec.overview.trim().length === 0) issues.push({
|
|
327
|
-
severity: "ERROR",
|
|
328
|
-
message: "Spec must have a Purpose/Overview section",
|
|
329
|
-
path: "overview"
|
|
330
|
-
});
|
|
331
|
-
if (spec.requirements.length === 0) issues.push({
|
|
332
|
-
severity: "ERROR",
|
|
333
|
-
message: "Spec must have at least one requirement",
|
|
334
|
-
path: "requirements"
|
|
335
|
-
});
|
|
336
|
-
for (const req of spec.requirements) {
|
|
337
|
-
if (!req.text.includes("SHALL") && !req.text.includes("MUST")) issues.push({
|
|
338
|
-
severity: "WARNING",
|
|
339
|
-
message: `Requirement should contain "SHALL" or "MUST": ${req.id}`,
|
|
340
|
-
path: `requirements.${req.id}`
|
|
341
|
-
});
|
|
342
|
-
if (req.scenarios.length === 0) issues.push({
|
|
343
|
-
severity: "WARNING",
|
|
344
|
-
message: `Requirement should have at least one scenario: ${req.id}`,
|
|
345
|
-
path: `requirements.${req.id}.scenarios`
|
|
346
|
-
});
|
|
347
|
-
if (req.text.length > 1e3) issues.push({
|
|
348
|
-
severity: "WARNING",
|
|
349
|
-
message: `Requirement text is too long (max 1000 chars): ${req.id}`,
|
|
350
|
-
path: `requirements.${req.id}.text`
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
return {
|
|
354
|
-
valid: issues.filter((i) => i.severity === "ERROR").length === 0,
|
|
355
|
-
issues
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
/**
|
|
359
|
-
* Validate a change proposal
|
|
360
|
-
*/
|
|
361
|
-
validateChange(change) {
|
|
362
|
-
const issues = [];
|
|
363
|
-
if (!change.why || change.why.length < 50) issues.push({
|
|
364
|
-
severity: "ERROR",
|
|
365
|
-
message: "Change \"Why\" section must be at least 50 characters",
|
|
366
|
-
path: "why"
|
|
367
|
-
});
|
|
368
|
-
if (change.why && change.why.length > 500) issues.push({
|
|
369
|
-
severity: "WARNING",
|
|
370
|
-
message: "Change \"Why\" section should be under 500 characters",
|
|
371
|
-
path: "why"
|
|
372
|
-
});
|
|
373
|
-
if (!change.whatChanges || change.whatChanges.trim().length === 0) issues.push({
|
|
374
|
-
severity: "ERROR",
|
|
375
|
-
message: "Change must have a \"What Changes\" section",
|
|
376
|
-
path: "whatChanges"
|
|
377
|
-
});
|
|
378
|
-
if (change.deltas.length === 0) issues.push({
|
|
379
|
-
severity: "WARNING",
|
|
380
|
-
message: "Change should have at least one delta",
|
|
381
|
-
path: "deltas"
|
|
382
|
-
});
|
|
383
|
-
if (change.deltas.length > 50) issues.push({
|
|
384
|
-
severity: "WARNING",
|
|
385
|
-
message: "Change has too many deltas (max 50)",
|
|
386
|
-
path: "deltas"
|
|
387
|
-
});
|
|
388
|
-
return {
|
|
389
|
-
valid: issues.filter((i) => i.severity === "ERROR").length === 0,
|
|
390
|
-
issues
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
};
|
|
394
|
-
|
|
395
315
|
//#endregion
|
|
396
316
|
//#region src/reactive-fs/reactive-state.ts
|
|
397
317
|
/**
|
|
@@ -578,8 +498,10 @@ const DEFAULT_IGNORE = [
|
|
|
578
498
|
".git",
|
|
579
499
|
"**/.DS_Store"
|
|
580
500
|
];
|
|
581
|
-
/**
|
|
582
|
-
const
|
|
501
|
+
/** 恢复重试间隔 (ms) */
|
|
502
|
+
const RECOVERY_INTERVAL_MS = 3e3;
|
|
503
|
+
/** 路径语义检查间隔 (ms) */
|
|
504
|
+
const PATH_LIVENESS_INTERVAL_MS = 3e3;
|
|
583
505
|
/**
|
|
584
506
|
* 项目监听器
|
|
585
507
|
*
|
|
@@ -602,17 +524,25 @@ var ProjectWatcher = class {
|
|
|
602
524
|
ignore;
|
|
603
525
|
initialized = false;
|
|
604
526
|
initPromise = null;
|
|
605
|
-
healthCheckTimer = null;
|
|
606
|
-
lastEventTime = 0;
|
|
607
|
-
healthCheckPending = false;
|
|
608
|
-
enableHealthCheck;
|
|
609
527
|
reinitializeTimer = null;
|
|
610
528
|
reinitializePending = false;
|
|
529
|
+
reinitializeReasonPending = null;
|
|
530
|
+
pathLivenessTimer = null;
|
|
531
|
+
projectDirFingerprint = null;
|
|
532
|
+
generation = 0;
|
|
533
|
+
reinitializeCount = 0;
|
|
534
|
+
lastReinitializeReason = null;
|
|
535
|
+
reinitializeReasonCounts = {
|
|
536
|
+
"drop-events": 0,
|
|
537
|
+
"watcher-error": 0,
|
|
538
|
+
"missing-project-dir": 0,
|
|
539
|
+
"project-dir-replaced": 0,
|
|
540
|
+
manual: 0
|
|
541
|
+
};
|
|
611
542
|
constructor(projectDir, options = {}) {
|
|
612
543
|
this.projectDir = getRealPath$1(projectDir);
|
|
613
544
|
this.debounceMs = options.debounceMs ?? DEBOUNCE_MS$1;
|
|
614
545
|
this.ignore = options.ignore ?? DEFAULT_IGNORE;
|
|
615
|
-
this.enableHealthCheck = options.enableHealthCheck ?? true;
|
|
616
546
|
}
|
|
617
547
|
/**
|
|
618
548
|
* 初始化 watcher
|
|
@@ -621,8 +551,11 @@ var ProjectWatcher = class {
|
|
|
621
551
|
async init() {
|
|
622
552
|
if (this.initialized) return;
|
|
623
553
|
if (this.initPromise) return this.initPromise;
|
|
624
|
-
this.initPromise = this.doInit()
|
|
625
|
-
|
|
554
|
+
this.initPromise = this.doInit().catch((error) => {
|
|
555
|
+
this.initPromise = null;
|
|
556
|
+
throw error;
|
|
557
|
+
});
|
|
558
|
+
return this.initPromise;
|
|
626
559
|
}
|
|
627
560
|
async doInit() {
|
|
628
561
|
this.subscription = await (await import("@parcel/watcher")).subscribe(this.projectDir, (err, events) => {
|
|
@@ -633,43 +566,98 @@ var ProjectWatcher = class {
|
|
|
633
566
|
this.handleEvents(events);
|
|
634
567
|
}, { ignore: this.ignore });
|
|
635
568
|
this.initialized = true;
|
|
636
|
-
this.
|
|
637
|
-
|
|
569
|
+
this.generation += 1;
|
|
570
|
+
this.projectDirFingerprint = this.getProjectDirFingerprint();
|
|
571
|
+
this.startPathLivenessMonitor();
|
|
638
572
|
}
|
|
639
573
|
/**
|
|
640
574
|
* 处理 watcher 错误
|
|
641
|
-
*
|
|
575
|
+
* 统一走错误驱动重建流程
|
|
642
576
|
*/
|
|
643
577
|
handleWatcherError(err) {
|
|
644
578
|
if ((err.message || String(err)).includes("Events were dropped")) {
|
|
645
579
|
if (!this.reinitializePending) {
|
|
646
580
|
console.warn("[ProjectWatcher] FSEvents dropped events, scheduling reinitialize...");
|
|
647
|
-
this.scheduleReinitialize();
|
|
581
|
+
this.scheduleReinitialize("drop-events");
|
|
648
582
|
}
|
|
649
583
|
return;
|
|
650
584
|
}
|
|
651
|
-
console.error("[ProjectWatcher]
|
|
585
|
+
console.error("[ProjectWatcher] Watcher error, scheduling reinitialize:", err);
|
|
586
|
+
this.scheduleReinitialize("watcher-error");
|
|
652
587
|
}
|
|
653
588
|
/**
|
|
654
589
|
* 延迟重建 watcher(防抖,避免频繁重建)
|
|
655
590
|
*/
|
|
656
|
-
scheduleReinitialize() {
|
|
591
|
+
scheduleReinitialize(reason) {
|
|
592
|
+
this.reinitializeReasonPending = reason;
|
|
657
593
|
if (this.reinitializePending) return;
|
|
658
594
|
this.reinitializePending = true;
|
|
659
595
|
if (this.reinitializeTimer) clearTimeout(this.reinitializeTimer);
|
|
660
596
|
this.reinitializeTimer = setTimeout(() => {
|
|
661
597
|
this.reinitializeTimer = null;
|
|
662
598
|
this.reinitializePending = false;
|
|
663
|
-
|
|
664
|
-
this.
|
|
665
|
-
|
|
599
|
+
const pendingReason = this.reinitializeReasonPending ?? reason;
|
|
600
|
+
this.reinitializeReasonPending = null;
|
|
601
|
+
console.log(`[ProjectWatcher] Reinitializing (reason: ${pendingReason})...`);
|
|
602
|
+
this.reinitialize(pendingReason);
|
|
603
|
+
}, RECOVERY_INTERVAL_MS);
|
|
604
|
+
this.reinitializeTimer.unref();
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* 读取项目目录指纹(目录不存在时返回 null)
|
|
608
|
+
* 用于检测 path 对应实体是否被替换(inode/dev 漂移)
|
|
609
|
+
*/
|
|
610
|
+
getProjectDirFingerprint() {
|
|
611
|
+
try {
|
|
612
|
+
const stat$1 = lstatSync(this.projectDir);
|
|
613
|
+
return `${stat$1.dev}:${stat$1.ino}`;
|
|
614
|
+
} catch {
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* 启动路径语义监测(避免 watcher 绑定到已失效句柄)
|
|
620
|
+
*/
|
|
621
|
+
startPathLivenessMonitor() {
|
|
622
|
+
this.stopPathLivenessMonitor();
|
|
623
|
+
this.pathLivenessTimer = setInterval(() => {
|
|
624
|
+
this.checkPathLiveness();
|
|
625
|
+
}, PATH_LIVENESS_INTERVAL_MS);
|
|
626
|
+
this.pathLivenessTimer.unref();
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* 停止路径语义监测
|
|
630
|
+
*/
|
|
631
|
+
stopPathLivenessMonitor() {
|
|
632
|
+
if (this.pathLivenessTimer) {
|
|
633
|
+
clearInterval(this.pathLivenessTimer);
|
|
634
|
+
this.pathLivenessTimer = null;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* 只读检查 projectDir 是否仍指向初始化时的目录实体
|
|
639
|
+
*/
|
|
640
|
+
checkPathLiveness() {
|
|
641
|
+
if (!this.initialized || this.reinitializePending) return;
|
|
642
|
+
const current = this.getProjectDirFingerprint();
|
|
643
|
+
if (current === null) {
|
|
644
|
+
console.warn("[ProjectWatcher] Project directory missing, scheduling reinitialize...");
|
|
645
|
+
this.scheduleReinitialize("missing-project-dir");
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
if (this.projectDirFingerprint === null) {
|
|
649
|
+
this.projectDirFingerprint = current;
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
if (current !== this.projectDirFingerprint) {
|
|
653
|
+
console.warn("[ProjectWatcher] Project directory replaced, scheduling reinitialize...");
|
|
654
|
+
this.scheduleReinitialize("project-dir-replaced");
|
|
655
|
+
}
|
|
666
656
|
}
|
|
667
657
|
/**
|
|
668
658
|
* 处理原始事件
|
|
669
659
|
*/
|
|
670
660
|
handleEvents(events) {
|
|
671
|
-
this.lastEventTime = Date.now();
|
|
672
|
-
this.healthCheckPending = false;
|
|
673
661
|
const watchEvents = events.map((e) => ({
|
|
674
662
|
type: e.type,
|
|
675
663
|
path: e.path
|
|
@@ -757,60 +745,29 @@ var ProjectWatcher = class {
|
|
|
757
745
|
return this.initialized;
|
|
758
746
|
}
|
|
759
747
|
/**
|
|
760
|
-
*
|
|
761
|
-
*/
|
|
762
|
-
startHealthCheck() {
|
|
763
|
-
this.stopHealthCheck();
|
|
764
|
-
this.healthCheckTimer = setInterval(() => {
|
|
765
|
-
this.performHealthCheck();
|
|
766
|
-
}, HEALTH_CHECK_INTERVAL_MS);
|
|
767
|
-
this.healthCheckTimer.unref();
|
|
768
|
-
}
|
|
769
|
-
/**
|
|
770
|
-
* 停止健康检查定时器
|
|
748
|
+
* 获取 watcher 运行时状态
|
|
771
749
|
*/
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
this.
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
/**
|
|
780
|
-
* 执行健康检查
|
|
781
|
-
*
|
|
782
|
-
* 工作流程:
|
|
783
|
-
* 1. 如果最近有事件,无需检查
|
|
784
|
-
* 2. 如果上次探测还在等待中,说明 watcher 可能失效,尝试重建
|
|
785
|
-
* 3. 否则,创建临时文件触发事件,等待下次检查验证
|
|
786
|
-
*/
|
|
787
|
-
async performHealthCheck() {
|
|
788
|
-
if (Date.now() - this.lastEventTime < HEALTH_CHECK_INTERVAL_MS) {
|
|
789
|
-
this.healthCheckPending = false;
|
|
790
|
-
return;
|
|
791
|
-
}
|
|
792
|
-
if (this.healthCheckPending) {
|
|
793
|
-
console.warn("[ProjectWatcher] Health check failed, watcher appears stale. Reinitializing...");
|
|
794
|
-
await this.reinitialize();
|
|
795
|
-
return;
|
|
796
|
-
}
|
|
797
|
-
this.healthCheckPending = true;
|
|
798
|
-
this.sendProbe();
|
|
750
|
+
get runtimeStatus() {
|
|
751
|
+
return {
|
|
752
|
+
generation: this.generation,
|
|
753
|
+
reinitializeCount: this.reinitializeCount,
|
|
754
|
+
lastReinitializeReason: this.lastReinitializeReason,
|
|
755
|
+
reinitializeReasonCounts: { ...this.reinitializeReasonCounts }
|
|
756
|
+
};
|
|
799
757
|
}
|
|
800
758
|
/**
|
|
801
|
-
*
|
|
759
|
+
* 记录重建统计
|
|
802
760
|
*/
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
} catch {}
|
|
761
|
+
markReinitialized(reason) {
|
|
762
|
+
this.reinitializeCount += 1;
|
|
763
|
+
this.lastReinitializeReason = reason;
|
|
764
|
+
this.reinitializeReasonCounts[reason] += 1;
|
|
808
765
|
}
|
|
809
766
|
/**
|
|
810
767
|
* 重新初始化 watcher
|
|
811
768
|
*/
|
|
812
|
-
async reinitialize() {
|
|
813
|
-
this.
|
|
769
|
+
async reinitialize(reason) {
|
|
770
|
+
this.stopPathLivenessMonitor();
|
|
814
771
|
if (this.subscription) {
|
|
815
772
|
try {
|
|
816
773
|
await this.subscription.unsubscribe();
|
|
@@ -819,38 +776,50 @@ var ProjectWatcher = class {
|
|
|
819
776
|
}
|
|
820
777
|
this.initialized = false;
|
|
821
778
|
this.initPromise = null;
|
|
822
|
-
this.
|
|
779
|
+
this.projectDirFingerprint = null;
|
|
823
780
|
if (!existsSync(this.projectDir)) {
|
|
824
781
|
console.warn("[ProjectWatcher] Project directory does not exist, waiting for it to be created...");
|
|
825
|
-
this.waitForProjectDir();
|
|
782
|
+
this.waitForProjectDir("missing-project-dir");
|
|
826
783
|
return;
|
|
827
784
|
}
|
|
828
785
|
try {
|
|
829
786
|
await this.init();
|
|
787
|
+
this.markReinitialized(reason);
|
|
830
788
|
console.log("[ProjectWatcher] Reinitialized successfully");
|
|
831
789
|
} catch (err) {
|
|
832
790
|
console.error("[ProjectWatcher] Failed to reinitialize:", err);
|
|
833
|
-
|
|
791
|
+
this.scheduleReinitialize(reason);
|
|
834
792
|
}
|
|
835
793
|
}
|
|
836
794
|
/**
|
|
837
795
|
* 等待项目目录被创建
|
|
838
796
|
*/
|
|
839
|
-
waitForProjectDir() {
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
797
|
+
waitForProjectDir(reason) {
|
|
798
|
+
this.reinitializeReasonPending = reason;
|
|
799
|
+
this.reinitializePending = true;
|
|
800
|
+
if (this.reinitializeTimer) {
|
|
801
|
+
clearTimeout(this.reinitializeTimer);
|
|
802
|
+
this.reinitializeTimer = null;
|
|
803
|
+
}
|
|
804
|
+
this.reinitializeTimer = setTimeout(() => {
|
|
805
|
+
this.reinitializeTimer = null;
|
|
806
|
+
this.reinitializePending = false;
|
|
807
|
+
if (!existsSync(this.projectDir)) {
|
|
808
|
+
this.waitForProjectDir(reason);
|
|
809
|
+
return;
|
|
845
810
|
}
|
|
846
|
-
|
|
847
|
-
|
|
811
|
+
const pendingReason = this.reinitializeReasonPending ?? reason;
|
|
812
|
+
this.reinitializeReasonPending = null;
|
|
813
|
+
console.log("[ProjectWatcher] Project directory created, reinitializing...");
|
|
814
|
+
this.reinitialize(pendingReason);
|
|
815
|
+
}, RECOVERY_INTERVAL_MS);
|
|
816
|
+
this.reinitializeTimer.unref();
|
|
848
817
|
}
|
|
849
818
|
/**
|
|
850
819
|
* 关闭 watcher
|
|
851
820
|
*/
|
|
852
821
|
async close() {
|
|
853
|
-
this.
|
|
822
|
+
this.stopPathLivenessMonitor();
|
|
854
823
|
if (this.debounceTimer) {
|
|
855
824
|
clearTimeout(this.debounceTimer);
|
|
856
825
|
this.debounceTimer = null;
|
|
@@ -860,6 +829,7 @@ var ProjectWatcher = class {
|
|
|
860
829
|
this.reinitializeTimer = null;
|
|
861
830
|
}
|
|
862
831
|
this.reinitializePending = false;
|
|
832
|
+
this.reinitializeReasonPending = null;
|
|
863
833
|
if (this.subscription) {
|
|
864
834
|
await this.subscription.unsubscribe();
|
|
865
835
|
this.subscription = null;
|
|
@@ -868,6 +838,7 @@ var ProjectWatcher = class {
|
|
|
868
838
|
this.pendingEvents = [];
|
|
869
839
|
this.initialized = false;
|
|
870
840
|
this.initPromise = null;
|
|
841
|
+
this.projectDirFingerprint = null;
|
|
871
842
|
}
|
|
872
843
|
};
|
|
873
844
|
/**
|
|
@@ -1032,6 +1003,22 @@ function isWatcherPoolInitialized() {
|
|
|
1032
1003
|
function getWatchedProjectDir() {
|
|
1033
1004
|
return globalProjectDir;
|
|
1034
1005
|
}
|
|
1006
|
+
/**
|
|
1007
|
+
* 获取 watcher 运行时状态
|
|
1008
|
+
*/
|
|
1009
|
+
function getWatcherRuntimeStatus() {
|
|
1010
|
+
if (!globalProjectWatcher) return null;
|
|
1011
|
+
const runtime = globalProjectWatcher.runtimeStatus;
|
|
1012
|
+
return {
|
|
1013
|
+
projectDir: globalProjectDir,
|
|
1014
|
+
initialized: globalProjectWatcher.isInitialized,
|
|
1015
|
+
subscriptionCount: globalProjectWatcher.subscriptionCount,
|
|
1016
|
+
generation: runtime.generation,
|
|
1017
|
+
reinitializeCount: runtime.reinitializeCount,
|
|
1018
|
+
lastReinitializeReason: runtime.lastReinitializeReason,
|
|
1019
|
+
reinitializeReasonCounts: runtime.reinitializeReasonCounts
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1035
1022
|
|
|
1036
1023
|
//#endregion
|
|
1037
1024
|
//#region src/reactive-fs/reactive-fs.ts
|
|
@@ -1229,6 +1216,86 @@ function getCacheSize() {
|
|
|
1229
1216
|
return stateCache$1.size;
|
|
1230
1217
|
}
|
|
1231
1218
|
|
|
1219
|
+
//#endregion
|
|
1220
|
+
//#region src/validator.ts
|
|
1221
|
+
/**
|
|
1222
|
+
* Validator for OpenSpec documents
|
|
1223
|
+
*/
|
|
1224
|
+
var Validator = class {
|
|
1225
|
+
/**
|
|
1226
|
+
* Validate a spec document
|
|
1227
|
+
*/
|
|
1228
|
+
validateSpec(spec) {
|
|
1229
|
+
const issues = [];
|
|
1230
|
+
if (!spec.overview || spec.overview.trim().length === 0) issues.push({
|
|
1231
|
+
severity: "ERROR",
|
|
1232
|
+
message: "Spec must have a Purpose/Overview section",
|
|
1233
|
+
path: "overview"
|
|
1234
|
+
});
|
|
1235
|
+
if (spec.requirements.length === 0) issues.push({
|
|
1236
|
+
severity: "ERROR",
|
|
1237
|
+
message: "Spec must have at least one requirement",
|
|
1238
|
+
path: "requirements"
|
|
1239
|
+
});
|
|
1240
|
+
for (const req of spec.requirements) {
|
|
1241
|
+
if (!req.text.includes("SHALL") && !req.text.includes("MUST")) issues.push({
|
|
1242
|
+
severity: "WARNING",
|
|
1243
|
+
message: `Requirement should contain "SHALL" or "MUST": ${req.id}`,
|
|
1244
|
+
path: `requirements.${req.id}`
|
|
1245
|
+
});
|
|
1246
|
+
if (req.scenarios.length === 0) issues.push({
|
|
1247
|
+
severity: "WARNING",
|
|
1248
|
+
message: `Requirement should have at least one scenario: ${req.id}`,
|
|
1249
|
+
path: `requirements.${req.id}.scenarios`
|
|
1250
|
+
});
|
|
1251
|
+
if (req.text.length > 1e3) issues.push({
|
|
1252
|
+
severity: "WARNING",
|
|
1253
|
+
message: `Requirement text is too long (max 1000 chars): ${req.id}`,
|
|
1254
|
+
path: `requirements.${req.id}.text`
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
return {
|
|
1258
|
+
valid: issues.filter((i) => i.severity === "ERROR").length === 0,
|
|
1259
|
+
issues
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Validate a change proposal
|
|
1264
|
+
*/
|
|
1265
|
+
validateChange(change) {
|
|
1266
|
+
const issues = [];
|
|
1267
|
+
if (!change.why || change.why.length < 50) issues.push({
|
|
1268
|
+
severity: "ERROR",
|
|
1269
|
+
message: "Change \"Why\" section must be at least 50 characters",
|
|
1270
|
+
path: "why"
|
|
1271
|
+
});
|
|
1272
|
+
if (change.why && change.why.length > 500) issues.push({
|
|
1273
|
+
severity: "WARNING",
|
|
1274
|
+
message: "Change \"Why\" section should be under 500 characters",
|
|
1275
|
+
path: "why"
|
|
1276
|
+
});
|
|
1277
|
+
if (!change.whatChanges || change.whatChanges.trim().length === 0) issues.push({
|
|
1278
|
+
severity: "ERROR",
|
|
1279
|
+
message: "Change must have a \"What Changes\" section",
|
|
1280
|
+
path: "whatChanges"
|
|
1281
|
+
});
|
|
1282
|
+
if (change.deltas.length === 0) issues.push({
|
|
1283
|
+
severity: "WARNING",
|
|
1284
|
+
message: "Change should have at least one delta",
|
|
1285
|
+
path: "deltas"
|
|
1286
|
+
});
|
|
1287
|
+
if (change.deltas.length > 50) issues.push({
|
|
1288
|
+
severity: "WARNING",
|
|
1289
|
+
message: "Change has too many deltas (max 50)",
|
|
1290
|
+
path: "deltas"
|
|
1291
|
+
});
|
|
1292
|
+
return {
|
|
1293
|
+
valid: issues.filter((i) => i.severity === "ERROR").length === 0,
|
|
1294
|
+
issues
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
};
|
|
1298
|
+
|
|
1232
1299
|
//#endregion
|
|
1233
1300
|
//#region src/adapter.ts
|
|
1234
1301
|
/**
|
|
@@ -1890,6 +1957,11 @@ const CURSOR_STYLE_VALUES = [
|
|
|
1890
1957
|
"underline",
|
|
1891
1958
|
"bar"
|
|
1892
1959
|
];
|
|
1960
|
+
const TERMINAL_RENDERER_ENGINE_VALUES = ["xterm", "ghostty"];
|
|
1961
|
+
const TerminalRendererEngineSchema = z.enum(TERMINAL_RENDERER_ENGINE_VALUES);
|
|
1962
|
+
function isTerminalRendererEngine(value) {
|
|
1963
|
+
return TERMINAL_RENDERER_ENGINE_VALUES.includes(value);
|
|
1964
|
+
}
|
|
1893
1965
|
const BASE_PACKAGE_MANAGER_RUNNERS = [
|
|
1894
1966
|
{
|
|
1895
1967
|
id: "npx",
|
|
@@ -2227,8 +2299,10 @@ const TerminalConfigSchema = z.object({
|
|
|
2227
2299
|
fontFamily: z.string().default(""),
|
|
2228
2300
|
cursorBlink: z.boolean().default(true),
|
|
2229
2301
|
cursorStyle: z.enum(CURSOR_STYLE_VALUES).default("block"),
|
|
2230
|
-
scrollback: z.number().min(0).max(1e5).default(1e3)
|
|
2302
|
+
scrollback: z.number().min(0).max(1e5).default(1e3),
|
|
2303
|
+
rendererEngine: z.string().default("xterm")
|
|
2231
2304
|
});
|
|
2305
|
+
const DashboardConfigSchema = z.object({ trendPointLimit: z.number().int().min(20).max(500).default(100) });
|
|
2232
2306
|
/**
|
|
2233
2307
|
* OpenSpecUI 配置 Schema
|
|
2234
2308
|
*
|
|
@@ -2240,13 +2314,15 @@ const OpenSpecUIConfigSchema = z.object({
|
|
|
2240
2314
|
args: z.array(z.string()).optional()
|
|
2241
2315
|
}).default({}),
|
|
2242
2316
|
theme: z.enum(THEME_VALUES).default("system"),
|
|
2243
|
-
terminal: TerminalConfigSchema.default(TerminalConfigSchema.parse({}))
|
|
2317
|
+
terminal: TerminalConfigSchema.default(TerminalConfigSchema.parse({})),
|
|
2318
|
+
dashboard: DashboardConfigSchema.default(DashboardConfigSchema.parse({}))
|
|
2244
2319
|
});
|
|
2245
2320
|
/** 默认配置(静态,用于测试和类型) */
|
|
2246
2321
|
const DEFAULT_CONFIG = {
|
|
2247
2322
|
cli: {},
|
|
2248
2323
|
theme: "system",
|
|
2249
|
-
terminal: TerminalConfigSchema.parse({})
|
|
2324
|
+
terminal: TerminalConfigSchema.parse({}),
|
|
2325
|
+
dashboard: DashboardConfigSchema.parse({})
|
|
2250
2326
|
};
|
|
2251
2327
|
/**
|
|
2252
2328
|
* 配置管理器
|
|
@@ -2312,6 +2388,10 @@ var ConfigManager = class {
|
|
|
2312
2388
|
terminal: {
|
|
2313
2389
|
...current.terminal,
|
|
2314
2390
|
...config.terminal
|
|
2391
|
+
},
|
|
2392
|
+
dashboard: {
|
|
2393
|
+
...current.dashboard,
|
|
2394
|
+
...config.dashboard
|
|
2315
2395
|
}
|
|
2316
2396
|
};
|
|
2317
2397
|
const serialized = JSON.stringify(merged, null, 2);
|
|
@@ -3048,6 +3128,17 @@ async function isToolConfigured(projectDir, toolId) {
|
|
|
3048
3128
|
return (await getConfiguredTools(projectDir)).includes(toolId);
|
|
3049
3129
|
}
|
|
3050
3130
|
|
|
3131
|
+
//#endregion
|
|
3132
|
+
//#region src/dashboard-types.ts
|
|
3133
|
+
const DASHBOARD_METRIC_KEYS = [
|
|
3134
|
+
"specifications",
|
|
3135
|
+
"requirements",
|
|
3136
|
+
"activeChanges",
|
|
3137
|
+
"inProgressChanges",
|
|
3138
|
+
"completedChanges",
|
|
3139
|
+
"taskCompletionPercent"
|
|
3140
|
+
];
|
|
3141
|
+
|
|
3051
3142
|
//#endregion
|
|
3052
3143
|
//#region src/opsx-types.ts
|
|
3053
3144
|
/** Check if an outputPath contains glob pattern characters */
|
|
@@ -3908,4 +3999,4 @@ const PtyServerMessageSchema = z.discriminatedUnion("type", [
|
|
|
3908
3999
|
]);
|
|
3909
4000
|
|
|
3910
4001
|
//#endregion
|
|
3911
|
-
export { AI_TOOLS, ApplyInstructionsSchema, ApplyTaskSchema, ArtifactInstructionsSchema, ArtifactStatusSchema, ChangeFileSchema, ChangeSchema, ChangeStatusSchema, CliExecutor, ConfigManager, DEFAULT_CONFIG, DeltaOperationType, DeltaSchema, DeltaSpecSchema, DependencyInfoSchema, MarkdownParser, OpenSpecAdapter, OpenSpecUIConfigSchema, OpenSpecWatcher, OpsxKernel, ProjectWatcher, PtyAttachMessageSchema, PtyBufferResponseSchema, PtyClientMessageSchema, PtyCloseMessageSchema, PtyCreateMessageSchema, PtyCreatedResponseSchema, PtyErrorCodeSchema, PtyErrorResponseSchema, PtyExitResponseSchema, PtyInputMessageSchema, PtyListMessageSchema, PtyListResponseSchema, PtyOutputResponseSchema, PtyPlatformSchema, PtyResizeMessageSchema, PtyServerMessageSchema, PtyTitleResponseSchema, ReactiveContext, ReactiveState, RequirementSchema, SchemaArtifactSchema, SchemaDetailSchema, SchemaInfoSchema, SchemaResolutionSchema, SpecSchema, TaskSchema, TemplatesSchema, TerminalConfigSchema, Validator, acquireWatcher, buildCliRunnerCandidates, clearCache, closeAllProjectWatchers, closeAllWatchers, contextStorage, createCleanCliEnv, createFileChangeObservable, getActiveWatcherCount, getAllToolIds, getAllTools, getAvailableToolIds, getAvailableTools, getCacheSize, getConfiguredTools, getDefaultCliCommand, getDefaultCliCommandString, getProjectWatcher, getToolById, getWatchedProjectDir, initWatcherPool, isGlobPattern, isToolConfigured, isWatcherPoolInitialized, parseCliCommand, reactiveExists, reactiveReadDir, reactiveReadFile, reactiveStat, sniffGlobalCli };
|
|
4002
|
+
export { AI_TOOLS, ApplyInstructionsSchema, ApplyTaskSchema, ArtifactInstructionsSchema, ArtifactStatusSchema, ChangeFileSchema, ChangeSchema, ChangeStatusSchema, CliExecutor, ConfigManager, DASHBOARD_METRIC_KEYS, DEFAULT_CONFIG, DashboardConfigSchema, DeltaOperationType, DeltaSchema, DeltaSpecSchema, DependencyInfoSchema, MarkdownParser, OpenSpecAdapter, OpenSpecUIConfigSchema, OpenSpecWatcher, OpsxKernel, ProjectWatcher, PtyAttachMessageSchema, PtyBufferResponseSchema, PtyClientMessageSchema, PtyCloseMessageSchema, PtyCreateMessageSchema, PtyCreatedResponseSchema, PtyErrorCodeSchema, PtyErrorResponseSchema, PtyExitResponseSchema, PtyInputMessageSchema, PtyListMessageSchema, PtyListResponseSchema, PtyOutputResponseSchema, PtyPlatformSchema, PtyResizeMessageSchema, PtyServerMessageSchema, PtyTitleResponseSchema, ReactiveContext, ReactiveState, RequirementSchema, SchemaArtifactSchema, SchemaDetailSchema, SchemaInfoSchema, SchemaResolutionSchema, SpecSchema, TaskSchema, TemplatesSchema, TerminalConfigSchema, TerminalRendererEngineSchema, Validator, acquireWatcher, buildCliRunnerCandidates, clearCache, closeAllProjectWatchers, closeAllWatchers, contextStorage, createCleanCliEnv, createFileChangeObservable, getActiveWatcherCount, getAllToolIds, getAllTools, getAvailableToolIds, getAvailableTools, getCacheSize, getConfiguredTools, getDefaultCliCommand, getDefaultCliCommandString, getProjectWatcher, getToolById, getWatchedProjectDir, getWatcherRuntimeStatus, initWatcherPool, isGlobPattern, isTerminalRendererEngine, isToolConfigured, isWatcherPoolInitialized, parseCliCommand, reactiveExists, reactiveReadDir, reactiveReadFile, reactiveStat, sniffGlobalCli };
|