@joshski/dust 0.1.100 → 0.1.101
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/.dust/principles/actionable-errors.md +18 -0
- package/.dust/principles/agent-agnostic-design.md +21 -0
- package/.dust/principles/agent-autonomy.md +19 -0
- package/.dust/principles/agent-context-inference.md +19 -0
- package/.dust/principles/agent-specific-enhancement.md +23 -0
- package/.dust/principles/atomic-commits.md +15 -0
- package/.dust/principles/batteries-included.md +17 -0
- package/.dust/principles/boy-scout-rule.md +15 -0
- package/.dust/principles/broken-windows.md +17 -0
- package/.dust/principles/clarity-over-brevity.md +13 -0
- package/.dust/principles/co-located-tests.md +13 -0
- package/.dust/principles/comprehensive-assertions.md +50 -0
- package/.dust/principles/comprehensive-test-coverage.md +15 -0
- package/.dust/principles/consistent-naming.md +13 -0
- package/.dust/principles/context-optimised-code.md +15 -0
- package/.dust/principles/context-window-efficiency.md +15 -0
- package/.dust/principles/cross-platform-compatibility.md +19 -0
- package/.dust/principles/debugging-tooling.md +19 -0
- package/.dust/principles/decoupled-code.md +16 -0
- package/.dust/principles/dependency-injection.md +15 -0
- package/.dust/principles/design-for-testability.md +17 -0
- package/.dust/principles/development-traceability.md +19 -0
- package/.dust/principles/easy-adoption.md +17 -0
- package/.dust/principles/enable-flow-state.md +20 -0
- package/.dust/principles/environment-independent-tests.md +19 -0
- package/.dust/principles/exploratory-tooling.md +19 -0
- package/.dust/principles/fast-feedback-loops.md +15 -0
- package/.dust/principles/fast-feedback.md +13 -0
- package/.dust/principles/functional-core-imperative-shell.md +15 -0
- package/.dust/principles/human-ai-collaboration.md +18 -0
- package/.dust/principles/ideal-agent-developer-experience.md +24 -0
- package/.dust/principles/intuitive-directory-structure.md +13 -0
- package/.dust/principles/keep-unit-tests-pure.md +25 -0
- package/.dust/principles/lightweight-planning.md +16 -0
- package/.dust/principles/lint-everything.md +19 -0
- package/.dust/principles/maintainable-codebase.md +21 -0
- package/.dust/principles/make-changes-with-confidence.md +23 -0
- package/.dust/principles/make-the-change-easy.md +15 -0
- package/.dust/principles/minimal-dependencies.md +13 -0
- package/.dust/principles/naming-matters.md +14 -0
- package/.dust/principles/progressive-disclosure.md +15 -0
- package/.dust/principles/readable-test-data.md +48 -0
- package/.dust/principles/reasonably-dry.md +13 -0
- package/.dust/principles/repository-hygiene.md +14 -0
- package/.dust/principles/reproducible-checks.md +13 -0
- package/.dust/principles/runtime-agnostic-tests.md +13 -0
- package/.dust/principles/self-contained-repository.md +17 -0
- package/.dust/principles/self-diagnosing-tests.md +54 -0
- package/.dust/principles/slow-feedback-coping.md +15 -0
- package/.dust/principles/small-units.md +17 -0
- package/.dust/principles/some-big-design-up-front.md +34 -0
- package/.dust/principles/stop-the-line.md +13 -0
- package/.dust/principles/stubs-over-mocks.md +19 -0
- package/.dust/principles/task-first-workflow.md +13 -0
- package/.dust/principles/test-isolation.md +19 -0
- package/.dust/principles/traceable-decisions.md +13 -0
- package/.dust/principles/trunk-based-development.md +19 -0
- package/.dust/principles/unit-test-coverage.md +13 -0
- package/.dust/principles/unsurprising-ux.md +15 -0
- package/.dust/principles/vcs-independence.md +13 -0
- package/dist/bucket/repository.d.ts +28 -0
- package/dist/cli/types.d.ts +1 -0
- package/dist/core-principles.js +184 -0
- package/dist/dust.js +470 -54
- package/dist/lint/validators/principle-hierarchy.d.ts +0 -1
- package/dist/patch/index.d.ts +8 -0
- package/dist/patch.js +54 -1
- package/package.json +7 -2
package/dist/dust.js
CHANGED
|
@@ -7,7 +7,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
7
7
|
var require_package = __commonJS((exports, module) => {
|
|
8
8
|
module.exports = {
|
|
9
9
|
name: "@joshski/dust",
|
|
10
|
-
version: "0.1.
|
|
10
|
+
version: "0.1.101",
|
|
11
11
|
description: "Flow state for AI coding agents",
|
|
12
12
|
type: "module",
|
|
13
13
|
bin: {
|
|
@@ -52,13 +52,18 @@ var require_package = __commonJS((exports, module) => {
|
|
|
52
52
|
"./patch": {
|
|
53
53
|
import: "./dist/patch.js",
|
|
54
54
|
types: "./dist/patch/index.d.ts"
|
|
55
|
+
},
|
|
56
|
+
"./core-principles": {
|
|
57
|
+
import: "./dist/core-principles.js",
|
|
58
|
+
types: "./dist/core-principles.d.ts"
|
|
55
59
|
}
|
|
56
60
|
},
|
|
57
61
|
files: [
|
|
58
62
|
"dist",
|
|
59
63
|
"bin",
|
|
60
64
|
"lib/istanbul/minimal-reporter.cjs",
|
|
61
|
-
"biome"
|
|
65
|
+
"biome",
|
|
66
|
+
".dust/principles"
|
|
62
67
|
],
|
|
63
68
|
repository: {
|
|
64
69
|
type: "git",
|
|
@@ -328,12 +333,12 @@ function readEnvConfig(env) {
|
|
|
328
333
|
}
|
|
329
334
|
|
|
330
335
|
// lib/cli/wire.ts
|
|
331
|
-
import { existsSync as
|
|
336
|
+
import { existsSync as existsSync3, statSync as statSync3 } from "node:fs";
|
|
332
337
|
import {
|
|
333
338
|
chmod as chmod2,
|
|
334
339
|
mkdir as mkdir2,
|
|
335
340
|
readdir as readdir2,
|
|
336
|
-
readFile as
|
|
341
|
+
readFile as readFile4,
|
|
337
342
|
rename,
|
|
338
343
|
writeFile as writeFile2
|
|
339
344
|
} from "node:fs/promises";
|
|
@@ -359,6 +364,7 @@ import { join as join2 } from "node:path";
|
|
|
359
364
|
var KNOWN_SETTINGS_KEYS = new Set([
|
|
360
365
|
"dustCommand",
|
|
361
366
|
"checks",
|
|
367
|
+
"excludeCorePrinciples",
|
|
362
368
|
"extraDirectories",
|
|
363
369
|
"installCommand",
|
|
364
370
|
"eventsUrl"
|
|
@@ -465,6 +471,23 @@ function validateDustEventsUrl(settings) {
|
|
|
465
471
|
}
|
|
466
472
|
return [];
|
|
467
473
|
}
|
|
474
|
+
function validateExcludeCorePrinciples(settings) {
|
|
475
|
+
if (!("excludeCorePrinciples" in settings)) {
|
|
476
|
+
return [];
|
|
477
|
+
}
|
|
478
|
+
if (!Array.isArray(settings.excludeCorePrinciples)) {
|
|
479
|
+
return [{ message: '"excludeCorePrinciples" must be an array of strings' }];
|
|
480
|
+
}
|
|
481
|
+
const violations = [];
|
|
482
|
+
for (let i = 0;i < settings.excludeCorePrinciples.length; i++) {
|
|
483
|
+
if (typeof settings.excludeCorePrinciples[i] !== "string") {
|
|
484
|
+
violations.push({
|
|
485
|
+
message: `excludeCorePrinciples[${i}] must be a string`
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return violations;
|
|
490
|
+
}
|
|
468
491
|
function validateSettingsJson(content) {
|
|
469
492
|
const violations = [];
|
|
470
493
|
let parsed;
|
|
@@ -491,6 +514,7 @@ function validateSettingsJson(content) {
|
|
|
491
514
|
}
|
|
492
515
|
}
|
|
493
516
|
violations.push(...validateChecksConfig(settings));
|
|
517
|
+
violations.push(...validateExcludeCorePrinciples(settings));
|
|
494
518
|
violations.push(...validateExtraDirectories(settings));
|
|
495
519
|
violations.push(...validateDustEventsUrl(settings));
|
|
496
520
|
if ("dustCommand" in settings && typeof settings.dustCommand !== "string") {
|
|
@@ -639,7 +663,7 @@ async function loadSettings(cwd, fileSystem, runtime) {
|
|
|
639
663
|
}
|
|
640
664
|
|
|
641
665
|
// lib/version.ts
|
|
642
|
-
var DUST_VERSION = "0.1.
|
|
666
|
+
var DUST_VERSION = "0.1.101";
|
|
643
667
|
|
|
644
668
|
// lib/session.ts
|
|
645
669
|
var DUST_UNATTENDED = "DUST_UNATTENDED";
|
|
@@ -6979,6 +7003,36 @@ function startRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
|
|
|
6979
7003
|
function shouldRecloneForBranchChange(existing, incoming) {
|
|
6980
7004
|
return existing.branch !== incoming.branch;
|
|
6981
7005
|
}
|
|
7006
|
+
function computeRepositoryReconciliation(existing, incoming) {
|
|
7007
|
+
const actions = [];
|
|
7008
|
+
for (const [name, repo] of incoming) {
|
|
7009
|
+
const existingRepo = existing.get(name);
|
|
7010
|
+
if (!existingRepo) {
|
|
7011
|
+
actions.push({ type: "add", repository: repo });
|
|
7012
|
+
} else if (shouldRecloneForBranchChange(existingRepo, repo)) {
|
|
7013
|
+
const from = existingRepo.branch ?? "(default)";
|
|
7014
|
+
const to = repo.branch ?? "(default)";
|
|
7015
|
+
actions.push({
|
|
7016
|
+
type: "reclone",
|
|
7017
|
+
name,
|
|
7018
|
+
repository: repo,
|
|
7019
|
+
reason: `branch changed from ${from} to ${to}`
|
|
7020
|
+
});
|
|
7021
|
+
} else if (existingRepo.agentProvider !== repo.agentProvider) {
|
|
7022
|
+
actions.push({
|
|
7023
|
+
type: "updateProvider",
|
|
7024
|
+
name,
|
|
7025
|
+
newProvider: repo.agentProvider
|
|
7026
|
+
});
|
|
7027
|
+
}
|
|
7028
|
+
}
|
|
7029
|
+
for (const name of existing.keys()) {
|
|
7030
|
+
if (!incoming.has(name)) {
|
|
7031
|
+
actions.push({ type: "remove", name });
|
|
7032
|
+
}
|
|
7033
|
+
}
|
|
7034
|
+
return actions;
|
|
7035
|
+
}
|
|
6982
7036
|
function parseRepository(data) {
|
|
6983
7037
|
if (typeof data === "object" && data !== null && "name" in data && "gitUrl" in data) {
|
|
6984
7038
|
const repositoryData = data;
|
|
@@ -7070,34 +7124,46 @@ async function removeRepositoryFromManager(repoName, manager, repoDeps, context)
|
|
|
7070
7124
|
manager.emit(removedEvent);
|
|
7071
7125
|
context.stdout(formatBucketEvent(removedEvent));
|
|
7072
7126
|
}
|
|
7073
|
-
|
|
7074
|
-
const
|
|
7075
|
-
for (const
|
|
7076
|
-
const repo = parseRepository(
|
|
7127
|
+
function parseRepositoryList(data) {
|
|
7128
|
+
const repos = new Map;
|
|
7129
|
+
for (const item of data) {
|
|
7130
|
+
const repo = parseRepository(item);
|
|
7077
7131
|
if (repo) {
|
|
7078
|
-
|
|
7079
|
-
}
|
|
7080
|
-
}
|
|
7081
|
-
for (const [name, repo] of incomingRepos) {
|
|
7082
|
-
const existing = manager.repositories.get(name);
|
|
7083
|
-
if (!existing) {
|
|
7084
|
-
await addRepository(repo, manager, repoDeps, context);
|
|
7085
|
-
} else if (shouldRecloneForBranchChange(existing.repository, repo)) {
|
|
7086
|
-
const from = existing.repository.branch ?? "(default)";
|
|
7087
|
-
const to = repo.branch ?? "(default)";
|
|
7088
|
-
log8(`${name}: branch changed from ${from} to ${to}, re-cloning`);
|
|
7089
|
-
await removeRepositoryFromManager(name, manager, repoDeps, context);
|
|
7090
|
-
await addRepository(repo, manager, repoDeps, context);
|
|
7091
|
-
} else if (existing.repository.agentProvider !== repo.agentProvider) {
|
|
7092
|
-
const from = existing.repository.agentProvider ?? "(unset)";
|
|
7093
|
-
const to = repo.agentProvider ?? "(unset)";
|
|
7094
|
-
log8(`${name}: agentProvider changed from ${from} to ${to}`);
|
|
7095
|
-
existing.repository.agentProvider = repo.agentProvider;
|
|
7132
|
+
repos.set(repo.name, repo);
|
|
7096
7133
|
}
|
|
7097
7134
|
}
|
|
7098
|
-
|
|
7099
|
-
|
|
7100
|
-
|
|
7135
|
+
return repos;
|
|
7136
|
+
}
|
|
7137
|
+
async function handleRepositoryList(repositories, manager, repoDeps, context) {
|
|
7138
|
+
const incoming = parseRepositoryList(repositories);
|
|
7139
|
+
const existing = new Map;
|
|
7140
|
+
for (const [name, state] of manager.repositories) {
|
|
7141
|
+
existing.set(name, state.repository);
|
|
7142
|
+
}
|
|
7143
|
+
const actions = computeRepositoryReconciliation(existing, incoming);
|
|
7144
|
+
for (const action of actions) {
|
|
7145
|
+
switch (action.type) {
|
|
7146
|
+
case "add":
|
|
7147
|
+
await addRepository(action.repository, manager, repoDeps, context);
|
|
7148
|
+
break;
|
|
7149
|
+
case "remove":
|
|
7150
|
+
await removeRepositoryFromManager(action.name, manager, repoDeps, context);
|
|
7151
|
+
break;
|
|
7152
|
+
case "reclone":
|
|
7153
|
+
log8(`${action.name}: ${action.reason}, re-cloning`);
|
|
7154
|
+
await removeRepositoryFromManager(action.name, manager, repoDeps, context);
|
|
7155
|
+
await addRepository(action.repository, manager, repoDeps, context);
|
|
7156
|
+
break;
|
|
7157
|
+
case "updateProvider": {
|
|
7158
|
+
const repoState = manager.repositories.get(action.name);
|
|
7159
|
+
if (!repoState)
|
|
7160
|
+
break;
|
|
7161
|
+
const from = repoState.repository.agentProvider ?? "(unset)";
|
|
7162
|
+
const to = action.newProvider ?? "(unset)";
|
|
7163
|
+
log8(`${action.name}: agentProvider changed from ${from} to ${to}`);
|
|
7164
|
+
repoState.repository.agentProvider = action.newProvider;
|
|
7165
|
+
break;
|
|
7166
|
+
}
|
|
7101
7167
|
}
|
|
7102
7168
|
}
|
|
7103
7169
|
}
|
|
@@ -7199,7 +7265,7 @@ function parseRepositoryItem(r) {
|
|
|
7199
7265
|
}
|
|
7200
7266
|
return item;
|
|
7201
7267
|
}
|
|
7202
|
-
function
|
|
7268
|
+
function parseRepositoryList2(message) {
|
|
7203
7269
|
if (!Array.isArray(message.repositories)) {
|
|
7204
7270
|
return null;
|
|
7205
7271
|
}
|
|
@@ -7269,7 +7335,7 @@ function parseConnectionRejected(message) {
|
|
|
7269
7335
|
return result;
|
|
7270
7336
|
}
|
|
7271
7337
|
var messageParsers = {
|
|
7272
|
-
"repository-list":
|
|
7338
|
+
"repository-list": parseRepositoryList2,
|
|
7273
7339
|
"task-available": parseTaskAvailable,
|
|
7274
7340
|
"tool-definitions": parseToolDefinitions,
|
|
7275
7341
|
"connection-ready": parseConnectionReady,
|
|
@@ -9056,6 +9122,57 @@ Run \`dust bucket tool ${toolName}\` to see available operations.`);
|
|
|
9056
9122
|
// lib/cli/commands/lint-markdown.ts
|
|
9057
9123
|
import { isAbsolute, join as join9, relative, sep } from "node:path";
|
|
9058
9124
|
|
|
9125
|
+
// lib/artifacts/principles.ts
|
|
9126
|
+
function extractLinksFromSection(content, sectionHeading) {
|
|
9127
|
+
const lines = content.split(`
|
|
9128
|
+
`);
|
|
9129
|
+
const links = [];
|
|
9130
|
+
let inSection = false;
|
|
9131
|
+
for (const line of lines) {
|
|
9132
|
+
if (line.startsWith("## ")) {
|
|
9133
|
+
inSection = line.trimEnd() === `## ${sectionHeading}`;
|
|
9134
|
+
continue;
|
|
9135
|
+
}
|
|
9136
|
+
if (!inSection)
|
|
9137
|
+
continue;
|
|
9138
|
+
if (line.startsWith("# "))
|
|
9139
|
+
break;
|
|
9140
|
+
const linkMatch = line.match(MARKDOWN_LINK_PATTERN);
|
|
9141
|
+
if (linkMatch) {
|
|
9142
|
+
const target = linkMatch[2];
|
|
9143
|
+
const slugMatch = target.match(/([^/]+)\.md$/);
|
|
9144
|
+
if (slugMatch) {
|
|
9145
|
+
links.push(slugMatch[1]);
|
|
9146
|
+
}
|
|
9147
|
+
}
|
|
9148
|
+
}
|
|
9149
|
+
return links;
|
|
9150
|
+
}
|
|
9151
|
+
function extractSingleLinkFromSection(content, sectionHeading) {
|
|
9152
|
+
const links = extractLinksFromSection(content, sectionHeading);
|
|
9153
|
+
return links.length === 1 ? links[0] : null;
|
|
9154
|
+
}
|
|
9155
|
+
async function parsePrinciple(fileSystem, dustPath, slug) {
|
|
9156
|
+
const principlePath = `${dustPath}/principles/${slug}.md`;
|
|
9157
|
+
if (!fileSystem.exists(principlePath)) {
|
|
9158
|
+
throw new Error(`Principle not found: "${slug}" (expected file at ${principlePath})`);
|
|
9159
|
+
}
|
|
9160
|
+
const content = await fileSystem.readFile(principlePath);
|
|
9161
|
+
const title = extractTitle(content);
|
|
9162
|
+
if (!title) {
|
|
9163
|
+
throw new Error(`Principle file has no title: ${principlePath}`);
|
|
9164
|
+
}
|
|
9165
|
+
const parentPrinciple = extractSingleLinkFromSection(content, "Parent Principle");
|
|
9166
|
+
const subPrinciples = extractLinksFromSection(content, "Sub-Principles");
|
|
9167
|
+
return {
|
|
9168
|
+
slug,
|
|
9169
|
+
title,
|
|
9170
|
+
content,
|
|
9171
|
+
parentPrinciple,
|
|
9172
|
+
subPrinciples
|
|
9173
|
+
};
|
|
9174
|
+
}
|
|
9175
|
+
|
|
9059
9176
|
// lib/artifacts/index.ts
|
|
9060
9177
|
var ARTIFACT_TYPES = [
|
|
9061
9178
|
"facts",
|
|
@@ -10250,7 +10367,119 @@ async function init(dependencies) {
|
|
|
10250
10367
|
}
|
|
10251
10368
|
|
|
10252
10369
|
// lib/cli/commands/list.ts
|
|
10253
|
-
import { basename as basename3 } from "node:path";
|
|
10370
|
+
import { basename as basename3, resolve as resolve4 } from "node:path";
|
|
10371
|
+
|
|
10372
|
+
// lib/core-principles.ts
|
|
10373
|
+
import { join as join10, dirname as dirname5 } from "node:path";
|
|
10374
|
+
import { fileURLToPath } from "node:url";
|
|
10375
|
+
import { existsSync, readdirSync, statSync as statSync2 } from "node:fs";
|
|
10376
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
10377
|
+
|
|
10378
|
+
// lib/artifacts/core-principles.ts
|
|
10379
|
+
function sortNodes(nodes) {
|
|
10380
|
+
nodes.sort((a, b) => a.title.localeCompare(b.title));
|
|
10381
|
+
for (const node of nodes) {
|
|
10382
|
+
sortNodes(node.children);
|
|
10383
|
+
}
|
|
10384
|
+
}
|
|
10385
|
+
function isInternalPrinciple(principleContent) {
|
|
10386
|
+
const lines = principleContent.split(`
|
|
10387
|
+
`);
|
|
10388
|
+
let inApplicabilitySection = false;
|
|
10389
|
+
for (const line of lines) {
|
|
10390
|
+
if (line.startsWith("## ")) {
|
|
10391
|
+
inApplicabilitySection = line.trimEnd() === "## Applicability";
|
|
10392
|
+
continue;
|
|
10393
|
+
}
|
|
10394
|
+
if (inApplicabilitySection) {
|
|
10395
|
+
if (line.startsWith("# "))
|
|
10396
|
+
break;
|
|
10397
|
+
if (line.trim() === "Internal") {
|
|
10398
|
+
return true;
|
|
10399
|
+
}
|
|
10400
|
+
}
|
|
10401
|
+
}
|
|
10402
|
+
return false;
|
|
10403
|
+
}
|
|
10404
|
+
function getCorePrincipleTree(allPrinciples, config) {
|
|
10405
|
+
const excludeSet = new Set(config.excludeCorePrinciples ?? []);
|
|
10406
|
+
const filteredPrinciples = allPrinciples.filter((p) => !isInternalPrinciple(p.content) && !excludeSet.has(p.slug));
|
|
10407
|
+
const filteredSlugs = new Set(filteredPrinciples.map((p) => p.slug));
|
|
10408
|
+
const nodeBySlug = new Map;
|
|
10409
|
+
for (const p of filteredPrinciples) {
|
|
10410
|
+
nodeBySlug.set(p.slug, {
|
|
10411
|
+
slug: p.slug,
|
|
10412
|
+
title: p.title,
|
|
10413
|
+
children: []
|
|
10414
|
+
});
|
|
10415
|
+
}
|
|
10416
|
+
const roots = [];
|
|
10417
|
+
for (const p of filteredPrinciples) {
|
|
10418
|
+
const node = nodeBySlug.get(p.slug);
|
|
10419
|
+
const parentSlug = p.parentPrinciple;
|
|
10420
|
+
if (!parentSlug || !filteredSlugs.has(parentSlug)) {
|
|
10421
|
+
roots.push(node);
|
|
10422
|
+
} else {
|
|
10423
|
+
const parentNode = nodeBySlug.get(parentSlug);
|
|
10424
|
+
if (parentNode) {
|
|
10425
|
+
parentNode.children.push(node);
|
|
10426
|
+
}
|
|
10427
|
+
}
|
|
10428
|
+
}
|
|
10429
|
+
sortNodes(roots);
|
|
10430
|
+
return roots;
|
|
10431
|
+
}
|
|
10432
|
+
|
|
10433
|
+
// lib/core-principles.ts
|
|
10434
|
+
function createReadableFileSystem() {
|
|
10435
|
+
return {
|
|
10436
|
+
exists: existsSync,
|
|
10437
|
+
isDirectory: (path2) => {
|
|
10438
|
+
try {
|
|
10439
|
+
return statSync2(path2).isDirectory();
|
|
10440
|
+
} catch {
|
|
10441
|
+
return false;
|
|
10442
|
+
}
|
|
10443
|
+
},
|
|
10444
|
+
readFile: (path2) => readFile3(path2, "utf-8"),
|
|
10445
|
+
readdir: async (path2) => readdirSync(path2)
|
|
10446
|
+
};
|
|
10447
|
+
}
|
|
10448
|
+
function locatePackagePrinciplesDir() {
|
|
10449
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
10450
|
+
const thisDir = dirname5(thisFile);
|
|
10451
|
+
const packageRoot = dirname5(thisDir);
|
|
10452
|
+
const principlesDir = join10(packageRoot, ".dust", "principles");
|
|
10453
|
+
if (!existsSync(principlesDir)) {
|
|
10454
|
+
throw new Error(`Core principles directory not found at ${principlesDir}. ` + "Ensure the @joshski/dust package is properly installed.");
|
|
10455
|
+
}
|
|
10456
|
+
return principlesDir;
|
|
10457
|
+
}
|
|
10458
|
+
async function readAllCorePrinciples() {
|
|
10459
|
+
const principlesDir = locatePackagePrinciplesDir();
|
|
10460
|
+
const packageRoot = dirname5(dirname5(principlesDir));
|
|
10461
|
+
const dustPath = join10(packageRoot, ".dust");
|
|
10462
|
+
const fileSystem = createReadableFileSystem();
|
|
10463
|
+
const files = readdirSync(principlesDir);
|
|
10464
|
+
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
10465
|
+
const principles = [];
|
|
10466
|
+
for (const file of mdFiles) {
|
|
10467
|
+
const slug = file.replace(/\.md$/, "");
|
|
10468
|
+
const principle = await parsePrinciple(fileSystem, dustPath, slug);
|
|
10469
|
+
principles.push(principle);
|
|
10470
|
+
}
|
|
10471
|
+
return principles;
|
|
10472
|
+
}
|
|
10473
|
+
async function getCorePrincipleHierarchy(config = {}) {
|
|
10474
|
+
const allPrinciples = await readAllCorePrinciples();
|
|
10475
|
+
return getCorePrincipleTree(allPrinciples, config);
|
|
10476
|
+
}
|
|
10477
|
+
function getCorePrinciplesPath() {
|
|
10478
|
+
const principlesDir = locatePackagePrinciplesDir();
|
|
10479
|
+
return `${principlesDir}/`;
|
|
10480
|
+
}
|
|
10481
|
+
|
|
10482
|
+
// lib/cli/commands/list.ts
|
|
10254
10483
|
function workflowTypeToStatus(type) {
|
|
10255
10484
|
switch (type) {
|
|
10256
10485
|
case "refine-idea":
|
|
@@ -10301,6 +10530,24 @@ function emitListEvent(emitEvent, type, items) {
|
|
|
10301
10530
|
});
|
|
10302
10531
|
}
|
|
10303
10532
|
}
|
|
10533
|
+
function formatPrincipleEntry(slug, openingSentence) {
|
|
10534
|
+
const lines = [`* ${slug}.md`];
|
|
10535
|
+
if (openingSentence) {
|
|
10536
|
+
lines.push(` ${openingSentence}`);
|
|
10537
|
+
}
|
|
10538
|
+
return lines;
|
|
10539
|
+
}
|
|
10540
|
+
function formatPrinciplesSection(header, entries) {
|
|
10541
|
+
if (entries.length === 0) {
|
|
10542
|
+
return [];
|
|
10543
|
+
}
|
|
10544
|
+
const lines = [header, ""];
|
|
10545
|
+
for (const entry of entries) {
|
|
10546
|
+
lines.push(...formatPrincipleEntry(entry.slug, entry.openingSentence));
|
|
10547
|
+
lines.push("");
|
|
10548
|
+
}
|
|
10549
|
+
return lines;
|
|
10550
|
+
}
|
|
10304
10551
|
async function buildPrincipleHierarchy(principlesPath, fileSystem) {
|
|
10305
10552
|
const files = await fileSystem.readdir(principlesPath);
|
|
10306
10553
|
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
@@ -10347,6 +10594,18 @@ function renderHierarchy(nodes, output, prefix = "") {
|
|
|
10347
10594
|
}
|
|
10348
10595
|
}
|
|
10349
10596
|
}
|
|
10597
|
+
function renderCorePrincipleHierarchy(nodes, output, prefix = "") {
|
|
10598
|
+
for (let i = 0;i < nodes.length; i++) {
|
|
10599
|
+
const node = nodes[i];
|
|
10600
|
+
const isLastNode = i === nodes.length - 1;
|
|
10601
|
+
const connector = isLastNode ? "└── " : "├── ";
|
|
10602
|
+
const childPrefix = isLastNode ? " " : "│ ";
|
|
10603
|
+
output(`${prefix}${connector}${node.title}`);
|
|
10604
|
+
if (node.children.length > 0) {
|
|
10605
|
+
renderCorePrincipleHierarchy(node.children, output, prefix + childPrefix);
|
|
10606
|
+
}
|
|
10607
|
+
}
|
|
10608
|
+
}
|
|
10350
10609
|
function parseTypesToList(commandArguments) {
|
|
10351
10610
|
if (commandArguments.length === 0) {
|
|
10352
10611
|
return [...ARTIFACT_TYPES];
|
|
@@ -10383,14 +10642,6 @@ function outputArtifact(parameters) {
|
|
|
10383
10642
|
async function processListType(context) {
|
|
10384
10643
|
const { type, dirPath, mdFiles, colors, fileSystem, workflowTasks } = context;
|
|
10385
10644
|
const { stdout, emitEvent } = context;
|
|
10386
|
-
if (type === "principles") {
|
|
10387
|
-
const hierarchy = await buildPrincipleHierarchy(dirPath, fileSystem);
|
|
10388
|
-
if (hierarchy.length > 0) {
|
|
10389
|
-
stdout(`${colors.dim}Hierarchy:${colors.reset}`);
|
|
10390
|
-
renderHierarchy(hierarchy, (line) => stdout(line));
|
|
10391
|
-
stdout("");
|
|
10392
|
-
}
|
|
10393
|
-
}
|
|
10394
10645
|
const collectedItems = [];
|
|
10395
10646
|
for (const file of mdFiles) {
|
|
10396
10647
|
const filePath = `${dirPath}/${file}`;
|
|
@@ -10417,6 +10668,101 @@ async function processListType(context) {
|
|
|
10417
10668
|
emitListEvent(emitEvent, type, collectedItems);
|
|
10418
10669
|
}
|
|
10419
10670
|
}
|
|
10671
|
+
async function loadCorePrinciples(localDirPath, excludeSet) {
|
|
10672
|
+
const corePath = getCorePrinciplesPath().replace(/\/$/, "");
|
|
10673
|
+
if (resolve4(localDirPath) === resolve4(corePath)) {
|
|
10674
|
+
return [];
|
|
10675
|
+
}
|
|
10676
|
+
const allCorePrinciples = await readAllCorePrinciples();
|
|
10677
|
+
return allCorePrinciples.filter((p) => !isInternalPrinciple(p.content) && !excludeSet.has(p.slug));
|
|
10678
|
+
}
|
|
10679
|
+
async function processPrinciplesList(context) {
|
|
10680
|
+
const { dustPath, colors, fileSystem, stdout, emitEvent } = context;
|
|
10681
|
+
const { excludeCorePrinciples, tree } = context;
|
|
10682
|
+
const excludeSet = new Set(excludeCorePrinciples ?? []);
|
|
10683
|
+
const localDirPath = `${dustPath}/principles`;
|
|
10684
|
+
const corePrinciples = await loadCorePrinciples(localDirPath, excludeSet);
|
|
10685
|
+
const hasCorePrinciples = corePrinciples.length > 0;
|
|
10686
|
+
const localDirExists = fileSystem.exists(localDirPath);
|
|
10687
|
+
const localFiles = localDirExists ? await fileSystem.readdir(localDirPath) : [];
|
|
10688
|
+
const localMdFiles = localFiles.filter((f) => f.endsWith(".md")).toSorted();
|
|
10689
|
+
const hasLocalPrinciples = localMdFiles.length > 0;
|
|
10690
|
+
if (!hasCorePrinciples && !hasLocalPrinciples) {
|
|
10691
|
+
return false;
|
|
10692
|
+
}
|
|
10693
|
+
stdout(SECTION_HEADERS["principles"]);
|
|
10694
|
+
stdout("");
|
|
10695
|
+
stdout(TYPE_EXPLANATIONS["principles"]);
|
|
10696
|
+
stdout("");
|
|
10697
|
+
if (tree) {
|
|
10698
|
+
if (hasCorePrinciples) {
|
|
10699
|
+
const coreHierarchy = await getCorePrincipleHierarchy({
|
|
10700
|
+
excludeCorePrinciples
|
|
10701
|
+
});
|
|
10702
|
+
stdout(`${colors.bold}Core${colors.reset}`);
|
|
10703
|
+
renderCorePrincipleHierarchy(coreHierarchy, (line) => stdout(line));
|
|
10704
|
+
stdout("");
|
|
10705
|
+
}
|
|
10706
|
+
if (hasLocalPrinciples) {
|
|
10707
|
+
const localHierarchy = await buildPrincipleHierarchy(localDirPath, fileSystem);
|
|
10708
|
+
stdout(`${colors.bold}Local${colors.reset}`);
|
|
10709
|
+
renderHierarchy(localHierarchy, (line) => stdout(line));
|
|
10710
|
+
stdout("");
|
|
10711
|
+
const collectedItems = [];
|
|
10712
|
+
for (const file of localMdFiles) {
|
|
10713
|
+
const filePath = `${localDirPath}/${file}`;
|
|
10714
|
+
const content = await fileSystem.readFile(filePath);
|
|
10715
|
+
const title = extractTitle(content);
|
|
10716
|
+
const relativePath = `.dust/principles/${file}`;
|
|
10717
|
+
const slug = file.replace(".md", "");
|
|
10718
|
+
const displayTitle = title || slug;
|
|
10719
|
+
collectedItems.push({ path: relativePath, title: displayTitle });
|
|
10720
|
+
}
|
|
10721
|
+
if (emitEvent) {
|
|
10722
|
+
emitListEvent(emitEvent, "principles", collectedItems);
|
|
10723
|
+
}
|
|
10724
|
+
} else if (emitEvent) {
|
|
10725
|
+
emitListEvent(emitEvent, "principles", []);
|
|
10726
|
+
}
|
|
10727
|
+
} else {
|
|
10728
|
+
if (hasCorePrinciples) {
|
|
10729
|
+
const corePath = getCorePrinciplesPath();
|
|
10730
|
+
const coreEntries = corePrinciples.toSorted((a, b) => a.slug.localeCompare(b.slug)).map((p) => ({
|
|
10731
|
+
slug: p.slug,
|
|
10732
|
+
openingSentence: extractOpeningSentence(p.content)
|
|
10733
|
+
}));
|
|
10734
|
+
const coreLines = formatPrinciplesSection(`\uD83C\uDFAF Core Principles (${corePath})`, coreEntries);
|
|
10735
|
+
for (const line of coreLines) {
|
|
10736
|
+
stdout(line);
|
|
10737
|
+
}
|
|
10738
|
+
}
|
|
10739
|
+
if (hasLocalPrinciples) {
|
|
10740
|
+
const localEntries = [];
|
|
10741
|
+
const collectedItems = [];
|
|
10742
|
+
for (const file of localMdFiles) {
|
|
10743
|
+
const filePath = `${localDirPath}/${file}`;
|
|
10744
|
+
const content = await fileSystem.readFile(filePath);
|
|
10745
|
+
const title = extractTitle(content);
|
|
10746
|
+
const openingSentence = extractOpeningSentence(content);
|
|
10747
|
+
const relativePath = `.dust/principles/${file}`;
|
|
10748
|
+
const slug = file.replace(".md", "");
|
|
10749
|
+
const displayTitle = title || slug;
|
|
10750
|
+
localEntries.push({ slug, openingSentence });
|
|
10751
|
+
collectedItems.push({ path: relativePath, title: displayTitle });
|
|
10752
|
+
}
|
|
10753
|
+
const localLines = formatPrinciplesSection("\uD83C\uDFAF Local Principles (.dust/principles/)", localEntries);
|
|
10754
|
+
for (const line of localLines) {
|
|
10755
|
+
stdout(line);
|
|
10756
|
+
}
|
|
10757
|
+
if (emitEvent) {
|
|
10758
|
+
emitListEvent(emitEvent, "principles", collectedItems);
|
|
10759
|
+
}
|
|
10760
|
+
} else if (emitEvent) {
|
|
10761
|
+
emitListEvent(emitEvent, "principles", []);
|
|
10762
|
+
}
|
|
10763
|
+
}
|
|
10764
|
+
return true;
|
|
10765
|
+
}
|
|
10420
10766
|
async function list(dependencies) {
|
|
10421
10767
|
const {
|
|
10422
10768
|
arguments: commandArguments,
|
|
@@ -10431,16 +10777,41 @@ async function list(dependencies) {
|
|
|
10431
10777
|
context.stderr("Run 'dust init' to initialize a Dust repository");
|
|
10432
10778
|
return { exitCode: 1 };
|
|
10433
10779
|
}
|
|
10434
|
-
const
|
|
10435
|
-
|
|
10436
|
-
|
|
10780
|
+
const treeFlag = commandArguments.includes("--tree");
|
|
10781
|
+
const argsWithoutFlags = commandArguments.filter((a) => !a.startsWith("--"));
|
|
10782
|
+
const typesToList = parseTypesToList(argsWithoutFlags);
|
|
10783
|
+
if (argsWithoutFlags.length > 0 && typesToList.length === 0) {
|
|
10784
|
+
context.stderr(`Invalid type: ${argsWithoutFlags[0]}`);
|
|
10437
10785
|
context.stderr(`Valid types: ${ARTIFACT_TYPES.join(", ")}`);
|
|
10438
10786
|
return { exitCode: 1 };
|
|
10439
10787
|
}
|
|
10440
|
-
const specificTypeRequested =
|
|
10788
|
+
const specificTypeRequested = argsWithoutFlags.length > 0;
|
|
10441
10789
|
const showTaskCreationHint = specificTypeRequested && typesToList.length === 1 && typesToList[0] === "tasks";
|
|
10442
10790
|
const workflowTasks = typesToList.includes("ideas") && fileSystem.exists(dustPath) ? await findAllWorkflowTasks(fileSystem, dustPath) : null;
|
|
10443
10791
|
for (const type of typesToList) {
|
|
10792
|
+
if (type === "principles") {
|
|
10793
|
+
const hasContent = await processPrinciplesList({
|
|
10794
|
+
dustPath,
|
|
10795
|
+
colors,
|
|
10796
|
+
fileSystem,
|
|
10797
|
+
stdout: context.stdout,
|
|
10798
|
+
emitEvent: context.emitEvent,
|
|
10799
|
+
excludeCorePrinciples: settings.excludeCorePrinciples,
|
|
10800
|
+
tree: treeFlag
|
|
10801
|
+
});
|
|
10802
|
+
if (!hasContent && specificTypeRequested) {
|
|
10803
|
+
context.stdout(SECTION_HEADERS[type]);
|
|
10804
|
+
context.stdout("");
|
|
10805
|
+
context.stdout(TYPE_EXPLANATIONS[type]);
|
|
10806
|
+
context.stdout("");
|
|
10807
|
+
context.stdout(`No ${type} found.`);
|
|
10808
|
+
context.stdout("");
|
|
10809
|
+
if (context.emitEvent) {
|
|
10810
|
+
emitListEvent(context.emitEvent, type, []);
|
|
10811
|
+
}
|
|
10812
|
+
}
|
|
10813
|
+
continue;
|
|
10814
|
+
}
|
|
10444
10815
|
const dirPath = `${dustPath}/${type}`;
|
|
10445
10816
|
const dirExists = fileSystem.exists(dirPath);
|
|
10446
10817
|
const files = dirExists ? await fileSystem.readdir(dirPath) : [];
|
|
@@ -10483,7 +10854,7 @@ async function list(dependencies) {
|
|
|
10483
10854
|
}
|
|
10484
10855
|
|
|
10485
10856
|
// lib/loop/loop.ts
|
|
10486
|
-
import { existsSync } from "node:fs";
|
|
10857
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
10487
10858
|
import os3 from "node:os";
|
|
10488
10859
|
|
|
10489
10860
|
// lib/loop/parse-args.ts
|
|
@@ -10549,7 +10920,7 @@ async function runLoop(dependencies, loopDependencies) {
|
|
|
10549
10920
|
const dockerDeps = {
|
|
10550
10921
|
spawn: loopDependencies.dockerDeps?.spawn ?? loopDependencies.spawn,
|
|
10551
10922
|
homedir: loopDependencies.dockerDeps?.homedir ?? os3.homedir,
|
|
10552
|
-
existsSync: loopDependencies.dockerDeps?.existsSync ??
|
|
10923
|
+
existsSync: loopDependencies.dockerDeps?.existsSync ?? existsSync2
|
|
10553
10924
|
};
|
|
10554
10925
|
const dockerResult = await prepareDockerConfig(context.cwd, dockerDeps, onLoopEvent);
|
|
10555
10926
|
if ("error" in dockerResult) {
|
|
@@ -10702,6 +11073,38 @@ async function migrate(dependencies) {
|
|
|
10702
11073
|
return { exitCode: 0 };
|
|
10703
11074
|
}
|
|
10704
11075
|
|
|
11076
|
+
// lib/cli/commands/new-fact.ts
|
|
11077
|
+
function newFactInstructions(vars) {
|
|
11078
|
+
const intro = vars.isClaudeCodeWeb ? "Follow these steps. Use a todo list to track your progress." : "Follow these steps:";
|
|
11079
|
+
return dedent`
|
|
11080
|
+
## Adding a New Fact
|
|
11081
|
+
|
|
11082
|
+
Facts are current state documentation. They capture how things work today—implementation details, architectural decisions, and system behavior. Unlike principles (which are aspirational) or ideas/tasks (which are future work), facts answer "how does this work today?"
|
|
11083
|
+
|
|
11084
|
+
${intro}
|
|
11085
|
+
1. Run \`${vars.bin} facts\` to see existing facts and avoid duplication
|
|
11086
|
+
2. Create a new markdown file in \`.dust/facts/\` with a descriptive kebab-case name (e.g., \`authentication-flow.md\`)
|
|
11087
|
+
3. Add a title as the first line using an H1 heading (e.g., \`# Authentication Flow\`)
|
|
11088
|
+
4. Write an opening sentence that summarizes the fact (this appears in \`${vars.bin} facts\` output)
|
|
11089
|
+
5. Add optional body sections with additional details, examples, or context
|
|
11090
|
+
6. Run \`${vars.bin} lint\` to catch any formatting issues
|
|
11091
|
+
7. Create a single atomic commit with a message in the format "Add fact: <title>"
|
|
11092
|
+
8. Push your commit to the remote repository
|
|
11093
|
+
|
|
11094
|
+
Facts should be:
|
|
11095
|
+
- **Current** - They describe how things work today, not how they should work
|
|
11096
|
+
- **Specific** - They document concrete implementation details
|
|
11097
|
+
- **Discoverable** - The opening sentence should help others find relevant facts
|
|
11098
|
+
`;
|
|
11099
|
+
}
|
|
11100
|
+
async function newFact(dependencies) {
|
|
11101
|
+
const { context, settings } = dependencies;
|
|
11102
|
+
const hooksInstalled = await manageGitHooks(dependencies);
|
|
11103
|
+
const vars = templateVariables(settings, hooksInstalled);
|
|
11104
|
+
context.stdout(newFactInstructions(vars));
|
|
11105
|
+
return { exitCode: 0 };
|
|
11106
|
+
}
|
|
11107
|
+
|
|
10705
11108
|
// lib/cli/commands/new-idea.ts
|
|
10706
11109
|
function newIdeaInstructions(vars) {
|
|
10707
11110
|
return dedent`
|
|
@@ -11000,16 +11403,28 @@ async function prePush(dependencies, gitRunner = defaultGitRunner, env = process
|
|
|
11000
11403
|
|
|
11001
11404
|
// lib/cli/shared/type-list.ts
|
|
11002
11405
|
async function tasks(dependencies) {
|
|
11003
|
-
return list({
|
|
11406
|
+
return list({
|
|
11407
|
+
...dependencies,
|
|
11408
|
+
arguments: ["tasks", ...dependencies.arguments]
|
|
11409
|
+
});
|
|
11004
11410
|
}
|
|
11005
11411
|
async function principles(dependencies) {
|
|
11006
|
-
return list({
|
|
11412
|
+
return list({
|
|
11413
|
+
...dependencies,
|
|
11414
|
+
arguments: ["principles", ...dependencies.arguments]
|
|
11415
|
+
});
|
|
11007
11416
|
}
|
|
11008
11417
|
async function ideas(dependencies) {
|
|
11009
|
-
return list({
|
|
11418
|
+
return list({
|
|
11419
|
+
...dependencies,
|
|
11420
|
+
arguments: ["ideas", ...dependencies.arguments]
|
|
11421
|
+
});
|
|
11010
11422
|
}
|
|
11011
11423
|
async function facts(dependencies) {
|
|
11012
|
-
return list({
|
|
11424
|
+
return list({
|
|
11425
|
+
...dependencies,
|
|
11426
|
+
arguments: ["facts", ...dependencies.arguments]
|
|
11427
|
+
});
|
|
11013
11428
|
}
|
|
11014
11429
|
|
|
11015
11430
|
// lib/cli/main.ts
|
|
@@ -11031,6 +11446,7 @@ var commandRegistry = {
|
|
|
11031
11446
|
"new task": newTask,
|
|
11032
11447
|
"new principle": newPrinciple,
|
|
11033
11448
|
"new idea": newIdea,
|
|
11449
|
+
"new fact": newFact,
|
|
11034
11450
|
"implement task": implementTask,
|
|
11035
11451
|
"pick task": pickTask,
|
|
11036
11452
|
"loop claude": loopClaude,
|
|
@@ -11134,9 +11550,9 @@ function createGlobScanner(readdirFn) {
|
|
|
11134
11550
|
};
|
|
11135
11551
|
}
|
|
11136
11552
|
var defaultFileSystemPrimitives = {
|
|
11137
|
-
existsSync:
|
|
11138
|
-
statSync:
|
|
11139
|
-
readFile:
|
|
11553
|
+
existsSync: existsSync3,
|
|
11554
|
+
statSync: statSync3,
|
|
11555
|
+
readFile: readFile4,
|
|
11140
11556
|
writeFile: writeFile2,
|
|
11141
11557
|
mkdir: mkdir2,
|
|
11142
11558
|
readdir: readdir2,
|