rafcode 3.0.0 → 3.8.0
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/.claude/settings.local.json +3 -1
- package/CLAUDE.md +0 -1
- package/RAF/38-dual-wielder/decisions.md +9 -0
- package/RAF/38-dual-wielder/input.md +6 -1
- package/RAF/38-dual-wielder/outcomes/8-e2e-test-codex-provider.md +139 -0
- package/RAF/38-dual-wielder/plans/8-e2e-test-codex-provider.md +95 -0
- package/RAF/39-pathless-rover/decisions.md +16 -0
- package/RAF/39-pathless-rover/input.md +2 -0
- package/RAF/39-pathless-rover/outcomes/1-fix-codex-stream-renderer.md +21 -0
- package/RAF/39-pathless-rover/outcomes/2-wire-provider-flag.md +28 -0
- package/RAF/39-pathless-rover/outcomes/3-remove-worktree-flag-do.md +41 -0
- package/RAF/39-pathless-rover/outcomes/4-remove-worktree-flag-plan-amend.md +30 -0
- package/RAF/39-pathless-rover/outcomes/5-update-prompts-and-docs.md +26 -0
- package/RAF/39-pathless-rover/plans/1-fix-codex-stream-renderer.md +43 -0
- package/RAF/39-pathless-rover/plans/2-wire-provider-flag.md +48 -0
- package/RAF/39-pathless-rover/plans/3-remove-worktree-flag-do.md +41 -0
- package/RAF/39-pathless-rover/plans/4-remove-worktree-flag-plan-amend.md +43 -0
- package/RAF/39-pathless-rover/plans/5-update-prompts-and-docs.md +31 -0
- package/RAF/40-numeric-order-fix/decisions.md +7 -0
- package/RAF/40-numeric-order-fix/input.md +19 -0
- package/RAF/40-numeric-order-fix/outcomes/1-fix-numeric-sort-order.md +18 -0
- package/RAF/40-numeric-order-fix/outcomes/2-add-npm-keywords.md +10 -0
- package/RAF/40-numeric-order-fix/plans/1-fix-numeric-sort-order.md +48 -0
- package/RAF/40-numeric-order-fix/plans/2-add-npm-keywords.md +23 -0
- package/RAF/41-echo-chamber/decisions.md +13 -0
- package/RAF/41-echo-chamber/input.md +4 -0
- package/RAF/41-echo-chamber/outcomes/1-update-codex-model-defaults.md +24 -0
- package/RAF/41-echo-chamber/outcomes/2-e2e-test-codex-provider.md +74 -0
- package/RAF/41-echo-chamber/plans/1-update-codex-model-defaults.md +28 -0
- package/RAF/41-echo-chamber/plans/2-e2e-test-codex-provider.md +103 -0
- package/RAF/42-patch-parade/decisions.md +29 -0
- package/RAF/42-patch-parade/input.md +9 -0
- package/RAF/42-patch-parade/outcomes/1-fix-codex-model-resolution.md +36 -0
- package/RAF/42-patch-parade/outcomes/2-fix-provider-aware-name-generation.md +31 -0
- package/RAF/42-patch-parade/outcomes/3-fix-codex-error-event-rendering.md +32 -0
- package/RAF/42-patch-parade/outcomes/4-update-cli-help-docs.md +28 -0
- package/RAF/42-patch-parade/outcomes/5-update-default-codex-models-to-gpt-5-4.md +33 -0
- package/RAF/42-patch-parade/outcomes/6-unify-model-config-schema.md +89 -0
- package/RAF/42-patch-parade/plans/1-fix-codex-model-resolution.md +35 -0
- package/RAF/42-patch-parade/plans/2-fix-provider-aware-name-generation.md +38 -0
- package/RAF/42-patch-parade/plans/3-fix-codex-error-event-rendering.md +32 -0
- package/RAF/42-patch-parade/plans/4-update-cli-help-docs.md +31 -0
- package/RAF/42-patch-parade/plans/5-update-default-codex-models-to-gpt-5-4.md +35 -0
- package/RAF/42-patch-parade/plans/6-unify-model-config-schema.md +46 -0
- package/RAF/43-swiss-army/decisions.md +34 -0
- package/RAF/43-swiss-army/input.md +7 -0
- package/RAF/43-swiss-army/outcomes/1-fix-model-validation.md +21 -0
- package/RAF/43-swiss-army/outcomes/2-update-commit-format.md +31 -0
- package/RAF/43-swiss-army/outcomes/3-wire-reasoning-effort.md +28 -0
- package/RAF/43-swiss-army/outcomes/4-remove-provider-flag.md +27 -0
- package/RAF/43-swiss-army/outcomes/5-config-wizard-validation.md +23 -0
- package/RAF/43-swiss-army/outcomes/6-add-fast-mode.md +32 -0
- package/RAF/43-swiss-army/outcomes/7-config-preset.md +31 -0
- package/RAF/43-swiss-army/plans/1-fix-model-validation.md +38 -0
- package/RAF/43-swiss-army/plans/2-update-commit-format.md +46 -0
- package/RAF/43-swiss-army/plans/3-wire-reasoning-effort.md +39 -0
- package/RAF/43-swiss-army/plans/4-remove-provider-flag.md +43 -0
- package/RAF/43-swiss-army/plans/5-config-wizard-validation.md +42 -0
- package/RAF/43-swiss-army/plans/6-add-fast-mode.md +46 -0
- package/RAF/43-swiss-army/plans/7-config-preset.md +51 -0
- package/RAF/44-config-api-change/decisions.md +22 -0
- package/RAF/44-config-api-change/input.md +5 -0
- package/RAF/44-config-api-change/outcomes/1-restructure-config-subcommands.md +19 -0
- package/RAF/44-config-api-change/outcomes/2-move-preset-under-config.md +17 -0
- package/RAF/44-config-api-change/outcomes/3-update-existing-tests-for-config-api.md +14 -0
- package/RAF/44-config-api-change/outcomes/4-update-config-command-docs.md +11 -0
- package/RAF/44-config-api-change/outcomes/5-fix-codex-name-generation.md +18 -0
- package/RAF/44-config-api-change/plans/1-restructure-config-subcommands.md +37 -0
- package/RAF/44-config-api-change/plans/2-move-preset-under-config.md +38 -0
- package/RAF/44-config-api-change/plans/3-update-existing-tests-for-config-api.md +38 -0
- package/RAF/44-config-api-change/plans/4-update-config-command-docs.md +36 -0
- package/RAF/44-config-api-change/plans/5-fix-codex-name-generation.md +49 -0
- package/RAF/45-signal-cairn/decisions.md +7 -0
- package/RAF/45-signal-cairn/input.md +2 -0
- package/RAF/45-signal-cairn/outcomes/1-rename-provider-to-harness.md +19 -0
- package/RAF/45-signal-cairn/outcomes/2-normalize-model-display-names.md +18 -0
- package/RAF/45-signal-cairn/plans/1-rename-provider-to-harness.md +40 -0
- package/RAF/45-signal-cairn/plans/2-normalize-model-display-names.md +41 -0
- package/RAF/45-signal-lantern/decisions.md +10 -0
- package/RAF/45-signal-lantern/input.md +2 -0
- package/RAF/45-signal-lantern/outcomes/1-add-effort-and-fast-to-do-model-display.md +15 -0
- package/RAF/45-signal-lantern/outcomes/2-capture-codex-post-run-token-usage.md +15 -0
- package/RAF/45-signal-lantern/outcomes/3-show-codex-token-summaries-without-fake-cost.md +14 -0
- package/RAF/45-signal-lantern/plans/1-add-effort-and-fast-to-do-model-display.md +38 -0
- package/RAF/45-signal-lantern/plans/2-capture-codex-post-run-token-usage.md +37 -0
- package/RAF/45-signal-lantern/plans/3-show-codex-token-summaries-without-fake-cost.md +40 -0
- package/RAF/46-lantern-arc/decisions.md +19 -0
- package/RAF/46-lantern-arc/input.md +6 -0
- package/RAF/46-lantern-arc/outcomes/1-remove-spark-alias.md +16 -0
- package/RAF/46-lantern-arc/outcomes/2-clean-up-worktree-plan-command.md +30 -0
- package/RAF/46-lantern-arc/outcomes/3-fix-token-usage-accumulation.md +32 -0
- package/RAF/46-lantern-arc/outcomes/4-display-effort-in-compact-mode.md +22 -0
- package/RAF/46-lantern-arc/outcomes/5-codex-fast-mode-research.md +38 -0
- package/RAF/46-lantern-arc/outcomes/6-optimize-llm-prompts.md +39 -0
- package/RAF/46-lantern-arc/plans/1-remove-spark-alias.md +38 -0
- package/RAF/46-lantern-arc/plans/2-clean-up-worktree-plan-command.md +33 -0
- package/RAF/46-lantern-arc/plans/3-fix-token-usage-accumulation.md +33 -0
- package/RAF/46-lantern-arc/plans/4-display-effort-in-compact-mode.md +28 -0
- package/RAF/46-lantern-arc/plans/5-codex-fast-mode-research.md +34 -0
- package/RAF/46-lantern-arc/plans/6-optimize-llm-prompts.md +48 -0
- package/RAF/47-signal-trim/decisions.md +13 -0
- package/RAF/47-signal-trim/input.md +2 -0
- package/RAF/47-signal-trim/plans/1-remove-cache-from-status.md +73 -0
- package/README.md +50 -63
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +47 -49
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/do.d.ts +2 -0
- package/dist/commands/do.d.ts.map +1 -1
- package/dist/commands/do.js +91 -230
- package/dist/commands/do.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +54 -259
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/preset.d.ts +3 -0
- package/dist/commands/preset.d.ts.map +1 -0
- package/dist/commands/preset.js +158 -0
- package/dist/commands/preset.js.map +1 -0
- package/dist/core/claude-runner.d.ts +2 -0
- package/dist/core/claude-runner.d.ts.map +1 -1
- package/dist/core/claude-runner.js +36 -12
- package/dist/core/claude-runner.js.map +1 -1
- package/dist/core/codex-runner.d.ts +1 -0
- package/dist/core/codex-runner.d.ts.map +1 -1
- package/dist/core/codex-runner.js +26 -7
- package/dist/core/codex-runner.js.map +1 -1
- package/dist/core/failure-analyzer.js +2 -1
- package/dist/core/failure-analyzer.js.map +1 -1
- package/dist/core/git.d.ts +2 -2
- package/dist/core/git.d.ts.map +1 -1
- package/dist/core/git.js +53 -3
- package/dist/core/git.js.map +1 -1
- package/dist/core/project-manager.d.ts.map +1 -1
- package/dist/core/project-manager.js +2 -2
- package/dist/core/project-manager.js.map +1 -1
- package/dist/core/pull-request.js +5 -5
- package/dist/core/pull-request.js.map +1 -1
- package/dist/core/runner-factory.d.ts +4 -4
- package/dist/core/runner-factory.d.ts.map +1 -1
- package/dist/core/runner-factory.js +8 -8
- package/dist/core/runner-factory.js.map +1 -1
- package/dist/core/runner-interface.d.ts +1 -1
- package/dist/core/runner-types.d.ts +17 -4
- package/dist/core/runner-types.d.ts.map +1 -1
- package/dist/core/state-derivation.js +3 -3
- package/dist/core/state-derivation.js.map +1 -1
- package/dist/parsers/codex-stream-renderer.d.ts +28 -4
- package/dist/parsers/codex-stream-renderer.d.ts.map +1 -1
- package/dist/parsers/codex-stream-renderer.js +110 -0
- package/dist/parsers/codex-stream-renderer.js.map +1 -1
- package/dist/prompts/amend.d.ts +0 -1
- package/dist/prompts/amend.d.ts.map +1 -1
- package/dist/prompts/amend.js +31 -104
- package/dist/prompts/amend.js.map +1 -1
- package/dist/prompts/execution.d.ts.map +1 -1
- package/dist/prompts/execution.js +17 -34
- package/dist/prompts/execution.js.map +1 -1
- package/dist/prompts/planning.d.ts.map +1 -1
- package/dist/prompts/planning.js +23 -123
- package/dist/prompts/planning.js.map +1 -1
- package/dist/types/config.d.ts +33 -32
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +14 -28
- package/dist/types/config.js.map +1 -1
- package/dist/utils/config.d.ts +36 -16
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +209 -104
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/name-generator.d.ts.map +1 -1
- package/dist/utils/name-generator.js +25 -12
- package/dist/utils/name-generator.js.map +1 -1
- package/dist/utils/paths.d.ts +5 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +9 -0
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/terminal-symbols.d.ts +15 -2
- package/dist/utils/terminal-symbols.d.ts.map +1 -1
- package/dist/utils/terminal-symbols.js +36 -4
- package/dist/utils/terminal-symbols.js.map +1 -1
- package/dist/utils/token-tracker.d.ts +6 -1
- package/dist/utils/token-tracker.d.ts.map +1 -1
- package/dist/utils/token-tracker.js +84 -51
- package/dist/utils/token-tracker.js.map +1 -1
- package/dist/utils/validation.d.ts +1 -2
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +4 -25
- package/dist/utils/validation.js.map +1 -1
- package/package.json +7 -2
- package/src/commands/config.ts +60 -63
- package/src/commands/do.ts +96 -262
- package/src/commands/plan.ts +55 -279
- package/src/commands/preset.ts +186 -0
- package/src/core/claude-runner.ts +45 -5
- package/src/core/codex-runner.ts +32 -7
- package/src/core/failure-analyzer.ts +2 -1
- package/src/core/git.ts +57 -3
- package/src/core/project-manager.ts +2 -1
- package/src/core/pull-request.ts +5 -5
- package/src/core/runner-factory.ts +9 -9
- package/src/core/runner-interface.ts +1 -1
- package/src/core/runner-types.ts +17 -4
- package/src/core/state-derivation.ts +3 -3
- package/src/parsers/codex-stream-renderer.ts +149 -4
- package/src/prompts/amend.ts +30 -105
- package/src/prompts/config-docs.md +206 -62
- package/src/prompts/execution.ts +17 -34
- package/src/prompts/planning.ts +23 -124
- package/src/types/config.ts +47 -59
- package/src/utils/config.ts +248 -115
- package/src/utils/name-generator.ts +29 -13
- package/src/utils/paths.ts +10 -0
- package/src/utils/terminal-symbols.ts +46 -6
- package/src/utils/token-tracker.ts +96 -57
- package/src/utils/validation.ts +5 -30
- package/tests/unit/amend-prompt.test.ts +3 -2
- package/tests/unit/claude-runner-interactive.test.ts +21 -3
- package/tests/unit/claude-runner.test.ts +39 -0
- package/tests/unit/codex-runner.test.ts +163 -0
- package/tests/unit/codex-stream-renderer.test.ts +127 -0
- package/tests/unit/command-output.test.ts +57 -0
- package/tests/unit/commit-planning-artifacts-worktree.test.ts +24 -7
- package/tests/unit/commit-planning-artifacts.test.ts +26 -4
- package/tests/unit/config-command.test.ts +215 -303
- package/tests/unit/config.test.ts +319 -235
- package/tests/unit/dependency-integration.test.ts +27 -1
- package/tests/unit/do-model-display.test.ts +35 -0
- package/tests/unit/execution-prompt.test.ts +49 -19
- package/tests/unit/name-generator.test.ts +82 -12
- package/tests/unit/plan-command-auto-flag.test.ts +7 -10
- package/tests/unit/plan-command.test.ts +14 -17
- package/tests/unit/planning-prompt.test.ts +9 -8
- package/tests/unit/terminal-symbols.test.ts +94 -3
- package/tests/unit/token-tracker.test.ts +180 -1
- package/tests/unit/validation.test.ts +9 -41
- package/tests/unit/worktree-flag-override.test.ts +0 -186
package/src/commands/plan.ts
CHANGED
|
@@ -13,27 +13,23 @@ import {
|
|
|
13
13
|
validateEnvironment,
|
|
14
14
|
reportValidation,
|
|
15
15
|
validateProjectName,
|
|
16
|
-
resolveModelOption,
|
|
17
16
|
} from '../utils/validation.js';
|
|
18
17
|
import { logger } from '../utils/logger.js';
|
|
19
|
-
import {
|
|
18
|
+
import { formatModelDisplay, getModel } from '../utils/config.js';
|
|
19
|
+
import type { ModelEntry } from '../types/config.js';
|
|
20
20
|
import { generateProjectNames } from '../utils/name-generator.js';
|
|
21
21
|
import { pickProjectName } from '../ui/name-picker.js';
|
|
22
22
|
import {
|
|
23
23
|
getPlansDir,
|
|
24
24
|
getRafDir,
|
|
25
|
-
getNextProjectNumber,
|
|
26
|
-
formatProjectNumber,
|
|
27
25
|
resolveProjectIdentifierWithDetails,
|
|
28
26
|
getInputPath,
|
|
29
|
-
getDecisionsPath,
|
|
30
|
-
getOutcomesDir,
|
|
31
27
|
extractTaskNameFromPlanFile,
|
|
32
28
|
decodeTaskId,
|
|
33
29
|
encodeTaskId,
|
|
34
30
|
TASK_ID_PATTERN,
|
|
31
|
+
numericFileSort,
|
|
35
32
|
} from '../utils/paths.js';
|
|
36
|
-
import { sanitizeProjectName } from '../utils/validation.js';
|
|
37
33
|
import {
|
|
38
34
|
deriveProjectState,
|
|
39
35
|
isProjectComplete,
|
|
@@ -42,24 +38,14 @@ import {
|
|
|
42
38
|
import {
|
|
43
39
|
getRepoBasename,
|
|
44
40
|
getRepoRoot,
|
|
45
|
-
createWorktree,
|
|
46
|
-
createWorktreeFromBranch,
|
|
47
|
-
branchExists,
|
|
48
41
|
validateWorktree,
|
|
49
|
-
removeWorktree,
|
|
50
|
-
computeWorktreeBaseDir,
|
|
51
|
-
pullMainBranch,
|
|
52
42
|
resolveWorktreeProjectByIdentifier,
|
|
53
43
|
} from '../core/worktree.js';
|
|
54
44
|
|
|
55
45
|
interface PlanCommandOptions {
|
|
56
46
|
amend?: boolean;
|
|
57
|
-
model?: string;
|
|
58
|
-
sonnet?: boolean;
|
|
59
47
|
auto?: boolean;
|
|
60
|
-
worktree?: boolean;
|
|
61
48
|
resume?: string;
|
|
62
|
-
provider?: string;
|
|
63
49
|
}
|
|
64
50
|
|
|
65
51
|
export function createPlanCommand(): Command {
|
|
@@ -70,28 +56,15 @@ export function createPlanCommand(): Command {
|
|
|
70
56
|
'-a, --amend',
|
|
71
57
|
'Add tasks to an existing project (requires project identifier as argument)'
|
|
72
58
|
)
|
|
73
|
-
.option('-m, --model <name>', 'Model to use (sonnet, haiku, opus)')
|
|
74
|
-
.option('--sonnet', 'Use Sonnet model (shorthand for --model sonnet)')
|
|
75
59
|
.option('-y, --auto', 'Skip permission prompts for file operations')
|
|
76
|
-
.option('-w, --worktree', 'Create a git worktree for isolated planning')
|
|
77
|
-
.option('--no-worktree', 'Disable worktree mode (overrides config)')
|
|
78
60
|
.option('-r, --resume <identifier>', 'Resume a planning session for an existing project')
|
|
79
|
-
.option('-p, --provider <provider>', 'CLI provider to use (claude, codex)')
|
|
80
61
|
.action(async (projectName: string | undefined, options: PlanCommandOptions) => {
|
|
81
|
-
|
|
82
|
-
let model: string;
|
|
83
|
-
try {
|
|
84
|
-
model = resolveModelOption(options.model, options.sonnet, 'plan');
|
|
85
|
-
} catch (error) {
|
|
86
|
-
logger.error((error as Error).message);
|
|
87
|
-
process.exit(1);
|
|
88
|
-
}
|
|
62
|
+
const modelEntry = getModel('plan');
|
|
89
63
|
|
|
90
64
|
const autoMode = options.auto ?? false;
|
|
91
|
-
const worktreeMode = options.worktree ?? getWorktreeDefault();
|
|
92
65
|
|
|
93
66
|
if (options.resume) {
|
|
94
|
-
await runResumeCommand(options.resume,
|
|
67
|
+
await runResumeCommand(options.resume, modelEntry);
|
|
95
68
|
} else if (options.amend) {
|
|
96
69
|
if (!projectName) {
|
|
97
70
|
logger.error('--amend requires a project identifier');
|
|
@@ -99,16 +72,16 @@ export function createPlanCommand(): Command {
|
|
|
99
72
|
logger.error(' or: raf plan --amend <project>');
|
|
100
73
|
process.exit(1);
|
|
101
74
|
}
|
|
102
|
-
await runAmendCommand(projectName,
|
|
75
|
+
await runAmendCommand(projectName, modelEntry, autoMode);
|
|
103
76
|
} else {
|
|
104
|
-
await runPlanCommand(projectName,
|
|
77
|
+
await runPlanCommand(projectName, modelEntry, autoMode);
|
|
105
78
|
}
|
|
106
79
|
});
|
|
107
80
|
|
|
108
81
|
return command;
|
|
109
82
|
}
|
|
110
83
|
|
|
111
|
-
async function runPlanCommand(projectName?: string,
|
|
84
|
+
async function runPlanCommand(projectName?: string, modelEntry?: ModelEntry, autoMode: boolean = false): Promise<void> {
|
|
112
85
|
// Validate environment
|
|
113
86
|
const validation = validateEnvironment();
|
|
114
87
|
reportValidation(validation);
|
|
@@ -124,7 +97,6 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
|
|
|
124
97
|
const mainResult = resolveProjectIdentifierWithDetails(rafDir, projectName);
|
|
125
98
|
|
|
126
99
|
let existingFolder: string | null = null;
|
|
127
|
-
let existingWorktreeMode = false;
|
|
128
100
|
|
|
129
101
|
if (mainResult.path) {
|
|
130
102
|
existingFolder = path.basename(mainResult.path);
|
|
@@ -134,7 +106,6 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
|
|
|
134
106
|
const wtResult = resolveWorktreeProjectByIdentifier(repoBasename, projectName);
|
|
135
107
|
if (wtResult) {
|
|
136
108
|
existingFolder = wtResult.folder;
|
|
137
|
-
existingWorktreeMode = true;
|
|
138
109
|
}
|
|
139
110
|
}
|
|
140
111
|
}
|
|
@@ -153,7 +124,7 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
|
|
|
153
124
|
});
|
|
154
125
|
|
|
155
126
|
if (answer === 'amend') {
|
|
156
|
-
await runAmendCommand(existingFolder,
|
|
127
|
+
await runAmendCommand(existingFolder, modelEntry, autoMode);
|
|
157
128
|
return;
|
|
158
129
|
} else if (answer === 'cancel') {
|
|
159
130
|
logger.info('Aborted.');
|
|
@@ -163,15 +134,6 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
|
|
|
163
134
|
}
|
|
164
135
|
}
|
|
165
136
|
|
|
166
|
-
// Validate git repo for worktree mode
|
|
167
|
-
if (worktreeMode) {
|
|
168
|
-
const repoRoot = getRepoRoot();
|
|
169
|
-
if (!repoRoot) {
|
|
170
|
-
logger.error('--worktree requires a git repository');
|
|
171
|
-
process.exit(1);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
137
|
// Open editor for user input
|
|
176
138
|
logger.info('Opening editor for project description...');
|
|
177
139
|
logger.info('(Save and close the editor when done)');
|
|
@@ -199,7 +161,8 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
|
|
|
199
161
|
// Get or generate project name
|
|
200
162
|
let finalProjectName = projectName;
|
|
201
163
|
if (!finalProjectName) {
|
|
202
|
-
const
|
|
164
|
+
const nameEntry = getModel('nameGeneration');
|
|
165
|
+
const nameModel = formatModelDisplay(nameEntry.model);
|
|
203
166
|
logger.info(`Generating project name suggestions with ${nameModel}...`);
|
|
204
167
|
const suggestedNames = await generateProjectNames(cleanInput);
|
|
205
168
|
logger.newline();
|
|
@@ -219,103 +182,32 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
|
|
|
219
182
|
process.exit(1);
|
|
220
183
|
}
|
|
221
184
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if (worktreeMode) {
|
|
227
|
-
// Worktree mode: create worktree, then project folder inside it
|
|
228
|
-
const repoBasename = getRepoBasename()!;
|
|
229
|
-
const repoRoot = getRepoRoot()!;
|
|
230
|
-
const rafDir = getRafDir();
|
|
231
|
-
|
|
232
|
-
// Sync main branch before creating worktree (if enabled)
|
|
233
|
-
if (getSyncMainBranch()) {
|
|
234
|
-
const syncResult = pullMainBranch();
|
|
235
|
-
if (syncResult.success) {
|
|
236
|
-
if (syncResult.hadChanges) {
|
|
237
|
-
logger.info(`Synced ${syncResult.mainBranch} from remote`);
|
|
238
|
-
}
|
|
239
|
-
} else {
|
|
240
|
-
logger.warn(`Could not sync main branch: ${syncResult.error}`);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Compute project number from main repo's RAF directory (scan worktrees too)
|
|
245
|
-
const projectNumber = getNextProjectNumber(rafDir, repoBasename);
|
|
246
|
-
const sanitizedName = sanitizeProjectName(finalProjectName);
|
|
247
|
-
const folderName = `${formatProjectNumber(projectNumber)}-${sanitizedName}`;
|
|
248
|
-
|
|
249
|
-
// Create worktree
|
|
250
|
-
const result = createWorktree(repoBasename, folderName);
|
|
251
|
-
if (!result.success) {
|
|
252
|
-
logger.error(`Failed to create worktree: ${result.error}`);
|
|
253
|
-
process.exit(1);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
worktreePath = result.worktreePath;
|
|
257
|
-
worktreeBranch = result.branch;
|
|
185
|
+
// Standard mode: create project in main repo
|
|
186
|
+
const projectManager = new ProjectManager();
|
|
187
|
+
const projectPath = projectManager.createProject(finalProjectName);
|
|
258
188
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
projectPath = path.join(worktreePath, rafRelativePath, folderName);
|
|
262
|
-
|
|
263
|
-
// Create project folder structure inside the worktree
|
|
264
|
-
fs.mkdirSync(projectPath, { recursive: true });
|
|
265
|
-
fs.mkdirSync(getPlansDir(projectPath), { recursive: true });
|
|
266
|
-
fs.mkdirSync(getOutcomesDir(projectPath), { recursive: true });
|
|
267
|
-
fs.writeFileSync(getDecisionsPath(projectPath), '# Project Decisions\n');
|
|
268
|
-
|
|
269
|
-
// Save input inside worktree project folder
|
|
270
|
-
fs.writeFileSync(getInputPath(projectPath), userInput);
|
|
271
|
-
|
|
272
|
-
logger.success(`Created worktree: ${worktreePath}`);
|
|
273
|
-
logger.success(`Branch: ${worktreeBranch}`);
|
|
274
|
-
logger.success(`Project: ${projectPath}`);
|
|
275
|
-
logger.newline();
|
|
276
|
-
} else {
|
|
277
|
-
// Standard mode: create project in main repo
|
|
278
|
-
const projectManager = new ProjectManager();
|
|
279
|
-
projectPath = projectManager.createProject(finalProjectName);
|
|
280
|
-
|
|
281
|
-
logger.success(`Created project: ${projectPath}`);
|
|
282
|
-
logger.newline();
|
|
189
|
+
logger.success(`Created project: ${projectPath}`);
|
|
190
|
+
logger.newline();
|
|
283
191
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
192
|
+
// Save input
|
|
193
|
+
projectManager.saveInput(projectPath, userInput);
|
|
287
194
|
|
|
288
195
|
// Set up shutdown handler
|
|
289
|
-
const claudeRunner = createRunner({ model });
|
|
196
|
+
const claudeRunner = createRunner({ model: modelEntry?.model, harness: modelEntry?.harness, reasoningEffort: modelEntry?.reasoningEffort, fast: modelEntry?.fast });
|
|
290
197
|
shutdownHandler.init();
|
|
291
198
|
shutdownHandler.registerClaudeRunner(claudeRunner);
|
|
292
199
|
|
|
293
200
|
// Register cleanup callback
|
|
294
201
|
shutdownHandler.onShutdown(() => {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
const plansDir = getPlansDir(projectPath);
|
|
298
|
-
const hasPlanFiles = fs.existsSync(plansDir) &&
|
|
299
|
-
fs.readdirSync(plansDir).filter((f) => f.endsWith('.md')).length > 0;
|
|
300
|
-
if (!hasPlanFiles) {
|
|
301
|
-
const removal = removeWorktree(worktreePath);
|
|
302
|
-
if (removal.success) {
|
|
303
|
-
logger.debug(`Cleaned up worktree: ${worktreePath}`);
|
|
304
|
-
} else {
|
|
305
|
-
logger.warn(`Failed to clean up worktree: ${removal.error}`);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
} else {
|
|
309
|
-
const projectManager = new ProjectManager();
|
|
310
|
-
projectManager.cleanupEmptyProject(projectPath);
|
|
311
|
-
}
|
|
202
|
+
const projectManager = new ProjectManager();
|
|
203
|
+
projectManager.cleanupEmptyProject(projectPath);
|
|
312
204
|
});
|
|
313
205
|
|
|
314
206
|
// Run planning session
|
|
315
207
|
logger.info('Starting planning session...');
|
|
316
208
|
logger.info('The planner will interview you about each task.');
|
|
317
|
-
if (
|
|
318
|
-
logger.info(`Using model: ${model}`);
|
|
209
|
+
if (modelEntry) {
|
|
210
|
+
logger.info(`Using model: ${formatModelDisplay(modelEntry.model, modelEntry.harness, { includeHarness: true })}`);
|
|
319
211
|
}
|
|
320
212
|
if (autoMode) {
|
|
321
213
|
logger.warn('Auto mode enabled: permission prompts will be skipped.');
|
|
@@ -325,14 +217,11 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
|
|
|
325
217
|
const { systemPrompt, userMessage } = getPlanningPrompt({
|
|
326
218
|
projectPath,
|
|
327
219
|
inputContent: userInput,
|
|
328
|
-
worktreeMode,
|
|
329
220
|
});
|
|
330
221
|
|
|
331
222
|
try {
|
|
332
223
|
const exitCode = await claudeRunner.runInteractive(systemPrompt, userMessage, {
|
|
333
224
|
dangerouslySkipPermissions: autoMode,
|
|
334
|
-
// Run session in the worktree root if in worktree mode
|
|
335
|
-
cwd: worktreePath ?? undefined,
|
|
336
225
|
});
|
|
337
226
|
|
|
338
227
|
if (exitCode !== 0) {
|
|
@@ -342,7 +231,7 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
|
|
|
342
231
|
// Check for created plan files
|
|
343
232
|
const plansDir = getPlansDir(projectPath);
|
|
344
233
|
const planFiles = fs.existsSync(plansDir)
|
|
345
|
-
? fs.readdirSync(plansDir).filter((f) => f.endsWith('.md')).sort()
|
|
234
|
+
? fs.readdirSync(plansDir).filter((f) => f.endsWith('.md')).sort(numericFileSort)
|
|
346
235
|
: [];
|
|
347
236
|
|
|
348
237
|
if (planFiles.length === 0) {
|
|
@@ -359,45 +248,23 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
|
|
|
359
248
|
// Commit planning artifacts (input.md, decisions.md, and plan files)
|
|
360
249
|
const planAbsolutePaths = planFiles.map((f) => path.join(plansDir, f));
|
|
361
250
|
await commitPlanningArtifacts(projectPath, {
|
|
362
|
-
cwd: worktreePath ?? undefined,
|
|
363
251
|
additionalFiles: planAbsolutePaths,
|
|
364
252
|
});
|
|
365
253
|
|
|
366
254
|
logger.newline();
|
|
367
|
-
|
|
368
|
-
logger.info(`Worktree: ${worktreePath}`);
|
|
369
|
-
logger.info(`Branch: ${worktreeBranch}`);
|
|
370
|
-
logger.info(`Run 'raf do ${finalProjectName} --worktree' to execute the plans.`);
|
|
371
|
-
} else {
|
|
372
|
-
logger.info(`Run 'raf do ${finalProjectName}' to execute the plans.`);
|
|
373
|
-
}
|
|
255
|
+
logger.info(`Run 'raf do ${finalProjectName}' to execute the plans.`);
|
|
374
256
|
}
|
|
375
257
|
} catch (error) {
|
|
376
258
|
logger.error(`Planning failed: ${error}`);
|
|
377
259
|
throw error;
|
|
378
260
|
} finally {
|
|
379
|
-
if
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
const hasPlanFiles = fs.existsSync(plansDir) &&
|
|
383
|
-
fs.readdirSync(plansDir).filter((f) => f.endsWith('.md')).length > 0;
|
|
384
|
-
if (!hasPlanFiles) {
|
|
385
|
-
const removal = removeWorktree(worktreePath);
|
|
386
|
-
if (removal.success) {
|
|
387
|
-
logger.debug(`Cleaned up worktree: ${worktreePath}`);
|
|
388
|
-
} else {
|
|
389
|
-
logger.warn(`Failed to clean up worktree: ${removal.error}`);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
} else if (!worktreeMode) {
|
|
393
|
-
// Cleanup empty project folder if no plans were created (standard mode only)
|
|
394
|
-
const projectManager = new ProjectManager();
|
|
395
|
-
projectManager.cleanupEmptyProject(projectPath);
|
|
396
|
-
}
|
|
261
|
+
// Cleanup empty project folder if no plans were created
|
|
262
|
+
const projectManager = new ProjectManager();
|
|
263
|
+
projectManager.cleanupEmptyProject(projectPath);
|
|
397
264
|
}
|
|
398
265
|
}
|
|
399
266
|
|
|
400
|
-
async function runAmendCommand(identifier: string,
|
|
267
|
+
async function runAmendCommand(identifier: string, modelEntry?: ModelEntry, autoMode: boolean = false): Promise<void> {
|
|
401
268
|
// Validate environment
|
|
402
269
|
const validation = validateEnvironment();
|
|
403
270
|
reportValidation(validation);
|
|
@@ -406,112 +273,26 @@ async function runAmendCommand(identifier: string, model?: string, autoMode: boo
|
|
|
406
273
|
process.exit(1);
|
|
407
274
|
}
|
|
408
275
|
|
|
276
|
+
// Auto-detect project location: check worktrees first, then main repo
|
|
409
277
|
let worktreePath: string | null = null;
|
|
410
|
-
let projectPath: string;
|
|
411
|
-
|
|
412
|
-
if (worktreeMode) {
|
|
413
|
-
// Worktree mode: resolve project from worktree directory
|
|
414
|
-
const repoBasename = getRepoBasename();
|
|
415
|
-
if (!repoBasename) {
|
|
416
|
-
logger.error('--worktree requires a git repository');
|
|
417
|
-
process.exit(1);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
const repoRoot = getRepoRoot()!;
|
|
421
|
-
const rafDir = getRafDir();
|
|
422
|
-
const rafRelativePath = path.relative(repoRoot, rafDir);
|
|
423
|
-
|
|
424
|
-
const worktreeBaseDir = computeWorktreeBaseDir(repoBasename);
|
|
425
|
-
|
|
426
|
-
// Search through existing worktree directories for the project
|
|
427
|
-
let matchedWorktreeDir: string | null = null;
|
|
428
|
-
let matchedProjectPath: string | null = null;
|
|
429
|
-
|
|
430
|
-
if (fs.existsSync(worktreeBaseDir)) {
|
|
431
|
-
const entries = fs.readdirSync(worktreeBaseDir, { withFileTypes: true });
|
|
432
|
-
|
|
433
|
-
for (const entry of entries) {
|
|
434
|
-
if (!entry.isDirectory()) continue;
|
|
435
|
-
const wtPath = path.join(worktreeBaseDir, entry.name);
|
|
436
|
-
const wtRafDir = path.join(wtPath, rafRelativePath);
|
|
437
|
-
if (!fs.existsSync(wtRafDir)) continue;
|
|
438
|
-
|
|
439
|
-
const resolution = resolveProjectIdentifierWithDetails(wtRafDir, identifier);
|
|
440
|
-
if (resolution.path) {
|
|
441
|
-
if (matchedWorktreeDir) {
|
|
442
|
-
logger.error(`Ambiguous: project "${identifier}" found in multiple worktrees`);
|
|
443
|
-
process.exit(1);
|
|
444
|
-
}
|
|
445
|
-
matchedWorktreeDir = wtPath;
|
|
446
|
-
matchedProjectPath = resolution.path;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
if (!matchedWorktreeDir || !matchedProjectPath) {
|
|
452
|
-
// Worktree not found — try to recreate it
|
|
453
|
-
// First, resolve the project from the main repo to get the folder name
|
|
454
|
-
const mainResolution = resolveProjectIdentifierWithDetails(rafDir, identifier);
|
|
455
|
-
if (!mainResolution.path) {
|
|
456
|
-
logger.error(`Project not found in any worktree or main repo: ${identifier}`);
|
|
457
|
-
logger.error(`Searched in: ${worktreeBaseDir}`);
|
|
458
|
-
process.exit(1);
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
const folderName = path.basename(mainResolution.path);
|
|
278
|
+
let projectPath: string | undefined;
|
|
462
279
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
const result = createWorktreeFromBranch(repoBasename, folderName);
|
|
466
|
-
if (!result.success) {
|
|
467
|
-
logger.error(`Failed to recreate worktree from branch: ${result.error}`);
|
|
468
|
-
process.exit(1);
|
|
469
|
-
}
|
|
470
|
-
matchedWorktreeDir = result.worktreePath;
|
|
471
|
-
matchedProjectPath = path.join(result.worktreePath, rafRelativePath, folderName);
|
|
472
|
-
logger.info(`Recreated worktree from branch: ${folderName}`);
|
|
473
|
-
} else {
|
|
474
|
-
// No branch — create fresh worktree and copy project files
|
|
475
|
-
// Sync main branch before creating worktree (if enabled)
|
|
476
|
-
if (getSyncMainBranch()) {
|
|
477
|
-
const syncResult = pullMainBranch();
|
|
478
|
-
if (syncResult.success) {
|
|
479
|
-
if (syncResult.hadChanges) {
|
|
480
|
-
logger.info(`Synced ${syncResult.mainBranch} from remote`);
|
|
481
|
-
}
|
|
482
|
-
} else {
|
|
483
|
-
logger.warn(`Could not sync main branch: ${syncResult.error}`);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
280
|
+
const repoBasename = getRepoBasename();
|
|
281
|
+
const rafDir = getRafDir();
|
|
486
282
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
fs.cpSync(mainResolution.path, wtProjectPath, { recursive: true });
|
|
496
|
-
matchedProjectPath = wtProjectPath;
|
|
497
|
-
logger.info(`Created fresh worktree and copied project files: ${folderName}`);
|
|
498
|
-
}
|
|
283
|
+
// 1. Try worktree resolution first (if we're in a git repo)
|
|
284
|
+
if (repoBasename) {
|
|
285
|
+
const wtResult = resolveWorktreeProjectByIdentifier(repoBasename, identifier);
|
|
286
|
+
if (wtResult) {
|
|
287
|
+
const repoRoot = getRepoRoot()!;
|
|
288
|
+
const rafRelativePath = path.relative(repoRoot, rafDir);
|
|
289
|
+
worktreePath = wtResult.worktreeRoot;
|
|
290
|
+
projectPath = path.join(wtResult.worktreeRoot, rafRelativePath, wtResult.folder);
|
|
499
291
|
}
|
|
292
|
+
}
|
|
500
293
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
// Validate the worktree is valid
|
|
505
|
-
const relProjectPath = path.relative(worktreePath, projectPath);
|
|
506
|
-
const wtValidation = validateWorktree(worktreePath, relProjectPath);
|
|
507
|
-
if (!wtValidation.isValidWorktree) {
|
|
508
|
-
logger.error(`Invalid worktree at: ${worktreePath}`);
|
|
509
|
-
logger.error('The worktree may have been removed or corrupted.');
|
|
510
|
-
process.exit(1);
|
|
511
|
-
}
|
|
512
|
-
} else {
|
|
513
|
-
// Standard mode: resolve from main repo
|
|
514
|
-
const rafDir = getRafDir();
|
|
294
|
+
// 2. Fall back to main repo
|
|
295
|
+
if (!projectPath) {
|
|
515
296
|
const resolution = resolveProjectIdentifierWithDetails(rafDir, identifier);
|
|
516
297
|
|
|
517
298
|
if (!resolution.path) {
|
|
@@ -570,7 +351,7 @@ async function runAmendCommand(identifier: string, model?: string, autoMode: boo
|
|
|
570
351
|
// Show existing tasks summary
|
|
571
352
|
logger.info('Amending existing project:');
|
|
572
353
|
logger.info(` Path: ${projectPath}`);
|
|
573
|
-
if (
|
|
354
|
+
if (worktreePath) {
|
|
574
355
|
logger.info(` Worktree: ${worktreePath}`);
|
|
575
356
|
}
|
|
576
357
|
logger.info(` Existing tasks: ${existingTasks.length}`);
|
|
@@ -616,15 +397,15 @@ async function runAmendCommand(identifier: string, model?: string, autoMode: boo
|
|
|
616
397
|
fs.writeFileSync(inputPath, updatedInput);
|
|
617
398
|
|
|
618
399
|
// Set up shutdown handler
|
|
619
|
-
const claudeRunner = createRunner({ model });
|
|
400
|
+
const claudeRunner = createRunner({ model: modelEntry?.model, harness: modelEntry?.harness, reasoningEffort: modelEntry?.reasoningEffort, fast: modelEntry?.fast });
|
|
620
401
|
shutdownHandler.init();
|
|
621
402
|
shutdownHandler.registerClaudeRunner(claudeRunner);
|
|
622
403
|
|
|
623
404
|
// Run amend planning session
|
|
624
405
|
logger.info('Starting amendment session...');
|
|
625
406
|
logger.info('The planner will interview you about each new task.');
|
|
626
|
-
if (
|
|
627
|
-
logger.info(`Using model: ${model}`);
|
|
407
|
+
if (modelEntry) {
|
|
408
|
+
logger.info(`Using model: ${formatModelDisplay(modelEntry.model, modelEntry.harness, { includeHarness: true })}`);
|
|
628
409
|
}
|
|
629
410
|
if (autoMode) {
|
|
630
411
|
logger.warn('Auto mode enabled: permission prompts will be skipped.');
|
|
@@ -636,7 +417,6 @@ async function runAmendCommand(identifier: string, model?: string, autoMode: boo
|
|
|
636
417
|
existingTasks,
|
|
637
418
|
nextTaskNumber,
|
|
638
419
|
newTaskDescription: cleanInput,
|
|
639
|
-
worktreeMode,
|
|
640
420
|
});
|
|
641
421
|
|
|
642
422
|
try {
|
|
@@ -652,7 +432,7 @@ async function runAmendCommand(identifier: string, model?: string, autoMode: boo
|
|
|
652
432
|
|
|
653
433
|
// Check for new plan files
|
|
654
434
|
const allPlanFiles = fs.existsSync(plansDir)
|
|
655
|
-
? fs.readdirSync(plansDir).filter((f) => f.endsWith('.md')).sort()
|
|
435
|
+
? fs.readdirSync(plansDir).filter((f) => f.endsWith('.md')).sort(numericFileSort)
|
|
656
436
|
: [];
|
|
657
437
|
|
|
658
438
|
const newPlanFiles = allPlanFiles.filter((f) => {
|
|
@@ -687,11 +467,7 @@ async function runAmendCommand(identifier: string, model?: string, autoMode: boo
|
|
|
687
467
|
if (newPlanFiles.length > 0) {
|
|
688
468
|
logger.newline();
|
|
689
469
|
logger.info(`Total tasks: ${allPlanFiles.length}`);
|
|
690
|
-
|
|
691
|
-
logger.info(`Run 'raf do ${identifier} --worktree' to execute the new tasks.`);
|
|
692
|
-
} else {
|
|
693
|
-
logger.info(`Run 'raf do ${identifier}' to execute the new tasks.`);
|
|
694
|
-
}
|
|
470
|
+
logger.info(`Run 'raf do ${identifier}' to execute the new tasks.`);
|
|
695
471
|
}
|
|
696
472
|
} catch (error) {
|
|
697
473
|
logger.error(`Amendment failed: ${error}`);
|
|
@@ -729,7 +505,7 @@ ${taskList}
|
|
|
729
505
|
`;
|
|
730
506
|
}
|
|
731
507
|
|
|
732
|
-
async function runResumeCommand(identifier: string,
|
|
508
|
+
async function runResumeCommand(identifier: string, modelEntry?: ModelEntry): Promise<void> {
|
|
733
509
|
// Validate environment
|
|
734
510
|
const validation = validateEnvironment();
|
|
735
511
|
reportValidation(validation);
|
|
@@ -793,13 +569,13 @@ async function runResumeCommand(identifier: string, model?: string): Promise<voi
|
|
|
793
569
|
}
|
|
794
570
|
|
|
795
571
|
logger.info(`Project: ${folderName}`);
|
|
796
|
-
if (
|
|
797
|
-
logger.info(`Model: ${model}`);
|
|
572
|
+
if (modelEntry) {
|
|
573
|
+
logger.info(`Model: ${formatModelDisplay(modelEntry.model, modelEntry.harness, { includeHarness: true })}`);
|
|
798
574
|
}
|
|
799
575
|
logger.newline();
|
|
800
576
|
|
|
801
577
|
// Set up shutdown handler
|
|
802
|
-
const claudeRunner = createRunner({ model });
|
|
578
|
+
const claudeRunner = createRunner({ model: modelEntry?.model, harness: modelEntry?.harness, reasoningEffort: modelEntry?.reasoningEffort, fast: modelEntry?.fast });
|
|
803
579
|
shutdownHandler.init();
|
|
804
580
|
shutdownHandler.registerClaudeRunner(claudeRunner);
|
|
805
581
|
|
|
@@ -817,7 +593,7 @@ async function runResumeCommand(identifier: string, model?: string): Promise<voi
|
|
|
817
593
|
// Check for created/updated plan files after resume session
|
|
818
594
|
const plansDir = getPlansDir(projectPath);
|
|
819
595
|
const planFiles = fs.existsSync(plansDir)
|
|
820
|
-
? fs.readdirSync(plansDir).filter((f) => f.endsWith('.md')).sort()
|
|
596
|
+
? fs.readdirSync(plansDir).filter((f) => f.endsWith('.md')).sort(numericFileSort)
|
|
821
597
|
: [];
|
|
822
598
|
|
|
823
599
|
if (planFiles.length > 0) {
|