lee-spec-kit 0.8.4 → 0.8.6
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/index.js +64 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1299,7 +1299,7 @@ Before taking the next workflow step:
|
|
|
1299
1299
|
9. In standalone mode, use the project repo through its managed feature worktree under the shared workspace \`.worktrees/\` root instead of checking the feature branch out in the main project repo
|
|
1300
1300
|
10. In standalone mode, do not hand-write \`git worktree add\`; run the exact \`nextAction.command\` from \`workflow-stage\` so the managed workspace path, stale directory cleanup, and \`.env\` / \`.env.*\` copy step stay consistent
|
|
1301
1301
|
11. Keep docs and code synchronized; if code changes materially, update the active feature docs in the same turn before stopping
|
|
1302
|
-
12. When docs are synced to code,
|
|
1302
|
+
12. When docs are synced to code, keep exactly one explicit marker like \`<!-- lee-spec-kit:workflow-sync 2026-04-16T12:34:56.789Z -->\` in a single active feature doc (prefer \`tasks.md\` or \`decisions.md\`): replace an existing marker timestamp or remove duplicates instead of appending another marker, so \`workflow-audit\` can prove the sync happened after the latest code change
|
|
1303
1303
|
|
|
1304
1304
|
Approval and remote actions:
|
|
1305
1305
|
|
|
@@ -1314,7 +1314,7 @@ Approval and remote actions:
|
|
|
1314
1314
|
Validation:
|
|
1315
1315
|
|
|
1316
1316
|
- Prefer \`npx lee-spec-kit commit-audit --json\` for commit-time staged docs path validation
|
|
1317
|
-
- Prefer \`npx lee-spec-kit workflow-audit --json\` as the default docs-sync validator for Codex hooks and end-of-turn checks; it expects the active feature docs to carry
|
|
1317
|
+
- Prefer \`npx lee-spec-kit workflow-audit --json\` as the default docs-sync validator for Codex hooks and end-of-turn checks; it expects the active feature docs to carry one fresh \`lee-spec-kit:workflow-sync\` marker after meaningful code/doc sync
|
|
1318
1318
|
`;
|
|
1319
1319
|
function renderManagedSegment(lang, docsRepo) {
|
|
1320
1320
|
return `${LEE_SPEC_KIT_AGENTS_BEGIN}
|
|
@@ -5433,10 +5433,10 @@ function resolvePrClosingIssueNumber(tasksContent, featureIssueNumber, lang) {
|
|
|
5433
5433
|
return issueNumber;
|
|
5434
5434
|
}
|
|
5435
5435
|
function assertRemoteIssueExists(issueNumber, cwd, lang) {
|
|
5436
|
-
if (!issueNumber) return;
|
|
5436
|
+
if (!issueNumber) return null;
|
|
5437
5437
|
const result = runProcess(
|
|
5438
5438
|
"gh",
|
|
5439
|
-
["issue", "view", issueNumber, "--json", "number,state"],
|
|
5439
|
+
["issue", "view", issueNumber, "--json", "number,state,title"],
|
|
5440
5440
|
cwd
|
|
5441
5441
|
);
|
|
5442
5442
|
if (result.code !== 0) {
|
|
@@ -5473,6 +5473,7 @@ function assertRemoteIssueExists(issueNumber, cwd, lang) {
|
|
|
5473
5473
|
})
|
|
5474
5474
|
);
|
|
5475
5475
|
}
|
|
5476
|
+
return payload;
|
|
5476
5477
|
}
|
|
5477
5478
|
function getRequiredIssueSections(lang) {
|
|
5478
5479
|
return getGithubDraftRequiredSections("issue", lang);
|
|
@@ -6227,16 +6228,18 @@ function githubCommand(program2) {
|
|
|
6227
6228
|
feature.issueNumber ? String(feature.issueNumber) : void 0,
|
|
6228
6229
|
config.lang
|
|
6229
6230
|
);
|
|
6230
|
-
|
|
6231
|
-
assertRemoteIssueExists(
|
|
6231
|
+
const remoteIssue = assertRemoteIssueExists(
|
|
6232
6232
|
closingIssueNumber,
|
|
6233
6233
|
projectGitCwd,
|
|
6234
6234
|
config.lang
|
|
6235
6235
|
);
|
|
6236
|
-
|
|
6236
|
+
const linkedIssueTitle = remoteIssue?.title?.trim() || "";
|
|
6237
|
+
const issueLinkedDefaultTitle = linkedIssueTitle || defaultTitle;
|
|
6238
|
+
title = closingIssueNumber && closingIssueNumber.trim() ? issueLinkedDefaultTitle : requestedTitle || defaultTitle;
|
|
6239
|
+
if (closingIssueNumber && options.title?.trim() && options.title.trim() !== issueLinkedDefaultTitle) {
|
|
6237
6240
|
throw createCliError(
|
|
6238
6241
|
"PRECONDITION_FAILED",
|
|
6239
|
-
`PR title must
|
|
6242
|
+
`PR title must match the linked issue title: "${issueLinkedDefaultTitle}".`
|
|
6240
6243
|
);
|
|
6241
6244
|
}
|
|
6242
6245
|
const normalizedBody = ensureIssueClosingLine(
|
|
@@ -7887,7 +7890,7 @@ function buildPostMergeCleanupCommand(state) {
|
|
|
7887
7890
|
}
|
|
7888
7891
|
if (state.worktreePath) {
|
|
7889
7892
|
commandParts.push(
|
|
7890
|
-
`if [ -d "${state.worktreePath}" ]; then git -C "${state.projectRootGitCwd}" worktree remove "${state.worktreePath}"; fi`
|
|
7893
|
+
`if [ -d "${state.worktreePath}" ]; then if git -C "${state.worktreePath}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then meaningful_changes=$(git -C "${state.worktreePath}" status --porcelain --untracked-files=normal 2>/dev/null || true); if [ -n "$meaningful_changes" ]; then printf '%s\\n' "Managed worktree has tracked or meaningful untracked changes; refusing cleanup: ${state.worktreePath}" >&2; exit 1; fi; git -C "${state.projectRootGitCwd}" worktree remove --force "${state.worktreePath}" || { git -C "${state.projectRootGitCwd}" worktree prune; rm -rf "${state.worktreePath}"; }; else leftover_meaningful=$(find "${state.worktreePath}" -mindepth 1 \\( -name ".next" -o -name "node_modules" -o -name "storybook-static" -o -name "dist" -o -name "build" -o -name "coverage" -o -name ".turbo" -o -name ".cache" \\) -prune -o -print -quit); if [ -n "$leftover_meaningful" ]; then printf '%s\\n' "Managed worktree leftover has files outside generated artifact directories; refusing cleanup: ${state.worktreePath}" >&2; exit 1; fi; git -C "${state.projectRootGitCwd}" worktree prune; rm -rf "${state.worktreePath}"; fi; fi`
|
|
7891
7894
|
);
|
|
7892
7895
|
}
|
|
7893
7896
|
if (state.headBranch) {
|
|
@@ -7896,7 +7899,7 @@ function buildPostMergeCleanupCommand(state) {
|
|
|
7896
7899
|
);
|
|
7897
7900
|
if (state.hasOriginRemote) {
|
|
7898
7901
|
commandParts.push(
|
|
7899
|
-
`if git -C "${state.projectRootGitCwd}" show-ref --verify --quiet "refs/remotes/origin/${state.headBranch}"; then git -C "${state.projectRootGitCwd}" push origin --delete "${state.headBranch}"; fi`
|
|
7902
|
+
`if git -C "${state.projectRootGitCwd}" show-ref --verify --quiet "refs/remotes/origin/${state.headBranch}"; then HUSKY=0 git -C "${state.projectRootGitCwd}" push origin --delete "${state.headBranch}"; fi`
|
|
7900
7903
|
);
|
|
7901
7904
|
commandParts.push(
|
|
7902
7905
|
`git -C "${state.projectRootGitCwd}" fetch --prune origin`
|
|
@@ -8100,14 +8103,15 @@ function resolveCurrentReviewState(tasks, prDraft, remoteReviewState) {
|
|
|
8100
8103
|
}
|
|
8101
8104
|
return "unknown";
|
|
8102
8105
|
}
|
|
8103
|
-
function buildCodeReviewActionOptions(reviewState) {
|
|
8106
|
+
function buildCodeReviewActionOptions(reviewState, reviewSyncCommand = null) {
|
|
8104
8107
|
if (reviewState === "merged") {
|
|
8105
8108
|
return [
|
|
8106
8109
|
buildStageOption(
|
|
8107
8110
|
"A",
|
|
8108
8111
|
"A",
|
|
8109
8112
|
"review_sync_approved",
|
|
8110
|
-
"Sync the already-merged PR state into tasks.md and pr.md before closing the feature."
|
|
8113
|
+
"Sync the already-merged PR state into tasks.md and pr.md before closing the feature.",
|
|
8114
|
+
reviewSyncCommand
|
|
8111
8115
|
),
|
|
8112
8116
|
buildStageOption(
|
|
8113
8117
|
"B",
|
|
@@ -8123,7 +8127,8 @@ function buildCodeReviewActionOptions(reviewState) {
|
|
|
8123
8127
|
"A",
|
|
8124
8128
|
"A",
|
|
8125
8129
|
"review_sync_approved",
|
|
8126
|
-
"Sync the approved PR review state into tasks.md and pr.md, then continue to the merge gate."
|
|
8130
|
+
"Sync the approved PR review state into tasks.md and pr.md, then continue to the merge gate.",
|
|
8131
|
+
reviewSyncCommand
|
|
8127
8132
|
),
|
|
8128
8133
|
buildStageOption(
|
|
8129
8134
|
"B",
|
|
@@ -8672,7 +8677,8 @@ Task commit boundary warning: ${describeTaskCommitGateFailure(committedTaskGate)
|
|
|
8672
8677
|
if (requirements.requireReview && (!reviewApprovedInDocs || currentReviewState !== "approved")) {
|
|
8673
8678
|
const reviewFixAllowed = currentReviewState === "changes_requested";
|
|
8674
8679
|
const reviewApprovalRequired = !reviewFixAllowed;
|
|
8675
|
-
const
|
|
8680
|
+
const reviewSyncCommand = currentReviewState === "merged" ? `npx lee-spec-kit github pr ${buildFeatureArgs(feature)} --merge --confirm OK` : null;
|
|
8681
|
+
const reviewActionOptions = reviewApprovalRequired ? buildCodeReviewActionOptions(currentReviewState, reviewSyncCommand) : void 0;
|
|
8676
8682
|
const reviewSummary = currentReviewState === "approved" ? "Record the approved PR review state in tasks.md and pr.md before proceeding to merge." : currentReviewState === "merged" ? "Sync the already-merged PR state into tasks.md and pr.md before marking the workflow as complete." : currentReviewState === "changes_requested" ? "Address the requested review changes and update the PR review evidence/decision before continuing." : currentReviewState === "review_pending_latest_commit" ? "Wait for a fresh review on the latest PR commit before taking the next review action." : currentReviewState === "review_rate_limited" ? "Wait for the current CodeRabbit review rate limit to clear, then re-check the latest PR review state before continuing." : currentReviewState === "draft" ? "Resolve the draft PR state before continuing to the merge boundary." : currentReviewState === "merge_blocked" ? "Resolve the current PR merge blocker before continuing to merge." : "Wait for PR review or inspect the current review state before taking the next review action.";
|
|
8677
8683
|
return {
|
|
8678
8684
|
status: "ok",
|
|
@@ -8683,7 +8689,8 @@ Task commit boundary warning: ${describeTaskCommitGateFailure(committedTaskGate)
|
|
|
8683
8689
|
nextAction: buildAction(
|
|
8684
8690
|
"code_review",
|
|
8685
8691
|
reviewSummary,
|
|
8686
|
-
reviewApprovalRequired
|
|
8692
|
+
reviewApprovalRequired,
|
|
8693
|
+
reviewSyncCommand
|
|
8687
8694
|
),
|
|
8688
8695
|
approvalRequired: reviewApprovalRequired,
|
|
8689
8696
|
implementationAllowed: reviewFixAllowed,
|
|
@@ -9105,7 +9112,20 @@ async function collectWorkflowAudit(cwd) {
|
|
|
9105
9112
|
const allMeaningfulFeatureDocPaths = meaningfulChangedFeatureDocPaths;
|
|
9106
9113
|
const latestCodeChangeAt = await getLatestMtimeIso(combinedChangedCodePaths);
|
|
9107
9114
|
const latestFeatureDocSyncAt = await getLatestWorkflowSyncMarkerAt(activeFeature);
|
|
9115
|
+
const duplicateWorkflowSyncMarkerPaths = await collectDuplicateWorkflowSyncMarkerPaths(activeFeature);
|
|
9108
9116
|
if (combinedChangedCodePaths.length === 0) {
|
|
9117
|
+
if (duplicateWorkflowSyncMarkerPaths.length > 0) {
|
|
9118
|
+
return {
|
|
9119
|
+
status: "needs_sync",
|
|
9120
|
+
reasonCode: "DUPLICATE_WORKFLOW_SYNC_MARKERS",
|
|
9121
|
+
docsDir: config.docsDir,
|
|
9122
|
+
activeFeatureRef,
|
|
9123
|
+
changedCodePaths: [],
|
|
9124
|
+
changedFeatureDocPaths: duplicateWorkflowSyncMarkerPaths,
|
|
9125
|
+
latestCodeChangeAt: null,
|
|
9126
|
+
latestFeatureDocSyncAt
|
|
9127
|
+
};
|
|
9128
|
+
}
|
|
9109
9129
|
return {
|
|
9110
9130
|
status: "ok",
|
|
9111
9131
|
reasonCode: "WORKFLOW_IN_SYNC",
|
|
@@ -9117,6 +9137,18 @@ async function collectWorkflowAudit(cwd) {
|
|
|
9117
9137
|
latestFeatureDocSyncAt
|
|
9118
9138
|
};
|
|
9119
9139
|
}
|
|
9140
|
+
if (duplicateWorkflowSyncMarkerPaths.length > 0) {
|
|
9141
|
+
return {
|
|
9142
|
+
status: "needs_sync",
|
|
9143
|
+
reasonCode: "DUPLICATE_WORKFLOW_SYNC_MARKERS",
|
|
9144
|
+
docsDir: config.docsDir,
|
|
9145
|
+
activeFeatureRef,
|
|
9146
|
+
changedCodePaths: combinedChangedCodePaths.map((item) => item.relativeToRepo),
|
|
9147
|
+
changedFeatureDocPaths: duplicateWorkflowSyncMarkerPaths,
|
|
9148
|
+
latestCodeChangeAt,
|
|
9149
|
+
latestFeatureDocSyncAt
|
|
9150
|
+
};
|
|
9151
|
+
}
|
|
9120
9152
|
if (!activeFeatureRef) {
|
|
9121
9153
|
return {
|
|
9122
9154
|
status: "needs_sync",
|
|
@@ -9242,6 +9274,23 @@ async function getLatestWorkflowSyncMarkerAt(activeFeature) {
|
|
|
9242
9274
|
}
|
|
9243
9275
|
return latest > 0 ? new Date(latest).toISOString() : null;
|
|
9244
9276
|
}
|
|
9277
|
+
async function collectDuplicateWorkflowSyncMarkerPaths(activeFeature) {
|
|
9278
|
+
if (!activeFeature) return [];
|
|
9279
|
+
const canonicalFiles = ["spec.md", "plan.md", "tasks.md", "decisions.md", "issue.md", "pr.md"];
|
|
9280
|
+
const markerPaths = [];
|
|
9281
|
+
let markerCount = 0;
|
|
9282
|
+
for (const fileName of canonicalFiles) {
|
|
9283
|
+
const absolutePath = path8.join(activeFeature.path, fileName);
|
|
9284
|
+
if (!await fs.pathExists(absolutePath)) continue;
|
|
9285
|
+
const content = await fs.readFile(absolutePath, "utf-8");
|
|
9286
|
+
const matches = [...content.matchAll(WORKFLOW_SYNC_MARKER_PATTERN)];
|
|
9287
|
+
if (matches.length > 0) {
|
|
9288
|
+
markerCount += matches.length;
|
|
9289
|
+
markerPaths.push(normalizeSlashes2(path8.relative(activeFeature.path, absolutePath)));
|
|
9290
|
+
}
|
|
9291
|
+
}
|
|
9292
|
+
return markerCount > 1 ? markerPaths : [];
|
|
9293
|
+
}
|
|
9245
9294
|
function extractWorkflowSyncMarkerTimes(content, nowMs, fileMtimeMs) {
|
|
9246
9295
|
const values = [];
|
|
9247
9296
|
for (const match of content.matchAll(WORKFLOW_SYNC_MARKER_PATTERN)) {
|