lee-spec-kit 0.6.15 → 0.6.17
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 +85 -11
- package/README.md +87 -11
- package/dist/index.js +668 -43
- package/package.json +1 -1
- package/templates/en/common/README.md +24 -0
- package/templates/en/common/agents/agents.md +1 -1
- package/templates/en/common/agents/skills/create-pr.md +4 -2
- package/templates/en/common/features/feature-base/issue.md +3 -7
- package/templates/en/common/features/feature-base/pr.md +3 -3
- package/templates/ko/common/README.md +24 -0
- package/templates/ko/common/agents/agents.md +1 -1
- package/templates/ko/common/agents/skills/create-pr.md +4 -2
- package/templates/ko/common/features/feature-base/issue.md +3 -7
- package/templates/ko/common/features/feature-base/pr.md +3 -3
package/dist/index.js
CHANGED
|
@@ -139,6 +139,7 @@ var I18N = {
|
|
|
139
139
|
"context.suggestionCommandHint": "\uB77C\uBCA8 \uCC38\uACE0 \uBA85\uB839: {command}",
|
|
140
140
|
"context.suggestionFinalPrompt": "\uD604\uC7AC \uCD94\uCC9C \uB77C\uBCA8: {labels}. \uC751\uB2F5\uC740 \uB77C\uBCA8 \uD1A0\uD070 \uD3EC\uD568 \uD615\uC2DD\uC73C\uB85C \uD574\uC8FC\uC138\uC694. (\uC608: {example}, `A \uC9C4\uD589\uD574`)",
|
|
141
141
|
"context.suggestion.createFeature": "\uC0C8 Feature\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4",
|
|
142
|
+
"context.suggestion.runOnboard": "\uCD08\uAE30 \uC124\uC815 \uC810\uAC80(onboard)\uC744 \uC2E4\uD589\uD569\uB2C8\uB2E4",
|
|
142
143
|
"context.suggestion.showDone": "\uC644\uB8CC\uB41C Feature \uBAA9\uB85D\uC744 \uD655\uC778\uD569\uB2C8\uB2E4",
|
|
143
144
|
"context.suggestion.showAll": "\uC804\uCCB4 Feature \uBAA9\uB85D\uC744 \uD655\uC778\uD569\uB2C8\uB2E4",
|
|
144
145
|
"context.suggestion.selectFeature": "\uC9C4\uD589\uD560 Feature\uB97C \uC120\uD0DD\uD574 \uC0C1\uC138 \uCEE8\uD14D\uC2A4\uD2B8\uB97C \uC5FD\uB2C8\uB2E4",
|
|
@@ -202,6 +203,7 @@ var I18N = {
|
|
|
202
203
|
"init.log.nextStepsTitle": "\uB2E4\uC74C \uB2E8\uACC4:",
|
|
203
204
|
"init.log.nextSteps1": " 1. {docsDir}/prd/README.md \uC791\uC131",
|
|
204
205
|
"init.log.nextSteps2": " 2. npx lee-spec-kit feature <name> \uC73C\uB85C \uAE30\uB2A5 \uCD94\uAC00",
|
|
206
|
+
"init.log.nextSteps3": " 3. npx lee-spec-kit onboard --strict \uB85C \uCD08\uAE30 \uC124\uC815 \uC810\uAC80",
|
|
205
207
|
"init.log.gitRepoDetectedCommit": "\u{1F4E6} Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uAC10\uC9C0, docs \uCEE4\uBC0B \uC911...",
|
|
206
208
|
"init.log.gitInit": "\u{1F4E6} Git \uCD08\uAE30\uD654 \uC911...",
|
|
207
209
|
"init.warn.stagedChangesSkip": '\u26A0\uFE0F \uD604\uC7AC Git index\uC5D0 \uC774\uBBF8 stage\uB41C \uBCC0\uACBD\uC774 \uC788\uC2B5\uB2C8\uB2E4. (--dir "." \uC778 \uACBD\uC6B0 \uCEE4\uBC0B \uBC94\uC704\uB97C \uC548\uC804\uD558\uAC8C \uC81C\uD55C\uD560 \uC218 \uC5C6\uC5B4 \uC790\uB3D9 \uCEE4\uBC0B\uC744 \uAC74\uB108\uB701\uB2C8\uB2E4)',
|
|
@@ -589,6 +591,7 @@ var I18N = {
|
|
|
589
591
|
"context.suggestionCommandHint": "Reference command: {command}",
|
|
590
592
|
"context.suggestionFinalPrompt": "Recommended labels now: {labels}. Please reply with a format that includes a label token. (e.g. {example}, `A proceed`)",
|
|
591
593
|
"context.suggestion.createFeature": "Create a new feature",
|
|
594
|
+
"context.suggestion.runOnboard": "Run onboarding checks",
|
|
592
595
|
"context.suggestion.showDone": "Show completed features",
|
|
593
596
|
"context.suggestion.showAll": "Show all features",
|
|
594
597
|
"context.suggestion.selectFeature": "Select a feature and open detailed context",
|
|
@@ -652,6 +655,7 @@ var I18N = {
|
|
|
652
655
|
"init.log.nextStepsTitle": "Next steps:",
|
|
653
656
|
"init.log.nextSteps1": " 1. Write {docsDir}/prd/README.md",
|
|
654
657
|
"init.log.nextSteps2": " 2. Add a feature with: npx lee-spec-kit feature <name>",
|
|
658
|
+
"init.log.nextSteps3": " 3. Run setup checks: npx lee-spec-kit onboard --strict",
|
|
655
659
|
"init.log.gitRepoDetectedCommit": "\u{1F4E6} Git repo detected, committing docs...",
|
|
656
660
|
"init.log.gitInit": "\u{1F4E6} Initializing Git...",
|
|
657
661
|
"init.warn.stagedChangesSkip": '\u26A0\uFE0F There are already staged changes in the Git index. (With --dir ".", commit scope cannot be safely restricted, so auto-commit is skipped.)',
|
|
@@ -2152,6 +2156,7 @@ async function runInit(options) {
|
|
|
2152
2156
|
chalk6.gray(tr(lang, "cli", "init.log.nextSteps1", { docsDir: targetDir }))
|
|
2153
2157
|
);
|
|
2154
2158
|
console.log(chalk6.gray(tr(lang, "cli", "init.log.nextSteps2")));
|
|
2159
|
+
console.log(chalk6.gray(tr(lang, "cli", "init.log.nextSteps3")));
|
|
2155
2160
|
console.log();
|
|
2156
2161
|
},
|
|
2157
2162
|
{ owner: "init" }
|
|
@@ -2159,7 +2164,8 @@ async function runInit(options) {
|
|
|
2159
2164
|
}
|
|
2160
2165
|
async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
|
|
2161
2166
|
try {
|
|
2162
|
-
const
|
|
2167
|
+
const gitWorkdir = docsRepo === "standalone" ? targetDir : cwd;
|
|
2168
|
+
const runGit2 = (args, workdir) => {
|
|
2163
2169
|
execFileSync("git", args, { cwd: workdir, stdio: "ignore" });
|
|
2164
2170
|
};
|
|
2165
2171
|
const getCachedStagedFiles = (workdir) => {
|
|
@@ -2205,14 +2211,14 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
|
|
|
2205
2211
|
}
|
|
2206
2212
|
};
|
|
2207
2213
|
try {
|
|
2208
|
-
|
|
2214
|
+
runGit2(["rev-parse", "--is-inside-work-tree"], gitWorkdir);
|
|
2209
2215
|
console.log(chalk6.blue(tr(lang, "cli", "init.log.gitRepoDetectedCommit")));
|
|
2210
2216
|
} catch {
|
|
2211
2217
|
console.log(chalk6.blue(tr(lang, "cli", "init.log.gitInit")));
|
|
2212
|
-
|
|
2218
|
+
runGit2(["init"], gitWorkdir);
|
|
2213
2219
|
}
|
|
2214
|
-
const relativePath = path19.relative(cwd, targetDir);
|
|
2215
|
-
const stagedBeforeAdd = getCachedStagedFiles(
|
|
2220
|
+
const relativePath = docsRepo === "standalone" ? "." : path19.relative(cwd, targetDir);
|
|
2221
|
+
const stagedBeforeAdd = getCachedStagedFiles(gitWorkdir);
|
|
2216
2222
|
if (relativePath === "." && stagedBeforeAdd && stagedBeforeAdd.length > 0) {
|
|
2217
2223
|
console.log(
|
|
2218
2224
|
chalk6.yellow(
|
|
@@ -2223,8 +2229,8 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
|
|
|
2223
2229
|
console.log();
|
|
2224
2230
|
return;
|
|
2225
2231
|
}
|
|
2226
|
-
if (relativePath !== "." && isPathIgnored(
|
|
2227
|
-
const repoRelativePath = toRepoRelativePath(
|
|
2232
|
+
if (relativePath !== "." && isPathIgnored(gitWorkdir, relativePath)) {
|
|
2233
|
+
const repoRelativePath = toRepoRelativePath(gitWorkdir, relativePath);
|
|
2228
2234
|
console.log(
|
|
2229
2235
|
chalk6.yellow(
|
|
2230
2236
|
tr(lang, "cli", "init.warn.docsPathIgnoredSkipCommit", {
|
|
@@ -2242,14 +2248,14 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
|
|
|
2242
2248
|
console.log();
|
|
2243
2249
|
return;
|
|
2244
2250
|
}
|
|
2245
|
-
|
|
2246
|
-
|
|
2251
|
+
runGit2(["add", relativePath], gitWorkdir);
|
|
2252
|
+
runGit2(
|
|
2247
2253
|
["commit", "-m", "init: docs \uAD6C\uC870 \uCD08\uAE30\uD654 (lee-spec-kit)", "--", relativePath],
|
|
2248
|
-
|
|
2254
|
+
gitWorkdir
|
|
2249
2255
|
);
|
|
2250
2256
|
if (docsRepo === "standalone" && pushDocs && docsRemote) {
|
|
2251
2257
|
try {
|
|
2252
|
-
|
|
2258
|
+
runGit2(["remote", "add", "origin", docsRemote], gitWorkdir);
|
|
2253
2259
|
console.log(
|
|
2254
2260
|
chalk6.green(tr(lang, "cli", "init.log.gitRemoteSet", { remote: docsRemote }))
|
|
2255
2261
|
);
|
|
@@ -6376,9 +6382,14 @@ function buildSuggestionOptions(lang, state, projectType, selectedComponent) {
|
|
|
6376
6382
|
const showDoneCommand = `npx lee-spec-kit context --done${componentArg}`;
|
|
6377
6383
|
const showAllCommand = `npx lee-spec-kit context --all${componentArg}`;
|
|
6378
6384
|
const showOpenCommand = `npx lee-spec-kit context${componentArg}`;
|
|
6385
|
+
const runOnboardCommand = "npx lee-spec-kit onboard --strict";
|
|
6379
6386
|
const rawSuggestions = [];
|
|
6380
6387
|
switch (state.status) {
|
|
6381
6388
|
case "no_features":
|
|
6389
|
+
rawSuggestions.push({
|
|
6390
|
+
summary: tr(lang, "cli", "context.suggestion.runOnboard"),
|
|
6391
|
+
command: runOnboardCommand
|
|
6392
|
+
});
|
|
6382
6393
|
rawSuggestions.push({
|
|
6383
6394
|
summary: tr(lang, "cli", "context.suggestion.createFeature"),
|
|
6384
6395
|
command: createFeatureCommand
|
|
@@ -6487,7 +6498,10 @@ function getCommandExecutionLockPath(action, config) {
|
|
|
6487
6498
|
return getProjectExecutionLockPath(action.cwd);
|
|
6488
6499
|
}
|
|
6489
6500
|
function contextCommand(program2) {
|
|
6490
|
-
program2.command("context [feature-name]").description("Show current feature context and next actions").option("--json", "Output in JSON format for agents").option(
|
|
6501
|
+
program2.command("context [feature-name]").description("Show current feature context and next actions").option("--json", "Output in JSON format for agents").option(
|
|
6502
|
+
"--json-compact",
|
|
6503
|
+
"Output compact JSON for agents (implies --json, reduced duplication)"
|
|
6504
|
+
).option("--component <component>", "Component name for multi projects").option("--all", "Include completed features when auto-detecting").option("--done", "Show completed (workflow-done) features only").option(
|
|
6491
6505
|
"--approve <reply>",
|
|
6492
6506
|
"Approve one labeled option (examples: A, A OK, A proceed, A \uC9C4\uD589\uD574)"
|
|
6493
6507
|
).option(
|
|
@@ -6508,7 +6522,7 @@ function contextCommand(program2) {
|
|
|
6508
6522
|
const lang = config?.lang ?? DEFAULT_LANG;
|
|
6509
6523
|
const cliError = toCliError(error);
|
|
6510
6524
|
const suggestions = getCliErrorSuggestions(cliError.code, lang);
|
|
6511
|
-
if (options.json) {
|
|
6525
|
+
if (options.json || options.jsonCompact) {
|
|
6512
6526
|
console.log(
|
|
6513
6527
|
JSON.stringify({
|
|
6514
6528
|
status: "error",
|
|
@@ -6600,6 +6614,111 @@ function getMultipleFeaturesRecommendation(projectType, selectedComponent) {
|
|
|
6600
6614
|
}
|
|
6601
6615
|
return "Multiple features detected across components. Please specify feature name (slug | F001 | F001-slug) or use --component.";
|
|
6602
6616
|
}
|
|
6617
|
+
function getFeatureRef(feature) {
|
|
6618
|
+
return feature.folderName || `${feature.type}:${feature.slug}`;
|
|
6619
|
+
}
|
|
6620
|
+
function toCompactFeature(feature) {
|
|
6621
|
+
if (!feature) return null;
|
|
6622
|
+
return {
|
|
6623
|
+
ref: getFeatureRef(feature),
|
|
6624
|
+
id: feature.id ?? null,
|
|
6625
|
+
slug: feature.slug,
|
|
6626
|
+
folderName: feature.folderName,
|
|
6627
|
+
type: feature.type,
|
|
6628
|
+
path: feature.path,
|
|
6629
|
+
currentStep: feature.currentStep,
|
|
6630
|
+
nextAction: feature.nextAction,
|
|
6631
|
+
completion: feature.completion,
|
|
6632
|
+
specStatus: feature.specStatus,
|
|
6633
|
+
planStatus: feature.planStatus,
|
|
6634
|
+
tasks: feature.tasks,
|
|
6635
|
+
prePrReview: {
|
|
6636
|
+
status: feature.prePrReview.status,
|
|
6637
|
+
findings: feature.prePrReview.findings,
|
|
6638
|
+
evidenceProvided: feature.prePrReview.evidenceProvided
|
|
6639
|
+
},
|
|
6640
|
+
prReview: {
|
|
6641
|
+
findings: feature.prReview.findings,
|
|
6642
|
+
evidenceProvided: feature.prReview.evidenceProvided
|
|
6643
|
+
},
|
|
6644
|
+
pr: {
|
|
6645
|
+
link: feature.pr.link,
|
|
6646
|
+
status: feature.pr.status,
|
|
6647
|
+
remote: feature.pr.remote
|
|
6648
|
+
},
|
|
6649
|
+
git: {
|
|
6650
|
+
docsBranch: feature.git.docsBranch,
|
|
6651
|
+
projectBranch: feature.git.projectBranch,
|
|
6652
|
+
projectBranchAvailable: feature.git.projectBranchAvailable,
|
|
6653
|
+
onExpectedBranch: feature.git.onExpectedBranch,
|
|
6654
|
+
docsEverCommitted: feature.git.docsEverCommitted,
|
|
6655
|
+
docsHasUncommittedChanges: feature.git.docsHasUncommittedChanges,
|
|
6656
|
+
projectHasUncommittedChanges: feature.git.projectHasUncommittedChanges,
|
|
6657
|
+
docsPathIgnored: feature.git.docsPathIgnored
|
|
6658
|
+
},
|
|
6659
|
+
docs: {
|
|
6660
|
+
specExists: feature.docs.specExists,
|
|
6661
|
+
planExists: feature.docs.planExists,
|
|
6662
|
+
tasksExists: feature.docs.tasksExists,
|
|
6663
|
+
issueDocIssueFieldExists: feature.docs.issueDocIssueFieldExists,
|
|
6664
|
+
prDocPrFieldExists: feature.docs.prDocPrFieldExists,
|
|
6665
|
+
prDocReviewStatusFieldExists: feature.docs.prDocReviewStatusFieldExists,
|
|
6666
|
+
prFieldExists: feature.docs.prFieldExists,
|
|
6667
|
+
prStatusFieldExists: feature.docs.prStatusFieldExists,
|
|
6668
|
+
prePrReviewFieldExists: feature.docs.prePrReviewFieldExists,
|
|
6669
|
+
prePrFindingsFieldExists: feature.docs.prePrFindingsFieldExists,
|
|
6670
|
+
prePrEvidenceFieldExists: feature.docs.prePrEvidenceFieldExists,
|
|
6671
|
+
prReviewFindingsFieldExists: feature.docs.prReviewFindingsFieldExists,
|
|
6672
|
+
prReviewEvidenceFieldExists: feature.docs.prReviewEvidenceFieldExists
|
|
6673
|
+
},
|
|
6674
|
+
warnings: feature.warnings
|
|
6675
|
+
};
|
|
6676
|
+
}
|
|
6677
|
+
function toCompactActionOption(option) {
|
|
6678
|
+
const base = {
|
|
6679
|
+
label: option.label,
|
|
6680
|
+
summary: option.summary,
|
|
6681
|
+
detail: option.detail,
|
|
6682
|
+
approvalPrompt: option.approvalPrompt,
|
|
6683
|
+
actionType: option.action.type,
|
|
6684
|
+
category: option.action.category,
|
|
6685
|
+
operationType: option.action.operationType,
|
|
6686
|
+
requiresUserCheck: !!option.action.requiresUserCheck
|
|
6687
|
+
};
|
|
6688
|
+
if (option.action.type === "command") {
|
|
6689
|
+
base.scope = option.action.scope;
|
|
6690
|
+
base.cwd = option.action.cwd;
|
|
6691
|
+
base.cmd = option.action.cmd;
|
|
6692
|
+
return base;
|
|
6693
|
+
}
|
|
6694
|
+
base.message = option.action.message;
|
|
6695
|
+
return base;
|
|
6696
|
+
}
|
|
6697
|
+
function toCompactSuggestionOption(option) {
|
|
6698
|
+
return {
|
|
6699
|
+
label: option.label,
|
|
6700
|
+
summary: option.summary,
|
|
6701
|
+
command: option.command
|
|
6702
|
+
};
|
|
6703
|
+
}
|
|
6704
|
+
function resolveContextRecommendation(state, projectType, selectedComponent) {
|
|
6705
|
+
if (state.status === "multiple_active") {
|
|
6706
|
+
return getMultipleFeaturesRecommendation(projectType, selectedComponent);
|
|
6707
|
+
}
|
|
6708
|
+
if (state.status === "no_features") {
|
|
6709
|
+
return "No features found. Run onboarding checks first, then create a feature.";
|
|
6710
|
+
}
|
|
6711
|
+
if (state.status === "no_open") {
|
|
6712
|
+
return "No open features found. Use `context --done` to inspect completed features.";
|
|
6713
|
+
}
|
|
6714
|
+
if (state.status === "no_match") {
|
|
6715
|
+
return "No features found.";
|
|
6716
|
+
}
|
|
6717
|
+
if (state.targetFeatures.length === 1) {
|
|
6718
|
+
return state.targetFeatures[0].nextAction;
|
|
6719
|
+
}
|
|
6720
|
+
return "No matched feature.";
|
|
6721
|
+
}
|
|
6603
6722
|
async function runContext(featureName, options) {
|
|
6604
6723
|
const cwd = process.cwd();
|
|
6605
6724
|
const config = await getConfig(cwd);
|
|
@@ -6659,7 +6778,8 @@ async function runContext(featureName, options) {
|
|
|
6659
6778
|
);
|
|
6660
6779
|
return;
|
|
6661
6780
|
}
|
|
6662
|
-
|
|
6781
|
+
const jsonMode = !!options.json || !!options.jsonCompact;
|
|
6782
|
+
if (jsonMode) {
|
|
6663
6783
|
const primaryAction = state.actionOptions[0] ?? null;
|
|
6664
6784
|
const finalApprovalPrompt = buildFinalApprovalPrompt(lang, state.actionOptions);
|
|
6665
6785
|
const approveCommand = buildApprovalCommand(
|
|
@@ -6674,6 +6794,76 @@ async function runContext(featureName, options) {
|
|
|
6674
6794
|
selectedComponent,
|
|
6675
6795
|
true
|
|
6676
6796
|
);
|
|
6797
|
+
const recommendation = resolveContextRecommendation(
|
|
6798
|
+
state,
|
|
6799
|
+
config.projectType,
|
|
6800
|
+
selectedComponent
|
|
6801
|
+
);
|
|
6802
|
+
if (options.jsonCompact) {
|
|
6803
|
+
const compactResult = {
|
|
6804
|
+
schema: "context.v2.compact",
|
|
6805
|
+
status: state.status,
|
|
6806
|
+
reasonCode: toReasonCode(state.status),
|
|
6807
|
+
selectionMode: state.selectionMode,
|
|
6808
|
+
selectionFallback: state.selectionFallback,
|
|
6809
|
+
branches: state.branches,
|
|
6810
|
+
warnings: state.warnings,
|
|
6811
|
+
contextVersion: state.contextVersion,
|
|
6812
|
+
matchedFeature: toCompactFeature(state.matchedFeature),
|
|
6813
|
+
candidateRefs: state.targetFeatures.length > 1 ? state.targetFeatures.map((feature) => getFeatureRef(feature)) : [],
|
|
6814
|
+
completedCandidateRefs: state.selectionMode === "open" ? state.doneFeatures.map((feature) => getFeatureRef(feature)) : [],
|
|
6815
|
+
openCandidateRefs: state.selectionMode === "open" ? state.openFeatures.map((feature) => getFeatureRef(feature)) : [],
|
|
6816
|
+
inProgressCandidateRefs: state.selectionMode === "open" ? state.inProgressFeatures.map((feature) => getFeatureRef(feature)) : [],
|
|
6817
|
+
readyToCloseCandidateRefs: state.selectionMode === "open" ? state.readyToCloseFeatures.map((feature) => getFeatureRef(feature)) : [],
|
|
6818
|
+
actionOptions: state.actionOptions.map((option) => toCompactActionOption(option)),
|
|
6819
|
+
suggestionOptions: suggestionOptions.map(
|
|
6820
|
+
(option) => toCompactSuggestionOption(option)
|
|
6821
|
+
),
|
|
6822
|
+
primaryActionLabel: primaryAction?.label ?? null,
|
|
6823
|
+
workflowPolicy,
|
|
6824
|
+
taskCommitGatePolicy,
|
|
6825
|
+
prePrReviewPolicy,
|
|
6826
|
+
checkPolicy: {
|
|
6827
|
+
docPath: "builtin://agents/policy",
|
|
6828
|
+
token: "<LABEL>",
|
|
6829
|
+
acceptedTokens: ["<LABEL>", "<LABEL> OK", "<LABEL> ...", "... <LABEL> ..."],
|
|
6830
|
+
tokenPattern: "^.*\\b([A-Z]+)\\b.*$",
|
|
6831
|
+
validLabels: state.actionOptions.map((o) => o.label),
|
|
6832
|
+
oneApprovalPerAction: true,
|
|
6833
|
+
requireFreshContext: true,
|
|
6834
|
+
contextVersion: state.contextVersion,
|
|
6835
|
+
config: config.approval ?? { mode: "builtin" }
|
|
6836
|
+
},
|
|
6837
|
+
approvalRequest: {
|
|
6838
|
+
finalPrompt: finalApprovalPrompt,
|
|
6839
|
+
userFacingLines: [
|
|
6840
|
+
...state.actionOptions.map((o) => o.approvalPrompt),
|
|
6841
|
+
finalApprovalPrompt
|
|
6842
|
+
].filter((line) => line.length > 0),
|
|
6843
|
+
labels: state.actionOptions.map((o) => o.label),
|
|
6844
|
+
approveCommand,
|
|
6845
|
+
executeCommand,
|
|
6846
|
+
executeRequiresTicket: !!state.actionOptions[0]?.action?.requiresUserCheck
|
|
6847
|
+
},
|
|
6848
|
+
suggestionRequest: {
|
|
6849
|
+
finalPrompt: suggestionFinalPrompt,
|
|
6850
|
+
userFacingLines: [
|
|
6851
|
+
...suggestionOptions.map((o) => `${o.label}: ${o.summary}`),
|
|
6852
|
+
suggestionFinalPrompt
|
|
6853
|
+
].filter((line) => line.length > 0),
|
|
6854
|
+
labels: suggestionOptions.map((o) => o.label)
|
|
6855
|
+
},
|
|
6856
|
+
prPolicy: {
|
|
6857
|
+
screenshots: {
|
|
6858
|
+
upload: config.pr?.screenshots?.upload ?? false
|
|
6859
|
+
}
|
|
6860
|
+
},
|
|
6861
|
+
requiredDocs,
|
|
6862
|
+
recommendation
|
|
6863
|
+
};
|
|
6864
|
+
console.log(JSON.stringify(compactResult, null, 2));
|
|
6865
|
+
return;
|
|
6866
|
+
}
|
|
6677
6867
|
const result = {
|
|
6678
6868
|
status: state.status,
|
|
6679
6869
|
reasonCode: toReasonCode(state.status),
|
|
@@ -6765,24 +6955,8 @@ async function runContext(featureName, options) {
|
|
|
6765
6955
|
}
|
|
6766
6956
|
},
|
|
6767
6957
|
requiredDocs,
|
|
6768
|
-
recommendation
|
|
6958
|
+
recommendation
|
|
6769
6959
|
};
|
|
6770
|
-
if (result.status === "multiple_active") {
|
|
6771
|
-
result.recommendation = getMultipleFeaturesRecommendation(
|
|
6772
|
-
config.projectType,
|
|
6773
|
-
selectedComponent
|
|
6774
|
-
);
|
|
6775
|
-
} else if (result.status === "no_features") {
|
|
6776
|
-
result.recommendation = "No features found. Create a feature first.";
|
|
6777
|
-
} else if (result.status === "no_open") {
|
|
6778
|
-
result.recommendation = "No open features found. Use `context --done` to inspect completed features.";
|
|
6779
|
-
} else if (result.status === "no_match") {
|
|
6780
|
-
result.recommendation = "No features found.";
|
|
6781
|
-
} else if (state.targetFeatures.length === 1) {
|
|
6782
|
-
result.recommendation = state.targetFeatures[0].nextAction;
|
|
6783
|
-
} else {
|
|
6784
|
-
result.recommendation = "No matched feature.";
|
|
6785
|
-
}
|
|
6786
6960
|
console.log(JSON.stringify(result, null, 2));
|
|
6787
6961
|
return;
|
|
6788
6962
|
}
|
|
@@ -7055,6 +7229,7 @@ async function runContext(featureName, options) {
|
|
|
7055
7229
|
async function runApprovedOption(state, config, lang, featureName, selectionOptions, options) {
|
|
7056
7230
|
const approval = options.approve || "";
|
|
7057
7231
|
const ticketToken = (options.ticket || "").trim();
|
|
7232
|
+
const jsonMode = !!options.json || !!options.jsonCompact;
|
|
7058
7233
|
let parsedLabel = null;
|
|
7059
7234
|
if (state.status !== "single_matched" || !state.matchedFeature) {
|
|
7060
7235
|
throw createCliError(
|
|
@@ -7115,7 +7290,7 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
7115
7290
|
label: parsedLabel,
|
|
7116
7291
|
featureRef
|
|
7117
7292
|
}) : null;
|
|
7118
|
-
if (
|
|
7293
|
+
if (jsonMode) {
|
|
7119
7294
|
console.log(
|
|
7120
7295
|
JSON.stringify(
|
|
7121
7296
|
{
|
|
@@ -7194,7 +7369,7 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
7194
7369
|
`Approved label "${parsedLabel}" is instruction-only. Re-run without \`--execute\` or pick a command option.`
|
|
7195
7370
|
);
|
|
7196
7371
|
}
|
|
7197
|
-
if (
|
|
7372
|
+
if (jsonMode) {
|
|
7198
7373
|
console.log(
|
|
7199
7374
|
JSON.stringify(
|
|
7200
7375
|
{
|
|
@@ -7219,7 +7394,7 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
7219
7394
|
console.log();
|
|
7220
7395
|
return;
|
|
7221
7396
|
}
|
|
7222
|
-
if (!
|
|
7397
|
+
if (!jsonMode) {
|
|
7223
7398
|
console.log();
|
|
7224
7399
|
console.log(chalk6.blue(`\u25B6 Executing option ${parsedLabel}...`));
|
|
7225
7400
|
console.log(chalk6.gray(` ${selectedAction.cmd}`));
|
|
@@ -7231,12 +7406,12 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
7231
7406
|
lockPath,
|
|
7232
7407
|
async () => executeCommandAction(
|
|
7233
7408
|
selectedAction.cmd,
|
|
7234
|
-
|
|
7409
|
+
jsonMode,
|
|
7235
7410
|
selectedAction.cwd
|
|
7236
7411
|
),
|
|
7237
7412
|
{ owner: `context-execute:${selectedAction.scope}` }
|
|
7238
7413
|
);
|
|
7239
|
-
if (
|
|
7414
|
+
if (jsonMode) {
|
|
7240
7415
|
console.log(
|
|
7241
7416
|
JSON.stringify(
|
|
7242
7417
|
{
|
|
@@ -8475,11 +8650,7 @@ function parsePrArtifactMode(raw, kind, lang) {
|
|
|
8475
8650
|
tg(lang, "artifactModeInvalid", { kind, value })
|
|
8476
8651
|
);
|
|
8477
8652
|
}
|
|
8478
|
-
function
|
|
8479
|
-
const normalized = (component || "").trim().toLowerCase();
|
|
8480
|
-
return ["be", "backend", "api", "server", "core"].includes(normalized);
|
|
8481
|
-
}
|
|
8482
|
-
function resolvePrArtifactPolicy(config, feature, options) {
|
|
8653
|
+
function resolvePrArtifactPolicy(config, options) {
|
|
8483
8654
|
const screenshotsMode = parsePrArtifactMode(
|
|
8484
8655
|
options.screenshots,
|
|
8485
8656
|
"screenshots",
|
|
@@ -8491,7 +8662,7 @@ function resolvePrArtifactPolicy(config, feature, options) {
|
|
|
8491
8662
|
config.lang
|
|
8492
8663
|
);
|
|
8493
8664
|
const includeScreenshots = screenshotsMode === "on" ? true : screenshotsMode === "off" ? false : config.pr?.screenshots?.upload ?? false;
|
|
8494
|
-
const includeMermaid = mermaidMode === "on" ? true : mermaidMode === "off" ? false :
|
|
8665
|
+
const includeMermaid = mermaidMode === "on" ? true : mermaidMode === "off" ? false : true;
|
|
8495
8666
|
return {
|
|
8496
8667
|
includeScreenshots,
|
|
8497
8668
|
includeMermaid
|
|
@@ -9545,7 +9716,7 @@ function githubCommand(program2) {
|
|
|
9545
9716
|
slug: feature.slug
|
|
9546
9717
|
});
|
|
9547
9718
|
const title = options.title?.trim() || defaultTitle;
|
|
9548
|
-
const artifactPolicy = resolvePrArtifactPolicy(config,
|
|
9719
|
+
const artifactPolicy = resolvePrArtifactPolicy(config, options);
|
|
9549
9720
|
const generatedBody = buildPrBody(
|
|
9550
9721
|
feature,
|
|
9551
9722
|
specContent,
|
|
@@ -10058,6 +10229,459 @@ async function runDetect(options) {
|
|
|
10058
10229
|
}
|
|
10059
10230
|
console.log();
|
|
10060
10231
|
}
|
|
10232
|
+
function t(lang, ko, en) {
|
|
10233
|
+
return lang === "ko" ? ko : en;
|
|
10234
|
+
}
|
|
10235
|
+
function quotePath(value) {
|
|
10236
|
+
return `"${value.replace(/"/g, '\\"')}"`;
|
|
10237
|
+
}
|
|
10238
|
+
function toSlug(value) {
|
|
10239
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project";
|
|
10240
|
+
}
|
|
10241
|
+
function runGit(args, cwd) {
|
|
10242
|
+
try {
|
|
10243
|
+
return execFileSync("git", args, {
|
|
10244
|
+
cwd,
|
|
10245
|
+
encoding: "utf-8",
|
|
10246
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
10247
|
+
}).trim();
|
|
10248
|
+
} catch {
|
|
10249
|
+
return void 0;
|
|
10250
|
+
}
|
|
10251
|
+
}
|
|
10252
|
+
function isGitRepo(cwd) {
|
|
10253
|
+
return runGit(["rev-parse", "--is-inside-work-tree"], cwd) === "true";
|
|
10254
|
+
}
|
|
10255
|
+
function hasHeadCommit(cwd) {
|
|
10256
|
+
return !!runGit(["rev-parse", "--verify", "HEAD"], cwd);
|
|
10257
|
+
}
|
|
10258
|
+
function getOriginUrl(cwd) {
|
|
10259
|
+
const out = runGit(["remote", "get-url", "origin"], cwd);
|
|
10260
|
+
return out || void 0;
|
|
10261
|
+
}
|
|
10262
|
+
function hasTemplateMarkers(content) {
|
|
10263
|
+
const patterns = [
|
|
10264
|
+
/\{\{projectName\}\}/,
|
|
10265
|
+
/\{\{date\}\}/,
|
|
10266
|
+
/\(Write your project mission here\)/,
|
|
10267
|
+
/\(Write project-specific architecture principles here/i,
|
|
10268
|
+
/\(Write project code quality standards here/i,
|
|
10269
|
+
/\(Write project security principles here/i,
|
|
10270
|
+
/\(Write your project-specific rules here\)/,
|
|
10271
|
+
/\(Override default rules or add additional rules here\)/,
|
|
10272
|
+
/\(Write project-specific workflows here\)/,
|
|
10273
|
+
/\(Write other rules here\)/,
|
|
10274
|
+
/\(프로젝트의 미션을 작성하세요\)/,
|
|
10275
|
+
/\(프로젝트별 아키텍처 원칙을 작성하세요/,
|
|
10276
|
+
/\(프로젝트의 코드 품질 기준을 작성하세요/,
|
|
10277
|
+
/\(프로젝트의 보안 원칙을 작성하세요/,
|
|
10278
|
+
/\(여기에 프로젝트만의 규칙을 작성하세요\)/,
|
|
10279
|
+
/\(기본 규칙을 오버라이드하거나 추가 규칙을 작성하세요\)/,
|
|
10280
|
+
/\(프로젝트만의 워크플로우가 있다면 작성하세요\)/,
|
|
10281
|
+
/\(기타 규칙을 작성하세요\)/
|
|
10282
|
+
];
|
|
10283
|
+
return patterns.some((pattern) => pattern.test(content));
|
|
10284
|
+
}
|
|
10285
|
+
async function countFeatureDirs(docsDir, projectType) {
|
|
10286
|
+
const pattern = projectType === "single" ? "features/*/" : "features/*/*/";
|
|
10287
|
+
const dirs = await glob(pattern, {
|
|
10288
|
+
cwd: docsDir,
|
|
10289
|
+
absolute: false,
|
|
10290
|
+
ignore: ["**/feature-base/**"]
|
|
10291
|
+
});
|
|
10292
|
+
return dirs.map((value) => value.replace(/\\/g, "/").replace(/\/+$/, "")).filter((value) => {
|
|
10293
|
+
const base = path19.posix.basename(value);
|
|
10294
|
+
return !!base && base !== "feature-base";
|
|
10295
|
+
}).length;
|
|
10296
|
+
}
|
|
10297
|
+
async function hasUserPrdFile(prdDir) {
|
|
10298
|
+
if (!await fs15.pathExists(prdDir)) return false;
|
|
10299
|
+
const files = await glob("**/*.md", {
|
|
10300
|
+
cwd: prdDir,
|
|
10301
|
+
nodir: true,
|
|
10302
|
+
absolute: false,
|
|
10303
|
+
ignore: ["**/node_modules/**"]
|
|
10304
|
+
});
|
|
10305
|
+
return files.some((relativePath) => path19.basename(relativePath).toLowerCase() !== "readme.md");
|
|
10306
|
+
}
|
|
10307
|
+
function finalizeChecks(checks) {
|
|
10308
|
+
const summary = checks.reduce(
|
|
10309
|
+
(acc, check) => {
|
|
10310
|
+
acc[check.status] += 1;
|
|
10311
|
+
return acc;
|
|
10312
|
+
},
|
|
10313
|
+
{ ok: 0, warn: 0, block: 0 }
|
|
10314
|
+
);
|
|
10315
|
+
const status = summary.block > 0 ? "blocked" : summary.warn > 0 ? "needs_action" : "ready";
|
|
10316
|
+
return { checks, summary, status };
|
|
10317
|
+
}
|
|
10318
|
+
function printOnboardResult(lang, result) {
|
|
10319
|
+
console.log();
|
|
10320
|
+
console.log(chalk6.bold(t(lang, "\u{1F9ED} Onboarding \uC810\uAC80", "\u{1F9ED} Onboarding Checks")));
|
|
10321
|
+
for (const check of result.checks) {
|
|
10322
|
+
const mark = check.status === "ok" ? chalk6.green("\u2705") : check.status === "warn" ? chalk6.yellow("\u26A0\uFE0F") : chalk6.red("\u274C");
|
|
10323
|
+
const level = check.status === "ok" ? chalk6.green("OK") : check.status === "warn" ? chalk6.yellow("WARN") : chalk6.red("BLOCK");
|
|
10324
|
+
console.log(`${mark} [${level}] ${check.title}`);
|
|
10325
|
+
console.log(` ${check.message}`);
|
|
10326
|
+
if (check.path) console.log(chalk6.gray(` path: ${check.path}`));
|
|
10327
|
+
if (check.suggestedCommand) {
|
|
10328
|
+
console.log(chalk6.gray(` ${t(lang, "\uB2E4\uC74C \uBA85\uB839", "next")}: ${check.suggestedCommand}`));
|
|
10329
|
+
}
|
|
10330
|
+
}
|
|
10331
|
+
console.log();
|
|
10332
|
+
console.log(
|
|
10333
|
+
chalk6.bold(
|
|
10334
|
+
t(
|
|
10335
|
+
lang,
|
|
10336
|
+
`\uC694\uC57D: OK ${result.summary.ok}, WARN ${result.summary.warn}, BLOCK ${result.summary.block}`,
|
|
10337
|
+
`Summary: OK ${result.summary.ok}, WARN ${result.summary.warn}, BLOCK ${result.summary.block}`
|
|
10338
|
+
)
|
|
10339
|
+
)
|
|
10340
|
+
);
|
|
10341
|
+
if (result.status === "ready") {
|
|
10342
|
+
console.log(chalk6.green(t(lang, "\uC628\uBCF4\uB529 \uC900\uBE44\uAC00 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "Onboarding checks passed.")));
|
|
10343
|
+
} else if (result.status === "needs_action") {
|
|
10344
|
+
console.log(chalk6.yellow(t(lang, "\uCD94\uAC00 \uC815\uB9AC\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.", "Some onboarding actions are required.")));
|
|
10345
|
+
} else {
|
|
10346
|
+
console.log(chalk6.red(t(lang, "\uC628\uBCF4\uB529 \uC120\uD589 \uC791\uC5C5\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.", "Onboarding is blocked by required setup.")));
|
|
10347
|
+
}
|
|
10348
|
+
console.log();
|
|
10349
|
+
}
|
|
10350
|
+
async function runOnboardChecks(config) {
|
|
10351
|
+
const lang = config.lang;
|
|
10352
|
+
const checks = [];
|
|
10353
|
+
const docsDir = config.docsDir;
|
|
10354
|
+
const docsGitReady = isGitRepo(docsDir);
|
|
10355
|
+
if (!docsGitReady) {
|
|
10356
|
+
checks.push({
|
|
10357
|
+
id: "docs_git_repo",
|
|
10358
|
+
status: "block",
|
|
10359
|
+
title: t(lang, "docs Git \uB808\uD3EC \uCD08\uAE30\uD654", "Docs git repository initialized"),
|
|
10360
|
+
message: t(
|
|
10361
|
+
lang,
|
|
10362
|
+
"docs \uACBD\uB85C\uAC00 Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uAC00 \uC544\uB2D9\uB2C8\uB2E4.",
|
|
10363
|
+
"Docs directory is not a git repository."
|
|
10364
|
+
),
|
|
10365
|
+
path: docsDir,
|
|
10366
|
+
suggestedCommand: `git -C ${quotePath(docsDir)} init`
|
|
10367
|
+
});
|
|
10368
|
+
} else {
|
|
10369
|
+
checks.push({
|
|
10370
|
+
id: "docs_git_repo",
|
|
10371
|
+
status: "ok",
|
|
10372
|
+
title: t(lang, "docs Git \uB808\uD3EC \uCD08\uAE30\uD654", "Docs git repository initialized"),
|
|
10373
|
+
message: t(lang, "docs Git \uB808\uD3EC\uAC00 \uD655\uC778\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "Docs git repository is available."),
|
|
10374
|
+
path: docsDir
|
|
10375
|
+
});
|
|
10376
|
+
if (!hasHeadCommit(docsDir)) {
|
|
10377
|
+
checks.push({
|
|
10378
|
+
id: "docs_initial_commit",
|
|
10379
|
+
status: "warn",
|
|
10380
|
+
title: t(lang, "docs \uCD08\uAE30 \uCEE4\uBC0B", "Docs initial commit"),
|
|
10381
|
+
message: t(
|
|
10382
|
+
lang,
|
|
10383
|
+
"docs \uCCAB \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uCD08\uAE30 \uC124\uC815 \uCEE4\uBC0B\uC744 \uBA3C\uC800 \uC0DD\uC131\uD558\uC138\uC694.",
|
|
10384
|
+
"No initial commit found in docs repo. Create an initial setup commit first."
|
|
10385
|
+
),
|
|
10386
|
+
path: docsDir,
|
|
10387
|
+
suggestedCommand: `git -C ${quotePath(docsDir)} add . && git -C ${quotePath(docsDir)} commit -m "docs: onboard setup"`
|
|
10388
|
+
});
|
|
10389
|
+
} else {
|
|
10390
|
+
checks.push({
|
|
10391
|
+
id: "docs_initial_commit",
|
|
10392
|
+
status: "ok",
|
|
10393
|
+
title: t(lang, "docs \uCD08\uAE30 \uCEE4\uBC0B", "Docs initial commit"),
|
|
10394
|
+
message: t(lang, "docs \uCD08\uAE30 \uCEE4\uBC0B\uC774 \uC874\uC7AC\uD569\uB2C8\uB2E4.", "Initial commit exists in docs repo."),
|
|
10395
|
+
path: docsDir
|
|
10396
|
+
});
|
|
10397
|
+
}
|
|
10398
|
+
const docsDirty = runGit(["status", "--porcelain=v1"], docsDir);
|
|
10399
|
+
if (docsDirty === void 0) {
|
|
10400
|
+
checks.push({
|
|
10401
|
+
id: "docs_worktree",
|
|
10402
|
+
status: "warn",
|
|
10403
|
+
title: t(lang, "docs \uC791\uC5C5 \uD2B8\uB9AC \uC0C1\uD0DC", "Docs worktree status"),
|
|
10404
|
+
message: t(
|
|
10405
|
+
lang,
|
|
10406
|
+
"docs \uBCC0\uACBD \uC0C1\uD0DC\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
10407
|
+
"Unable to read docs worktree status."
|
|
10408
|
+
),
|
|
10409
|
+
path: docsDir
|
|
10410
|
+
});
|
|
10411
|
+
} else if (docsDirty.trim().length > 0) {
|
|
10412
|
+
checks.push({
|
|
10413
|
+
id: "docs_worktree",
|
|
10414
|
+
status: "warn",
|
|
10415
|
+
title: t(lang, "docs \uC791\uC5C5 \uD2B8\uB9AC \uC0C1\uD0DC", "Docs worktree status"),
|
|
10416
|
+
message: t(
|
|
10417
|
+
lang,
|
|
10418
|
+
"\uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC740 docs \uBCC0\uACBD\uC0AC\uD56D\uC774 \uC788\uC2B5\uB2C8\uB2E4.",
|
|
10419
|
+
"Uncommitted docs changes were found."
|
|
10420
|
+
),
|
|
10421
|
+
path: docsDir,
|
|
10422
|
+
suggestedCommand: `git -C ${quotePath(docsDir)} add . && git -C ${quotePath(docsDir)} commit -m "docs: onboard updates"`
|
|
10423
|
+
});
|
|
10424
|
+
} else {
|
|
10425
|
+
checks.push({
|
|
10426
|
+
id: "docs_worktree",
|
|
10427
|
+
status: "ok",
|
|
10428
|
+
title: t(lang, "docs \uC791\uC5C5 \uD2B8\uB9AC \uC0C1\uD0DC", "Docs worktree status"),
|
|
10429
|
+
message: t(lang, "docs \uC791\uC5C5 \uD2B8\uB9AC\uAC00 \uAE68\uB057\uD569\uB2C8\uB2E4.", "Docs worktree is clean."),
|
|
10430
|
+
path: docsDir
|
|
10431
|
+
});
|
|
10432
|
+
}
|
|
10433
|
+
}
|
|
10434
|
+
const constitutionPath = path19.join(docsDir, "agents", "constitution.md");
|
|
10435
|
+
if (!await fs15.pathExists(constitutionPath)) {
|
|
10436
|
+
checks.push({
|
|
10437
|
+
id: "constitution_exists",
|
|
10438
|
+
status: "block",
|
|
10439
|
+
title: t(lang, "Constitution \uC791\uC131", "Constitution setup"),
|
|
10440
|
+
message: t(
|
|
10441
|
+
lang,
|
|
10442
|
+
"`agents/constitution.md` \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
10443
|
+
"`agents/constitution.md` is missing."
|
|
10444
|
+
),
|
|
10445
|
+
path: constitutionPath,
|
|
10446
|
+
suggestedCommand: `npx lee-spec-kit update --agents`
|
|
10447
|
+
});
|
|
10448
|
+
} else {
|
|
10449
|
+
const content = await fs15.readFile(constitutionPath, "utf-8");
|
|
10450
|
+
if (hasTemplateMarkers(content)) {
|
|
10451
|
+
checks.push({
|
|
10452
|
+
id: "constitution_filled",
|
|
10453
|
+
status: "block",
|
|
10454
|
+
title: t(lang, "Constitution \uC791\uC131", "Constitution setup"),
|
|
10455
|
+
message: t(
|
|
10456
|
+
lang,
|
|
10457
|
+
"Constitution\uC5D0 \uD15C\uD50C\uB9BF placeholder\uAC00 \uB0A8\uC544 \uC788\uC2B5\uB2C8\uB2E4. \uD504\uB85C\uC81D\uD2B8 \uAE30\uC900\uC73C\uB85C \uBA3C\uC800 \uC791\uC131\uD558\uC138\uC694.",
|
|
10458
|
+
"Constitution still contains template placeholders. Fill project-specific content first."
|
|
10459
|
+
),
|
|
10460
|
+
path: constitutionPath
|
|
10461
|
+
});
|
|
10462
|
+
} else {
|
|
10463
|
+
checks.push({
|
|
10464
|
+
id: "constitution_filled",
|
|
10465
|
+
status: "ok",
|
|
10466
|
+
title: t(lang, "Constitution \uC791\uC131", "Constitution setup"),
|
|
10467
|
+
message: t(lang, "Constitution\uC774 \uC791\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "Constitution looks filled."),
|
|
10468
|
+
path: constitutionPath
|
|
10469
|
+
});
|
|
10470
|
+
}
|
|
10471
|
+
}
|
|
10472
|
+
const customPath = path19.join(docsDir, "agents", "custom.md");
|
|
10473
|
+
if (await fs15.pathExists(customPath)) {
|
|
10474
|
+
const content = await fs15.readFile(customPath, "utf-8");
|
|
10475
|
+
if (hasTemplateMarkers(content)) {
|
|
10476
|
+
checks.push({
|
|
10477
|
+
id: "custom_optional",
|
|
10478
|
+
status: "warn",
|
|
10479
|
+
title: t(lang, "Custom \uADDC\uCE59 \uBB38\uC11C", "Custom rules doc"),
|
|
10480
|
+
message: t(
|
|
10481
|
+
lang,
|
|
10482
|
+
"`agents/custom.md`\uB294 \uC120\uD0DD \uD56D\uBAA9\uC774\uC9C0\uB9CC, \uD604\uC7AC \uD15C\uD50C\uB9BF \uC0C1\uD0DC\uC785\uB2C8\uB2E4. \uD544\uC694\uD558\uBA74 \uADDC\uCE59\uC744 \uC791\uC131\uD558\uC138\uC694.",
|
|
10483
|
+
"`agents/custom.md` is optional, but it still looks like template content. Fill it if your project needs custom rules."
|
|
10484
|
+
),
|
|
10485
|
+
path: customPath
|
|
10486
|
+
});
|
|
10487
|
+
} else {
|
|
10488
|
+
checks.push({
|
|
10489
|
+
id: "custom_optional",
|
|
10490
|
+
status: "ok",
|
|
10491
|
+
title: t(lang, "Custom \uADDC\uCE59 \uBB38\uC11C", "Custom rules doc"),
|
|
10492
|
+
message: t(lang, "Custom \uADDC\uCE59 \uBB38\uC11C\uAC00 \uAD6C\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "Custom rules doc looks configured."),
|
|
10493
|
+
path: customPath
|
|
10494
|
+
});
|
|
10495
|
+
}
|
|
10496
|
+
}
|
|
10497
|
+
const prdDir = path19.join(docsDir, "prd");
|
|
10498
|
+
const featureCount = await countFeatureDirs(docsDir, config.projectType);
|
|
10499
|
+
const prdReady = await hasUserPrdFile(prdDir);
|
|
10500
|
+
if (!prdReady) {
|
|
10501
|
+
checks.push({
|
|
10502
|
+
id: "prd_ready",
|
|
10503
|
+
status: featureCount === 0 ? "block" : "warn",
|
|
10504
|
+
title: t(lang, "PRD \uC900\uBE44 \uC0C1\uD0DC", "PRD readiness"),
|
|
10505
|
+
message: featureCount === 0 ? t(
|
|
10506
|
+
lang,
|
|
10507
|
+
"PRD \uBB38\uC11C\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. Feature \uC0DD\uC131 \uC804\uC5D0 PRD\uBD80\uD130 \uC791\uC131\uD558\uC138\uC694.",
|
|
10508
|
+
"PRD is empty. Write PRD first before creating features."
|
|
10509
|
+
) : t(
|
|
10510
|
+
lang,
|
|
10511
|
+
"PRD \uBB38\uC11C\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. \uC774\uBBF8 Feature\uAC00 \uC788\uB2E4\uBA74 PRD\uB97C \uBCF4\uAC15\uD558\uC138\uC694.",
|
|
10512
|
+
"PRD is empty. If features already exist, fill PRD as soon as possible."
|
|
10513
|
+
),
|
|
10514
|
+
path: prdDir,
|
|
10515
|
+
suggestedCommand: `touch ${quotePath(path19.join(prdDir, `${toSlug(config.projectName || "project")}-prd.md`))}`
|
|
10516
|
+
});
|
|
10517
|
+
} else {
|
|
10518
|
+
checks.push({
|
|
10519
|
+
id: "prd_ready",
|
|
10520
|
+
status: "ok",
|
|
10521
|
+
title: t(lang, "PRD \uC900\uBE44 \uC0C1\uD0DC", "PRD readiness"),
|
|
10522
|
+
message: t(lang, "PRD \uBB38\uC11C\uAC00 \uD655\uC778\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "PRD document is present."),
|
|
10523
|
+
path: prdDir
|
|
10524
|
+
});
|
|
10525
|
+
}
|
|
10526
|
+
const workflowPolicy = resolveWorkflowPolicy(config.workflow);
|
|
10527
|
+
if (workflowPolicy.mode === "github") {
|
|
10528
|
+
const projectKeys = config.projectType === "multi" ? resolveProjectComponents(config.projectType, config.components) : ["single"];
|
|
10529
|
+
for (const key of projectKeys) {
|
|
10530
|
+
const resolved = resolveProjectGitCwd(config, key, lang);
|
|
10531
|
+
const title = t(
|
|
10532
|
+
lang,
|
|
10533
|
+
config.projectType === "multi" ? `\uD504\uB85C\uC81D\uD2B8 Git \uC5F0\uACB0 (${key})` : "\uD504\uB85C\uC81D\uD2B8 Git \uC5F0\uACB0",
|
|
10534
|
+
config.projectType === "multi" ? `Project git connectivity (${key})` : "Project git connectivity"
|
|
10535
|
+
);
|
|
10536
|
+
if (resolved.warning || !resolved.cwd) {
|
|
10537
|
+
checks.push({
|
|
10538
|
+
id: `project_git_${key}`,
|
|
10539
|
+
status: "block",
|
|
10540
|
+
title,
|
|
10541
|
+
message: resolved.warning || t(
|
|
10542
|
+
lang,
|
|
10543
|
+
"\uD504\uB85C\uC81D\uD2B8 \uB808\uD3EC \uACBD\uB85C\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
10544
|
+
"Project repository path could not be resolved."
|
|
10545
|
+
)
|
|
10546
|
+
});
|
|
10547
|
+
continue;
|
|
10548
|
+
}
|
|
10549
|
+
if (!isGitRepo(resolved.cwd)) {
|
|
10550
|
+
checks.push({
|
|
10551
|
+
id: `project_git_${key}`,
|
|
10552
|
+
status: "block",
|
|
10553
|
+
title,
|
|
10554
|
+
message: t(
|
|
10555
|
+
lang,
|
|
10556
|
+
"\uD504\uB85C\uC81D\uD2B8 \uACBD\uB85C\uAC00 Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uAC00 \uC544\uB2D9\uB2C8\uB2E4.",
|
|
10557
|
+
"Project path is not a git repository."
|
|
10558
|
+
),
|
|
10559
|
+
path: resolved.cwd,
|
|
10560
|
+
suggestedCommand: `git -C ${quotePath(resolved.cwd)} init`
|
|
10561
|
+
});
|
|
10562
|
+
continue;
|
|
10563
|
+
}
|
|
10564
|
+
const origin = getOriginUrl(resolved.cwd);
|
|
10565
|
+
if (!origin) {
|
|
10566
|
+
checks.push({
|
|
10567
|
+
id: `project_origin_${key}`,
|
|
10568
|
+
status: "block",
|
|
10569
|
+
title: t(
|
|
10570
|
+
lang,
|
|
10571
|
+
config.projectType === "multi" ? `\uD504\uB85C\uC81D\uD2B8 origin \uC124\uC815 (${key})` : "\uD504\uB85C\uC81D\uD2B8 origin \uC124\uC815",
|
|
10572
|
+
config.projectType === "multi" ? `Project origin configured (${key})` : "Project origin configured"
|
|
10573
|
+
),
|
|
10574
|
+
message: t(
|
|
10575
|
+
lang,
|
|
10576
|
+
"GitHub \uC6CC\uD06C\uD50C\uB85C\uC6B0\uB97C \uC704\uD574 \uD504\uB85C\uC81D\uD2B8 \uB808\uD3EC\uC758 origin remote\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
10577
|
+
"Project repo origin remote is required for github workflow."
|
|
10578
|
+
),
|
|
10579
|
+
path: resolved.cwd,
|
|
10580
|
+
suggestedCommand: `git -C ${quotePath(resolved.cwd)} remote add origin <git-url>`
|
|
10581
|
+
});
|
|
10582
|
+
} else {
|
|
10583
|
+
checks.push({
|
|
10584
|
+
id: `project_origin_${key}`,
|
|
10585
|
+
status: "ok",
|
|
10586
|
+
title: t(
|
|
10587
|
+
lang,
|
|
10588
|
+
config.projectType === "multi" ? `\uD504\uB85C\uC81D\uD2B8 origin \uC124\uC815 (${key})` : "\uD504\uB85C\uC81D\uD2B8 origin \uC124\uC815",
|
|
10589
|
+
config.projectType === "multi" ? `Project origin configured (${key})` : "Project origin configured"
|
|
10590
|
+
),
|
|
10591
|
+
message: t(
|
|
10592
|
+
lang,
|
|
10593
|
+
`origin\uC774 \uC124\uC815\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4: ${origin}`,
|
|
10594
|
+
`origin is configured: ${origin}`
|
|
10595
|
+
),
|
|
10596
|
+
path: resolved.cwd
|
|
10597
|
+
});
|
|
10598
|
+
}
|
|
10599
|
+
}
|
|
10600
|
+
}
|
|
10601
|
+
if (config.docsRepo === "standalone" && config.pushDocs) {
|
|
10602
|
+
const origin = getOriginUrl(docsDir);
|
|
10603
|
+
if (!origin) {
|
|
10604
|
+
checks.push({
|
|
10605
|
+
id: "docs_origin",
|
|
10606
|
+
status: "block",
|
|
10607
|
+
title: t(lang, "docs origin \uC124\uC815", "Docs origin configured"),
|
|
10608
|
+
message: t(
|
|
10609
|
+
lang,
|
|
10610
|
+
"standalone + pushDocs=true \uC124\uC815\uC5D0\uC11C\uB294 docs origin remote\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
10611
|
+
"docs origin remote is required when standalone + pushDocs=true."
|
|
10612
|
+
),
|
|
10613
|
+
path: docsDir,
|
|
10614
|
+
suggestedCommand: `git -C ${quotePath(docsDir)} remote add origin <docs-git-url>`
|
|
10615
|
+
});
|
|
10616
|
+
} else {
|
|
10617
|
+
checks.push({
|
|
10618
|
+
id: "docs_origin",
|
|
10619
|
+
status: "ok",
|
|
10620
|
+
title: t(lang, "docs origin \uC124\uC815", "Docs origin configured"),
|
|
10621
|
+
message: t(lang, `origin\uC774 \uC124\uC815\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4: ${origin}`, `origin is configured: ${origin}`),
|
|
10622
|
+
path: docsDir
|
|
10623
|
+
});
|
|
10624
|
+
}
|
|
10625
|
+
}
|
|
10626
|
+
return finalizeChecks(checks);
|
|
10627
|
+
}
|
|
10628
|
+
function onboardCommand(program2) {
|
|
10629
|
+
program2.command("onboard").description("Run onboarding checks for initial setup").option("--json", "Output in JSON format for agents").option("--strict", "Exit with code 1 when WARN/BLOCK exists").action(async (options) => {
|
|
10630
|
+
try {
|
|
10631
|
+
await runOnboard(options);
|
|
10632
|
+
} catch (error) {
|
|
10633
|
+
const config = await getConfig(process.cwd());
|
|
10634
|
+
const lang = config?.lang ?? DEFAULT_LANG;
|
|
10635
|
+
const cliError = toCliError(error);
|
|
10636
|
+
const suggestions = getCliErrorSuggestions(cliError.code, lang);
|
|
10637
|
+
if (options.json) {
|
|
10638
|
+
console.log(
|
|
10639
|
+
JSON.stringify({
|
|
10640
|
+
status: "error",
|
|
10641
|
+
reasonCode: cliError.code,
|
|
10642
|
+
error: cliError.message,
|
|
10643
|
+
suggestions
|
|
10644
|
+
})
|
|
10645
|
+
);
|
|
10646
|
+
} else {
|
|
10647
|
+
console.error(
|
|
10648
|
+
chalk6.red(tr(lang, "cli", "common.errorLabel")),
|
|
10649
|
+
chalk6.red(`[${cliError.code}] ${cliError.message}`)
|
|
10650
|
+
);
|
|
10651
|
+
printCliErrorSuggestions(suggestions, lang);
|
|
10652
|
+
}
|
|
10653
|
+
process.exit(1);
|
|
10654
|
+
}
|
|
10655
|
+
});
|
|
10656
|
+
}
|
|
10657
|
+
async function runOnboard(options) {
|
|
10658
|
+
const config = await getConfig(process.cwd());
|
|
10659
|
+
if (!config) {
|
|
10660
|
+
throw createCliError(
|
|
10661
|
+
"CONFIG_NOT_FOUND",
|
|
10662
|
+
tr(DEFAULT_LANG, "cli", "common.configNotFound")
|
|
10663
|
+
);
|
|
10664
|
+
}
|
|
10665
|
+
const lang = config.lang;
|
|
10666
|
+
const result = await runOnboardChecks(config);
|
|
10667
|
+
if (options.json) {
|
|
10668
|
+
const payload = {
|
|
10669
|
+
status: "ok",
|
|
10670
|
+
reasonCode: result.status === "ready" ? "ONBOARD_READY" : result.status === "needs_action" ? "ONBOARD_NEEDS_ACTION" : "ONBOARD_BLOCKED",
|
|
10671
|
+
docsDir: config.docsDir,
|
|
10672
|
+
docsRepo: config.docsRepo || "embedded",
|
|
10673
|
+
workflow: resolveWorkflowPolicy(config.workflow),
|
|
10674
|
+
summary: result.summary,
|
|
10675
|
+
checks: result.checks
|
|
10676
|
+
};
|
|
10677
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
10678
|
+
} else {
|
|
10679
|
+
printOnboardResult(lang, result);
|
|
10680
|
+
}
|
|
10681
|
+
if (options.strict && (result.summary.warn > 0 || result.summary.block > 0)) {
|
|
10682
|
+
process.exitCode = 1;
|
|
10683
|
+
}
|
|
10684
|
+
}
|
|
10061
10685
|
function isBannerDisabled() {
|
|
10062
10686
|
const v = (process.env.LEE_SPEC_KIT_NO_BANNER || "").trim();
|
|
10063
10687
|
return v === "1";
|
|
@@ -10237,4 +10861,5 @@ flowCommand(program);
|
|
|
10237
10861
|
githubCommand(program);
|
|
10238
10862
|
docsCommand(program);
|
|
10239
10863
|
detectCommand(program);
|
|
10864
|
+
onboardCommand(program);
|
|
10240
10865
|
await program.parseAsync();
|