dflow-sdd-ddd 0.5.0 → 0.7.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/CHANGELOG.md +68 -0
- package/README.en.md +53 -4
- package/README.md +31 -3
- package/bin/dflow.js +7 -3
- package/docs/evaluating-dflow.en.md +12 -6
- package/docs/evaluating-dflow.md +7 -3
- package/docs/npm-publish-checklist.md +8 -0
- package/docs/using-with-claude-code.en.md +125 -17
- package/docs/using-with-claude-code.md +101 -15
- package/docs/using-with-codex.en.md +49 -30
- package/docs/using-with-codex.md +41 -26
- package/docs/using-with-github-copilot.en.md +44 -9
- package/docs/using-with-github-copilot.md +35 -8
- package/lib/init.js +242 -11
- package/package.json +1 -1
package/lib/init.js
CHANGED
|
@@ -10,6 +10,8 @@ const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
|
10
10
|
const TEMPLATE_ROOT = path.join(PACKAGE_ROOT, 'templates');
|
|
11
11
|
const COMMAND_REGISTRY_START = '<!-- dflow-command-registry:start -->';
|
|
12
12
|
const COMMAND_REGISTRY_END = '<!-- dflow-command-registry:end -->';
|
|
13
|
+
const COMMAND_ADAPTER_GENERATED_MARKER = '<!-- dflow-generated: command-adapter -->';
|
|
14
|
+
const SKILL_ADAPTER_GENERATED_MARKER = '<!-- dflow-generated: skill-adapter -->';
|
|
13
15
|
const EXPECTED_COMMAND_IDS = [
|
|
14
16
|
'new-feature',
|
|
15
17
|
'modify-existing',
|
|
@@ -23,6 +25,28 @@ const EXPECTED_COMMAND_IDS = [
|
|
|
23
25
|
'next',
|
|
24
26
|
'cancel'
|
|
25
27
|
];
|
|
28
|
+
const LEGACY_COMMAND_ADAPTERS = [
|
|
29
|
+
{
|
|
30
|
+
version: '0.5.0',
|
|
31
|
+
agent: 'claude',
|
|
32
|
+
source: 'generated:legacy-claude-command-adapter-v0.5.0',
|
|
33
|
+
pathPattern: '.claude/commands/dflow/dflow-<id>.md',
|
|
34
|
+
fingerprint: 'v0.5.0 buildThinCommandWrapper',
|
|
35
|
+
commands: [
|
|
36
|
+
{ id: 'new-feature', label: '/dflow:new-feature', argHint: 'feature request' },
|
|
37
|
+
{ id: 'modify-existing', label: '/dflow:modify-existing', argHint: 'change request' },
|
|
38
|
+
{ id: 'bug-fix', label: '/dflow:bug-fix', argHint: 'expected vs actual' },
|
|
39
|
+
{ id: 'new-phase', label: '/dflow:new-phase', argHint: 'feature id or phase goal' },
|
|
40
|
+
{ id: 'finish-feature', label: '/dflow:finish-feature', argHint: 'feature id' },
|
|
41
|
+
{ id: 'verify', label: '/dflow:verify', argHint: 'area or feature id' },
|
|
42
|
+
{ id: 'pr-review', label: '/dflow:pr-review', argHint: 'change or branch' },
|
|
43
|
+
{ id: 'report-dflow-feedback', label: '/dflow:report-dflow-feedback', argHint: 'issue or improvement' },
|
|
44
|
+
{ id: 'status', label: '/dflow:status', argHint: '-' },
|
|
45
|
+
{ id: 'next', label: '/dflow:next', argHint: '-' },
|
|
46
|
+
{ id: 'cancel', label: '/dflow:cancel', argHint: '-' }
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
];
|
|
26
50
|
|
|
27
51
|
const PROSE_LANGUAGE_PATTERN =
|
|
28
52
|
/^[A-Za-z]{2,3}(?:-[A-Za-z]{4})?(?:-(?:[A-Za-z]{2}|[0-9]{3}))?(?:-(?:[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*$/;
|
|
@@ -262,10 +286,12 @@ async function runConfigureAgents(options = {}) {
|
|
|
262
286
|
const plan = await buildConfigureAgentsPlan(cwd, {
|
|
263
287
|
...projectContext,
|
|
264
288
|
aiAgents,
|
|
265
|
-
commandAdapters: Boolean(options.commandAdapters)
|
|
289
|
+
commandAdapters: Boolean(options.commandAdapters),
|
|
290
|
+
skills: Boolean(options.skills)
|
|
266
291
|
});
|
|
267
292
|
|
|
268
|
-
|
|
293
|
+
const warnings = plan.warnings || [];
|
|
294
|
+
renderPreview(stdout, plan, warnings);
|
|
269
295
|
const confirmed = await askConfirmation(rl, 'Create these files? (y/N) ');
|
|
270
296
|
|
|
271
297
|
if (!confirmed) {
|
|
@@ -276,6 +302,7 @@ async function runConfigureAgents(options = {}) {
|
|
|
276
302
|
rl = undefined;
|
|
277
303
|
|
|
278
304
|
const result = await writeFilePlan(cwd, plan);
|
|
305
|
+
result.warnings.unshift(...warnings);
|
|
279
306
|
result.warnings.push(...collectUnresolvedPlaceholderWarnings(plan, result.created));
|
|
280
307
|
|
|
281
308
|
printResultReport(stdout, result, plan.deferred);
|
|
@@ -1000,6 +1027,7 @@ async function buildConfigureAgentsPlan(cwd, answers) {
|
|
|
1000
1027
|
optionalFiles: answers.optionalFiles || []
|
|
1001
1028
|
});
|
|
1002
1029
|
const items = [];
|
|
1030
|
+
const warnings = [];
|
|
1003
1031
|
|
|
1004
1032
|
let content = await readPackagedTemplate(answers.edition, 'scaffolding/AI-AGENT-GUIDE.md');
|
|
1005
1033
|
content = substitutePlaceholders(content, substitution);
|
|
@@ -1022,9 +1050,16 @@ async function buildConfigureAgentsPlan(cwd, answers) {
|
|
|
1022
1050
|
|
|
1023
1051
|
await finalizePlanItems(cwd, items);
|
|
1024
1052
|
|
|
1053
|
+
if (answers.commandAdapters) {
|
|
1054
|
+
await addLegacyCommandAdapterCleanupItems(cwd, items, answers.aiAgents, warnings);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
await addSkillAdapterItems(cwd, items, answers.aiAgents, answers.skills, warnings);
|
|
1058
|
+
|
|
1025
1059
|
return {
|
|
1026
1060
|
items,
|
|
1027
1061
|
deferred: [],
|
|
1062
|
+
warnings,
|
|
1028
1063
|
unresolvedInitPlaceholders: Array.from(substitution.entries())
|
|
1029
1064
|
.filter(([placeholder, value]) => placeholder === value)
|
|
1030
1065
|
.map(([placeholder]) => placeholder)
|
|
@@ -1134,10 +1169,10 @@ function addCommandAdapterItems(items, aiAgents, commandRegistry) {
|
|
|
1134
1169
|
if (aiAgents.includes('claude')) {
|
|
1135
1170
|
for (const command of commandRegistry) {
|
|
1136
1171
|
items.push({
|
|
1137
|
-
relativePath: `.claude/commands/dflow
|
|
1172
|
+
relativePath: `.claude/commands/dflow/${command.id}.md`,
|
|
1138
1173
|
source: 'generated:claude-command-adapter',
|
|
1139
1174
|
notes: 'command adapter, derived from dflow command registry',
|
|
1140
|
-
content: buildThinCommandWrapper(command),
|
|
1175
|
+
content: buildThinCommandWrapper(command, `/dflow:${command.id}`),
|
|
1141
1176
|
overwrite: true
|
|
1142
1177
|
});
|
|
1143
1178
|
}
|
|
@@ -1149,13 +1184,165 @@ function addCommandAdapterItems(items, aiAgents, commandRegistry) {
|
|
|
1149
1184
|
relativePath: `.github/prompts/dflow-${command.id}.prompt.md`,
|
|
1150
1185
|
source: 'generated:copilot-command-adapter',
|
|
1151
1186
|
notes: 'command adapter, derived from dflow command registry',
|
|
1152
|
-
content: buildThinCommandWrapper(command),
|
|
1187
|
+
content: buildThinCommandWrapper(command, `/dflow-${command.id}`),
|
|
1153
1188
|
overwrite: true
|
|
1154
1189
|
});
|
|
1155
1190
|
}
|
|
1156
1191
|
}
|
|
1157
1192
|
}
|
|
1158
1193
|
|
|
1194
|
+
async function addLegacyCommandAdapterCleanupItems(cwd, items, aiAgents, warnings) {
|
|
1195
|
+
for (const legacy of LEGACY_COMMAND_ADAPTERS) {
|
|
1196
|
+
if (!aiAgents.includes(legacy.agent)) {
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
for (const command of legacy.commands) {
|
|
1201
|
+
const relativePath = legacy.pathPattern.replace('<id>', command.id);
|
|
1202
|
+
const targetPath = path.join(cwd, relativePath);
|
|
1203
|
+
let stats;
|
|
1204
|
+
|
|
1205
|
+
try {
|
|
1206
|
+
stats = await fs.stat(targetPath);
|
|
1207
|
+
} catch (error) {
|
|
1208
|
+
if (error.code === 'ENOENT') {
|
|
1209
|
+
continue;
|
|
1210
|
+
}
|
|
1211
|
+
throw error;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
if (!stats.isFile()) {
|
|
1215
|
+
warnings.push(`Found legacy Dflow command adapter path but it is not a file; not removed: ${relativePath}`);
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
const content = await fs.readFile(targetPath, 'utf8');
|
|
1220
|
+
const expectedContent = buildLegacyCommandAdapterFingerprint(legacy, command);
|
|
1221
|
+
if (normalizeCommandAdapterFingerprint(content) !== normalizeCommandAdapterFingerprint(expectedContent)) {
|
|
1222
|
+
warnings.push(`Found legacy Dflow command adapter with non-generated content; not removed: ${relativePath}. Inspect it manually before deleting.`);
|
|
1223
|
+
continue;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
items.push({
|
|
1227
|
+
relativePath,
|
|
1228
|
+
source: legacy.source,
|
|
1229
|
+
notes: `stale dflow adapter generated by Dflow ${legacy.version}`,
|
|
1230
|
+
action: 'remove',
|
|
1231
|
+
size: Buffer.byteLength(content, 'utf8'),
|
|
1232
|
+
expectedContent
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
function buildDflowSkillAdapter() {
|
|
1239
|
+
return `---
|
|
1240
|
+
name: dflow
|
|
1241
|
+
description: >
|
|
1242
|
+
Dflow SDD/DDD workflow guardian for this project. PRIMARY: the canonical
|
|
1243
|
+
/dflow:* commands (/dflow:new-feature, /dflow:modify-existing, /dflow:bug-fix,
|
|
1244
|
+
/dflow:new-phase, /dflow:finish-feature, /dflow:pr-review, /dflow:verify,
|
|
1245
|
+
/dflow:report-dflow-feedback, /dflow:status, /dflow:next, /dflow:cancel).
|
|
1246
|
+
SECONDARY (auto-trigger safety net) — engage ONLY for: adding or changing
|
|
1247
|
+
product/domain behavior, new requirements, a feature or bug-fix workflow, or
|
|
1248
|
+
spec-impacting architecture/domain-model decisions. Do NOT engage for pure
|
|
1249
|
+
refactors, infrastructure chores, formatting, or general code questions.
|
|
1250
|
+
When engaged by natural language, DO NOT auto-enter a workflow: judge the
|
|
1251
|
+
intent, suggest the matching /dflow: command, and wait for confirmation.
|
|
1252
|
+
---
|
|
1253
|
+
|
|
1254
|
+
${SKILL_ADAPTER_GENERATED_MARKER}
|
|
1255
|
+
|
|
1256
|
+
# Dflow SDD/DDD Workflow Guardian
|
|
1257
|
+
|
|
1258
|
+
This project uses Dflow for spec-first AI-assisted development. The canonical
|
|
1259
|
+
workflow contract, command registry, spec locations, and SDD/DDD rules live in:
|
|
1260
|
+
|
|
1261
|
+
- \`dflow/specs/shared/AI-AGENT-GUIDE.md\`
|
|
1262
|
+
|
|
1263
|
+
When this skill engages, read that guide and follow the matching \`/dflow:\`
|
|
1264
|
+
workflow or control command defined there. Do not duplicate or invent workflow
|
|
1265
|
+
steps here — the guide is the single source of truth.
|
|
1266
|
+
|
|
1267
|
+
If engaged by natural language (not an explicit \`/dflow:\` command): identify
|
|
1268
|
+
which \`/dflow:\` command fits, suggest it, and wait for the developer to confirm
|
|
1269
|
+
before entering any workflow.
|
|
1270
|
+
`;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
async function addSkillAdapterItems(cwd, items, aiAgents, skills, warnings) {
|
|
1274
|
+
if (!skills) {
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
if (!aiAgents.includes('claude')) {
|
|
1279
|
+
warnings.push(
|
|
1280
|
+
'The --skills flag currently supports Claude Code only; no skill adapter was generated because Claude Code was not a selected target.'
|
|
1281
|
+
);
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
const relativePath = '.claude/skills/dflow/SKILL.md';
|
|
1286
|
+
const targetPath = path.join(cwd, relativePath);
|
|
1287
|
+
|
|
1288
|
+
// The thin skill is edition-neutral (it only points to the per-edition guide),
|
|
1289
|
+
// so there is nothing edition-specific to go stale — re-running just rewrites
|
|
1290
|
+
// the same marker-guarded file (idempotent). No LEGACY skill set exists yet
|
|
1291
|
+
// (skills are new in PROPOSAL-038); future skill cleanup would extend the same
|
|
1292
|
+
// LEGACY_* / addLegacyCommandAdapterCleanupItems marker-fingerprint pattern.
|
|
1293
|
+
let existingContent;
|
|
1294
|
+
try {
|
|
1295
|
+
existingContent = await fs.readFile(targetPath, 'utf8');
|
|
1296
|
+
} catch (error) {
|
|
1297
|
+
if (error.code !== 'ENOENT') {
|
|
1298
|
+
throw error;
|
|
1299
|
+
}
|
|
1300
|
+
existingContent = undefined;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
if (existingContent !== undefined && !existingContent.includes(SKILL_ADAPTER_GENERATED_MARKER)) {
|
|
1304
|
+
warnings.push(
|
|
1305
|
+
'Existing .claude/skills/dflow/SKILL.md is not a Dflow-generated skill; left unchanged. Remove or rename it to let Dflow manage this skill.'
|
|
1306
|
+
);
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
items.push({
|
|
1311
|
+
relativePath,
|
|
1312
|
+
source: 'generated:claude-skill-adapter',
|
|
1313
|
+
notes: 'skill adapter, thin skill pointing to AI-AGENT-GUIDE.md',
|
|
1314
|
+
content: buildDflowSkillAdapter(),
|
|
1315
|
+
overwrite: true
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
function buildLegacyCommandAdapterFingerprint(legacy, command) {
|
|
1320
|
+
if (legacy.version === '0.5.0' && legacy.fingerprint === 'v0.5.0 buildThinCommandWrapper') {
|
|
1321
|
+
return buildLegacyV050ThinCommandWrapper(command);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
throw new InitError(`Internal error: unsupported legacy command adapter fingerprint: ${legacy.version}`);
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
function buildLegacyV050ThinCommandWrapper(command) {
|
|
1328
|
+
const argHint = command.argHint === '-'
|
|
1329
|
+
? 'Argument hint: none.'
|
|
1330
|
+
: `Argument hint: ${command.argHint}.`;
|
|
1331
|
+
|
|
1332
|
+
return `# /dflow-${command.id}
|
|
1333
|
+
|
|
1334
|
+
Execute the canonical \`${command.label}\` Dflow workflow or control command.
|
|
1335
|
+
|
|
1336
|
+
Definition: \`dflow/specs/shared/AI-AGENT-GUIDE.md\`
|
|
1337
|
+
|
|
1338
|
+
${argHint}
|
|
1339
|
+
`;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
function normalizeCommandAdapterFingerprint(content) {
|
|
1343
|
+
return String(content).replace(/\r\n/g, '\n');
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1159
1346
|
function buildCodexCommandTriggerSection(commandRegistry) {
|
|
1160
1347
|
const triggers = commandRegistry
|
|
1161
1348
|
.map((command) => {
|
|
@@ -1172,18 +1359,25 @@ Codex does not install Dflow command files. When the developer asks for a
|
|
|
1172
1359
|
canonical Dflow command, treat it as a text trigger, read the canonical guide,
|
|
1173
1360
|
and execute the matching workflow or control command.
|
|
1174
1361
|
|
|
1362
|
+
If the CLI intercepts a slash-prefixed Dflow name such as \`/dflow:status\` as
|
|
1363
|
+
an unknown command, the developer may resend it without the slash, for example
|
|
1364
|
+
\`dflow:status\`. Treat that as the same Dflow text trigger, read the guide,
|
|
1365
|
+
and execute it.
|
|
1366
|
+
|
|
1175
1367
|
Recognized canonical triggers:
|
|
1176
1368
|
|
|
1177
1369
|
${triggers}
|
|
1178
1370
|
`;
|
|
1179
1371
|
}
|
|
1180
1372
|
|
|
1181
|
-
function buildThinCommandWrapper(command) {
|
|
1373
|
+
function buildThinCommandWrapper(command, displayName = command.label) {
|
|
1182
1374
|
const argHint = command.argHint === '-'
|
|
1183
1375
|
? 'Argument hint: none.'
|
|
1184
1376
|
: `Argument hint: ${command.argHint}.`;
|
|
1185
1377
|
|
|
1186
|
-
return `#
|
|
1378
|
+
return `# ${displayName}
|
|
1379
|
+
|
|
1380
|
+
${COMMAND_ADAPTER_GENERATED_MARKER}
|
|
1187
1381
|
|
|
1188
1382
|
Execute the canonical \`${command.label}\` Dflow workflow or control command.
|
|
1189
1383
|
|
|
@@ -1646,6 +1840,7 @@ async function writeFilePlan(cwd, plan) {
|
|
|
1646
1840
|
const result = {
|
|
1647
1841
|
created: [],
|
|
1648
1842
|
updated: [],
|
|
1843
|
+
removed: [],
|
|
1649
1844
|
skipped: [],
|
|
1650
1845
|
warnings: []
|
|
1651
1846
|
};
|
|
@@ -1654,6 +1849,37 @@ async function writeFilePlan(cwd, plan) {
|
|
|
1654
1849
|
const targetPath = path.join(cwd, item.relativePath);
|
|
1655
1850
|
|
|
1656
1851
|
try {
|
|
1852
|
+
if (item.action === 'remove') {
|
|
1853
|
+
let stats;
|
|
1854
|
+
try {
|
|
1855
|
+
stats = await fs.stat(targetPath);
|
|
1856
|
+
} catch (error) {
|
|
1857
|
+
if (error.code === 'ENOENT') {
|
|
1858
|
+
result.skipped.push(item.relativePath);
|
|
1859
|
+
result.warnings.push(`Skipped missing stale adapter: ${item.relativePath}`);
|
|
1860
|
+
continue;
|
|
1861
|
+
}
|
|
1862
|
+
throw error;
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
if (!stats.isFile()) {
|
|
1866
|
+
result.skipped.push(item.relativePath);
|
|
1867
|
+
result.warnings.push(`Skipped stale adapter removal because target is not a file: ${item.relativePath}`);
|
|
1868
|
+
continue;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
const currentContent = await fs.readFile(targetPath, 'utf8');
|
|
1872
|
+
if (normalizeCommandAdapterFingerprint(currentContent) !== normalizeCommandAdapterFingerprint(item.expectedContent || '')) {
|
|
1873
|
+
result.skipped.push(item.relativePath);
|
|
1874
|
+
result.warnings.push(`Skipped stale adapter removal because content changed after preview: ${item.relativePath}`);
|
|
1875
|
+
continue;
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
await fs.unlink(targetPath);
|
|
1879
|
+
result.removed.push(item.relativePath);
|
|
1880
|
+
continue;
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1657
1883
|
if (await pathExists(targetPath)) {
|
|
1658
1884
|
if (item.overwrite) {
|
|
1659
1885
|
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
@@ -1705,9 +1931,11 @@ async function writeFilePlan(cwd, plan) {
|
|
|
1705
1931
|
);
|
|
1706
1932
|
}
|
|
1707
1933
|
|
|
1708
|
-
const message =
|
|
1709
|
-
? `
|
|
1710
|
-
:
|
|
1934
|
+
const message = item.action === 'remove'
|
|
1935
|
+
? `Write failed while removing ${item.relativePath}: ${error.message}`
|
|
1936
|
+
: error.code === 'ENOTDIR' || error.code === 'EEXIST'
|
|
1937
|
+
? `Cannot create parent directory for ${item.relativePath}: a parent path is a file.`
|
|
1938
|
+
: `Write failed while creating ${item.relativePath}: ${error.message}`;
|
|
1711
1939
|
throw new WritePhaseError(message, result);
|
|
1712
1940
|
}
|
|
1713
1941
|
}
|
|
@@ -1751,6 +1979,9 @@ function printResultReport(stdout, result, deferred) {
|
|
|
1751
1979
|
stdout.write('\nUpdated:\n');
|
|
1752
1980
|
printList(stdout, result.updated);
|
|
1753
1981
|
|
|
1982
|
+
stdout.write('\nRemoved:\n');
|
|
1983
|
+
printList(stdout, result.removed);
|
|
1984
|
+
|
|
1754
1985
|
stdout.write('\nSkipped:\n');
|
|
1755
1986
|
printList(stdout, result.skipped);
|
|
1756
1987
|
|
|
@@ -1777,7 +2008,7 @@ Recommended next steps:
|
|
|
1777
2008
|
|
|
1778
2009
|
function printConfigureAgentsNextSteps(stdout, commandAdapters = false) {
|
|
1779
2010
|
const commandAdapterStep = commandAdapters
|
|
1780
|
-
? '- Command adapters use
|
|
2011
|
+
? '- Command adapters use tool-specific invocation names: Claude Code `/dflow:<id>`; GitHub Copilot prompt menu `/dflow-<id>` or canonical `/dflow:<id>` as text; Codex CLI plain text without a slash, such as `dflow:status`. Canonical `/dflow:*` names remain defined in dflow/specs/shared/AI-AGENT-GUIDE.md. If upgrading from Dflow 0.5.0, stale `.claude/commands/dflow/dflow-*.md` files generated by 0.5.0 are detected and listed for removal in the confirmation preview, so Claude Code does not show both old and new command names; edited or non-Dflow files are kept with a warning.\n'
|
|
1781
2012
|
: '';
|
|
1782
2013
|
|
|
1783
2014
|
stdout.write(`
|