lee-spec-kit 0.8.0 → 0.8.1
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/dist/{bootstrap-ZIJP7O72.js → bootstrap-G37N6RGB.js} +3 -3
- package/dist/{bootstrap-ZIJP7O72.js.map → bootstrap-G37N6RGB.js.map} +1 -1
- package/dist/{chunk-RYSDBL6X.js → chunk-LYFRLOFQ.js} +34 -9
- package/dist/chunk-LYFRLOFQ.js.map +1 -0
- package/dist/{hooks-IP6FICAV.js → hooks-4S33YUIB.js} +82 -4
- package/dist/hooks-4S33YUIB.js.map +1 -0
- package/dist/index.js +542 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/en/common/agents/agents.md +4 -0
- package/templates/en/common/agents/skills/create-feature.md +3 -1
- package/templates/en/common/agents/skills/create-issue.md +4 -0
- package/templates/en/common/agents/skills/create-pr.md +2 -0
- package/templates/en/common/agents/skills/execute-task.md +3 -0
- package/templates/ko/common/agents/agents.md +4 -0
- package/templates/ko/common/agents/skills/create-feature.md +3 -1
- package/templates/ko/common/agents/skills/create-issue.md +4 -0
- package/templates/ko/common/agents/skills/create-pr.md +2 -0
- package/templates/ko/common/agents/skills/execute-task.md +3 -0
- package/dist/chunk-RYSDBL6X.js.map +0 -1
- package/dist/hooks-IP6FICAV.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { hasLeeSpecKitCodexBootstrap } from './chunk-
|
|
2
|
+
import { hasLeeSpecKitCodexBootstrap } from './chunk-LYFRLOFQ.js';
|
|
3
3
|
import { runGitCapture, runGitOrThrow } from './chunk-GR7JQBWF.js';
|
|
4
4
|
import { __dirname as __dirname$1 } from './chunk-7V7RMGEU.js';
|
|
5
5
|
import { program } from 'commander';
|
|
@@ -1282,17 +1282,24 @@ On session start or after context compression/reset:
|
|
|
1282
1282
|
3. Read any unread \`requiredDocs[*].command\` from that output
|
|
1283
1283
|
4. Cache built-in docs per session and only re-read them when the user explicitly asks for a policy refresh, \`npx lee-spec-kit update\` changed the policy, or the session restarted
|
|
1284
1284
|
|
|
1285
|
-
Before
|
|
1285
|
+
Before taking the next workflow step:
|
|
1286
1286
|
|
|
1287
1287
|
1. Confirm the active feature from the request, docs tree, issue/PR context, or the most recently active feature folder
|
|
1288
1288
|
2. Read the active feature docs as the SSOT: \`spec.md\`, \`plan.md\`, \`tasks.md\`, and \`decisions.md\`
|
|
1289
1289
|
3. When relevant, also read \`issue.md\` and \`pr.md\`
|
|
1290
|
-
4.
|
|
1291
|
-
5.
|
|
1290
|
+
4. Run \`npx lee-spec-kit workflow-stage <feature-ref> --json\` and follow only the returned \`nextAction\`
|
|
1291
|
+
5. Do not start implementation unless \`stage === "implementation"\` and \`implementationAllowed === true\`
|
|
1292
|
+
6. Treat stages before implementation as hard gates:
|
|
1293
|
+
- spec / plan / tasks approvals
|
|
1294
|
+
- issue preparation / issue creation
|
|
1295
|
+
- branch creation
|
|
1296
|
+
7. Keep docs and code synchronized; if code changes materially, update the active feature docs in the same turn before stopping
|
|
1297
|
+
8. When docs are synced to code, refresh an explicit marker like \`<!-- lee-spec-kit:workflow-sync 2026-04-16T12:34:56.789Z -->\` in the active feature docs (prefer \`tasks.md\` or \`decisions.md\`) so \`workflow-audit\` can prove the sync happened after the latest code change
|
|
1292
1298
|
|
|
1293
1299
|
Approval and remote actions:
|
|
1294
1300
|
|
|
1295
1301
|
- Ask the user for approval only at documented workflow approval boundaries or before remote/destructive actions
|
|
1302
|
+
- If \`workflow-stage --json\` reports \`approvalRequired === true\`, stop at that boundary and ask the user before proceeding
|
|
1296
1303
|
- Before \`git commit\`, prefer \`npx lee-spec-kit commit-audit --json\` when hooks or manual checks need commit-time docs path enforcement
|
|
1297
1304
|
- Before remote GitHub actions, share the plan or artifact being sent
|
|
1298
1305
|
- Respect repo policy from docs and config first; hooks only enforce guardrails and continuation checks
|
|
@@ -2476,16 +2483,10 @@ async function runInit(options) {
|
|
|
2476
2483
|
"Standalone workspace root could not be resolved. Re-run init from the shared workspace root above the docs directory."
|
|
2477
2484
|
);
|
|
2478
2485
|
}
|
|
2479
|
-
await upsertLeeSpecKitAgentsMd(path8.join(
|
|
2486
|
+
await upsertLeeSpecKitAgentsMd(path8.join(standaloneWorkspaceRoot, "AGENTS.md"), {
|
|
2480
2487
|
lang,
|
|
2481
2488
|
docsRepo
|
|
2482
2489
|
});
|
|
2483
|
-
if (standaloneWorkspaceRoot !== path8.resolve(targetDir)) {
|
|
2484
|
-
await upsertLeeSpecKitAgentsMd(path8.join(standaloneWorkspaceRoot, "AGENTS.md"), {
|
|
2485
|
-
lang,
|
|
2486
|
-
docsRepo
|
|
2487
|
-
});
|
|
2488
|
-
}
|
|
2489
2490
|
}
|
|
2490
2491
|
} catch {
|
|
2491
2492
|
}
|
|
@@ -3438,7 +3439,6 @@ async function collectAgentsMdTargets(cwd, config) {
|
|
|
3438
3439
|
targets.add(path8.join(repoRoot, "AGENTS.md"));
|
|
3439
3440
|
return [...targets];
|
|
3440
3441
|
}
|
|
3441
|
-
targets.add(path8.join(config.docsDir, "AGENTS.md"));
|
|
3442
3442
|
const workspaceRoot = resolveConfiguredStandaloneWorkspaceRoot(config);
|
|
3443
3443
|
if (!workspaceRoot) {
|
|
3444
3444
|
throw createCliError(
|
|
@@ -3446,9 +3446,7 @@ async function collectAgentsMdTargets(cwd, config) {
|
|
|
3446
3446
|
"Standalone workspaceRoot is missing or invalid. Run `npx lee-spec-kit update --agents-md` from the shared workspace root to migrate this project."
|
|
3447
3447
|
);
|
|
3448
3448
|
}
|
|
3449
|
-
|
|
3450
|
-
targets.add(path8.join(workspaceRoot, "AGENTS.md"));
|
|
3451
|
-
}
|
|
3449
|
+
targets.add(path8.join(workspaceRoot, "AGENTS.md"));
|
|
3452
3450
|
return [...targets];
|
|
3453
3451
|
}
|
|
3454
3452
|
function isPlainObject(value) {
|
|
@@ -6738,7 +6736,7 @@ function registerCodexIntegration(parent) {
|
|
|
6738
6736
|
getCodexConfigPath,
|
|
6739
6737
|
removeLeeSpecKitCodexBootstrap,
|
|
6740
6738
|
upsertLeeSpecKitCodexBootstrap
|
|
6741
|
-
} = await import('./bootstrap-
|
|
6739
|
+
} = await import('./bootstrap-G37N6RGB.js');
|
|
6742
6740
|
const filePath = getCodexConfigPath();
|
|
6743
6741
|
if (options.remove) {
|
|
6744
6742
|
const result2 = await removeLeeSpecKitCodexBootstrap(filePath);
|
|
@@ -6783,7 +6781,7 @@ function registerCodexHooksIntegration(parent) {
|
|
|
6783
6781
|
removeLeeSpecKitCodexHooks,
|
|
6784
6782
|
resolveCodexHooksRepoRoot,
|
|
6785
6783
|
upsertLeeSpecKitCodexHooks
|
|
6786
|
-
} = await import('./hooks-
|
|
6784
|
+
} = await import('./hooks-4S33YUIB.js');
|
|
6787
6785
|
const repoRoot = config.docsRepo === "standalone" ? resolveConfiguredStandaloneWorkspaceRoot(config) : resolveCodexHooksRepoRoot(process.cwd());
|
|
6788
6786
|
if (!repoRoot) {
|
|
6789
6787
|
throw createCliError(
|
|
@@ -6818,6 +6816,529 @@ function integrationsCommand(program2) {
|
|
|
6818
6816
|
registerCodexIntegration(integrations);
|
|
6819
6817
|
registerCodexHooksIntegration(integrations);
|
|
6820
6818
|
}
|
|
6819
|
+
var DOC_STATUS_LABELS = ["Doc Status", "\uBB38\uC11C \uC0C1\uD0DC"];
|
|
6820
|
+
var ISSUE_LABELS = ["Issue", "Issue Number", "\uC774\uC288", "\uC774\uC288 \uBC88\uD638"];
|
|
6821
|
+
var BRANCH_LABELS = ["Branch", "\uBE0C\uB79C\uCE58"];
|
|
6822
|
+
var PR_LABELS = ["PR", "Pull Request"];
|
|
6823
|
+
var PR_STATUS_LABELS = ["PR Status", "PR \uC0C1\uD0DC"];
|
|
6824
|
+
var PRE_PR_REVIEW_LABELS = ["Pre-PR Review", "PR \uC804 \uB9AC\uBDF0"];
|
|
6825
|
+
var PRE_PR_EVIDENCE_LABELS = ["Pre-PR Evidence", "PR \uC804 \uB9AC\uBDF0 Evidence"];
|
|
6826
|
+
var PRE_PR_DECISION_LABELS = ["Pre-PR Decision", "PR \uC804 \uB9AC\uBDF0 Decision"];
|
|
6827
|
+
function resolveWorkflowRequirements(config) {
|
|
6828
|
+
const workflow = config.workflow || {};
|
|
6829
|
+
const workflowMode = workflow.mode || workflow.preset || "github";
|
|
6830
|
+
const isLocalWorkflow = workflowMode === "local";
|
|
6831
|
+
return {
|
|
6832
|
+
requireIssue: workflow.requireIssue ?? !isLocalWorkflow,
|
|
6833
|
+
requireBranch: workflow.requireBranch ?? true,
|
|
6834
|
+
requirePr: workflow.requirePr ?? !isLocalWorkflow,
|
|
6835
|
+
requireReview: workflow.requireReview ?? !isLocalWorkflow,
|
|
6836
|
+
requireMerge: workflow.requireMerge ?? !isLocalWorkflow,
|
|
6837
|
+
prePrReviewEnabled: workflow.prePrReview?.enabled ?? !isLocalWorkflow
|
|
6838
|
+
};
|
|
6839
|
+
}
|
|
6840
|
+
function parseApprovalStatus(raw) {
|
|
6841
|
+
const value = (raw || "").trim().toLowerCase();
|
|
6842
|
+
if (value === "draft") return "draft";
|
|
6843
|
+
if (value === "review") return "review";
|
|
6844
|
+
if (value === "approved") return "approved";
|
|
6845
|
+
return null;
|
|
6846
|
+
}
|
|
6847
|
+
function extractFieldValue(content, labels) {
|
|
6848
|
+
for (const label of Array.isArray(labels) ? labels : [labels]) {
|
|
6849
|
+
const escaped = label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6850
|
+
const match = content.match(
|
|
6851
|
+
new RegExp(`^\\s*-\\s*\\*\\*${escaped}\\*\\*:\\s*(.*?)\\s*$`, "mi")
|
|
6852
|
+
);
|
|
6853
|
+
if (!match) continue;
|
|
6854
|
+
const value = match[1].trim();
|
|
6855
|
+
if (value) return value;
|
|
6856
|
+
}
|
|
6857
|
+
return null;
|
|
6858
|
+
}
|
|
6859
|
+
function parseMarkdownCheckbox(line) {
|
|
6860
|
+
const match = line.match(/^\s*-\s*\[([ xX])\]\s+/);
|
|
6861
|
+
if (!match) return null;
|
|
6862
|
+
return match[1].toLowerCase() === "x";
|
|
6863
|
+
}
|
|
6864
|
+
function withoutFencedCodeBlocks(content) {
|
|
6865
|
+
const lines = [];
|
|
6866
|
+
let inFence = false;
|
|
6867
|
+
for (const line of content.split("\n")) {
|
|
6868
|
+
if (/^\s*```/.test(line)) {
|
|
6869
|
+
inFence = !inFence;
|
|
6870
|
+
continue;
|
|
6871
|
+
}
|
|
6872
|
+
if (!inFence) {
|
|
6873
|
+
lines.push(line);
|
|
6874
|
+
}
|
|
6875
|
+
}
|
|
6876
|
+
return lines;
|
|
6877
|
+
}
|
|
6878
|
+
function parseTasksDoc(content) {
|
|
6879
|
+
const issueRaw = extractFieldValue(content, ISSUE_LABELS);
|
|
6880
|
+
const issueNumberMatch = issueRaw?.match(/^#(\d+)$/);
|
|
6881
|
+
const issueNumber = issueNumberMatch ? Number(issueNumberMatch[1]) : null;
|
|
6882
|
+
const branchRaw = extractFieldValue(content, BRANCH_LABELS);
|
|
6883
|
+
const prRaw = extractFieldValue(content, PR_LABELS);
|
|
6884
|
+
const prePrDecision = extractFieldValue(content, PRE_PR_DECISION_LABELS);
|
|
6885
|
+
const tasks = [];
|
|
6886
|
+
const nonCodeLines = withoutFencedCodeBlocks(content);
|
|
6887
|
+
for (const line of nonCodeLines) {
|
|
6888
|
+
const match = line.match(
|
|
6889
|
+
/^\s*-\s*\[(TODO|DOING|DONE|REVIEW)\](?:\[[^\]]+\])*\s+(.+?)\s*$/i
|
|
6890
|
+
);
|
|
6891
|
+
if (!match) continue;
|
|
6892
|
+
tasks.push({
|
|
6893
|
+
raw: line,
|
|
6894
|
+
status: match[1].toUpperCase(),
|
|
6895
|
+
title: match[2].trim()
|
|
6896
|
+
});
|
|
6897
|
+
}
|
|
6898
|
+
const allTasksChecked = nonCodeLines.some(
|
|
6899
|
+
(line) => /(All tasks are|모든 태스크가)/i.test(line) && parseMarkdownCheckbox(line) === true
|
|
6900
|
+
);
|
|
6901
|
+
const testsChecked = nonCodeLines.some(
|
|
6902
|
+
(line) => /(Tests executed and passing|테스트 실행 및 통과)/i.test(line) && parseMarkdownCheckbox(line) === true
|
|
6903
|
+
);
|
|
6904
|
+
const finalOutcomeChecked = nonCodeLines.some(
|
|
6905
|
+
(line) => /(Final outcome shared and any required user confirmation recorded|Final user approval|최종 결과를 공유했고, 필요한 사용자 확인을 문서화된 workflow checkpoint 기준으로 기록함)/i.test(
|
|
6906
|
+
line
|
|
6907
|
+
) && parseMarkdownCheckbox(line) === true
|
|
6908
|
+
);
|
|
6909
|
+
const prStatus = (() => {
|
|
6910
|
+
const value = (extractFieldValue(content, PR_STATUS_LABELS) || "").trim().toLowerCase();
|
|
6911
|
+
if (value === "review") return "review";
|
|
6912
|
+
if (value === "approved") return "approved";
|
|
6913
|
+
return null;
|
|
6914
|
+
})();
|
|
6915
|
+
const prePrReviewStatus = (() => {
|
|
6916
|
+
const value = (extractFieldValue(content, PRE_PR_REVIEW_LABELS) || "").trim().toLowerCase();
|
|
6917
|
+
if (value === "pending") return "pending";
|
|
6918
|
+
if (value === "running") return "running";
|
|
6919
|
+
if (value === "done") return "done";
|
|
6920
|
+
return null;
|
|
6921
|
+
})();
|
|
6922
|
+
const prePrDecisionOutcome = (() => {
|
|
6923
|
+
const value = (prePrDecision || "").trim().toLowerCase();
|
|
6924
|
+
const match = value.match(/\b(approve|changes_requested|blocked)\b/);
|
|
6925
|
+
return match?.[1] || null;
|
|
6926
|
+
})();
|
|
6927
|
+
return {
|
|
6928
|
+
docStatus: parseApprovalStatus(
|
|
6929
|
+
extractFieldValue(content, DOC_STATUS_LABELS) || void 0
|
|
6930
|
+
),
|
|
6931
|
+
issueNumber,
|
|
6932
|
+
branch: sanitizeMetadataValue(branchRaw),
|
|
6933
|
+
prLink: sanitizeMetadataValue(prRaw),
|
|
6934
|
+
prStatus,
|
|
6935
|
+
prePrReviewStatus,
|
|
6936
|
+
prePrEvidence: sanitizeMetadataValue(
|
|
6937
|
+
extractFieldValue(content, PRE_PR_EVIDENCE_LABELS)
|
|
6938
|
+
),
|
|
6939
|
+
prePrDecision: sanitizeMetadataValue(prePrDecision),
|
|
6940
|
+
prePrDecisionOutcome,
|
|
6941
|
+
tasks,
|
|
6942
|
+
completion: {
|
|
6943
|
+
allTasksChecked,
|
|
6944
|
+
testsChecked,
|
|
6945
|
+
finalOutcomeChecked
|
|
6946
|
+
}
|
|
6947
|
+
};
|
|
6948
|
+
}
|
|
6949
|
+
function sanitizeMetadataValue(value) {
|
|
6950
|
+
if (!value) return null;
|
|
6951
|
+
const trimmed = value.trim().replace(/^`(.+)`$/, "$1");
|
|
6952
|
+
if (!trimmed || trimmed === "-") return null;
|
|
6953
|
+
return trimmed;
|
|
6954
|
+
}
|
|
6955
|
+
function parseWorkflowDraftMetadataExtended(content) {
|
|
6956
|
+
const metadata = parseWorkflowDraftMetadata(content);
|
|
6957
|
+
const prStatusRaw = extractFieldValue(content, PR_STATUS_LABELS);
|
|
6958
|
+
const normalizedPrStatus = (prStatusRaw || "").trim().toLowerCase();
|
|
6959
|
+
return {
|
|
6960
|
+
...metadata,
|
|
6961
|
+
issueRef: sanitizeMetadataValue(extractFieldValue(content, ISSUE_LABELS)),
|
|
6962
|
+
prRef: sanitizeMetadataValue(extractFieldValue(content, PR_LABELS)),
|
|
6963
|
+
prStatus: normalizedPrStatus === "review" ? "review" : normalizedPrStatus === "approved" ? "approved" : null
|
|
6964
|
+
};
|
|
6965
|
+
}
|
|
6966
|
+
async function readFileIfExists(filePath) {
|
|
6967
|
+
if (!await fs.pathExists(filePath)) return null;
|
|
6968
|
+
return fs.readFile(filePath, "utf-8");
|
|
6969
|
+
}
|
|
6970
|
+
function buildFeatureRef(feature) {
|
|
6971
|
+
return feature.folderName;
|
|
6972
|
+
}
|
|
6973
|
+
function buildFeatureArgs(feature) {
|
|
6974
|
+
return feature.type && feature.type !== "single" ? `${buildFeatureRef(feature)} --component ${feature.type}` : buildFeatureRef(feature);
|
|
6975
|
+
}
|
|
6976
|
+
function resolveExpectedBranch(feature, tasks) {
|
|
6977
|
+
if (tasks.branch) return tasks.branch;
|
|
6978
|
+
if (!tasks.issueNumber) return null;
|
|
6979
|
+
return `feat/${tasks.issueNumber}-${feature.slug}`;
|
|
6980
|
+
}
|
|
6981
|
+
function getCurrentProjectBranch(feature) {
|
|
6982
|
+
return runGitCapture(["branch", "--show-current"], feature.git.projectGitCwd) || runGitCapture(["rev-parse", "--abbrev-ref", "HEAD"], feature.git.projectGitCwd) || null;
|
|
6983
|
+
}
|
|
6984
|
+
function nextTodoTask(tasks) {
|
|
6985
|
+
return tasks.tasks.find((task) => task.status === "DOING") || tasks.tasks.find((task) => task.status === "TODO") || null;
|
|
6986
|
+
}
|
|
6987
|
+
function allTasksDone(tasks) {
|
|
6988
|
+
return tasks.tasks.length > 0 && tasks.tasks.every((task) => task.status === "DONE");
|
|
6989
|
+
}
|
|
6990
|
+
function prePrSatisfied(tasks) {
|
|
6991
|
+
return tasks.prePrReviewStatus === "done" && !!tasks.prePrEvidence && !!tasks.prePrDecision && tasks.prePrDecisionOutcome === "approve";
|
|
6992
|
+
}
|
|
6993
|
+
function issueExistsRemotely(issueNumber, feature) {
|
|
6994
|
+
if (!issueNumber) return false;
|
|
6995
|
+
const result = runProcess(
|
|
6996
|
+
"gh",
|
|
6997
|
+
["issue", "view", String(issueNumber), "--json", "number"],
|
|
6998
|
+
feature.git.projectGitCwd
|
|
6999
|
+
);
|
|
7000
|
+
return result.code === 0;
|
|
7001
|
+
}
|
|
7002
|
+
function prExistsRemotely(prRef, feature) {
|
|
7003
|
+
if (!prRef) return false;
|
|
7004
|
+
const result = runProcess(
|
|
7005
|
+
"gh",
|
|
7006
|
+
["pr", "view", prRef, "--json", "url"],
|
|
7007
|
+
feature.git.projectGitCwd
|
|
7008
|
+
);
|
|
7009
|
+
return result.code === 0;
|
|
7010
|
+
}
|
|
7011
|
+
function buildAction(category, summary, approvalRequired, command = null) {
|
|
7012
|
+
return {
|
|
7013
|
+
category,
|
|
7014
|
+
summary,
|
|
7015
|
+
approvalRequired,
|
|
7016
|
+
command
|
|
7017
|
+
};
|
|
7018
|
+
}
|
|
7019
|
+
function resolveFeatureSelectionError(selection) {
|
|
7020
|
+
const reasonCode = selection.status === "no_features" ? "NO_FEATURES" : "FEATURE_SELECTION_REQUIRED";
|
|
7021
|
+
return {
|
|
7022
|
+
status: "error",
|
|
7023
|
+
reasonCode,
|
|
7024
|
+
docsDir: selection.config.docsDir,
|
|
7025
|
+
featureRef: null,
|
|
7026
|
+
stage: null,
|
|
7027
|
+
nextAction: null,
|
|
7028
|
+
approvalRequired: false,
|
|
7029
|
+
implementationAllowed: false,
|
|
7030
|
+
blockedReasonCode: null
|
|
7031
|
+
};
|
|
7032
|
+
}
|
|
7033
|
+
async function collectWorkflowStage(cwd, selector, component) {
|
|
7034
|
+
const config = await getConfig(cwd);
|
|
7035
|
+
if (!config) {
|
|
7036
|
+
return {
|
|
7037
|
+
status: "error",
|
|
7038
|
+
reasonCode: "CONFIG_NOT_FOUND",
|
|
7039
|
+
docsDir: null,
|
|
7040
|
+
featureRef: null,
|
|
7041
|
+
stage: null,
|
|
7042
|
+
nextAction: null,
|
|
7043
|
+
approvalRequired: false,
|
|
7044
|
+
implementationAllowed: false,
|
|
7045
|
+
blockedReasonCode: null
|
|
7046
|
+
};
|
|
7047
|
+
}
|
|
7048
|
+
const selection = await resolveFeatureSelection(cwd, selector, component);
|
|
7049
|
+
if (selection.status !== "selected" || !selection.matchedFeature) {
|
|
7050
|
+
return resolveFeatureSelectionError(selection);
|
|
7051
|
+
}
|
|
7052
|
+
const feature = selection.matchedFeature;
|
|
7053
|
+
const requirements = resolveWorkflowRequirements(config);
|
|
7054
|
+
const paths = getFeatureDocPaths(feature);
|
|
7055
|
+
const specContent = await readFileIfExists(path8.join(config.docsDir, paths.specPath));
|
|
7056
|
+
const planContent = await readFileIfExists(path8.join(config.docsDir, paths.planPath));
|
|
7057
|
+
const tasksContent = await readFileIfExists(path8.join(config.docsDir, paths.tasksPath));
|
|
7058
|
+
const issueContent = await readFileIfExists(path8.join(config.docsDir, paths.issuePath));
|
|
7059
|
+
const prContent = await readFileIfExists(path8.join(config.docsDir, paths.prPath));
|
|
7060
|
+
const specStatus = parseApprovalStatus(
|
|
7061
|
+
extractFieldValue(specContent || "", ["Status", "\uC0C1\uD0DC"]) || void 0
|
|
7062
|
+
);
|
|
7063
|
+
const planStatus = parseApprovalStatus(
|
|
7064
|
+
extractFieldValue(planContent || "", ["Status", "\uC0C1\uD0DC"]) || void 0
|
|
7065
|
+
);
|
|
7066
|
+
const tasks = parseTasksDoc(tasksContent || "");
|
|
7067
|
+
const issueDraft = parseWorkflowDraftMetadataExtended(issueContent || "");
|
|
7068
|
+
const prDraft = parseWorkflowDraftMetadataExtended(prContent || "");
|
|
7069
|
+
if (specStatus !== "approved") {
|
|
7070
|
+
const approvalRequired = specStatus === "review";
|
|
7071
|
+
return {
|
|
7072
|
+
status: "ok",
|
|
7073
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
7074
|
+
docsDir: config.docsDir,
|
|
7075
|
+
featureRef: buildFeatureRef(feature),
|
|
7076
|
+
stage: "spec",
|
|
7077
|
+
nextAction: buildAction(
|
|
7078
|
+
approvalRequired ? "spec_approve" : "spec_write",
|
|
7079
|
+
approvalRequired ? "Get user approval and update spec.md status to Approved." : "Write or refine spec.md until it is ready for approval.",
|
|
7080
|
+
approvalRequired
|
|
7081
|
+
),
|
|
7082
|
+
approvalRequired,
|
|
7083
|
+
implementationAllowed: false,
|
|
7084
|
+
blockedReasonCode: "SPEC_NOT_APPROVED"
|
|
7085
|
+
};
|
|
7086
|
+
}
|
|
7087
|
+
if (planStatus !== "approved") {
|
|
7088
|
+
const approvalRequired = planStatus === "review";
|
|
7089
|
+
return {
|
|
7090
|
+
status: "ok",
|
|
7091
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
7092
|
+
docsDir: config.docsDir,
|
|
7093
|
+
featureRef: buildFeatureRef(feature),
|
|
7094
|
+
stage: "plan",
|
|
7095
|
+
nextAction: buildAction(
|
|
7096
|
+
approvalRequired ? "plan_approve" : "plan_write",
|
|
7097
|
+
approvalRequired ? "Get user approval and update plan.md status to Approved." : "Write or refine plan.md until it is ready for approval.",
|
|
7098
|
+
approvalRequired
|
|
7099
|
+
),
|
|
7100
|
+
approvalRequired,
|
|
7101
|
+
implementationAllowed: false,
|
|
7102
|
+
blockedReasonCode: "PLAN_NOT_APPROVED"
|
|
7103
|
+
};
|
|
7104
|
+
}
|
|
7105
|
+
if (tasks.tasks.length === 0 || tasks.docStatus !== "approved") {
|
|
7106
|
+
const approvalRequired = tasks.docStatus === "review";
|
|
7107
|
+
return {
|
|
7108
|
+
status: "ok",
|
|
7109
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
7110
|
+
docsDir: config.docsDir,
|
|
7111
|
+
featureRef: buildFeatureRef(feature),
|
|
7112
|
+
stage: "tasks",
|
|
7113
|
+
nextAction: buildAction(
|
|
7114
|
+
approvalRequired ? "tasks_approve" : "tasks_write",
|
|
7115
|
+
approvalRequired ? "Get user approval and update tasks.md Doc Status to Approved." : "Add and refine tasks until tasks.md is execution-ready and Approved.",
|
|
7116
|
+
approvalRequired
|
|
7117
|
+
),
|
|
7118
|
+
approvalRequired,
|
|
7119
|
+
implementationAllowed: false,
|
|
7120
|
+
blockedReasonCode: "TASKS_NOT_READY"
|
|
7121
|
+
};
|
|
7122
|
+
}
|
|
7123
|
+
if (requirements.requireIssue) {
|
|
7124
|
+
const issueReady = issueDraft.status === "ready";
|
|
7125
|
+
const issueCreated = tasks.issueNumber !== null && issueExistsRemotely(tasks.issueNumber, feature);
|
|
7126
|
+
if (!issueCreated || !issueReady) {
|
|
7127
|
+
return {
|
|
7128
|
+
status: "ok",
|
|
7129
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
7130
|
+
docsDir: config.docsDir,
|
|
7131
|
+
featureRef: buildFeatureRef(feature),
|
|
7132
|
+
stage: "issue",
|
|
7133
|
+
nextAction: issueReady && !issueCreated ? buildAction(
|
|
7134
|
+
"issue_create",
|
|
7135
|
+
"Create the GitHub issue from issue.md and sync the issue number into tasks.md.",
|
|
7136
|
+
true,
|
|
7137
|
+
`npx lee-spec-kit github issue ${buildFeatureArgs(feature)} --create --confirm OK`
|
|
7138
|
+
) : buildAction(
|
|
7139
|
+
"issue_prepare",
|
|
7140
|
+
"Prepare issue.md and set its Status to Ready before issue creation.",
|
|
7141
|
+
false
|
|
7142
|
+
),
|
|
7143
|
+
approvalRequired: issueReady && !issueCreated,
|
|
7144
|
+
implementationAllowed: false,
|
|
7145
|
+
blockedReasonCode: "ISSUE_NOT_CREATED"
|
|
7146
|
+
};
|
|
7147
|
+
}
|
|
7148
|
+
}
|
|
7149
|
+
if (requirements.requireBranch && !allTasksDone(tasks)) {
|
|
7150
|
+
const expectedBranch = resolveExpectedBranch(feature, tasks);
|
|
7151
|
+
const currentBranch = getCurrentProjectBranch(feature);
|
|
7152
|
+
if (expectedBranch && currentBranch !== expectedBranch) {
|
|
7153
|
+
return {
|
|
7154
|
+
status: "ok",
|
|
7155
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
7156
|
+
docsDir: config.docsDir,
|
|
7157
|
+
featureRef: buildFeatureRef(feature),
|
|
7158
|
+
stage: "branch",
|
|
7159
|
+
nextAction: buildAction(
|
|
7160
|
+
"branch_create",
|
|
7161
|
+
`Switch the project repo to ${expectedBranch} before implementation starts.`,
|
|
7162
|
+
false,
|
|
7163
|
+
`git checkout -b ${expectedBranch}`
|
|
7164
|
+
),
|
|
7165
|
+
approvalRequired: false,
|
|
7166
|
+
implementationAllowed: false,
|
|
7167
|
+
blockedReasonCode: "BRANCH_NOT_READY"
|
|
7168
|
+
};
|
|
7169
|
+
}
|
|
7170
|
+
}
|
|
7171
|
+
if (!allTasksDone(tasks)) {
|
|
7172
|
+
const currentTask = nextTodoTask(tasks);
|
|
7173
|
+
return {
|
|
7174
|
+
status: "ok",
|
|
7175
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
7176
|
+
docsDir: config.docsDir,
|
|
7177
|
+
featureRef: buildFeatureRef(feature),
|
|
7178
|
+
stage: "implementation",
|
|
7179
|
+
nextAction: buildAction(
|
|
7180
|
+
"task_execute",
|
|
7181
|
+
currentTask ? `Continue the next implementation task: ${currentTask.title}` : "Continue the active implementation task.",
|
|
7182
|
+
false
|
|
7183
|
+
),
|
|
7184
|
+
approvalRequired: false,
|
|
7185
|
+
implementationAllowed: true,
|
|
7186
|
+
blockedReasonCode: null
|
|
7187
|
+
};
|
|
7188
|
+
}
|
|
7189
|
+
if (!tasks.completion.allTasksChecked || !tasks.completion.testsChecked || !tasks.completion.finalOutcomeChecked) {
|
|
7190
|
+
return {
|
|
7191
|
+
status: "ok",
|
|
7192
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
7193
|
+
docsDir: config.docsDir,
|
|
7194
|
+
featureRef: buildFeatureRef(feature),
|
|
7195
|
+
stage: "implementation_approve",
|
|
7196
|
+
nextAction: buildAction(
|
|
7197
|
+
"implementation_approve",
|
|
7198
|
+
"Share the completed implementation, get user approval, and record the completion checkpoint in tasks.md.",
|
|
7199
|
+
true
|
|
7200
|
+
),
|
|
7201
|
+
approvalRequired: true,
|
|
7202
|
+
implementationAllowed: false,
|
|
7203
|
+
blockedReasonCode: "IMPLEMENTATION_APPROVAL_REQUIRED"
|
|
7204
|
+
};
|
|
7205
|
+
}
|
|
7206
|
+
if (requirements.prePrReviewEnabled && !prePrSatisfied(tasks)) {
|
|
7207
|
+
return {
|
|
7208
|
+
status: "ok",
|
|
7209
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
7210
|
+
docsDir: config.docsDir,
|
|
7211
|
+
featureRef: buildFeatureRef(feature),
|
|
7212
|
+
stage: "pre_pr_review",
|
|
7213
|
+
nextAction: buildAction(
|
|
7214
|
+
"pre_pr_review",
|
|
7215
|
+
"Run and record the Pre-PR review until tasks.md shows an approve decision with evidence.",
|
|
7216
|
+
false
|
|
7217
|
+
),
|
|
7218
|
+
approvalRequired: false,
|
|
7219
|
+
implementationAllowed: false,
|
|
7220
|
+
blockedReasonCode: "PRE_PR_REVIEW_NOT_APPROVED"
|
|
7221
|
+
};
|
|
7222
|
+
}
|
|
7223
|
+
if (requirements.requirePr) {
|
|
7224
|
+
const prReady = prDraft.status === "ready";
|
|
7225
|
+
const prCreated = !!tasks.prLink && prExistsRemotely(tasks.prLink, feature);
|
|
7226
|
+
if (!prCreated || !prReady) {
|
|
7227
|
+
return {
|
|
7228
|
+
status: "ok",
|
|
7229
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
7230
|
+
docsDir: config.docsDir,
|
|
7231
|
+
featureRef: buildFeatureRef(feature),
|
|
7232
|
+
stage: "pr",
|
|
7233
|
+
nextAction: prReady && !prCreated ? buildAction(
|
|
7234
|
+
"pr_create",
|
|
7235
|
+
"Create the GitHub PR from pr.md and sync the PR metadata into tasks.md.",
|
|
7236
|
+
true,
|
|
7237
|
+
`npx lee-spec-kit github pr ${buildFeatureArgs(feature)} --create --confirm OK`
|
|
7238
|
+
) : buildAction(
|
|
7239
|
+
"pr_prepare",
|
|
7240
|
+
"Prepare pr.md and set its Status to Ready before PR creation.",
|
|
7241
|
+
false
|
|
7242
|
+
),
|
|
7243
|
+
approvalRequired: prReady && !prCreated,
|
|
7244
|
+
implementationAllowed: false,
|
|
7245
|
+
blockedReasonCode: "PR_NOT_CREATED"
|
|
7246
|
+
};
|
|
7247
|
+
}
|
|
7248
|
+
}
|
|
7249
|
+
if (requirements.requireReview && (tasks.prStatus !== "approved" || prDraft.prStatus !== "approved")) {
|
|
7250
|
+
return {
|
|
7251
|
+
status: "ok",
|
|
7252
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
7253
|
+
docsDir: config.docsDir,
|
|
7254
|
+
featureRef: buildFeatureRef(feature),
|
|
7255
|
+
stage: "code_review",
|
|
7256
|
+
nextAction: buildAction(
|
|
7257
|
+
"code_review",
|
|
7258
|
+
"Complete PR review and record the final approved review state in tasks.md.",
|
|
7259
|
+
false
|
|
7260
|
+
),
|
|
7261
|
+
approvalRequired: false,
|
|
7262
|
+
implementationAllowed: false,
|
|
7263
|
+
blockedReasonCode: "PR_REVIEW_NOT_APPROVED"
|
|
7264
|
+
};
|
|
7265
|
+
}
|
|
7266
|
+
if (requirements.requireMerge) {
|
|
7267
|
+
return {
|
|
7268
|
+
status: "ok",
|
|
7269
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
7270
|
+
docsDir: config.docsDir,
|
|
7271
|
+
featureRef: buildFeatureRef(feature),
|
|
7272
|
+
stage: "merge",
|
|
7273
|
+
nextAction: buildAction(
|
|
7274
|
+
"pr_merge",
|
|
7275
|
+
"Merge the PR and sync the merged state back into tasks.md.",
|
|
7276
|
+
true,
|
|
7277
|
+
`npx lee-spec-kit github pr ${buildFeatureArgs(feature)} --merge --confirm OK`
|
|
7278
|
+
),
|
|
7279
|
+
approvalRequired: true,
|
|
7280
|
+
implementationAllowed: false,
|
|
7281
|
+
blockedReasonCode: null
|
|
7282
|
+
};
|
|
7283
|
+
}
|
|
7284
|
+
return {
|
|
7285
|
+
status: "ok",
|
|
7286
|
+
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
7287
|
+
docsDir: config.docsDir,
|
|
7288
|
+
featureRef: buildFeatureRef(feature),
|
|
7289
|
+
stage: "done",
|
|
7290
|
+
nextAction: null,
|
|
7291
|
+
approvalRequired: false,
|
|
7292
|
+
implementationAllowed: false,
|
|
7293
|
+
blockedReasonCode: null
|
|
7294
|
+
};
|
|
7295
|
+
}
|
|
7296
|
+
|
|
7297
|
+
// src/commands/workflow-stage.ts
|
|
7298
|
+
function workflowStageCommand(program2) {
|
|
7299
|
+
program2.command("workflow-stage [feature-name]").description("Resolve the current high-level workflow stage for the active feature").option("--json", "Output JSON for agents and hooks").option("--component <component>", "Component name for multi projects").action(async (featureName, options) => {
|
|
7300
|
+
try {
|
|
7301
|
+
const payload = await collectWorkflowStage(
|
|
7302
|
+
process.cwd(),
|
|
7303
|
+
featureName,
|
|
7304
|
+
options.component
|
|
7305
|
+
);
|
|
7306
|
+
if (options.json) {
|
|
7307
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
7308
|
+
return;
|
|
7309
|
+
}
|
|
7310
|
+
if (payload.status !== "ok") {
|
|
7311
|
+
console.log(`${payload.status}: ${payload.reasonCode}`);
|
|
7312
|
+
process.exitCode = 1;
|
|
7313
|
+
return;
|
|
7314
|
+
}
|
|
7315
|
+
console.log(`stage: ${payload.stage}`);
|
|
7316
|
+
if (payload.nextAction) {
|
|
7317
|
+
console.log(`next: ${payload.nextAction.category}`);
|
|
7318
|
+
}
|
|
7319
|
+
} catch (error) {
|
|
7320
|
+
const cliError = toCliError(error);
|
|
7321
|
+
if (options.json) {
|
|
7322
|
+
console.log(
|
|
7323
|
+
JSON.stringify(
|
|
7324
|
+
{
|
|
7325
|
+
status: "error",
|
|
7326
|
+
reasonCode: cliError.code,
|
|
7327
|
+
error: cliError.message
|
|
7328
|
+
},
|
|
7329
|
+
null,
|
|
7330
|
+
2
|
|
7331
|
+
)
|
|
7332
|
+
);
|
|
7333
|
+
process.exitCode = 1;
|
|
7334
|
+
return;
|
|
7335
|
+
}
|
|
7336
|
+
process.stderr.write(`[${cliError.code}] ${cliError.message}
|
|
7337
|
+
`);
|
|
7338
|
+
process.exitCode = 1;
|
|
7339
|
+
}
|
|
7340
|
+
});
|
|
7341
|
+
}
|
|
6821
7342
|
var FEATURE_DOC_FILE_PATTERN = /^features\/(?:[^/]+\/)?F\d{3,}[^/]*\/(spec|plan|tasks|decisions|issue|pr)\.md$/i;
|
|
6822
7343
|
var CODE_FILE_PATTERN = /(^|\/)(Dockerfile|Makefile)$|\.(c|cc|cjs|cpp|cs|css|cts|go|h|hpp|html|java|js|json|jsx|kt|mjs|mts|php|py|rb|rs|scss|sh|sql|swift|ts|tsx|vue|yaml|yml|zsh)$/i;
|
|
6823
7344
|
var WORKFLOW_SYNC_MARKER_PATTERN = /<!--\s*lee-spec-kit:workflow-sync\s+([0-9]{4}-[0-9]{2}-[0-9]{2}T[^ ]+?)\s*-->/gi;
|
|
@@ -7384,10 +7905,10 @@ function parseStagedPaths(output) {
|
|
|
7384
7905
|
staged.set(`path:${normalizeSlashes3(parts[1])}`, `${status}:path`);
|
|
7385
7906
|
}
|
|
7386
7907
|
return [...staged.entries()].map(([encodedPath, encodedStatus]) => {
|
|
7387
|
-
const [role,
|
|
7908
|
+
const [role, path26] = encodedPath.split(":", 2);
|
|
7388
7909
|
const [status, entryRole] = encodedStatus.split(":", 2);
|
|
7389
7910
|
return {
|
|
7390
|
-
path:
|
|
7911
|
+
path: path26,
|
|
7391
7912
|
status,
|
|
7392
7913
|
role: entryRole || role || "path"
|
|
7393
7914
|
};
|
|
@@ -7632,6 +8153,7 @@ function configureRootCommandSurface() {
|
|
|
7632
8153
|
["docs", "Workflow Policy Commands:"],
|
|
7633
8154
|
["detect", "Workflow Policy Commands:"],
|
|
7634
8155
|
["github", "Workflow Policy Commands:"],
|
|
8156
|
+
["workflow-stage", "Workflow Policy Commands:"],
|
|
7635
8157
|
["integrations", "Codex Integration Commands:"],
|
|
7636
8158
|
["commit-audit", "Codex Integration Commands:"],
|
|
7637
8159
|
["workflow-audit", "Codex Integration Commands:"]
|
|
@@ -7658,6 +8180,7 @@ configCommand(program);
|
|
|
7658
8180
|
githubCommand(program);
|
|
7659
8181
|
docsCommand(program);
|
|
7660
8182
|
detectCommand(program);
|
|
8183
|
+
workflowStageCommand(program);
|
|
7661
8184
|
integrationsCommand(program);
|
|
7662
8185
|
workflowAuditCommand(program);
|
|
7663
8186
|
commitAuditCommand(program);
|