@joshski/dust 0.1.91 → 0.1.93
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/artifacts/index.d.ts +1 -0
- package/dist/artifacts/workflow-tasks.d.ts +1 -1
- package/dist/artifacts.js +6 -3
- package/dist/dust.js +97 -31
- package/package.json +1 -1
|
@@ -40,6 +40,7 @@ export interface ArtifactsRepository {
|
|
|
40
40
|
createRefineIdeaTask(options: {
|
|
41
41
|
ideaSlug: string;
|
|
42
42
|
description?: string;
|
|
43
|
+
openQuestionResponses?: OpenQuestionResponse[];
|
|
43
44
|
dustCommand?: string;
|
|
44
45
|
}): Promise<CreateIdeaTransitionTaskResult>;
|
|
45
46
|
createDecomposeIdeaTask(options: DecomposeIdeaOptions & {
|
|
@@ -45,7 +45,7 @@ export interface DecomposeIdeaOptions {
|
|
|
45
45
|
description?: string;
|
|
46
46
|
openQuestionResponses?: OpenQuestionResponse[];
|
|
47
47
|
}
|
|
48
|
-
export declare function createRefineIdeaTask(fileSystem: FileSystem, dustPath: string, ideaSlug: string, description?: string, dustCommand?: string): Promise<CreateIdeaTransitionTaskResult>;
|
|
48
|
+
export declare function createRefineIdeaTask(fileSystem: FileSystem, dustPath: string, ideaSlug: string, description?: string, openQuestionResponses?: OpenQuestionResponse[], dustCommand?: string): Promise<CreateIdeaTransitionTaskResult>;
|
|
49
49
|
export declare function decomposeIdea(fileSystem: FileSystem, dustPath: string, options: DecomposeIdeaOptions, dustCommand?: string): Promise<CreateIdeaTransitionTaskResult>;
|
|
50
50
|
export declare function createShelveIdeaTask(fileSystem: FileSystem, dustPath: string, ideaSlug: string, description?: string, _dustCommand?: string): Promise<CreateIdeaTransitionTaskResult>;
|
|
51
51
|
export declare function createIdeaTask(fileSystem: FileSystem, dustPath: string, options: {
|
package/dist/artifacts.js
CHANGED
|
@@ -460,14 +460,17 @@ async function createIdeaTransitionTask(fileSystem, dustPath, workflowType, pref
|
|
|
460
460
|
await fileSystem.writeFile(filePath, content);
|
|
461
461
|
return { filePath };
|
|
462
462
|
}
|
|
463
|
-
async function createRefineIdeaTask(fileSystem, dustPath, ideaSlug, description, dustCommand) {
|
|
463
|
+
async function createRefineIdeaTask(fileSystem, dustPath, ideaSlug, description, openQuestionResponses, dustCommand) {
|
|
464
464
|
const cmd = dustCommand ?? "dust";
|
|
465
465
|
return createIdeaTransitionTask(fileSystem, dustPath, "refine-idea", "Refine Idea: ", ideaSlug, (ideaTitle) => `Thoroughly research this idea and refine it into a well-defined proposal. Read the idea file, explore the codebase for relevant context, and identify any ambiguity. Where aspects are unclear or could go multiple ways, add open questions to the idea file. Run \`${cmd} principles\` for alignment and \`${cmd} facts\` for relevant design decisions. See [${ideaTitle}](../ideas/${ideaSlug}.md). If you add open questions, use \`## Open Questions\` with \`### Question?\` headings and one or more \`#### Option\` headings beneath each question, and only add questions that are meaningful decisions worth asking.`, [
|
|
466
466
|
"Idea is thoroughly researched with relevant codebase context",
|
|
467
467
|
"Open questions are added for any ambiguous or underspecified aspects",
|
|
468
468
|
"Open questions follow the required heading format and focus on high-value decisions",
|
|
469
469
|
"Idea file is updated with findings"
|
|
470
|
-
], "Refines Idea", {
|
|
470
|
+
], "Refines Idea", {
|
|
471
|
+
description,
|
|
472
|
+
resolvedQuestions: openQuestionResponses
|
|
473
|
+
});
|
|
471
474
|
}
|
|
472
475
|
async function decomposeIdea(fileSystem, dustPath, options, dustCommand) {
|
|
473
476
|
const cmd = dustCommand ?? "dust";
|
|
@@ -632,7 +635,7 @@ function buildArtifactsRepository(fileSystem, dustPath) {
|
|
|
632
635
|
return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, "")).sort();
|
|
633
636
|
},
|
|
634
637
|
async createRefineIdeaTask(options) {
|
|
635
|
-
return createRefineIdeaTask(fileSystem, dustPath, options.ideaSlug, options.description, options.dustCommand);
|
|
638
|
+
return createRefineIdeaTask(fileSystem, dustPath, options.ideaSlug, options.description, options.openQuestionResponses, options.dustCommand);
|
|
636
639
|
},
|
|
637
640
|
async createDecomposeIdeaTask(options) {
|
|
638
641
|
return decomposeIdea(fileSystem, dustPath, options, options.dustCommand);
|
package/dist/dust.js
CHANGED
|
@@ -500,7 +500,7 @@ async function loadSettings(cwd, fileSystem) {
|
|
|
500
500
|
}
|
|
501
501
|
|
|
502
502
|
// lib/version.ts
|
|
503
|
-
var DUST_VERSION = "0.1.
|
|
503
|
+
var DUST_VERSION = "0.1.93";
|
|
504
504
|
|
|
505
505
|
// lib/session.ts
|
|
506
506
|
var DUST_UNATTENDED = "DUST_UNATTENDED";
|
|
@@ -2072,6 +2072,7 @@ import { spawn as nodeSpawn5 } from "node:child_process";
|
|
|
2072
2072
|
import { accessSync, statSync } from "node:fs";
|
|
2073
2073
|
import { chmod, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
2074
2074
|
import { homedir as homedir2 } from "node:os";
|
|
2075
|
+
import { basename as basename2, resolve } from "node:path";
|
|
2075
2076
|
|
|
2076
2077
|
// lib/bucket/auth.ts
|
|
2077
2078
|
import { join as join5 } from "node:path";
|
|
@@ -2562,6 +2563,25 @@ function selectAgentCapabilities(input) {
|
|
|
2562
2563
|
}
|
|
2563
2564
|
return agents;
|
|
2564
2565
|
}
|
|
2566
|
+
async function fetchCodexModelsFromApi(apiKey) {
|
|
2567
|
+
try {
|
|
2568
|
+
const response = await fetch("https://api.openai.com/v1/models", {
|
|
2569
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
2570
|
+
});
|
|
2571
|
+
if (!response.ok) {
|
|
2572
|
+
log2(`OpenAI models API returned ${response.status}`);
|
|
2573
|
+
return [];
|
|
2574
|
+
}
|
|
2575
|
+
const body = await response.json();
|
|
2576
|
+
if (!body.data || !Array.isArray(body.data)) {
|
|
2577
|
+
return [];
|
|
2578
|
+
}
|
|
2579
|
+
return body.data.map((m) => m.id).filter((id) => id.includes("codex")).sort();
|
|
2580
|
+
} catch (error) {
|
|
2581
|
+
log2(`OpenAI models API fetch failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2582
|
+
return [];
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2565
2585
|
async function discoverAgentCapabilities(dependencies) {
|
|
2566
2586
|
const [claudeVersionProbe, codexVersionProbe] = await Promise.all([
|
|
2567
2587
|
probeCommand(dependencies.spawn, "claude", ["--version"]),
|
|
@@ -2569,10 +2589,18 @@ async function discoverAgentCapabilities(dependencies) {
|
|
|
2569
2589
|
]);
|
|
2570
2590
|
let codexModelsProbe = null;
|
|
2571
2591
|
if (codexVersionProbe.success) {
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2592
|
+
const getEnv = dependencies.getEnv ?? ((name) => process.env[name]);
|
|
2593
|
+
const apiKey = getEnv("OPENAI_API_KEY");
|
|
2594
|
+
if (apiKey) {
|
|
2595
|
+
const fetcher = dependencies.fetchCodexModelsFromApi ?? fetchCodexModelsFromApi;
|
|
2596
|
+
const models = await fetcher(apiKey);
|
|
2597
|
+
if (models.length > 0) {
|
|
2598
|
+
codexModelsProbe = {
|
|
2599
|
+
success: true,
|
|
2600
|
+
stdout: JSON.stringify(models)
|
|
2601
|
+
};
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2576
2604
|
}
|
|
2577
2605
|
return {
|
|
2578
2606
|
type: "agent-capabilities",
|
|
@@ -5600,7 +5628,7 @@ function createDefaultBucketDependencies() {
|
|
|
5600
5628
|
getTerminalSize: defaultGetTerminalSize,
|
|
5601
5629
|
writeStdout: defaultWriteStdout,
|
|
5602
5630
|
isTTY: process.stdout.isTTY ?? false,
|
|
5603
|
-
sleep: (ms) => new Promise((
|
|
5631
|
+
sleep: (ms) => new Promise((resolve2) => setTimeout(resolve2, ms)),
|
|
5604
5632
|
getReposDir: () => getReposDir(process.env, homedir2()),
|
|
5605
5633
|
auth: {
|
|
5606
5634
|
createServer: createLocalServer,
|
|
@@ -5744,8 +5772,8 @@ function createTUIContext(state, context, useTUI) {
|
|
|
5744
5772
|
function waitForConnection(token, bucketDeps) {
|
|
5745
5773
|
const wsUrl = getWebSocketUrl();
|
|
5746
5774
|
const ws = bucketDeps.createWebSocket(wsUrl, token);
|
|
5747
|
-
return new Promise((
|
|
5748
|
-
ws.onopen = () =>
|
|
5775
|
+
return new Promise((resolve2, reject) => {
|
|
5776
|
+
ws.onopen = () => resolve2(ws);
|
|
5749
5777
|
ws.onerror = (error) => reject(new Error(error.message));
|
|
5750
5778
|
ws.onclose = (event) => reject(new Error(`Connection closed (code ${event.code})`));
|
|
5751
5779
|
});
|
|
@@ -6140,24 +6168,51 @@ async function bucketWorker(dependencies, bucketDeps = createDefaultBucketDepend
|
|
|
6140
6168
|
} catch {
|
|
6141
6169
|
state.ui.connectedHost = wsUrl;
|
|
6142
6170
|
}
|
|
6171
|
+
function findRepoPathByRepositoryId(repositories, repositoryId) {
|
|
6172
|
+
for (const repoState of repositories.values()) {
|
|
6173
|
+
if (repoState.repository.id === repositoryId) {
|
|
6174
|
+
return repoState.path;
|
|
6175
|
+
}
|
|
6176
|
+
}
|
|
6177
|
+
return;
|
|
6178
|
+
}
|
|
6143
6179
|
let tuiHandle;
|
|
6144
6180
|
let cleanupKeypress;
|
|
6145
6181
|
let cleanupSignals;
|
|
6146
|
-
const forwardToolExecution = (request) => {
|
|
6182
|
+
const forwardToolExecution = async (request) => {
|
|
6147
6183
|
const ws = state.ws;
|
|
6148
6184
|
if (!ws || ws.readyState !== WS_OPEN) {
|
|
6149
6185
|
log10(`tool execution rejected: ${request.toolName} (WebSocket not connected)`);
|
|
6150
|
-
return
|
|
6186
|
+
return {
|
|
6151
6187
|
status: "error",
|
|
6152
6188
|
error: "Bucket session is not connected"
|
|
6153
|
-
}
|
|
6189
|
+
};
|
|
6154
6190
|
}
|
|
6155
6191
|
const requestId = crypto.randomUUID();
|
|
6192
|
+
const repoPath = findRepoPathByRepositoryId(state.repositories, Number(request.repositoryId));
|
|
6156
6193
|
const toolDef = state.tools.find((t) => t.name === request.toolName);
|
|
6157
6194
|
const namedArgs = {};
|
|
6158
6195
|
if (toolDef) {
|
|
6159
6196
|
for (let i = 0;i < toolDef.parameters.length && i < request.arguments.length; i++) {
|
|
6160
|
-
|
|
6197
|
+
const param = toolDef.parameters[i];
|
|
6198
|
+
const value = request.arguments[i];
|
|
6199
|
+
if (param.type === "file" && typeof value === "string") {
|
|
6200
|
+
const filePath = repoPath ? resolve(repoPath, value) : value;
|
|
6201
|
+
try {
|
|
6202
|
+
const data = await readFile(filePath);
|
|
6203
|
+
namedArgs[param.name] = {
|
|
6204
|
+
filename: basename2(value),
|
|
6205
|
+
data: data.toString("base64")
|
|
6206
|
+
};
|
|
6207
|
+
} catch {
|
|
6208
|
+
return {
|
|
6209
|
+
status: "error",
|
|
6210
|
+
error: `Unable to read file: ${value}`
|
|
6211
|
+
};
|
|
6212
|
+
}
|
|
6213
|
+
} else {
|
|
6214
|
+
namedArgs[param.name] = value;
|
|
6215
|
+
}
|
|
6161
6216
|
}
|
|
6162
6217
|
} else {
|
|
6163
6218
|
for (let i = 0;i < request.arguments.length; i++) {
|
|
@@ -6172,14 +6227,14 @@ async function bucketWorker(dependencies, bucketDeps = createDefaultBucketDepend
|
|
|
6172
6227
|
arguments: namedArgs
|
|
6173
6228
|
};
|
|
6174
6229
|
log10(`forwarding tool execution: ${request.toolName} requestId=${requestId}`);
|
|
6175
|
-
return new Promise((
|
|
6230
|
+
return new Promise((resolve2, reject) => {
|
|
6176
6231
|
const timeoutId = setTimeout(() => {
|
|
6177
6232
|
pendingToolExecutions.delete(requestId);
|
|
6178
6233
|
log10(`tool execution timed out: ${request.toolName} requestId=${requestId}`);
|
|
6179
6234
|
reject(new Error("Timed out waiting for tool execution result"));
|
|
6180
6235
|
}, 30000);
|
|
6181
6236
|
pendingToolExecutions.set(requestId, {
|
|
6182
|
-
resolve,
|
|
6237
|
+
resolve: resolve2,
|
|
6183
6238
|
reject,
|
|
6184
6239
|
timeoutId
|
|
6185
6240
|
});
|
|
@@ -6197,10 +6252,10 @@ async function bucketWorker(dependencies, bucketDeps = createDefaultBucketDepend
|
|
|
6197
6252
|
if (useTUI) {
|
|
6198
6253
|
tuiHandle = setupTUI(state, bucketDeps);
|
|
6199
6254
|
}
|
|
6200
|
-
await new Promise((
|
|
6255
|
+
await new Promise((resolve2) => {
|
|
6201
6256
|
const doShutdown = async () => {
|
|
6202
6257
|
await shutdown(state, bucketDeps, context);
|
|
6203
|
-
|
|
6258
|
+
resolve2();
|
|
6204
6259
|
};
|
|
6205
6260
|
const onKey = createKeypressHandler(useTUI, state, () => {
|
|
6206
6261
|
doShutdown();
|
|
@@ -6405,7 +6460,7 @@ function createGitRunner(spawnFn) {
|
|
|
6405
6460
|
}
|
|
6406
6461
|
var defaultGitRunner = createGitRunner(spawn);
|
|
6407
6462
|
function runBufferedProcess(spawnFn, command, commandArguments, cwd, shell, timeoutMs) {
|
|
6408
|
-
return new Promise((
|
|
6463
|
+
return new Promise((resolve2) => {
|
|
6409
6464
|
const proc = spawnFn(command, commandArguments, { cwd, shell });
|
|
6410
6465
|
const chunks = [];
|
|
6411
6466
|
let resolved = false;
|
|
@@ -6417,7 +6472,7 @@ function runBufferedProcess(spawnFn, command, commandArguments, cwd, shell, time
|
|
|
6417
6472
|
proc.stdout?.destroy();
|
|
6418
6473
|
proc.stderr?.destroy();
|
|
6419
6474
|
proc.unref();
|
|
6420
|
-
|
|
6475
|
+
resolve2({
|
|
6421
6476
|
exitCode: 1,
|
|
6422
6477
|
output: chunks.join(""),
|
|
6423
6478
|
timedOut: true
|
|
@@ -6435,14 +6490,14 @@ function runBufferedProcess(spawnFn, command, commandArguments, cwd, shell, time
|
|
|
6435
6490
|
return;
|
|
6436
6491
|
if (timer !== undefined)
|
|
6437
6492
|
clearTimeout(timer);
|
|
6438
|
-
|
|
6493
|
+
resolve2({ exitCode: code ?? 1, output: chunks.join("") });
|
|
6439
6494
|
});
|
|
6440
6495
|
proc.on("error", (error) => {
|
|
6441
6496
|
if (resolved)
|
|
6442
6497
|
return;
|
|
6443
6498
|
if (timer !== undefined)
|
|
6444
6499
|
clearTimeout(timer);
|
|
6445
|
-
|
|
6500
|
+
resolve2({ exitCode: 1, output: error.message });
|
|
6446
6501
|
});
|
|
6447
6502
|
});
|
|
6448
6503
|
}
|
|
@@ -6903,7 +6958,7 @@ function validateWorkflowTaskBodySection(filePath, content, ideasPath, fileSyste
|
|
|
6903
6958
|
}
|
|
6904
6959
|
|
|
6905
6960
|
// lib/lint/validators/link-validator.ts
|
|
6906
|
-
import { dirname as dirname3, resolve } from "node:path";
|
|
6961
|
+
import { dirname as dirname3, resolve as resolve2 } from "node:path";
|
|
6907
6962
|
var SEMANTIC_RULES = [
|
|
6908
6963
|
{
|
|
6909
6964
|
section: "## Principles",
|
|
@@ -6936,7 +6991,7 @@ function validateLinks(filePath, content, fileSystem) {
|
|
|
6936
6991
|
});
|
|
6937
6992
|
} else {
|
|
6938
6993
|
const targetPath = linkTarget.split("#")[0];
|
|
6939
|
-
const resolvedPath =
|
|
6994
|
+
const resolvedPath = resolve2(fileDir, targetPath);
|
|
6940
6995
|
if (!fileSystem.exists(resolvedPath)) {
|
|
6941
6996
|
violations.push({
|
|
6942
6997
|
file: filePath,
|
|
@@ -6989,7 +7044,7 @@ function validateSemanticLinks(filePath, content) {
|
|
|
6989
7044
|
continue;
|
|
6990
7045
|
}
|
|
6991
7046
|
const targetPath = linkTarget.split("#")[0];
|
|
6992
|
-
const resolvedPath =
|
|
7047
|
+
const resolvedPath = resolve2(fileDir, targetPath);
|
|
6993
7048
|
if (!resolvedPath.includes(rule.requiredPath)) {
|
|
6994
7049
|
violations.push({
|
|
6995
7050
|
file: filePath,
|
|
@@ -7040,7 +7095,7 @@ function validatePrincipleHierarchyLinks(filePath, content) {
|
|
|
7040
7095
|
continue;
|
|
7041
7096
|
}
|
|
7042
7097
|
const targetPath = linkTarget.split("#")[0];
|
|
7043
|
-
const resolvedPath =
|
|
7098
|
+
const resolvedPath = resolve2(fileDir, targetPath);
|
|
7044
7099
|
if (!resolvedPath.includes("/.dust/principles/")) {
|
|
7045
7100
|
violations.push({
|
|
7046
7101
|
file: filePath,
|
|
@@ -7055,7 +7110,7 @@ function validatePrincipleHierarchyLinks(filePath, content) {
|
|
|
7055
7110
|
}
|
|
7056
7111
|
|
|
7057
7112
|
// lib/lint/validators/principle-hierarchy.ts
|
|
7058
|
-
import { dirname as dirname4, resolve as
|
|
7113
|
+
import { dirname as dirname4, resolve as resolve3 } from "node:path";
|
|
7059
7114
|
var REQUIRED_PRINCIPLE_HEADINGS = ["## Parent Principle", "## Sub-Principles"];
|
|
7060
7115
|
function validatePrincipleHierarchySections(filePath, content) {
|
|
7061
7116
|
const violations = [];
|
|
@@ -7090,7 +7145,7 @@ function extractPrincipleRelationships(filePath, content) {
|
|
|
7090
7145
|
const linkTarget = match[2];
|
|
7091
7146
|
if (!linkTarget.startsWith("#") && !linkTarget.startsWith("http://") && !linkTarget.startsWith("https://")) {
|
|
7092
7147
|
const targetPath = linkTarget.split("#")[0];
|
|
7093
|
-
const resolvedPath =
|
|
7148
|
+
const resolvedPath = resolve3(fileDir, targetPath);
|
|
7094
7149
|
if (resolvedPath.includes("/.dust/principles/")) {
|
|
7095
7150
|
if (currentSection === "## Parent Principle") {
|
|
7096
7151
|
parentPrinciples.push(resolvedPath);
|
|
@@ -7742,7 +7797,7 @@ async function init(dependencies) {
|
|
|
7742
7797
|
}
|
|
7743
7798
|
|
|
7744
7799
|
// lib/cli/commands/list.ts
|
|
7745
|
-
import { basename as
|
|
7800
|
+
import { basename as basename3 } from "node:path";
|
|
7746
7801
|
function workflowTypeToStatus(type) {
|
|
7747
7802
|
switch (type) {
|
|
7748
7803
|
case "refine-idea":
|
|
@@ -7775,7 +7830,7 @@ async function buildPrincipleHierarchy(principlesPath, fileSystem) {
|
|
|
7775
7830
|
const filePath = `${principlesPath}/${file}`;
|
|
7776
7831
|
const content = await fileSystem.readFile(filePath);
|
|
7777
7832
|
relationships.push(extractPrincipleRelationships(filePath, content));
|
|
7778
|
-
const title = extractTitle(content) ||
|
|
7833
|
+
const title = extractTitle(content) || basename3(file, ".md");
|
|
7779
7834
|
titleMap.set(filePath, title);
|
|
7780
7835
|
}
|
|
7781
7836
|
const relMap = new Map;
|
|
@@ -7793,7 +7848,7 @@ async function buildPrincipleHierarchy(principlesPath, fileSystem) {
|
|
|
7793
7848
|
}
|
|
7794
7849
|
return {
|
|
7795
7850
|
filePath,
|
|
7796
|
-
title: titleMap.get(filePath) ||
|
|
7851
|
+
title: titleMap.get(filePath) || basename3(filePath, ".md"),
|
|
7797
7852
|
children
|
|
7798
7853
|
};
|
|
7799
7854
|
}
|
|
@@ -7812,7 +7867,12 @@ function renderHierarchy(nodes, output, prefix = "") {
|
|
|
7812
7867
|
}
|
|
7813
7868
|
}
|
|
7814
7869
|
async function list(dependencies) {
|
|
7815
|
-
const {
|
|
7870
|
+
const {
|
|
7871
|
+
arguments: commandArguments,
|
|
7872
|
+
context,
|
|
7873
|
+
fileSystem,
|
|
7874
|
+
settings
|
|
7875
|
+
} = dependencies;
|
|
7816
7876
|
const dustPath = `${context.cwd}/.dust`;
|
|
7817
7877
|
const colors = getColors();
|
|
7818
7878
|
if (!fileSystem.exists(dustPath)) {
|
|
@@ -7827,6 +7887,7 @@ async function list(dependencies) {
|
|
|
7827
7887
|
return { exitCode: 1 };
|
|
7828
7888
|
}
|
|
7829
7889
|
const specificTypeRequested = commandArguments.length > 0;
|
|
7890
|
+
const showTaskCreationHint = specificTypeRequested && typesToList.length === 1 && typesToList[0] === "tasks";
|
|
7830
7891
|
const workflowTasks = typesToList.includes("ideas") && fileSystem.exists(dustPath) ? await findAllWorkflowTasks(fileSystem, dustPath) : null;
|
|
7831
7892
|
for (const type of typesToList) {
|
|
7832
7893
|
const dirPath = `${dustPath}/${type}`;
|
|
@@ -7911,6 +7972,11 @@ async function list(dependencies) {
|
|
|
7911
7972
|
});
|
|
7912
7973
|
}
|
|
7913
7974
|
}
|
|
7975
|
+
if (showTaskCreationHint) {
|
|
7976
|
+
context.stdout("➕ Add a New Task");
|
|
7977
|
+
context.stdout("");
|
|
7978
|
+
context.stdout(`Run \`${settings.dustCommand} new task\``);
|
|
7979
|
+
}
|
|
7914
7980
|
return { exitCode: 0 };
|
|
7915
7981
|
}
|
|
7916
7982
|
|
|
@@ -8113,7 +8179,7 @@ function newTaskInstructions(vars) {
|
|
|
8113
8179
|
steps.push(" - The goal is a task description with minimal ambiguity at implementation time");
|
|
8114
8180
|
steps.push("4. Create a new markdown file in `.dust/tasks/` with a descriptive kebab-case name (e.g., `add-user-authentication.md`)");
|
|
8115
8181
|
steps.push("5. Add a title as the first line using an H1 heading (e.g., `# Add user authentication`)");
|
|
8116
|
-
steps.push('6. Write a comprehensive description starting with an imperative opening sentence (e.g., "Add caching to the API layer." not "This task adds caching."). Include technical details and references to relevant files.');
|
|
8182
|
+
steps.push('6. Write a comprehensive description starting with an imperative opening sentence (e.g., "Add caching to the API layer." not "This task adds caching."). The opening sentence must be 150 characters or fewer. Include technical details and references to relevant files.');
|
|
8117
8183
|
steps.push("7. Add a `## Principles` section with links to relevant principles this task supports (e.g., `- [Principle Name](../principles/principle-name.md)`)");
|
|
8118
8184
|
steps.push("8. Add a `## Blocked By` section listing any tasks that must complete first, or `(none)` if there are no blockers");
|
|
8119
8185
|
steps.push("9. Add a `## Definition of Done` section with a checklist of completion criteria using `- [ ]` for each item");
|