@nick848/sf-cli 1.0.3 → 1.0.4
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/CHANGELOG.md +15 -0
- package/dist/cli/index.js +495 -86
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.mts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +495 -86
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +495 -86
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -3605,12 +3605,20 @@ function getScheduleRuleDescription(step) {
|
|
|
3605
3605
|
var DEFAULT_CONFIRMATION_POINTS = [
|
|
3606
3606
|
{
|
|
3607
3607
|
type: "spec-review",
|
|
3608
|
-
name: "\u89C4\
|
|
3609
|
-
description: "\u89C4\
|
|
3608
|
+
name: "\u89C4\u683C\u786E\u8BA4",
|
|
3609
|
+
description: "\u89C4\u683C\u62C6\u5206\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u786E\u8BA4\u89C4\u683C\u6587\u4EF6\u540E\u7EE7\u7EED",
|
|
3610
3610
|
triggerStep: "explore",
|
|
3611
3611
|
targetStep: "new",
|
|
3612
3612
|
required: true
|
|
3613
3613
|
},
|
|
3614
|
+
{
|
|
3615
|
+
type: "spec-review",
|
|
3616
|
+
name: "\u89C4\u683C\u786E\u8BA4",
|
|
3617
|
+
description: "\u89C4\u683C\u62C6\u5206\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u786E\u8BA4\u89C4\u683C\u6587\u4EF6\u540E\u7EE7\u7EED",
|
|
3618
|
+
triggerStep: "propose",
|
|
3619
|
+
targetStep: "apply",
|
|
3620
|
+
required: true
|
|
3621
|
+
},
|
|
3614
3622
|
{
|
|
3615
3623
|
type: "architecture",
|
|
3616
3624
|
name: "\u67B6\u6784\u8C03\u6574\u786E\u8BA4",
|
|
@@ -3646,19 +3654,15 @@ var ConfirmationManager = class {
|
|
|
3646
3654
|
confirmationPoints;
|
|
3647
3655
|
confirmations = /* @__PURE__ */ new Map();
|
|
3648
3656
|
constructor(customPoints) {
|
|
3649
|
-
|
|
3650
|
-
this.confirmationPoints = new Map(points.map((p) => [p.type, p]));
|
|
3657
|
+
this.confirmationPoints = customPoints || DEFAULT_CONFIRMATION_POINTS;
|
|
3651
3658
|
}
|
|
3652
3659
|
/**
|
|
3653
3660
|
* 获取指定阶段的确认点
|
|
3654
3661
|
*/
|
|
3655
3662
|
getConfirmationPointForTransition(from, to) {
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
}
|
|
3660
|
-
}
|
|
3661
|
-
return void 0;
|
|
3663
|
+
return this.confirmationPoints.find(
|
|
3664
|
+
(point) => point.triggerStep === from && point.targetStep === to
|
|
3665
|
+
);
|
|
3662
3666
|
}
|
|
3663
3667
|
/**
|
|
3664
3668
|
* 检查是否需要确认
|
|
@@ -3671,7 +3675,7 @@ var ConfirmationManager = class {
|
|
|
3671
3675
|
* 获取确认点详情
|
|
3672
3676
|
*/
|
|
3673
3677
|
getConfirmationPoint(type) {
|
|
3674
|
-
return this.confirmationPoints.
|
|
3678
|
+
return this.confirmationPoints.find((point) => point.type === type);
|
|
3675
3679
|
}
|
|
3676
3680
|
/**
|
|
3677
3681
|
* 记录确认
|
|
@@ -3715,14 +3719,14 @@ var ConfirmationManager = class {
|
|
|
3715
3719
|
* 获取所有确认点
|
|
3716
3720
|
*/
|
|
3717
3721
|
getAllConfirmationPoints() {
|
|
3718
|
-
return
|
|
3722
|
+
return [...this.confirmationPoints];
|
|
3719
3723
|
}
|
|
3720
3724
|
/**
|
|
3721
3725
|
* 获取指定阶段需要清除的确认点
|
|
3722
3726
|
*/
|
|
3723
3727
|
getConfirmationsToClear(targetStep) {
|
|
3724
3728
|
const types = [];
|
|
3725
|
-
for (const point of this.confirmationPoints
|
|
3729
|
+
for (const point of this.confirmationPoints) {
|
|
3726
3730
|
const stepOrder = ["explore", "new", "continue", "propose", "apply", "archive"];
|
|
3727
3731
|
const targetIndex = stepOrder.indexOf(targetStep);
|
|
3728
3732
|
const triggerIndex = stepOrder.indexOf(point.triggerStep);
|
|
@@ -3845,6 +3849,26 @@ var WorkflowEngine = class {
|
|
|
3845
3849
|
devStandards: this.devStandards
|
|
3846
3850
|
};
|
|
3847
3851
|
}
|
|
3852
|
+
/**
|
|
3853
|
+
* 获取规格文件路径
|
|
3854
|
+
*/
|
|
3855
|
+
getSpecFilePath() {
|
|
3856
|
+
if (!this.state) return null;
|
|
3857
|
+
return path5.join(this.openspecPath, "changes", `${this.state.id}-spec.md`);
|
|
3858
|
+
}
|
|
3859
|
+
/**
|
|
3860
|
+
* 检查规格文件是否存在
|
|
3861
|
+
*/
|
|
3862
|
+
async hasSpecFile() {
|
|
3863
|
+
const specPath = this.getSpecFilePath();
|
|
3864
|
+
if (!specPath) return false;
|
|
3865
|
+
try {
|
|
3866
|
+
await fs4.access(specPath);
|
|
3867
|
+
return true;
|
|
3868
|
+
} catch {
|
|
3869
|
+
return false;
|
|
3870
|
+
}
|
|
3871
|
+
}
|
|
3848
3872
|
/**
|
|
3849
3873
|
* 启动新工作流
|
|
3850
3874
|
*/
|
|
@@ -6708,16 +6732,23 @@ async function handleNew(args, ctx) {
|
|
|
6708
6732
|
if (workflowEngine) {
|
|
6709
6733
|
const existingState = workflowEngine.getState();
|
|
6710
6734
|
if (existingState && existingState.status === "running") {
|
|
6735
|
+
if (existingState.currentStep === "explore" || existingState.currentStep === "propose") {
|
|
6736
|
+
const specPath = path5.join(workingDir, "openspec", "changes", `${existingState.id}-spec.md`);
|
|
6737
|
+
if (fs10.existsSync(specPath)) {
|
|
6738
|
+
return {
|
|
6739
|
+
output: chalk9.yellow("\u5F53\u524D\u5DE5\u4F5C\u6D41\u6B63\u5728\u7B49\u5F85\u89C4\u683C\u786E\u8BA4") + chalk9.gray(`
|
|
6740
|
+
|
|
6741
|
+
\u5DE5\u4F5C\u6D41: ${existingState.title}`) + chalk9.gray(`
|
|
6742
|
+
\u53D8\u66F4ID: ${existingState.id}`) + chalk9.cyan("\n\n\u89C4\u683C\u6587\u4EF6\u5DF2\u751F\u6210:") + chalk9.white(`
|
|
6743
|
+
${specPath}`) + chalk9.yellow("\n\n\u8BF7\u786E\u8BA4\u89C4\u683C\u540E\u7EE7\u7EED:") + chalk9.gray("\n /opsx:confirm spec-review - \u786E\u8BA4\u89C4\u683C") + chalk9.gray("\n /opsx:status - \u67E5\u770B\u8BE6\u60C5")
|
|
6744
|
+
};
|
|
6745
|
+
}
|
|
6746
|
+
}
|
|
6711
6747
|
return {
|
|
6712
6748
|
output: chalk9.yellow("\u5F53\u524D\u5DF2\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") + chalk9.gray(`
|
|
6713
6749
|
|
|
6714
6750
|
\u5DE5\u4F5C\u6D41: ${existingState.title}`) + chalk9.gray(`
|
|
6715
|
-
\u5F53\u524D\u9636\u6BB5: ${existingState.currentStep}`) + chalk9.gray(
|
|
6716
|
-
|
|
6717
|
-
\u9009\u9879:`) + chalk9.gray(`
|
|
6718
|
-
1. \u7EE7\u7EED\u5F53\u524D\u5DE5\u4F5C\u6D41: /opsx:${existingState.currentStep}`) + chalk9.gray(`
|
|
6719
|
-
2. \u53D6\u6D88\u5F53\u524D\u5DE5\u4F5C\u6D41: /opsx:cancel`) + chalk9.gray(`
|
|
6720
|
-
3. \u67E5\u770B\u5DE5\u4F5C\u6D41\u72B6\u6001: /opsx:status`)
|
|
6751
|
+
\u5F53\u524D\u9636\u6BB5: ${existingState.currentStep}`) + chalk9.gray("\n\n\u9009\u9879:") + chalk9.gray("\n 1. \u7EE7\u7EED\u5F53\u524D\u5DE5\u4F5C\u6D41: /opsx:status") + chalk9.gray("\n 2. \u53D6\u6D88\u5F53\u524D\u5DE5\u4F5C\u6D41: /opsx:cancel")
|
|
6721
6752
|
};
|
|
6722
6753
|
}
|
|
6723
6754
|
}
|
|
@@ -6732,6 +6763,7 @@ async function handleNew(args, ctx) {
|
|
|
6732
6763
|
async function newFeature(options, workingDir, workflowEngine) {
|
|
6733
6764
|
const cwd = workingDir || process.cwd();
|
|
6734
6765
|
const { requirement, forceComplexity } = options;
|
|
6766
|
+
const lines = [];
|
|
6735
6767
|
try {
|
|
6736
6768
|
const stats = await fs4.stat(cwd);
|
|
6737
6769
|
if (!stats.isDirectory()) {
|
|
@@ -6739,51 +6771,283 @@ async function newFeature(options, workingDir, workflowEngine) {
|
|
|
6739
6771
|
output: chalk9.red(`\u9519\u8BEF: ${cwd} \u4E0D\u662F\u6709\u6548\u76EE\u5F55`)
|
|
6740
6772
|
};
|
|
6741
6773
|
}
|
|
6742
|
-
} catch
|
|
6774
|
+
} catch {
|
|
6743
6775
|
return {
|
|
6744
6776
|
output: chalk9.red(`\u9519\u8BEF: \u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u65E0\u6743\u9650\u8BBF\u95EE ${cwd}`)
|
|
6745
6777
|
};
|
|
6746
6778
|
}
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6779
|
+
lines.push(chalk9.cyan("\u{1F50D} \u5206\u6790\u9879\u76EE..."));
|
|
6780
|
+
const context = await readProjectContext(cwd);
|
|
6781
|
+
lines.push(chalk9.gray(` \u9879\u76EE: ${context.name}`));
|
|
6782
|
+
lines.push(chalk9.gray(` \u7C7B\u578B: ${context.type}`));
|
|
6783
|
+
lines.push(chalk9.gray(` \u6846\u67B6: ${context.framework || "\u672A\u8BC6\u522B"}`));
|
|
6784
|
+
lines.push("");
|
|
6785
|
+
lines.push(chalk9.cyan("\u{1F4CA} \u5206\u6790\u9700\u6C42\u590D\u6742\u5EA6..."));
|
|
6786
|
+
const analysis = forceComplexity ? createForcedAnalysis(forceComplexity) : analyzeComplexity(requirement, context);
|
|
6787
|
+
lines.push(chalk9.gray(` \u590D\u6742\u5EA6: ${analysis.score}/10`));
|
|
6788
|
+
lines.push(chalk9.gray(` \u6D41\u7A0B\u7C7B\u578B: ${analysis.recommendation === "complex" ? "\u590D\u6742\u6D41\u7A0B" : "\u7B80\u5355\u6D41\u7A0B"}`));
|
|
6789
|
+
for (const factor of analysis.factors) {
|
|
6790
|
+
lines.push(chalk9.gray(` - ${factor}`));
|
|
6791
|
+
}
|
|
6792
|
+
lines.push("");
|
|
6793
|
+
lines.push(chalk9.cyan("\u{1F4CB} \u521D\u59CB\u5316\u5DE5\u4F5C\u6D41..."));
|
|
6794
|
+
const workflow = workflowEngine || new WorkflowEngine();
|
|
6795
|
+
if (!workflowEngine) {
|
|
6796
|
+
await workflow.initialize(cwd);
|
|
6797
|
+
}
|
|
6798
|
+
const state = await workflow.start(requirement, analysis.score, {
|
|
6799
|
+
title: extractTitle(requirement)
|
|
6800
|
+
});
|
|
6801
|
+
lines.push(chalk9.gray(` \u53D8\u66F4ID: ${state.id}`));
|
|
6802
|
+
lines.push(chalk9.gray(` \u5DE5\u4F5C\u6D41: ${state.type}`));
|
|
6803
|
+
lines.push("");
|
|
6804
|
+
lines.push(chalk9.cyan("\u{1F4DD} \u751F\u6210\u89C4\u683C\u62C6\u5206..."));
|
|
6805
|
+
const spec = await generateSpec(requirement, context, analysis, state.id);
|
|
6806
|
+
const specPath = await saveSpecFile(cwd, spec);
|
|
6807
|
+
lines.push(chalk9.green(" \u2713 \u89C4\u683C\u6587\u4EF6\u5DF2\u751F\u6210"));
|
|
6808
|
+
lines.push(chalk9.gray(` \u8DEF\u5F84: ${specPath}`));
|
|
6809
|
+
lines.push("");
|
|
6810
|
+
lines.push(chalk9.cyan.bold("\u{1F4CB} \u89C4\u683C\u6982\u89C8:"));
|
|
6811
|
+
lines.push(chalk9.white(`
|
|
6812
|
+
${spec.summary}`));
|
|
6813
|
+
if (spec.items.length > 0) {
|
|
6814
|
+
lines.push("");
|
|
6815
|
+
lines.push(chalk9.cyan(" \u4EFB\u52A1\u62C6\u5206:"));
|
|
6816
|
+
for (const item of spec.items) {
|
|
6817
|
+
const priorityIcon = item.priority === "high" ? "\u{1F534}" : item.priority === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
6818
|
+
lines.push(chalk9.gray(` ${priorityIcon} [${item.id}] ${item.title}`));
|
|
6819
|
+
}
|
|
6820
|
+
}
|
|
6821
|
+
if (spec.risks.length > 0) {
|
|
6822
|
+
lines.push("");
|
|
6823
|
+
lines.push(chalk9.yellow(" \u26A0\uFE0F \u98CE\u9669\u63D0\u793A:"));
|
|
6824
|
+
for (const risk of spec.risks) {
|
|
6825
|
+
lines.push(chalk9.gray(` - ${risk}`));
|
|
6826
|
+
}
|
|
6827
|
+
}
|
|
6828
|
+
lines.push("");
|
|
6829
|
+
lines.push(chalk9.yellow.bold("\u23F3 \u7B49\u5F85\u89C4\u683C\u786E\u8BA4"));
|
|
6830
|
+
lines.push(chalk9.gray("\n\u8BF7\u68C0\u67E5\u751F\u6210\u7684\u89C4\u683C\u6587\u4EF6\uFF0C\u786E\u8BA4\u540E\u7EE7\u7EED:"));
|
|
6831
|
+
lines.push(chalk9.white("\n /opsx:confirm spec-review - \u786E\u8BA4\u89C4\u683C\uFF0C\u8FDB\u5165\u4E0B\u4E00\u9636\u6BB5"));
|
|
6832
|
+
lines.push(chalk9.white(" /opsx:rollback explore - \u89C4\u683C\u4E0D\u7B26\uFF0C\u91CD\u65B0\u62C6\u5206"));
|
|
6833
|
+
lines.push(chalk9.white(" /opsx:status - \u67E5\u770B\u5DE5\u4F5C\u6D41\u72B6\u6001"));
|
|
6834
|
+
return { output: lines.join("\n") };
|
|
6835
|
+
}
|
|
6836
|
+
async function generateSpec(requirement, context, analysis, changeId) {
|
|
6837
|
+
const spec = {
|
|
6838
|
+
changeId,
|
|
6839
|
+
requirement,
|
|
6840
|
+
summary: "",
|
|
6841
|
+
items: [],
|
|
6842
|
+
architectureNotes: [],
|
|
6843
|
+
risks: [],
|
|
6844
|
+
suggestions: []
|
|
6845
|
+
};
|
|
6846
|
+
spec.summary = generateSummary(requirement);
|
|
6847
|
+
if (analysis.recommendation === "complex") {
|
|
6848
|
+
spec.items = generateComplexTasks(requirement, context, analysis);
|
|
6849
|
+
spec.architectureNotes = generateArchitectureNotes(requirement, context);
|
|
6850
|
+
} else {
|
|
6851
|
+
spec.items = generateSimpleTasks(requirement);
|
|
6852
|
+
}
|
|
6853
|
+
spec.risks = generateRisks(requirement, context, analysis);
|
|
6854
|
+
spec.suggestions = generateSuggestions(requirement, context, analysis);
|
|
6855
|
+
return spec;
|
|
6856
|
+
}
|
|
6857
|
+
function generateSummary(requirement) {
|
|
6858
|
+
const firstSentence = requirement.split(/[。!?\n]/)[0];
|
|
6859
|
+
return firstSentence.length > 100 ? firstSentence.slice(0, 97) + "..." : firstSentence;
|
|
6860
|
+
}
|
|
6861
|
+
function generateComplexTasks(requirement, context, analysis) {
|
|
6862
|
+
const items = [];
|
|
6863
|
+
let itemId = 1;
|
|
6864
|
+
const featurePatterns = [
|
|
6865
|
+
{ pattern: /用户|登录|注册|认证|权限/, title: "\u7528\u6237\u8BA4\u8BC1\u6A21\u5757", priority: "high" },
|
|
6866
|
+
{ pattern: /数据|存储|缓存|数据库/, title: "\u6570\u636E\u5C42\u5B9E\u73B0", priority: "high" },
|
|
6867
|
+
{ pattern: /接口|API|请求|响应/, title: "API \u63A5\u53E3\u5F00\u53D1", priority: "high" },
|
|
6868
|
+
{ pattern: /界面|页面|组件|UI/, title: "\u754C\u9762\u5F00\u53D1", priority: "medium" },
|
|
6869
|
+
{ pattern: /测试|单测|覆盖/, title: "\u6D4B\u8BD5\u7528\u4F8B\u7F16\u5199", priority: "medium" },
|
|
6870
|
+
{ pattern: /文档|说明/, title: "\u6587\u6863\u7F16\u5199", priority: "low" },
|
|
6871
|
+
{ pattern: /配置|设置/, title: "\u914D\u7F6E\u7BA1\u7406", priority: "low" },
|
|
6872
|
+
{ pattern: /优化|性能/, title: "\u6027\u80FD\u4F18\u5316", priority: "medium" },
|
|
6873
|
+
{ pattern: /安全|加密/, title: "\u5B89\u5168\u5B9E\u73B0", priority: "high" },
|
|
6874
|
+
{ pattern: /日志|监控/, title: "\u65E5\u5FD7\u76D1\u63A7", priority: "low" }
|
|
6875
|
+
];
|
|
6876
|
+
for (const { pattern, title, priority } of featurePatterns) {
|
|
6877
|
+
if (pattern.test(requirement)) {
|
|
6878
|
+
items.push({
|
|
6879
|
+
id: `T${itemId.toString().padStart(3, "0")}`,
|
|
6880
|
+
title,
|
|
6881
|
+
description: `${title}\u76F8\u5173\u7684\u529F\u80FD\u5B9E\u73B0`,
|
|
6882
|
+
priority,
|
|
6883
|
+
dependencies: itemId > 1 ? [`T${(itemId - 1).toString().padStart(3, "0")}`] : [],
|
|
6884
|
+
estimatedComplexity: priority === "high" ? 3 : priority === "medium" ? 2 : 1
|
|
6885
|
+
});
|
|
6886
|
+
itemId++;
|
|
6887
|
+
}
|
|
6888
|
+
}
|
|
6889
|
+
if (items.length === 0) {
|
|
6890
|
+
items.push({
|
|
6891
|
+
id: "T001",
|
|
6892
|
+
title: "\u9700\u6C42\u5206\u6790\u4E0E\u8BBE\u8BA1",
|
|
6893
|
+
description: "\u5206\u6790\u9700\u6C42\u7EC6\u8282\uFF0C\u8BBE\u8BA1\u5B9E\u73B0\u65B9\u6848",
|
|
6894
|
+
priority: "high",
|
|
6895
|
+
dependencies: [],
|
|
6896
|
+
estimatedComplexity: 2
|
|
6756
6897
|
});
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
"",
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6898
|
+
items.push({
|
|
6899
|
+
id: "T002",
|
|
6900
|
+
title: "\u6838\u5FC3\u529F\u80FD\u5B9E\u73B0",
|
|
6901
|
+
description: requirement,
|
|
6902
|
+
priority: "high",
|
|
6903
|
+
dependencies: ["T001"],
|
|
6904
|
+
estimatedComplexity: analysis.score
|
|
6905
|
+
});
|
|
6906
|
+
items.push({
|
|
6907
|
+
id: "T003",
|
|
6908
|
+
title: "\u6D4B\u8BD5\u4E0E\u9A8C\u8BC1",
|
|
6909
|
+
description: "\u7F16\u5199\u6D4B\u8BD5\u7528\u4F8B\uFF0C\u9A8C\u8BC1\u529F\u80FD\u6B63\u786E\u6027",
|
|
6910
|
+
priority: "medium",
|
|
6911
|
+
dependencies: ["T002"],
|
|
6912
|
+
estimatedComplexity: 2
|
|
6913
|
+
});
|
|
6914
|
+
}
|
|
6915
|
+
return items;
|
|
6916
|
+
}
|
|
6917
|
+
function generateSimpleTasks(requirement, context) {
|
|
6918
|
+
return [
|
|
6919
|
+
{
|
|
6920
|
+
id: "T001",
|
|
6921
|
+
title: "\u5B9E\u73B0\u53D8\u66F4",
|
|
6922
|
+
description: requirement,
|
|
6923
|
+
priority: "high",
|
|
6924
|
+
dependencies: [],
|
|
6925
|
+
estimatedComplexity: 3
|
|
6926
|
+
},
|
|
6927
|
+
{
|
|
6928
|
+
id: "T002",
|
|
6929
|
+
title: "\u6D4B\u8BD5\u9A8C\u8BC1",
|
|
6930
|
+
description: "\u9A8C\u8BC1\u53D8\u66F4\u6B63\u786E\u6027",
|
|
6931
|
+
priority: "medium",
|
|
6932
|
+
dependencies: ["T001"],
|
|
6933
|
+
estimatedComplexity: 1
|
|
6782
6934
|
}
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6935
|
+
];
|
|
6936
|
+
}
|
|
6937
|
+
function generateArchitectureNotes(requirement, context) {
|
|
6938
|
+
const notes = [];
|
|
6939
|
+
if (context.framework) {
|
|
6940
|
+
notes.push(`\u9879\u76EE\u4F7F\u7528 ${context.framework} \u6846\u67B6\uFF0C\u9700\u9075\u5FAA\u5176\u6700\u4F73\u5B9E\u8DF5`);
|
|
6786
6941
|
}
|
|
6942
|
+
if (requirement.includes("\u6A21\u5757") || requirement.includes("\u7EC4\u4EF6")) {
|
|
6943
|
+
notes.push("\u5EFA\u8BAE\u91C7\u7528\u6A21\u5757\u5316\u8BBE\u8BA1\uFF0C\u4FDD\u6301\u7EC4\u4EF6\u804C\u8D23\u5355\u4E00");
|
|
6944
|
+
}
|
|
6945
|
+
if (requirement.includes("API") || requirement.includes("\u63A5\u53E3")) {
|
|
6946
|
+
notes.push("API \u8BBE\u8BA1\u9700\u8003\u8651\u7248\u672C\u63A7\u5236\u548C\u5411\u540E\u517C\u5BB9");
|
|
6947
|
+
}
|
|
6948
|
+
if (context.structure.srcStructure) {
|
|
6949
|
+
notes.push(`\u73B0\u6709\u6E90\u7801\u7ED3\u6784: ${context.structure.srcStructure}`);
|
|
6950
|
+
}
|
|
6951
|
+
return notes;
|
|
6952
|
+
}
|
|
6953
|
+
function generateRisks(requirement, context, analysis) {
|
|
6954
|
+
const risks = [];
|
|
6955
|
+
if (!context.framework) {
|
|
6956
|
+
risks.push("\u9879\u76EE\u6846\u67B6\u672A\u8BC6\u522B\uFF0C\u53EF\u80FD\u5F71\u54CD\u4EE3\u7801\u98CE\u683C\u4E00\u81F4\u6027");
|
|
6957
|
+
}
|
|
6958
|
+
if (analysis.score >= 7) {
|
|
6959
|
+
risks.push("\u9700\u6C42\u590D\u6742\u5EA6\u8F83\u9AD8\uFF0C\u5EFA\u8BAE\u5206\u9636\u6BB5\u5B9E\u73B0");
|
|
6960
|
+
}
|
|
6961
|
+
if (requirement.includes("\u8FC1\u79FB") || requirement.includes("\u91CD\u6784")) {
|
|
6962
|
+
risks.push("\u6D89\u53CA\u73B0\u6709\u4EE3\u7801\u4FEE\u6539\uFF0C\u9700\u6CE8\u610F\u56DE\u5F52\u6D4B\u8BD5");
|
|
6963
|
+
}
|
|
6964
|
+
if (requirement.includes("\u6743\u9650") || requirement.includes("\u5B89\u5168")) {
|
|
6965
|
+
risks.push("\u6D89\u53CA\u5B89\u5168\u654F\u611F\u529F\u80FD\uFF0C\u9700\u8981\u989D\u5916\u5BA1\u67E5");
|
|
6966
|
+
}
|
|
6967
|
+
return risks;
|
|
6968
|
+
}
|
|
6969
|
+
function generateSuggestions(requirement, context, analysis) {
|
|
6970
|
+
const suggestions = [];
|
|
6971
|
+
if (context.norms.devStandards) {
|
|
6972
|
+
suggestions.push("\u9879\u76EE\u5DF2\u6709\u5F00\u53D1\u89C4\u8303\uFF0C\u8BF7\u9075\u5FAA\u73B0\u6709\u89C4\u8303");
|
|
6973
|
+
}
|
|
6974
|
+
if (analysis.recommendation === "complex") {
|
|
6975
|
+
suggestions.push("\u590D\u6742\u9700\u6C42\u5EFA\u8BAE\u5148\u8FDB\u884C\u6280\u672F\u8BC4\u5BA1");
|
|
6976
|
+
suggestions.push("\u5EFA\u8BAE\u8C03\u7528 $architect \u83B7\u53D6\u67B6\u6784\u5EFA\u8BAE");
|
|
6977
|
+
}
|
|
6978
|
+
if (context.techStack.length > 0) {
|
|
6979
|
+
suggestions.push(`\u6280\u672F\u6808: ${context.techStack.join(", ")}`);
|
|
6980
|
+
}
|
|
6981
|
+
return suggestions;
|
|
6982
|
+
}
|
|
6983
|
+
async function saveSpecFile(cwd, spec) {
|
|
6984
|
+
const changesDir = path5.join(cwd, "openspec", "changes");
|
|
6985
|
+
await fs4.mkdir(changesDir, { recursive: true });
|
|
6986
|
+
const specPath = path5.join(changesDir, `${spec.changeId}-spec.md`);
|
|
6987
|
+
const content = formatSpecFile(spec);
|
|
6988
|
+
await fs4.writeFile(specPath, content, "utf-8");
|
|
6989
|
+
return specPath;
|
|
6990
|
+
}
|
|
6991
|
+
function formatSpecFile(spec) {
|
|
6992
|
+
const lines = [];
|
|
6993
|
+
lines.push(`# Spec: ${spec.summary}`);
|
|
6994
|
+
lines.push("");
|
|
6995
|
+
lines.push(`> \u53D8\u66F4ID: ${spec.changeId}`);
|
|
6996
|
+
lines.push(`> \u751F\u6210\u65F6\u95F4: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
6997
|
+
lines.push("");
|
|
6998
|
+
lines.push("---");
|
|
6999
|
+
lines.push("");
|
|
7000
|
+
lines.push("## \u9700\u6C42\u6982\u8FF0");
|
|
7001
|
+
lines.push("");
|
|
7002
|
+
lines.push(spec.requirement);
|
|
7003
|
+
lines.push("");
|
|
7004
|
+
lines.push("## \u4EFB\u52A1\u62C6\u5206");
|
|
7005
|
+
lines.push("");
|
|
7006
|
+
for (const item of spec.items) {
|
|
7007
|
+
const priorityLabel = item.priority === "high" ? "\u{1F534} \u9AD8" : item.priority === "medium" ? "\u{1F7E1} \u4E2D" : "\u{1F7E2} \u4F4E";
|
|
7008
|
+
lines.push(`### ${item.id}: ${item.title}`);
|
|
7009
|
+
lines.push("");
|
|
7010
|
+
lines.push(`- **\u4F18\u5148\u7EA7**: ${priorityLabel}`);
|
|
7011
|
+
lines.push(`- **\u63CF\u8FF0**: ${item.description}`);
|
|
7012
|
+
lines.push(`- **\u9884\u4F30\u590D\u6742\u5EA6**: ${item.estimatedComplexity}/5`);
|
|
7013
|
+
if (item.dependencies.length > 0) {
|
|
7014
|
+
lines.push(`- **\u4F9D\u8D56**: ${item.dependencies.join(", ")}`);
|
|
7015
|
+
}
|
|
7016
|
+
lines.push("");
|
|
7017
|
+
}
|
|
7018
|
+
if (spec.architectureNotes.length > 0) {
|
|
7019
|
+
lines.push("## \u67B6\u6784\u8BF4\u660E");
|
|
7020
|
+
lines.push("");
|
|
7021
|
+
for (const note of spec.architectureNotes) {
|
|
7022
|
+
lines.push(`- ${note}`);
|
|
7023
|
+
}
|
|
7024
|
+
lines.push("");
|
|
7025
|
+
}
|
|
7026
|
+
if (spec.risks.length > 0) {
|
|
7027
|
+
lines.push("## \u26A0\uFE0F \u98CE\u9669\u8BC4\u4F30");
|
|
7028
|
+
lines.push("");
|
|
7029
|
+
for (const risk of spec.risks) {
|
|
7030
|
+
lines.push(`- ${risk}`);
|
|
7031
|
+
}
|
|
7032
|
+
lines.push("");
|
|
7033
|
+
}
|
|
7034
|
+
if (spec.suggestions.length > 0) {
|
|
7035
|
+
lines.push("## \u{1F4A1} \u5EFA\u8BAE");
|
|
7036
|
+
lines.push("");
|
|
7037
|
+
for (const suggestion of spec.suggestions) {
|
|
7038
|
+
lines.push(`- ${suggestion}`);
|
|
7039
|
+
}
|
|
7040
|
+
lines.push("");
|
|
7041
|
+
}
|
|
7042
|
+
lines.push("---");
|
|
7043
|
+
lines.push("");
|
|
7044
|
+
lines.push("## \u786E\u8BA4\u72B6\u6001");
|
|
7045
|
+
lines.push("");
|
|
7046
|
+
lines.push("- [ ] \u89C4\u683C\u5DF2\u5BA1\u9605");
|
|
7047
|
+
lines.push("- [ ] \u4EFB\u52A1\u62C6\u5206\u5DF2\u786E\u8BA4");
|
|
7048
|
+
lines.push("");
|
|
7049
|
+
lines.push("**\u786E\u8BA4\u540E\u6267\u884C**: `/opsx:confirm spec-review`");
|
|
7050
|
+
return lines.join("\n");
|
|
6787
7051
|
}
|
|
6788
7052
|
function parseArgs(args) {
|
|
6789
7053
|
let forceComplexity;
|
|
@@ -6808,42 +7072,52 @@ async function readProjectContext(cwd) {
|
|
|
6808
7072
|
type: "unknown",
|
|
6809
7073
|
framework: null,
|
|
6810
7074
|
techStack: [],
|
|
6811
|
-
description: ""
|
|
7075
|
+
description: "",
|
|
7076
|
+
structure: {
|
|
7077
|
+
directories: [],
|
|
7078
|
+
keyFiles: [],
|
|
7079
|
+
srcStructure: ""
|
|
7080
|
+
},
|
|
7081
|
+
norms: {
|
|
7082
|
+
devStandards: "",
|
|
7083
|
+
patterns: "",
|
|
7084
|
+
weights: ""
|
|
7085
|
+
}
|
|
6812
7086
|
};
|
|
7087
|
+
const [agentsContext, configContext, normsContext, structureContext] = await Promise.all([
|
|
7088
|
+
readAgentsMd(cwd),
|
|
7089
|
+
readConfigYaml(cwd),
|
|
7090
|
+
readNorms(cwd),
|
|
7091
|
+
analyzeStructure(cwd)
|
|
7092
|
+
]);
|
|
7093
|
+
return {
|
|
7094
|
+
...defaultContext,
|
|
7095
|
+
...agentsContext,
|
|
7096
|
+
...configContext,
|
|
7097
|
+
norms: normsContext,
|
|
7098
|
+
structure: structureContext
|
|
7099
|
+
};
|
|
7100
|
+
}
|
|
7101
|
+
async function readAgentsMd(cwd) {
|
|
6813
7102
|
const agentsPath = path5.join(cwd, "AGENTS.md");
|
|
6814
7103
|
try {
|
|
6815
7104
|
const stats = await fs4.stat(agentsPath);
|
|
6816
7105
|
if (stats.size > MAX_FILE_SIZE2) {
|
|
6817
7106
|
console.warn(`\u8B66\u544A: AGENTS.md \u6587\u4EF6\u8FC7\u5927 (${stats.size} bytes)\uFF0C\u8DF3\u8FC7\u8BFB\u53D6`);
|
|
6818
|
-
return
|
|
7107
|
+
return {};
|
|
6819
7108
|
}
|
|
6820
7109
|
const content = await fs4.readFile(agentsPath, "utf-8");
|
|
6821
|
-
return parseAgentsMd(content
|
|
7110
|
+
return parseAgentsMd(content);
|
|
6822
7111
|
} catch (e) {
|
|
6823
7112
|
const err = e;
|
|
6824
7113
|
if (err.code !== "ENOENT") {
|
|
6825
7114
|
console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 AGENTS.md - ${err.message}`);
|
|
6826
7115
|
}
|
|
7116
|
+
return {};
|
|
6827
7117
|
}
|
|
6828
|
-
const configPath = path5.join(cwd, "openspec", "config.yaml");
|
|
6829
|
-
try {
|
|
6830
|
-
const stats = await fs4.stat(configPath);
|
|
6831
|
-
if (stats.size > MAX_FILE_SIZE2) {
|
|
6832
|
-
console.warn(`\u8B66\u544A: config.yaml \u6587\u4EF6\u8FC7\u5927\uFF0C\u8DF3\u8FC7\u8BFB\u53D6`);
|
|
6833
|
-
return defaultContext;
|
|
6834
|
-
}
|
|
6835
|
-
const content = await fs4.readFile(configPath, "utf-8");
|
|
6836
|
-
return parseConfigYaml(content, defaultContext);
|
|
6837
|
-
} catch (e) {
|
|
6838
|
-
const err = e;
|
|
6839
|
-
if (err.code !== "ENOENT") {
|
|
6840
|
-
console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 config.yaml - ${err.message}`);
|
|
6841
|
-
}
|
|
6842
|
-
}
|
|
6843
|
-
return defaultContext;
|
|
6844
7118
|
}
|
|
6845
|
-
function parseAgentsMd(content
|
|
6846
|
-
const context = {
|
|
7119
|
+
function parseAgentsMd(content) {
|
|
7120
|
+
const context = {};
|
|
6847
7121
|
const nameMatch = content.match(/\|\s*项目名称\s*\|\s*([^\s|]+)/);
|
|
6848
7122
|
if (nameMatch) {
|
|
6849
7123
|
context.name = nameMatch[1];
|
|
@@ -6860,10 +7134,32 @@ function parseAgentsMd(content, defaults) {
|
|
|
6860
7134
|
if (descMatch) {
|
|
6861
7135
|
context.description = descMatch[1].trim();
|
|
6862
7136
|
}
|
|
7137
|
+
const techStackMatch = content.match(/技术栈[::]\s*([^\n]+)/);
|
|
7138
|
+
if (techStackMatch) {
|
|
7139
|
+
context.techStack = techStackMatch[1].split(/[,,、]/).map((s) => s.trim()).filter(Boolean);
|
|
7140
|
+
}
|
|
6863
7141
|
return context;
|
|
6864
7142
|
}
|
|
6865
|
-
function
|
|
6866
|
-
const
|
|
7143
|
+
async function readConfigYaml(cwd) {
|
|
7144
|
+
const configPath = path5.join(cwd, "openspec", "config.yaml");
|
|
7145
|
+
try {
|
|
7146
|
+
const stats = await fs4.stat(configPath);
|
|
7147
|
+
if (stats.size > MAX_FILE_SIZE2) {
|
|
7148
|
+
console.warn("\u8B66\u544A: config.yaml \u6587\u4EF6\u8FC7\u5927\uFF0C\u8DF3\u8FC7\u8BFB\u53D6");
|
|
7149
|
+
return {};
|
|
7150
|
+
}
|
|
7151
|
+
const content = await fs4.readFile(configPath, "utf-8");
|
|
7152
|
+
return parseConfigYaml(content);
|
|
7153
|
+
} catch (e) {
|
|
7154
|
+
const err = e;
|
|
7155
|
+
if (err.code !== "ENOENT") {
|
|
7156
|
+
console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 config.yaml - ${err.message}`);
|
|
7157
|
+
}
|
|
7158
|
+
return {};
|
|
7159
|
+
}
|
|
7160
|
+
}
|
|
7161
|
+
function parseConfigYaml(content) {
|
|
7162
|
+
const context = {};
|
|
6867
7163
|
const nameMatch = content.match(/name:\s*(.+)/);
|
|
6868
7164
|
if (nameMatch) {
|
|
6869
7165
|
context.name = nameMatch[1].trim();
|
|
@@ -6878,6 +7174,67 @@ function parseConfigYaml(content, defaults) {
|
|
|
6878
7174
|
}
|
|
6879
7175
|
return context;
|
|
6880
7176
|
}
|
|
7177
|
+
async function readNorms(cwd) {
|
|
7178
|
+
const normsDir = path5.join(cwd, ".sf-cli", "norms");
|
|
7179
|
+
const norms = {
|
|
7180
|
+
devStandards: "",
|
|
7181
|
+
patterns: "",
|
|
7182
|
+
weights: ""
|
|
7183
|
+
};
|
|
7184
|
+
try {
|
|
7185
|
+
const devStandardsPath = path5.join(normsDir, "devstanded.md");
|
|
7186
|
+
norms.devStandards = await fs4.readFile(devStandardsPath, "utf-8").catch(() => "");
|
|
7187
|
+
} catch {
|
|
7188
|
+
}
|
|
7189
|
+
try {
|
|
7190
|
+
const patternsPath = path5.join(normsDir, "patterns.json");
|
|
7191
|
+
norms.patterns = await fs4.readFile(patternsPath, "utf-8").catch(() => "");
|
|
7192
|
+
} catch {
|
|
7193
|
+
}
|
|
7194
|
+
try {
|
|
7195
|
+
const weightsPath = path5.join(normsDir, "weights.json");
|
|
7196
|
+
norms.weights = await fs4.readFile(weightsPath, "utf-8").catch(() => "");
|
|
7197
|
+
} catch {
|
|
7198
|
+
}
|
|
7199
|
+
return norms;
|
|
7200
|
+
}
|
|
7201
|
+
async function analyzeStructure(cwd) {
|
|
7202
|
+
const structure = {
|
|
7203
|
+
directories: [],
|
|
7204
|
+
keyFiles: [],
|
|
7205
|
+
srcStructure: ""
|
|
7206
|
+
};
|
|
7207
|
+
try {
|
|
7208
|
+
const entries = await fs4.readdir(cwd, { withFileTypes: true });
|
|
7209
|
+
for (const entry of entries) {
|
|
7210
|
+
if (entry.isDirectory() && !["node_modules", "dist", ".git", "build"].includes(entry.name)) {
|
|
7211
|
+
structure.directories.push(entry.name);
|
|
7212
|
+
}
|
|
7213
|
+
}
|
|
7214
|
+
const keyFiles = [
|
|
7215
|
+
"package.json",
|
|
7216
|
+
"tsconfig.json",
|
|
7217
|
+
"AGENTS.md",
|
|
7218
|
+
"README.md"
|
|
7219
|
+
];
|
|
7220
|
+
for (const file of keyFiles) {
|
|
7221
|
+
const filePath = path5.join(cwd, file);
|
|
7222
|
+
try {
|
|
7223
|
+
await fs4.access(filePath);
|
|
7224
|
+
structure.keyFiles.push(file);
|
|
7225
|
+
} catch {
|
|
7226
|
+
}
|
|
7227
|
+
}
|
|
7228
|
+
const srcDir = path5.join(cwd, "src");
|
|
7229
|
+
try {
|
|
7230
|
+
const srcEntries = await fs4.readdir(srcDir, { withFileTypes: true });
|
|
7231
|
+
structure.srcStructure = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name).join("/");
|
|
7232
|
+
} catch {
|
|
7233
|
+
}
|
|
7234
|
+
} catch (e) {
|
|
7235
|
+
}
|
|
7236
|
+
return { structure };
|
|
7237
|
+
}
|
|
6881
7238
|
function analyzeComplexity(requirement, context) {
|
|
6882
7239
|
let score = 3;
|
|
6883
7240
|
const factors = [];
|
|
@@ -7041,7 +7398,7 @@ async function handleOpsx(command, args, ctx) {
|
|
|
7041
7398
|
case "rollback":
|
|
7042
7399
|
return handleRollback(workflow, args);
|
|
7043
7400
|
case "confirm":
|
|
7044
|
-
return handleConfirm(workflow, args);
|
|
7401
|
+
return handleConfirm(workflow, args, ctx);
|
|
7045
7402
|
case "next":
|
|
7046
7403
|
return handleNext(workflow);
|
|
7047
7404
|
case "auto":
|
|
@@ -7409,6 +7766,16 @@ async function handleConfirm(workflow, args, ctx) {
|
|
|
7409
7766
|
const type = args[0];
|
|
7410
7767
|
if (!type) {
|
|
7411
7768
|
const pendingPoint = workflow.getCurrentConfirmationPoint();
|
|
7769
|
+
const specPath = await checkPendingSpec(ctx.options.workingDirectory, state.id);
|
|
7770
|
+
if (specPath) {
|
|
7771
|
+
return {
|
|
7772
|
+
output: chalk9.cyan("\u{1F4CB} \u89C4\u683C\u6587\u4EF6\u5F85\u786E\u8BA4") + chalk9.gray(`
|
|
7773
|
+
|
|
7774
|
+
\u89C4\u683C\u6587\u4EF6: ${specPath}`) + chalk9.gray(`
|
|
7775
|
+
|
|
7776
|
+
\u8BF7\u68C0\u67E5\u89C4\u683C\u6587\u4EF6\u540E\u786E\u8BA4:`) + chalk9.white("\n /opsx:confirm spec-review - \u786E\u8BA4\u89C4\u683C") + chalk9.white("\n /opsx:rollback explore - \u91CD\u65B0\u62C6\u5206")
|
|
7777
|
+
};
|
|
7778
|
+
}
|
|
7412
7779
|
if (pendingPoint) {
|
|
7413
7780
|
return {
|
|
7414
7781
|
output: chalk9.cyan("\u5F85\u786E\u8BA4\u7684\u68C0\u67E5\u70B9:") + chalk9.white(`
|
|
@@ -7430,12 +7797,54 @@ ${generateConfirmationPrompt(pendingPoint)}`) + chalk9.gray(`
|
|
|
7430
7797
|
}
|
|
7431
7798
|
const comment = args.slice(1).join(" ");
|
|
7432
7799
|
workflow.confirm(type, comment);
|
|
7433
|
-
|
|
7434
|
-
|
|
7800
|
+
const lines = [];
|
|
7801
|
+
lines.push(chalk9.green("\u2713 \u5DF2\u786E\u8BA4"));
|
|
7802
|
+
lines.push(chalk9.white(`
|
|
7803
|
+
${point.name}`));
|
|
7804
|
+
if (comment) {
|
|
7805
|
+
lines.push(chalk9.gray(`
|
|
7806
|
+
\u5907\u6CE8: ${comment}`));
|
|
7807
|
+
}
|
|
7808
|
+
if (type === "spec-review") {
|
|
7809
|
+
const allowed = workflow.getAllowedTransitions();
|
|
7810
|
+
if (allowed.length > 0) {
|
|
7811
|
+
const nextStep = allowed[0];
|
|
7812
|
+
try {
|
|
7813
|
+
const transition = await workflow.transition(nextStep);
|
|
7814
|
+
lines.push("");
|
|
7815
|
+
lines.push(chalk9.cyan(`\u2713 \u5DF2\u81EA\u52A8\u8FDB\u5165 ${nextStep} \u9636\u6BB5`));
|
|
7816
|
+
lines.push(chalk9.gray(`\u8F6C\u6362: ${transition.from} \u2192 ${transition.to}`));
|
|
7817
|
+
if (nextStep === "new") {
|
|
7818
|
+
lines.push(chalk9.yellow("\n\u4E0B\u4E00\u6B65: \u8BBE\u8BA1\u65B9\u6848"));
|
|
7819
|
+
lines.push(chalk9.gray(" \u53EF\u8C03\u7528 $architect \u6216 $frontend-dev \u8FDB\u884C\u8BBE\u8BA1"));
|
|
7820
|
+
} else if (nextStep === "apply") {
|
|
7821
|
+
lines.push(chalk9.yellow("\n\u4E0B\u4E00\u6B65: \u6267\u884C\u53D8\u66F4"));
|
|
7822
|
+
lines.push(chalk9.gray(" \u53EF\u8C03\u7528 $frontend-dev \u6267\u884C\u4EE3\u7801\u4FEE\u6539"));
|
|
7823
|
+
}
|
|
7824
|
+
return { output: lines.join("\n") };
|
|
7825
|
+
} catch (e) {
|
|
7826
|
+
if (e instanceof ConfirmationRequiredError) {
|
|
7827
|
+
lines.push(chalk9.yellow("\n\u26A0 \u8FD8\u9700\u8981\u786E\u8BA4:") + chalk9.white(`
|
|
7828
|
+
${generateConfirmationPrompt(e.point)}`) + chalk9.cyan(`
|
|
7435
7829
|
|
|
7436
|
-
${point.
|
|
7437
|
-
|
|
7438
|
-
|
|
7830
|
+
\u4F7F\u7528 /opsx:confirm ${e.point.type}`));
|
|
7831
|
+
return { output: lines.join("\n") };
|
|
7832
|
+
}
|
|
7833
|
+
throw e;
|
|
7834
|
+
}
|
|
7835
|
+
}
|
|
7836
|
+
}
|
|
7837
|
+
lines.push(chalk9.yellow("\n\u4F7F\u7528 /opsx:next \u7EE7\u7EED\u4E0B\u4E00\u9636\u6BB5"));
|
|
7838
|
+
return { output: lines.join("\n") };
|
|
7839
|
+
}
|
|
7840
|
+
async function checkPendingSpec(workingDirectory, changeId) {
|
|
7841
|
+
const specPath = path5.join(workingDirectory, "openspec", "changes", `${changeId}-spec.md`);
|
|
7842
|
+
try {
|
|
7843
|
+
await fs10.promises.access(specPath);
|
|
7844
|
+
return specPath;
|
|
7845
|
+
} catch {
|
|
7846
|
+
return null;
|
|
7847
|
+
}
|
|
7439
7848
|
}
|
|
7440
7849
|
async function handleNext(workflow, args, ctx) {
|
|
7441
7850
|
const state = workflow.getState();
|