lee-spec-kit 0.5.1 → 0.5.2
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.en.md +4 -1
- package/README.md +4 -1
- package/dist/index.js +117 -250
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -203,9 +203,12 @@ npx lee-spec-kit context F001 --approve A --execute --execute-strict
|
|
|
203
203
|
`--json` output includes:
|
|
204
204
|
|
|
205
205
|
- `reasonCode`: status reason code (`SINGLE_MATCHED`, `MULTIPLE_ACTIVE_FEATURES`, etc.)
|
|
206
|
+
- `operationType`: action nature (`local` | `remote` | `manual`)
|
|
206
207
|
- `actionOptions`: maps labels to atomic actions plus `summary`/`approvalPrompt` for user-facing label explanation
|
|
208
|
+
- `primaryActionLabel` / `primaryActionType` / `primaryActionCategory` / `primaryActionOperationType`: metadata for the first atomic action
|
|
209
|
+
- `selectionFallback`: fallback used when branch auto-detection does not match (`none` | `open_features` | `all_features` | `done_features`)
|
|
207
210
|
- `workflowPolicy`: current completion policy (`mode`, `requireIssue`, `requireBranch`, `requirePr`, `requireReview`)
|
|
208
|
-
- `checkPolicy`: approval validation policy (`token: "<LABEL>"`, `acceptedTokens`, `tokenPattern`, `validLabels`, `requireExplanationBeforeApproval`, `requiredExplanationFields`, `contextVersion`, ...)
|
|
211
|
+
- `checkPolicy`: approval validation policy (`hint`, `policyOnly`, `token: "<LABEL>"`, `acceptedTokens`, `tokenPattern`, `validLabels`, `requireExplanationBeforeApproval`, `requiredExplanationFields`, `contextVersion`, ...)
|
|
209
212
|
|
|
210
213
|
Error payloads (`status: "error"`) include `reasonCode` and labeled `suggestions` (`A/B/C`) (e.g. `INVALID_APPROVAL`, `CONTEXT_STALE`, `EXECUTION_FAILED`, `EXECUTION_NOT_COMMAND`).
|
|
211
214
|
|
package/README.md
CHANGED
|
@@ -222,12 +222,15 @@ npx lee-spec-kit context F001 --approve A --execute --execute-strict
|
|
|
222
222
|
- `reasonCode`: 상태 이유 코드 (`SINGLE_MATCHED`, `MULTIPLE_ACTIVE_FEATURES` 등)
|
|
223
223
|
- `type: "command"`: `scope`(project|docs), `cwd`, `cmd` 제공 (복사하여 붙여넣기 가능한 형태로 `cd ... && git ...` 형태로 출력)
|
|
224
224
|
- `type: "instruction"`: 사람이 수행해야 하는 안내 메시지
|
|
225
|
+
- `operationType`: 액션 성격 (`local` | `remote` | `manual`)
|
|
225
226
|
- `actionOptions`: `label`(`A`, `B`, `C`...)과 해당 `action` 매핑 + `summary`/`approvalPrompt`(라벨 설명 템플릿)
|
|
227
|
+
- `primaryActionLabel`/`primaryActionType`/`primaryActionCategory`/`primaryActionOperationType`: 첫 번째 원자 액션의 요약 메타데이터
|
|
228
|
+
- `selectionFallback`: 자동 감지 실패 시 사용된 폴백 (`none` | `open_features` | `all_features` | `done_features`)
|
|
226
229
|
- `category`: 액션 분류 (자동화/반자동용 `approval.mode: "category"`에서 사용)
|
|
227
230
|
- `requiresUserCheck`: 사용자 확인 필요 여부 (에이전트는 **사용자 응답을 `<라벨>` 또는 `<라벨> OK` 형식(예: `A`, `A OK`)으로 제한**하는 것을 권장 / 설정의 `approval`로 오버라이드 가능)
|
|
228
231
|
- `workflowPolicy`: 현재 완료 조건 정책 (`mode`, `requireIssue`, `requireBranch`, `requirePr`, `requireReview`)
|
|
229
232
|
|
|
230
|
-
또한 `checkPolicy`가 포함되어, 에이전트가 사용자 확인 정책을 적용할 때 참고할 수 있습니다. (`docPath`, `hint`, `token: "<LABEL>"`, `acceptedTokens`, `tokenPattern`, `validLabels`, `requireExplanationBeforeApproval`, `requiredExplanationFields`, `contextVersion`, `config`)
|
|
233
|
+
또한 `checkPolicy`가 포함되어, 에이전트가 사용자 확인 정책을 적용할 때 참고할 수 있습니다. (`docPath`, `hint`, `policyOnly`, `token: "<LABEL>"`, `acceptedTokens`, `tokenPattern`, `validLabels`, `requireExplanationBeforeApproval`, `requiredExplanationFields`, `contextVersion`, `config`)
|
|
231
234
|
|
|
232
235
|
오류 응답(`status: "error"`)에는 `reasonCode`와 `suggestions`(라벨형 다음 동작: `A/B/C`)가 포함됩니다. (예: `INVALID_APPROVAL`, `CONTEXT_STALE`, `EXECUTION_FAILED`, `EXECUTION_NOT_COMMAND`)
|
|
233
236
|
|
package/dist/index.js
CHANGED
|
@@ -128,7 +128,7 @@ var I18N = {
|
|
|
128
128
|
"context.tipShowAll": "\uC804\uCCB4 \uBCF4\uAE30",
|
|
129
129
|
"context.tipShowDone": "\uC644\uB8CC\uB9CC \uBCF4\uAE30",
|
|
130
130
|
"context.checkRequired": "[\uD655\uC778 \uD544\uC694] ",
|
|
131
|
-
"context.checkPolicyHint": "\u2139\uFE0F \uC0AC\uC6A9\uC790 \uD655\uC778 \
|
|
131
|
+
"context.checkPolicyHint": "\u2139\uFE0F \uC0AC\uC6A9\uC790 \uD655\uC778 \uC815\uCC45 \uC548\uB0B4(\uD604\uC7AC Next Action \uC544\uB2D8): /docs/agents/agents.md \uCC38\uACE0 (git push/merge/merge commit \uD3EC\uD568). [\uD655\uC778 \uD544\uC694]\uAC00 \uC788\uC73C\uBA74 \uC0AC\uC6A9\uC790\uC5D0\uAC8C `<\uB77C\uBCA8>` \uB610\uB294 `<\uB77C\uBCA8> OK` (\uC608: `A`, `A OK`) \uC751\uB2F5\uC744 \uBC1B\uC740 \uB4A4 \uC9C4\uD589 (config: approval\uB85C \uC870\uC815 \uAC00\uB2A5)",
|
|
132
132
|
"context.actionOptionHint": "\uC2B9\uC778 \uC751\uB2F5 \uD615\uC2DD: `<\uB77C\uBCA8>` \uB610\uB294 `<\uB77C\uBCA8> OK` (\uC608: `A`, `A OK`)",
|
|
133
133
|
"context.actionExplainHint": "\uC2B9\uC778 \uC694\uCCAD \uC804, \uAC01 \uB77C\uBCA8\uC774 \uBB34\uC5C7\uC744 \uC2E4\uD589/\uBCC0\uACBD\uD558\uB294\uC9C0 \uD55C \uC904 \uC694\uC57D\uACFC \uD568\uAED8 \uC124\uBA85\uD558\uC138\uC694.",
|
|
134
134
|
"context.tipDocsCommitRules": "\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uADDC\uCE59: /docs/agents/git-workflow.md \uCC38\uACE0",
|
|
@@ -325,7 +325,7 @@ var I18N = {
|
|
|
325
325
|
"context.tipShowAll": "Show all",
|
|
326
326
|
"context.tipShowDone": "Show done only",
|
|
327
327
|
"context.checkRequired": "[CHECK required] ",
|
|
328
|
-
"context.checkPolicyHint": "\u2139\uFE0F User check policy: see /docs/agents/agents.md (includes git push/merge and merge commits)
|
|
328
|
+
"context.checkPolicyHint": "\u2139\uFE0F User check policy notice (not the current next action): see /docs/agents/agents.md (includes git push/merge and merge commits). If you see [CHECK required], wait for `<label>` or `<label> OK` (e.g. `A`, `A OK`) before proceeding (config: approval can override)",
|
|
329
329
|
"context.actionOptionHint": "Approval reply format: `<label>` or `<label> OK` (e.g. `A`, `A OK`)",
|
|
330
330
|
"context.actionExplainHint": "Before requesting approval, explain what each label will run/change with a one-line summary.",
|
|
331
331
|
"context.tipDocsCommitRules": "Commit message rules: /docs/agents/git-workflow.md",
|
|
@@ -3869,6 +3869,22 @@ async function runConfig(options) {
|
|
|
3869
3869
|
{ owner: "config" }
|
|
3870
3870
|
);
|
|
3871
3871
|
}
|
|
3872
|
+
var REMOTE_ACTION_CATEGORIES = /* @__PURE__ */ new Set([
|
|
3873
|
+
"issue_create",
|
|
3874
|
+
"pr_create",
|
|
3875
|
+
"pr_status_update",
|
|
3876
|
+
"code_review"
|
|
3877
|
+
]);
|
|
3878
|
+
var LOCAL_ACTION_CATEGORIES = /* @__PURE__ */ new Set([
|
|
3879
|
+
"docs_commit",
|
|
3880
|
+
"branch_create",
|
|
3881
|
+
"task_execute"
|
|
3882
|
+
]);
|
|
3883
|
+
var REMOTE_COMMAND_PATTERN = /\b(?:git\s+push|git\s+merge|gh\s+(?:issue|pr)\b)/i;
|
|
3884
|
+
function resolveComponentOption(options) {
|
|
3885
|
+
const component = (options.component || options.repo || "").trim().toLowerCase();
|
|
3886
|
+
return component || void 0;
|
|
3887
|
+
}
|
|
3872
3888
|
function getActionLabel(index) {
|
|
3873
3889
|
let n = index + 1;
|
|
3874
3890
|
let label = "";
|
|
@@ -3879,6 +3895,29 @@ function getActionLabel(index) {
|
|
|
3879
3895
|
}
|
|
3880
3896
|
return label;
|
|
3881
3897
|
}
|
|
3898
|
+
function resolveActionOperationType(action) {
|
|
3899
|
+
if (action.operationType) return action.operationType;
|
|
3900
|
+
if (action.type === "command") {
|
|
3901
|
+
if (REMOTE_COMMAND_PATTERN.test(action.cmd)) return "remote";
|
|
3902
|
+
return "local";
|
|
3903
|
+
}
|
|
3904
|
+
if (action.category && REMOTE_ACTION_CATEGORIES.has(action.category)) {
|
|
3905
|
+
return "remote";
|
|
3906
|
+
}
|
|
3907
|
+
if (action.category && LOCAL_ACTION_CATEGORIES.has(action.category)) {
|
|
3908
|
+
return "local";
|
|
3909
|
+
}
|
|
3910
|
+
return "manual";
|
|
3911
|
+
}
|
|
3912
|
+
function annotateActionOperationType(action) {
|
|
3913
|
+
return {
|
|
3914
|
+
...action,
|
|
3915
|
+
operationType: resolveActionOperationType(action)
|
|
3916
|
+
};
|
|
3917
|
+
}
|
|
3918
|
+
function annotateActions(actions) {
|
|
3919
|
+
return actions.map((action) => annotateActionOperationType(action));
|
|
3920
|
+
}
|
|
3882
3921
|
function getActionSummary(action) {
|
|
3883
3922
|
if (action.category === "docs_commit") return "Commit docs updates";
|
|
3884
3923
|
if (action.category === "issue_create") return "Create and record issue";
|
|
@@ -3898,6 +3937,12 @@ function getActionSummary(action) {
|
|
|
3898
3937
|
}
|
|
3899
3938
|
return action.message;
|
|
3900
3939
|
}
|
|
3940
|
+
function formatActionSummary(action) {
|
|
3941
|
+
if (action.type === "command") {
|
|
3942
|
+
return `(${action.scope}) ${action.cmd}`;
|
|
3943
|
+
}
|
|
3944
|
+
return action.message;
|
|
3945
|
+
}
|
|
3901
3946
|
function toActionOptions(actions) {
|
|
3902
3947
|
return actions.map((action, index) => {
|
|
3903
3948
|
const label = getActionLabel(index);
|
|
@@ -3922,6 +3967,7 @@ function buildActionSnapshot(actionOptions) {
|
|
|
3922
3967
|
cwd: action.cwd,
|
|
3923
3968
|
cmd: action.cmd,
|
|
3924
3969
|
category: action.category,
|
|
3970
|
+
operationType: action.operationType,
|
|
3925
3971
|
requiresUserCheck: !!action.requiresUserCheck
|
|
3926
3972
|
};
|
|
3927
3973
|
}
|
|
@@ -3930,6 +3976,7 @@ function buildActionSnapshot(actionOptions) {
|
|
|
3930
3976
|
type: action.type,
|
|
3931
3977
|
message: action.message,
|
|
3932
3978
|
category: action.category,
|
|
3979
|
+
operationType: action.operationType,
|
|
3933
3980
|
requiresUserCheck: !!action.requiresUserCheck
|
|
3934
3981
|
};
|
|
3935
3982
|
});
|
|
@@ -3944,20 +3991,21 @@ function getContextVersion(feature, actionOptions) {
|
|
|
3944
3991
|
});
|
|
3945
3992
|
return createHash("sha256").update(payload).digest("hex").slice(0, 12);
|
|
3946
3993
|
}
|
|
3947
|
-
function
|
|
3948
|
-
const
|
|
3949
|
-
if (!
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
return actionOptions.map((o) => o.label).join(", ");
|
|
3994
|
+
function matchesFeatureSelector(f, selector) {
|
|
3995
|
+
const s = selector.trim();
|
|
3996
|
+
if (!s) return false;
|
|
3997
|
+
if (f.folderName.toLowerCase() === s.toLowerCase()) return true;
|
|
3998
|
+
if (f.slug.toLowerCase() === s.toLowerCase()) return true;
|
|
3999
|
+
if (f.id && f.id.toLowerCase() === s.toLowerCase()) return true;
|
|
4000
|
+
return false;
|
|
3955
4001
|
}
|
|
3956
|
-
function
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
return
|
|
4002
|
+
function detectFromBranch(branchName, features) {
|
|
4003
|
+
const match = branchName.match(/^feat\/\d+-(.+)$/);
|
|
4004
|
+
if (!match) return [];
|
|
4005
|
+
const detected = match[1];
|
|
4006
|
+
return features.filter(
|
|
4007
|
+
(f) => f.slug.toLowerCase() === detected.toLowerCase() || f.folderName.toLowerCase() === detected.toLowerCase()
|
|
4008
|
+
);
|
|
3961
4009
|
}
|
|
3962
4010
|
function toSelectionStatus(features, selectionMode, openFeatures, targetFeatures) {
|
|
3963
4011
|
const isNoOpen = selectionMode === "open" && features.length > 0 && openFeatures.length === 0;
|
|
@@ -3974,15 +4022,9 @@ function toReasonCode(status) {
|
|
|
3974
4022
|
if (status === "multiple_active") return "MULTIPLE_ACTIVE_FEATURES";
|
|
3975
4023
|
return "NO_MATCHED_FEATURES";
|
|
3976
4024
|
}
|
|
3977
|
-
async function
|
|
3978
|
-
if (!config) {
|
|
3979
|
-
throw createCliError(
|
|
3980
|
-
"CONFIG_NOT_FOUND",
|
|
3981
|
-
tr(DEFAULT_LANG, "cli", "common.configNotFound")
|
|
3982
|
-
);
|
|
3983
|
-
}
|
|
4025
|
+
async function resolveContextSelection(config, featureName, options) {
|
|
3984
4026
|
const { features, branches, warnings } = await scanFeatures(config);
|
|
3985
|
-
const selectedComponent = (options
|
|
4027
|
+
const selectedComponent = resolveComponentOption(options);
|
|
3986
4028
|
const scopedFeatures = selectedComponent ? features.filter((f) => f.type === selectedComponent) : features;
|
|
3987
4029
|
const doneFeatures = scopedFeatures.filter((f) => f.completion.workflowDone);
|
|
3988
4030
|
const openFeatures = scopedFeatures.filter((f) => !f.completion.workflowDone);
|
|
@@ -3994,6 +4036,7 @@ async function resolveContextState(config, featureName, options) {
|
|
|
3994
4036
|
);
|
|
3995
4037
|
let targetFeatures = [];
|
|
3996
4038
|
let selectionMode = "explicit";
|
|
4039
|
+
let selectionFallback = "none";
|
|
3997
4040
|
if (featureName) {
|
|
3998
4041
|
targetFeatures = scopedFeatures.filter(
|
|
3999
4042
|
(f) => matchesFeatureSelector(f, featureName)
|
|
@@ -4026,15 +4069,19 @@ async function resolveContextState(config, featureName, options) {
|
|
|
4026
4069
|
}
|
|
4027
4070
|
if (targetFeatures.length > 0) {
|
|
4028
4071
|
selectionMode = "branch";
|
|
4072
|
+
selectionFallback = "none";
|
|
4029
4073
|
} else if (options.all) {
|
|
4030
4074
|
targetFeatures = scopedFeatures;
|
|
4031
4075
|
selectionMode = "all";
|
|
4076
|
+
selectionFallback = "all_features";
|
|
4032
4077
|
} else if (options.done) {
|
|
4033
4078
|
targetFeatures = doneFeatures;
|
|
4034
4079
|
selectionMode = "done";
|
|
4080
|
+
selectionFallback = "done_features";
|
|
4035
4081
|
} else {
|
|
4036
4082
|
targetFeatures = openFeatures;
|
|
4037
4083
|
selectionMode = "open";
|
|
4084
|
+
selectionFallback = "open_features";
|
|
4038
4085
|
}
|
|
4039
4086
|
}
|
|
4040
4087
|
const status = toSelectionStatus(
|
|
@@ -4044,7 +4091,7 @@ async function resolveContextState(config, featureName, options) {
|
|
|
4044
4091
|
targetFeatures
|
|
4045
4092
|
);
|
|
4046
4093
|
const matchedFeature = targetFeatures.length === 1 ? targetFeatures[0] : null;
|
|
4047
|
-
const actions = matchedFeature?.actions ?? [];
|
|
4094
|
+
const actions = annotateActions(matchedFeature?.actions ?? []);
|
|
4048
4095
|
const actionOptions = toActionOptions(actions);
|
|
4049
4096
|
const contextVersion = getContextVersion(matchedFeature, actionOptions);
|
|
4050
4097
|
return {
|
|
@@ -4056,6 +4103,7 @@ async function resolveContextState(config, featureName, options) {
|
|
|
4056
4103
|
inProgressFeatures,
|
|
4057
4104
|
readyToCloseFeatures,
|
|
4058
4105
|
selectionMode,
|
|
4106
|
+
selectionFallback,
|
|
4059
4107
|
targetFeatures,
|
|
4060
4108
|
status,
|
|
4061
4109
|
matchedFeature,
|
|
@@ -4064,6 +4112,32 @@ async function resolveContextState(config, featureName, options) {
|
|
|
4064
4112
|
contextVersion
|
|
4065
4113
|
};
|
|
4066
4114
|
}
|
|
4115
|
+
|
|
4116
|
+
// src/commands/context.ts
|
|
4117
|
+
async function resolveContextState(config, featureName, options) {
|
|
4118
|
+
if (!config) {
|
|
4119
|
+
throw createCliError(
|
|
4120
|
+
"CONFIG_NOT_FOUND",
|
|
4121
|
+
tr(DEFAULT_LANG, "cli", "common.configNotFound")
|
|
4122
|
+
);
|
|
4123
|
+
}
|
|
4124
|
+
return resolveContextSelection(config, featureName, options);
|
|
4125
|
+
}
|
|
4126
|
+
function parseApprovalLabel(input) {
|
|
4127
|
+
const match = input.trim().match(/^([A-Z]+)(?:\s+OK)?$/i);
|
|
4128
|
+
if (!match) return null;
|
|
4129
|
+
return match[1].toUpperCase();
|
|
4130
|
+
}
|
|
4131
|
+
function listLabels(actionOptions) {
|
|
4132
|
+
if (actionOptions.length === 0) return "-";
|
|
4133
|
+
return actionOptions.map((o) => o.label).join(", ");
|
|
4134
|
+
}
|
|
4135
|
+
function formatActionSummary2(action) {
|
|
4136
|
+
if (action.type === "command") {
|
|
4137
|
+
return `(${action.scope}) ${action.cmd}`;
|
|
4138
|
+
}
|
|
4139
|
+
return action.message;
|
|
4140
|
+
}
|
|
4067
4141
|
function executeCommandAction(cmd, jsonMode, cwd) {
|
|
4068
4142
|
const shellPath = process.env.SHELL || (process.platform === "win32" ? process.env.ComSpec || "cmd.exe" : "/bin/sh");
|
|
4069
4143
|
if (jsonMode) {
|
|
@@ -4122,22 +4196,6 @@ function contextCommand(program2) {
|
|
|
4122
4196
|
}
|
|
4123
4197
|
);
|
|
4124
4198
|
}
|
|
4125
|
-
function matchesFeatureSelector(f, selector) {
|
|
4126
|
-
const s = selector.trim();
|
|
4127
|
-
if (!s) return false;
|
|
4128
|
-
if (f.folderName.toLowerCase() === s.toLowerCase()) return true;
|
|
4129
|
-
if (f.slug.toLowerCase() === s.toLowerCase()) return true;
|
|
4130
|
-
if (f.id && f.id.toLowerCase() === s.toLowerCase()) return true;
|
|
4131
|
-
return false;
|
|
4132
|
-
}
|
|
4133
|
-
function detectFromBranch(branchName, features) {
|
|
4134
|
-
const match = branchName.match(/^feat\/\d+-(.+)$/);
|
|
4135
|
-
if (!match) return [];
|
|
4136
|
-
const detected = match[1];
|
|
4137
|
-
return features.filter(
|
|
4138
|
-
(f) => f.slug.toLowerCase() === detected.toLowerCase() || f.folderName.toLowerCase() === detected.toLowerCase()
|
|
4139
|
-
);
|
|
4140
|
-
}
|
|
4141
4199
|
function getListLabel(f, stepsMap, lang, workflowPolicy) {
|
|
4142
4200
|
if (f.completion.implementationDone && !f.completion.workflowDone) {
|
|
4143
4201
|
if (f.git.docsHasUncommittedChanges) {
|
|
@@ -4231,10 +4289,12 @@ async function runContext(featureName, options) {
|
|
|
4231
4289
|
return;
|
|
4232
4290
|
}
|
|
4233
4291
|
if (options.json) {
|
|
4292
|
+
const primaryAction = state.actionOptions[0] ?? null;
|
|
4234
4293
|
const result = {
|
|
4235
4294
|
status: state.status,
|
|
4236
4295
|
reasonCode: toReasonCode(state.status),
|
|
4237
4296
|
selectionMode: state.selectionMode,
|
|
4297
|
+
selectionFallback: state.selectionFallback,
|
|
4238
4298
|
branches: state.branches,
|
|
4239
4299
|
warnings: state.warnings,
|
|
4240
4300
|
matchedFeature: state.matchedFeature,
|
|
@@ -4246,10 +4306,15 @@ async function runContext(featureName, options) {
|
|
|
4246
4306
|
readyToCloseCandidates: state.selectionMode === "open" ? state.readyToCloseFeatures : [],
|
|
4247
4307
|
actions: state.actions,
|
|
4248
4308
|
actionOptions: state.actionOptions,
|
|
4309
|
+
primaryActionLabel: primaryAction?.label ?? null,
|
|
4310
|
+
primaryActionType: primaryAction?.action.type ?? null,
|
|
4311
|
+
primaryActionCategory: primaryAction?.action.category ?? null,
|
|
4312
|
+
primaryActionOperationType: primaryAction?.action.operationType ?? null,
|
|
4249
4313
|
workflowPolicy,
|
|
4250
4314
|
checkPolicy: {
|
|
4251
4315
|
docPath: "/docs/agents/agents.md",
|
|
4252
4316
|
hint: tr(lang, "cli", "context.checkPolicyHint"),
|
|
4317
|
+
policyOnly: true,
|
|
4253
4318
|
token: "<LABEL>",
|
|
4254
4319
|
acceptedTokens: ["<LABEL>", "<LABEL> OK"],
|
|
4255
4320
|
tokenPattern: "^([A-Z]+)(?:\\s+OK)?$",
|
|
@@ -4268,7 +4333,8 @@ async function runContext(featureName, options) {
|
|
|
4268
4333
|
label: o.label,
|
|
4269
4334
|
summary: o.summary,
|
|
4270
4335
|
approvalPrompt: o.approvalPrompt,
|
|
4271
|
-
requiresUserCheck: !!o.action.requiresUserCheck
|
|
4336
|
+
requiresUserCheck: !!o.action.requiresUserCheck,
|
|
4337
|
+
operationType: o.action.operationType
|
|
4272
4338
|
}))
|
|
4273
4339
|
},
|
|
4274
4340
|
prPolicy: {
|
|
@@ -4476,7 +4542,7 @@ async function runContext(featureName, options) {
|
|
|
4476
4542
|
console.log();
|
|
4477
4543
|
return;
|
|
4478
4544
|
}
|
|
4479
|
-
const actionOptions =
|
|
4545
|
+
const actionOptions = state.actionOptions;
|
|
4480
4546
|
console.log(chalk6.green(chalk6.bold("\u{1F449} Next Options (Atomic):")));
|
|
4481
4547
|
let hasDocsCommand = false;
|
|
4482
4548
|
actionOptions.forEach(({ label, action }) => {
|
|
@@ -4562,7 +4628,7 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
4562
4628
|
}
|
|
4563
4629
|
console.log();
|
|
4564
4630
|
console.log(chalk6.green(`\u2705 Approved option: ${parsedLabel}`));
|
|
4565
|
-
console.log(chalk6.gray(` - Action: ${
|
|
4631
|
+
console.log(chalk6.gray(` - Action: ${formatActionSummary2(selectedAction)}`));
|
|
4566
4632
|
if (selectedAction.type === "command") {
|
|
4567
4633
|
console.log(chalk6.gray(" - Run with: --execute"));
|
|
4568
4634
|
} else {
|
|
@@ -5230,208 +5296,6 @@ function doctorCommand(program2) {
|
|
|
5230
5296
|
}
|
|
5231
5297
|
});
|
|
5232
5298
|
}
|
|
5233
|
-
function resolveComponentOption(options) {
|
|
5234
|
-
const component = (options.component || options.repo || "").trim().toLowerCase();
|
|
5235
|
-
return component || void 0;
|
|
5236
|
-
}
|
|
5237
|
-
function getActionLabel2(index) {
|
|
5238
|
-
let n = index + 1;
|
|
5239
|
-
let label = "";
|
|
5240
|
-
while (n > 0) {
|
|
5241
|
-
const rem = (n - 1) % 26;
|
|
5242
|
-
label = String.fromCharCode(65 + rem) + label;
|
|
5243
|
-
n = Math.floor((n - 1) / 26);
|
|
5244
|
-
}
|
|
5245
|
-
return label;
|
|
5246
|
-
}
|
|
5247
|
-
function getActionSummary2(action) {
|
|
5248
|
-
if (action.category === "docs_commit") return "Commit docs updates";
|
|
5249
|
-
if (action.category === "issue_create") return "Create and record issue";
|
|
5250
|
-
if (action.category === "branch_create") return "Create feature branch";
|
|
5251
|
-
if (action.category === "pr_create") return "Create PR and record link";
|
|
5252
|
-
if (action.category === "pr_status_update") return "Update PR status";
|
|
5253
|
-
if (action.category === "code_review") return "Process code review feedback";
|
|
5254
|
-
if (action.category === "task_execute") return "Proceed with task execution";
|
|
5255
|
-
if (action.category === "feature_done") return "Feature is complete";
|
|
5256
|
-
if (action.category === "spec_approve") return "Request spec approval";
|
|
5257
|
-
if (action.category === "plan_approve") return "Request plan approval";
|
|
5258
|
-
if (action.category === "tasks_approve") return "Request tasks approval";
|
|
5259
|
-
if (action.category === "pr_metadata_migrate") return "Update tasks.md to latest PR fields";
|
|
5260
|
-
if (action.category === "fallback") return "Re-check context and rerun";
|
|
5261
|
-
if (action.type === "command") {
|
|
5262
|
-
return action.scope === "docs" ? "Run docs command" : "Run project command";
|
|
5263
|
-
}
|
|
5264
|
-
return action.message;
|
|
5265
|
-
}
|
|
5266
|
-
function formatActionSummary2(action) {
|
|
5267
|
-
if (action.type === "command") {
|
|
5268
|
-
return `(${action.scope}) ${action.cmd}`;
|
|
5269
|
-
}
|
|
5270
|
-
return action.message;
|
|
5271
|
-
}
|
|
5272
|
-
function toActionOptions2(actions) {
|
|
5273
|
-
return actions.map((action, index) => {
|
|
5274
|
-
const label = getActionLabel2(index);
|
|
5275
|
-
const summary = getActionSummary2(action);
|
|
5276
|
-
const detail = formatActionSummary2(action);
|
|
5277
|
-
return {
|
|
5278
|
-
label,
|
|
5279
|
-
summary,
|
|
5280
|
-
detail,
|
|
5281
|
-
approvalPrompt: `${label}: ${summary}`,
|
|
5282
|
-
action
|
|
5283
|
-
};
|
|
5284
|
-
});
|
|
5285
|
-
}
|
|
5286
|
-
function buildActionSnapshot2(actionOptions) {
|
|
5287
|
-
return actionOptions.map(({ label, action }) => {
|
|
5288
|
-
if (action.type === "command") {
|
|
5289
|
-
return {
|
|
5290
|
-
label,
|
|
5291
|
-
type: action.type,
|
|
5292
|
-
scope: action.scope,
|
|
5293
|
-
cwd: action.cwd,
|
|
5294
|
-
cmd: action.cmd,
|
|
5295
|
-
category: action.category,
|
|
5296
|
-
requiresUserCheck: !!action.requiresUserCheck
|
|
5297
|
-
};
|
|
5298
|
-
}
|
|
5299
|
-
return {
|
|
5300
|
-
label,
|
|
5301
|
-
type: action.type,
|
|
5302
|
-
message: action.message,
|
|
5303
|
-
category: action.category,
|
|
5304
|
-
requiresUserCheck: !!action.requiresUserCheck
|
|
5305
|
-
};
|
|
5306
|
-
});
|
|
5307
|
-
}
|
|
5308
|
-
function getContextVersion2(feature, actionOptions) {
|
|
5309
|
-
if (!feature) return null;
|
|
5310
|
-
const payload = JSON.stringify({
|
|
5311
|
-
id: feature.id || "",
|
|
5312
|
-
folderName: feature.folderName,
|
|
5313
|
-
currentStep: feature.currentStep,
|
|
5314
|
-
actionSnapshot: buildActionSnapshot2(actionOptions)
|
|
5315
|
-
});
|
|
5316
|
-
return createHash("sha256").update(payload).digest("hex").slice(0, 12);
|
|
5317
|
-
}
|
|
5318
|
-
function matchesFeatureSelector2(f, selector) {
|
|
5319
|
-
const s = selector.trim();
|
|
5320
|
-
if (!s) return false;
|
|
5321
|
-
if (f.folderName.toLowerCase() === s.toLowerCase()) return true;
|
|
5322
|
-
if (f.slug.toLowerCase() === s.toLowerCase()) return true;
|
|
5323
|
-
if (f.id && f.id.toLowerCase() === s.toLowerCase()) return true;
|
|
5324
|
-
return false;
|
|
5325
|
-
}
|
|
5326
|
-
function detectFromBranch2(branchName, features) {
|
|
5327
|
-
const match = branchName.match(/^feat\/\d+-(.+)$/);
|
|
5328
|
-
if (!match) return [];
|
|
5329
|
-
const detected = match[1];
|
|
5330
|
-
return features.filter(
|
|
5331
|
-
(f) => f.slug.toLowerCase() === detected.toLowerCase() || f.folderName.toLowerCase() === detected.toLowerCase()
|
|
5332
|
-
);
|
|
5333
|
-
}
|
|
5334
|
-
function toSelectionStatus2(features, selectionMode, openFeatures, targetFeatures) {
|
|
5335
|
-
const isNoOpen = selectionMode === "open" && features.length > 0 && openFeatures.length === 0;
|
|
5336
|
-
if (features.length === 0) return "no_features";
|
|
5337
|
-
if (isNoOpen) return "no_open";
|
|
5338
|
-
if (targetFeatures.length === 1) return "single_matched";
|
|
5339
|
-
if (targetFeatures.length > 1) return "multiple_active";
|
|
5340
|
-
return "no_match";
|
|
5341
|
-
}
|
|
5342
|
-
function toReasonCode2(status) {
|
|
5343
|
-
if (status === "no_features") return "NO_FEATURES";
|
|
5344
|
-
if (status === "no_open") return "NO_OPEN_FEATURES";
|
|
5345
|
-
if (status === "single_matched") return "SINGLE_MATCHED";
|
|
5346
|
-
if (status === "multiple_active") return "MULTIPLE_ACTIVE_FEATURES";
|
|
5347
|
-
return "NO_MATCHED_FEATURES";
|
|
5348
|
-
}
|
|
5349
|
-
async function resolveContextSelection(config, featureName, options) {
|
|
5350
|
-
const { features, branches, warnings } = await scanFeatures(config);
|
|
5351
|
-
const selectedComponent = resolveComponentOption(options);
|
|
5352
|
-
const scopedFeatures = selectedComponent ? features.filter((f) => f.type === selectedComponent) : features;
|
|
5353
|
-
const doneFeatures = scopedFeatures.filter((f) => f.completion.workflowDone);
|
|
5354
|
-
const openFeatures = scopedFeatures.filter((f) => !f.completion.workflowDone);
|
|
5355
|
-
const inProgressFeatures = openFeatures.filter(
|
|
5356
|
-
(f) => !f.completion.implementationDone
|
|
5357
|
-
);
|
|
5358
|
-
const readyToCloseFeatures = openFeatures.filter(
|
|
5359
|
-
(f) => f.completion.implementationDone
|
|
5360
|
-
);
|
|
5361
|
-
let targetFeatures = [];
|
|
5362
|
-
let selectionMode = "explicit";
|
|
5363
|
-
if (featureName) {
|
|
5364
|
-
targetFeatures = scopedFeatures.filter(
|
|
5365
|
-
(f) => matchesFeatureSelector2(f, featureName)
|
|
5366
|
-
);
|
|
5367
|
-
selectionMode = "explicit";
|
|
5368
|
-
} else {
|
|
5369
|
-
if (config.projectType === "single") {
|
|
5370
|
-
const branchName = branches.project.single || "";
|
|
5371
|
-
targetFeatures = detectFromBranch2(branchName, scopedFeatures);
|
|
5372
|
-
} else if (selectedComponent) {
|
|
5373
|
-
const branchName = branches.project[selectedComponent] || "";
|
|
5374
|
-
targetFeatures = detectFromBranch2(
|
|
5375
|
-
branchName,
|
|
5376
|
-
scopedFeatures
|
|
5377
|
-
);
|
|
5378
|
-
} else {
|
|
5379
|
-
const matches = [];
|
|
5380
|
-
const componentKeys = [...new Set(scopedFeatures.map((f) => f.type))].filter((key) => key !== "single");
|
|
5381
|
-
for (const component of componentKeys) {
|
|
5382
|
-
const branchName = branches.project[component] || "";
|
|
5383
|
-
if (!branchName) continue;
|
|
5384
|
-
matches.push(
|
|
5385
|
-
...detectFromBranch2(
|
|
5386
|
-
branchName,
|
|
5387
|
-
scopedFeatures.filter((f) => f.type === component)
|
|
5388
|
-
)
|
|
5389
|
-
);
|
|
5390
|
-
}
|
|
5391
|
-
targetFeatures = matches;
|
|
5392
|
-
}
|
|
5393
|
-
if (targetFeatures.length > 0) {
|
|
5394
|
-
selectionMode = "branch";
|
|
5395
|
-
} else if (options.all) {
|
|
5396
|
-
targetFeatures = scopedFeatures;
|
|
5397
|
-
selectionMode = "all";
|
|
5398
|
-
} else if (options.done) {
|
|
5399
|
-
targetFeatures = doneFeatures;
|
|
5400
|
-
selectionMode = "done";
|
|
5401
|
-
} else {
|
|
5402
|
-
targetFeatures = openFeatures;
|
|
5403
|
-
selectionMode = "open";
|
|
5404
|
-
}
|
|
5405
|
-
}
|
|
5406
|
-
const status = toSelectionStatus2(
|
|
5407
|
-
scopedFeatures,
|
|
5408
|
-
selectionMode,
|
|
5409
|
-
openFeatures,
|
|
5410
|
-
targetFeatures
|
|
5411
|
-
);
|
|
5412
|
-
const matchedFeature = targetFeatures.length === 1 ? targetFeatures[0] : null;
|
|
5413
|
-
const actions = matchedFeature?.actions ?? [];
|
|
5414
|
-
const actionOptions = toActionOptions2(actions);
|
|
5415
|
-
const contextVersion = getContextVersion2(matchedFeature, actionOptions);
|
|
5416
|
-
return {
|
|
5417
|
-
features: scopedFeatures,
|
|
5418
|
-
branches,
|
|
5419
|
-
warnings,
|
|
5420
|
-
doneFeatures,
|
|
5421
|
-
openFeatures,
|
|
5422
|
-
inProgressFeatures,
|
|
5423
|
-
readyToCloseFeatures,
|
|
5424
|
-
selectionMode,
|
|
5425
|
-
targetFeatures,
|
|
5426
|
-
status,
|
|
5427
|
-
matchedFeature,
|
|
5428
|
-
actions,
|
|
5429
|
-
actionOptions,
|
|
5430
|
-
contextVersion
|
|
5431
|
-
};
|
|
5432
|
-
}
|
|
5433
|
-
|
|
5434
|
-
// src/commands/view.ts
|
|
5435
5299
|
function resolveComponentOption2(options) {
|
|
5436
5300
|
if (options.repo && options.component && options.repo.trim().toLowerCase() !== options.component.trim().toLowerCase()) {
|
|
5437
5301
|
throw createCliError(
|
|
@@ -5489,8 +5353,9 @@ async function runView(featureName, options) {
|
|
|
5489
5353
|
if (options.json) {
|
|
5490
5354
|
const payload = {
|
|
5491
5355
|
status: state.status,
|
|
5492
|
-
reasonCode:
|
|
5356
|
+
reasonCode: toReasonCode(state.status),
|
|
5493
5357
|
selectionMode: state.selectionMode,
|
|
5358
|
+
selectionFallback: state.selectionFallback,
|
|
5494
5359
|
counts: {
|
|
5495
5360
|
features: state.features.length,
|
|
5496
5361
|
open: state.openFeatures.length,
|
|
@@ -5537,7 +5402,7 @@ async function runView(featureName, options) {
|
|
|
5537
5402
|
}
|
|
5538
5403
|
if (!state.matchedFeature) {
|
|
5539
5404
|
console.log();
|
|
5540
|
-
console.log(chalk6.blue(`Selection: ${state.status} (${
|
|
5405
|
+
console.log(chalk6.blue(`Selection: ${state.status} (${toReasonCode(state.status)})`));
|
|
5541
5406
|
const rows = state.targetFeatures.length > 0 ? state.targetFeatures : state.features;
|
|
5542
5407
|
for (const f2 of rows) {
|
|
5543
5408
|
const statusText = f2.completion.workflowDone ? chalk6.green("WORKFLOW_DONE") : f2.completion.implementationDone ? chalk6.cyan("DONE") : chalk6.yellow("IN_PROGRESS");
|
|
@@ -5725,16 +5590,18 @@ async function runFlow(featureName, options) {
|
|
|
5725
5590
|
context: {
|
|
5726
5591
|
before: {
|
|
5727
5592
|
status: before.status,
|
|
5728
|
-
reasonCode:
|
|
5593
|
+
reasonCode: toReasonCode(before.status),
|
|
5729
5594
|
selectionMode: before.selectionMode,
|
|
5595
|
+
selectionFallback: before.selectionFallback,
|
|
5730
5596
|
matchedFeature: before.matchedFeature,
|
|
5731
5597
|
actionOptions: before.actionOptions,
|
|
5732
5598
|
contextVersion: before.contextVersion
|
|
5733
5599
|
},
|
|
5734
5600
|
after: {
|
|
5735
5601
|
status: after.status,
|
|
5736
|
-
reasonCode:
|
|
5602
|
+
reasonCode: toReasonCode(after.status),
|
|
5737
5603
|
selectionMode: after.selectionMode,
|
|
5604
|
+
selectionFallback: after.selectionFallback,
|
|
5738
5605
|
matchedFeature: after.matchedFeature,
|
|
5739
5606
|
actionOptions: after.actionOptions,
|
|
5740
5607
|
contextVersion: after.contextVersion
|
|
@@ -5753,7 +5620,7 @@ async function runFlow(featureName, options) {
|
|
|
5753
5620
|
console.log(chalk6.bold("\u{1F501} Flow Summary"));
|
|
5754
5621
|
console.log(
|
|
5755
5622
|
chalk6.gray(
|
|
5756
|
-
`- Before: ${before.status} (${
|
|
5623
|
+
`- Before: ${before.status} (${toReasonCode(before.status)}) / After: ${after.status} (${toReasonCode(after.status)})`
|
|
5757
5624
|
)
|
|
5758
5625
|
);
|
|
5759
5626
|
if (approvalResult && typeof approvalResult === "object") {
|