@joshski/dust 0.1.93 → 0.1.95
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 +18 -11
- package/dist/artifacts/workflow-tasks.d.ts +4 -1
- package/dist/artifacts.js +114 -104
- package/dist/audits/checks-audit.d.ts +55 -0
- package/dist/audits.js +1641 -132
- package/dist/bucket/command-events-proxy.d.ts +1 -0
- package/dist/bucket/events.d.ts +7 -9
- package/dist/bucket/paths.d.ts +4 -12
- package/dist/bucket/repository-loop.d.ts +2 -1
- package/dist/bucket/repository.d.ts +8 -0
- package/dist/bucket/server-messages.d.ts +1 -0
- package/dist/bucket/tool-prompt.d.ts +10 -1
- package/dist/cli/commands/next.d.ts +6 -0
- package/dist/cli/types.d.ts +2 -0
- package/dist/config/settings.d.ts +4 -3
- package/dist/dust.js +4429 -2489
- package/dist/env-config.d.ts +103 -0
- package/dist/logging/index.d.ts +20 -1
- package/dist/logging.js +19 -10
- package/dist/loop/events.d.ts +52 -0
- package/dist/loop/git-pull.d.ts +9 -0
- package/dist/loop/iteration.d.ts +42 -0
- package/dist/loop/wire-events.d.ts +5 -0
- package/dist/proxy/claude-api-proxy.d.ts +28 -0
- package/dist/session.d.ts +4 -2
- package/dist/validation/index.d.ts +4 -1
- package/dist/validation.js +61 -33
- package/lib/istanbul/minimal-reporter.cjs +1 -1
- package/package.json +2 -2
- package/dist/cli/commands/loop.d.ts +0 -118
- /package/dist/cli/{commands → shared}/agent-shared.d.ts +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized environment configuration.
|
|
3
|
+
*
|
|
4
|
+
* Reads all environment variables once at startup and provides typed access.
|
|
5
|
+
* Following the "Functional Core, Imperative Shell" principle: the shell reads
|
|
6
|
+
* process.env once, then the functional core receives typed configuration.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Logging-related environment configuration.
|
|
10
|
+
* - DEBUG: Pattern for stdout debug logging (comma-separated wildcards)
|
|
11
|
+
* - DUST_LOG_DIR: Override default log directory location
|
|
12
|
+
* - DUST_LOG_FILE: Inherited log file path for child processes
|
|
13
|
+
*/
|
|
14
|
+
export interface LoggingConfig {
|
|
15
|
+
debug: string | undefined;
|
|
16
|
+
logDir: string | undefined;
|
|
17
|
+
logFile: string | undefined;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Dustbucket connection configuration.
|
|
21
|
+
* - DUST_BUCKET_HOST: Override dustbucket host for auth
|
|
22
|
+
* - DUST_BUCKET_TOKEN: Authentication token (takes precedence over stored credential)
|
|
23
|
+
* - DUST_BUCKET_AGENT_CONNECT_URL: Override WebSocket URL
|
|
24
|
+
*/
|
|
25
|
+
export interface BucketConfig {
|
|
26
|
+
host: string | undefined;
|
|
27
|
+
token: string | undefined;
|
|
28
|
+
agentConnectUrl: string | undefined;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Session-related configuration for dust commands.
|
|
32
|
+
* - DUST_PROXY_PORT: Proxy server port
|
|
33
|
+
* - DUST_UNATTENDED: Whether running in unattended mode
|
|
34
|
+
* - DUST_SKIP_AGENT: Skip agent startup
|
|
35
|
+
* - DUST_REPOSITORY_ID: Repository identifier for bucket operations
|
|
36
|
+
* - DUST_REPOS_DIR: Override default repositories directory
|
|
37
|
+
*/
|
|
38
|
+
export interface SessionConfig {
|
|
39
|
+
proxyPort: string | undefined;
|
|
40
|
+
unattended: string | undefined;
|
|
41
|
+
skipAgent: string | undefined;
|
|
42
|
+
repositoryId: string | undefined;
|
|
43
|
+
reposDir: string | undefined;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Runtime environment detection.
|
|
47
|
+
* - BUN_INSTALL: Whether Bun package manager is available
|
|
48
|
+
* - DUST_EVENTS_URL: Override events posting URL
|
|
49
|
+
*/
|
|
50
|
+
export interface RuntimeConfig {
|
|
51
|
+
bunInstall: string | undefined;
|
|
52
|
+
eventsUrl: string | undefined;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Agent detection environment variables.
|
|
56
|
+
* - CLAUDECODE: Claude Code is running
|
|
57
|
+
* - CLAUDE_CODE_REMOTE: Claude Code Web is running
|
|
58
|
+
* - CODEX_HOME: Codex home directory
|
|
59
|
+
* - CODEX_CI: Codex CI mode
|
|
60
|
+
*/
|
|
61
|
+
export interface AgentDetectionConfig {
|
|
62
|
+
claudeCode: string | undefined;
|
|
63
|
+
claudeCodeRemote: string | undefined;
|
|
64
|
+
codexHome: string | undefined;
|
|
65
|
+
codexCi: string | undefined;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Authentication tokens.
|
|
69
|
+
* - CLAUDE_CODE_OAUTH_TOKEN: OAuth token for Claude Code
|
|
70
|
+
* - OPENAI_API_KEY: OpenAI API key for Codex
|
|
71
|
+
*/
|
|
72
|
+
export interface AuthConfig {
|
|
73
|
+
claudeCodeOauthToken: string | undefined;
|
|
74
|
+
openaiApiKey: string | undefined;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Testing-related configuration.
|
|
78
|
+
* - CLAUDE_CODE_VCR_MODE: VCR recording mode ('record' or 'replay')
|
|
79
|
+
*/
|
|
80
|
+
export interface TestingConfig {
|
|
81
|
+
vcrMode: string | undefined;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Complete environment configuration.
|
|
85
|
+
* Captures all environment variables used by dust, organized by subsystem.
|
|
86
|
+
*/
|
|
87
|
+
export interface EnvConfig {
|
|
88
|
+
logging: LoggingConfig;
|
|
89
|
+
bucket: BucketConfig;
|
|
90
|
+
session: SessionConfig;
|
|
91
|
+
runtime: RuntimeConfig;
|
|
92
|
+
agentDetection: AgentDetectionConfig;
|
|
93
|
+
auth: AuthConfig;
|
|
94
|
+
testing: TestingConfig;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Read and validate all environment variables once.
|
|
98
|
+
* This function should be called once at startup in the imperative shell.
|
|
99
|
+
*
|
|
100
|
+
* @param env - The environment variables object (typically process.env)
|
|
101
|
+
* @returns Typed environment configuration
|
|
102
|
+
*/
|
|
103
|
+
export declare function readEnvConfig(env: Record<string, string | undefined>): EnvConfig;
|
package/dist/logging/index.d.ts
CHANGED
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
*
|
|
46
46
|
* No external dependencies.
|
|
47
47
|
*/
|
|
48
|
+
import type { LoggingConfig } from '../env-config';
|
|
48
49
|
import { type LogSink } from './sink';
|
|
49
50
|
export type LogFn = (...messages: unknown[]) => void;
|
|
50
51
|
export interface LoggerOptions {
|
|
@@ -66,9 +67,27 @@ export interface LoggingService {
|
|
|
66
67
|
* encapsulated inside the returned object.
|
|
67
68
|
*/
|
|
68
69
|
export interface LoggingServiceOptions {
|
|
70
|
+
/**
|
|
71
|
+
* Logging configuration from environment.
|
|
72
|
+
* Required - callers must pass configuration explicitly.
|
|
73
|
+
*/
|
|
74
|
+
config: LoggingConfig;
|
|
75
|
+
/**
|
|
76
|
+
* Custom stdout writer (defaults to process.stdout.write).
|
|
77
|
+
*/
|
|
69
78
|
stdout?: (line: string) => boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Current working directory for default log path resolution.
|
|
81
|
+
* Defaults to process.cwd().
|
|
82
|
+
*/
|
|
83
|
+
cwd?: () => string;
|
|
84
|
+
/**
|
|
85
|
+
* Function to set DUST_LOG_FILE for child process inheritance.
|
|
86
|
+
* Defaults to setting process.env[DUST_LOG_FILE].
|
|
87
|
+
*/
|
|
88
|
+
setLogFileEnv?: (path: string) => void;
|
|
70
89
|
}
|
|
71
|
-
export declare function createLoggingService(options
|
|
90
|
+
export declare function createLoggingService(options: LoggingServiceOptions): LoggingService;
|
|
72
91
|
/**
|
|
73
92
|
* Activate file logging for this command. See {@link LoggingService.enableFileLogs}.
|
|
74
93
|
*/
|
package/dist/logging.js
CHANGED
|
@@ -61,7 +61,10 @@ class FileSink {
|
|
|
61
61
|
// lib/logging/index.ts
|
|
62
62
|
var DUST_LOG_FILE = "DUST_LOG_FILE";
|
|
63
63
|
function createLoggingService(options) {
|
|
64
|
-
const
|
|
64
|
+
const { config } = options;
|
|
65
|
+
const writeStdout = options.stdout ?? process.stdout.write.bind(process.stdout);
|
|
66
|
+
const getCwd = options.cwd ?? (() => process.cwd());
|
|
67
|
+
const setLogFileEnv = options.setLogFileEnv ?? ((path) => process.env[DUST_LOG_FILE] = path);
|
|
65
68
|
let patterns = null;
|
|
66
69
|
let initialized = false;
|
|
67
70
|
let activeFileSink = null;
|
|
@@ -70,7 +73,7 @@ function createLoggingService(options) {
|
|
|
70
73
|
if (initialized)
|
|
71
74
|
return;
|
|
72
75
|
initialized = true;
|
|
73
|
-
const parsed = parsePatterns(
|
|
76
|
+
const parsed = parsePatterns(config.debug);
|
|
74
77
|
patterns = parsed.length > 0 ? parsed : null;
|
|
75
78
|
}
|
|
76
79
|
function getOrCreateFileSink(path) {
|
|
@@ -83,20 +86,20 @@ function createLoggingService(options) {
|
|
|
83
86
|
}
|
|
84
87
|
return {
|
|
85
88
|
enableFileLogs(scope, sinkForTesting) {
|
|
86
|
-
const existing =
|
|
87
|
-
const logDir =
|
|
89
|
+
const existing = config.logFile;
|
|
90
|
+
const logDir = config.logDir ?? join(getCwd(), "log");
|
|
88
91
|
const path = existing ?? join(logDir, `${scope}.log`);
|
|
89
92
|
if (!existing) {
|
|
90
|
-
|
|
93
|
+
setLogFileEnv(path);
|
|
91
94
|
}
|
|
92
95
|
activeFileSink = sinkForTesting ?? new FileSink(path);
|
|
93
96
|
},
|
|
94
|
-
createLogger(name,
|
|
97
|
+
createLogger(name, loggerOptions) {
|
|
95
98
|
let perLoggerSink;
|
|
96
|
-
if (
|
|
99
|
+
if (loggerOptions?.file === false) {
|
|
97
100
|
perLoggerSink = null;
|
|
98
|
-
} else if (typeof
|
|
99
|
-
perLoggerSink = getOrCreateFileSink(
|
|
101
|
+
} else if (typeof loggerOptions?.file === "string") {
|
|
102
|
+
perLoggerSink = getOrCreateFileSink(loggerOptions.file);
|
|
100
103
|
}
|
|
101
104
|
return (...messages) => {
|
|
102
105
|
init();
|
|
@@ -119,7 +122,13 @@ function createLoggingService(options) {
|
|
|
119
122
|
}
|
|
120
123
|
};
|
|
121
124
|
}
|
|
122
|
-
var defaultService = createLoggingService(
|
|
125
|
+
var defaultService = createLoggingService({
|
|
126
|
+
config: {
|
|
127
|
+
debug: process.env.DEBUG,
|
|
128
|
+
logDir: process.env.DUST_LOG_DIR,
|
|
129
|
+
logFile: process.env.DUST_LOG_FILE
|
|
130
|
+
}
|
|
131
|
+
});
|
|
123
132
|
var enableFileLogs = defaultService.enableFileLogs.bind(defaultService);
|
|
124
133
|
var createLogger = defaultService.createLogger.bind(defaultService);
|
|
125
134
|
var isEnabled = defaultService.isEnabled.bind(defaultService);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface LoopWarningEvent {
|
|
2
|
+
type: 'loop.warning';
|
|
3
|
+
}
|
|
4
|
+
export interface LoopStartedEvent {
|
|
5
|
+
type: 'loop.started';
|
|
6
|
+
maxIterations: number;
|
|
7
|
+
agentType?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface LoopSyncingEvent {
|
|
10
|
+
type: 'loop.syncing';
|
|
11
|
+
}
|
|
12
|
+
export interface LoopSyncSkippedEvent {
|
|
13
|
+
type: 'loop.sync_skipped';
|
|
14
|
+
reason: string;
|
|
15
|
+
}
|
|
16
|
+
export interface LoopCheckingTasksEvent {
|
|
17
|
+
type: 'loop.checking_tasks';
|
|
18
|
+
}
|
|
19
|
+
export interface LoopNoTasksEvent {
|
|
20
|
+
type: 'loop.no_tasks';
|
|
21
|
+
}
|
|
22
|
+
export interface LoopTasksFoundEvent {
|
|
23
|
+
type: 'loop.tasks_found';
|
|
24
|
+
}
|
|
25
|
+
export interface LoopIterationCompleteEvent {
|
|
26
|
+
type: 'loop.iteration_complete';
|
|
27
|
+
iteration: number;
|
|
28
|
+
maxIterations: number;
|
|
29
|
+
}
|
|
30
|
+
export interface LoopEndedEvent {
|
|
31
|
+
type: 'loop.ended';
|
|
32
|
+
maxIterations: number;
|
|
33
|
+
}
|
|
34
|
+
export interface LoopDockerDetectedEvent {
|
|
35
|
+
type: 'loop.docker_detected';
|
|
36
|
+
imageTag: string;
|
|
37
|
+
}
|
|
38
|
+
export interface LoopDockerBuildingEvent {
|
|
39
|
+
type: 'loop.docker_building';
|
|
40
|
+
imageTag: string;
|
|
41
|
+
}
|
|
42
|
+
export interface LoopDockerBuiltEvent {
|
|
43
|
+
type: 'loop.docker_built';
|
|
44
|
+
imageTag: string;
|
|
45
|
+
}
|
|
46
|
+
export interface LoopDockerErrorEvent {
|
|
47
|
+
type: 'loop.docker_error';
|
|
48
|
+
error: string;
|
|
49
|
+
}
|
|
50
|
+
export type LoopEvent = LoopWarningEvent | LoopStartedEvent | LoopSyncingEvent | LoopSyncSkippedEvent | LoopCheckingTasksEvent | LoopNoTasksEvent | LoopTasksFoundEvent | LoopIterationCompleteEvent | LoopEndedEvent | LoopDockerDetectedEvent | LoopDockerBuildingEvent | LoopDockerBuiltEvent | LoopDockerErrorEvent;
|
|
51
|
+
export type LoopEmitFn = (event: LoopEvent) => void;
|
|
52
|
+
export declare function formatLoopEvent(event: LoopEvent): string | null;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { spawn as nodeSpawn } from 'node:child_process';
|
|
2
|
+
import { run as claudeRun } from '../claude/run';
|
|
3
|
+
import type { DockerSpawnConfig } from '../claude/types';
|
|
4
|
+
import type { DockerDependencies } from '../docker/docker-agent';
|
|
5
|
+
import { type SessionConfig } from '../env-config';
|
|
6
|
+
import type { CommandDependencies } from '../cli/types';
|
|
7
|
+
import { type InvalidTask, type UnblockedTask } from '../cli/commands/next';
|
|
8
|
+
import type { LoopEmitFn } from './events';
|
|
9
|
+
import type { PostEventFn, SendAgentEventFn } from './wire-events';
|
|
10
|
+
export interface LoopDependencies {
|
|
11
|
+
spawn: typeof nodeSpawn;
|
|
12
|
+
run: typeof claudeRun;
|
|
13
|
+
sleep: (ms: number) => Promise<void>;
|
|
14
|
+
postEvent: PostEventFn;
|
|
15
|
+
session: SessionConfig;
|
|
16
|
+
agentType?: string;
|
|
17
|
+
fetch?: typeof fetch;
|
|
18
|
+
/** Optional overrides for Docker dependency functions (for testing) */
|
|
19
|
+
dockerDeps?: Partial<DockerDependencies>;
|
|
20
|
+
}
|
|
21
|
+
export declare function createDefaultDependencies(): LoopDependencies;
|
|
22
|
+
type IterationResult = 'no_tasks' | 'ran_claude' | 'claude_error' | 'resolved_pull_conflict';
|
|
23
|
+
type LogFn = (message: string) => void;
|
|
24
|
+
export interface IterationOptions {
|
|
25
|
+
onRawEvent?: (rawEvent: Record<string, unknown>) => void;
|
|
26
|
+
hooksInstalled?: boolean;
|
|
27
|
+
signal?: AbortSignal;
|
|
28
|
+
logger?: LogFn;
|
|
29
|
+
repositoryId?: string;
|
|
30
|
+
/** Docker spawn config when running in Docker mode */
|
|
31
|
+
docker?: DockerSpawnConfig;
|
|
32
|
+
/** Pre-formatted tools section to inject into the prompt */
|
|
33
|
+
toolsSection?: string;
|
|
34
|
+
/** Port of the command events proxy for this iteration */
|
|
35
|
+
proxyPort?: number;
|
|
36
|
+
}
|
|
37
|
+
export declare function findAvailableTasks(dependencies: CommandDependencies): Promise<{
|
|
38
|
+
tasks: UnblockedTask[];
|
|
39
|
+
invalidTasks: InvalidTask[];
|
|
40
|
+
}>;
|
|
41
|
+
export declare function runOneIteration(dependencies: CommandDependencies, loopDependencies: LoopDependencies, onLoopEvent: LoopEmitFn, onAgentEvent?: SendAgentEventFn, options?: IterationOptions): Promise<IterationResult>;
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AgentSessionEvent, EventMessage } from '../agent-events';
|
|
2
|
+
export type PostEventFn = (url: string, payload: EventMessage) => Promise<void>;
|
|
3
|
+
export type SendAgentEventFn = (event: AgentSessionEvent) => void;
|
|
4
|
+
export declare function createPostEvent(fetchFn: typeof fetch): PostEventFn;
|
|
5
|
+
export declare function createWireEventSender(eventsUrl: string | undefined, sessionId: string, postEvent: PostEventFn, onError: (error: unknown) => void, getAgentSessionId?: () => string | undefined, repository?: string): SendAgentEventFn;
|
|
@@ -51,6 +51,34 @@ export interface ClaudeApiProxyServer {
|
|
|
51
51
|
port: number;
|
|
52
52
|
stop: () => void;
|
|
53
53
|
}
|
|
54
|
+
export interface ProxyRequestConfig {
|
|
55
|
+
url: string;
|
|
56
|
+
headers: Record<string, string>;
|
|
57
|
+
}
|
|
58
|
+
export declare function mergeAnthropicBetaHeader(incomingHeader: string | string[] | undefined): string;
|
|
59
|
+
/**
|
|
60
|
+
* Build the proxy request configuration for forwarding to the Anthropic API.
|
|
61
|
+
* Returns the upstream URL and headers with the token injected.
|
|
62
|
+
*/
|
|
63
|
+
export declare function buildProxyRequest(pathname: string, search: string, token: string, incomingHeaders: Record<string, string | string[] | undefined>): ProxyRequestConfig;
|
|
64
|
+
/**
|
|
65
|
+
* Filter response headers from upstream response for forwarding to the client.
|
|
66
|
+
* Removes transfer-encoding as we're not chunking.
|
|
67
|
+
*/
|
|
68
|
+
export declare function filterResponseHeaders(upstreamHeaders: Headers): Record<string, string>;
|
|
69
|
+
export interface ErrorResponse {
|
|
70
|
+
statusCode: number;
|
|
71
|
+
contentType: string;
|
|
72
|
+
body: string;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Build a 401 response for when no OAuth token is available.
|
|
76
|
+
*/
|
|
77
|
+
export declare function buildNoTokenResponse(): ErrorResponse;
|
|
78
|
+
/**
|
|
79
|
+
* Build a 502 response for when the upstream request fails.
|
|
80
|
+
*/
|
|
81
|
+
export declare function buildUpstreamErrorResponse(error: unknown): ErrorResponse;
|
|
54
82
|
/**
|
|
55
83
|
* Creates a Claude API proxy server.
|
|
56
84
|
* The server accepts HTTP requests and forwards them to the Anthropic API
|
package/dist/session.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import type { SessionConfig } from './env-config';
|
|
1
2
|
export declare const DUST_UNATTENDED = "DUST_UNATTENDED";
|
|
2
3
|
export declare const DUST_SKIP_AGENT = "DUST_SKIP_AGENT";
|
|
3
4
|
export declare const DUST_REPOSITORY_ID = "DUST_REPOSITORY_ID";
|
|
4
5
|
export declare const DUST_PROXY_PORT = "DUST_PROXY_PORT";
|
|
5
|
-
export declare function isUnattended(
|
|
6
|
-
export declare function buildUnattendedEnv(options
|
|
6
|
+
export declare function isUnattended(session: SessionConfig): boolean;
|
|
7
|
+
export declare function buildUnattendedEnv(options: {
|
|
7
8
|
repositoryId?: string;
|
|
8
9
|
proxyPort?: number;
|
|
10
|
+
session: SessionConfig;
|
|
9
11
|
}): Record<string, string>;
|
|
@@ -14,6 +14,9 @@ export interface ValidationResult {
|
|
|
14
14
|
valid: boolean;
|
|
15
15
|
violations: Violation[];
|
|
16
16
|
}
|
|
17
|
+
export interface ValidatePatchOptions {
|
|
18
|
+
cwd?: string;
|
|
19
|
+
}
|
|
17
20
|
/**
|
|
18
21
|
* Validates a patch of artifact changes against existing .dust/ content.
|
|
19
22
|
*
|
|
@@ -21,4 +24,4 @@ export interface ValidationResult {
|
|
|
21
24
|
* @param dustPath - Absolute path to the .dust directory
|
|
22
25
|
* @param patch - Proposed new/changed files, with paths relative to dustPath
|
|
23
26
|
*/
|
|
24
|
-
export declare function validatePatch(fileSystem: ReadableFileSystem, dustPath: string, patch: ArtifactPatch): Promise<ValidationResult>;
|
|
27
|
+
export declare function validatePatch(fileSystem: ReadableFileSystem, dustPath: string, patch: ArtifactPatch, options?: ValidatePatchOptions): Promise<ValidationResult>;
|
package/dist/validation.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// lib/validation/index.ts
|
|
2
|
+
import { relative } from "node:path";
|
|
3
|
+
|
|
1
4
|
// lib/markdown/markdown-utilities.ts
|
|
2
5
|
function extractTitle(content) {
|
|
3
6
|
const match = content.match(/^#\s+(.+)$/m);
|
|
@@ -161,7 +164,8 @@ async function validateContentDirectoryFiles(dirPath, fileSystem) {
|
|
|
161
164
|
var IDEA_TRANSITION_PREFIXES = [
|
|
162
165
|
"Refine Idea: ",
|
|
163
166
|
"Decompose Idea: ",
|
|
164
|
-
"Shelve Idea: "
|
|
167
|
+
"Shelve Idea: ",
|
|
168
|
+
"Expedite Idea: "
|
|
165
169
|
];
|
|
166
170
|
function titleToFilename(title) {
|
|
167
171
|
return `${title.toLowerCase().replace(/\./g, "-").replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "")}.md`;
|
|
@@ -201,7 +205,8 @@ function validateTitleFilenameMatch(filePath, content) {
|
|
|
201
205
|
var WORKFLOW_PREFIX_TO_SECTION = {
|
|
202
206
|
"Refine Idea: ": "Refines Idea",
|
|
203
207
|
"Decompose Idea: ": "Decomposes Idea",
|
|
204
|
-
"Shelve Idea: ": "Shelves Idea"
|
|
208
|
+
"Shelve Idea: ": "Shelves Idea",
|
|
209
|
+
"Expedite Idea: ": "Expedites Idea"
|
|
205
210
|
};
|
|
206
211
|
function validateIdeaOpenQuestions(filePath, content) {
|
|
207
212
|
const violations = [];
|
|
@@ -450,24 +455,28 @@ function validateLinks(filePath, content, fileSystem) {
|
|
|
450
455
|
let match = linkPattern.exec(line);
|
|
451
456
|
while (match) {
|
|
452
457
|
const linkTarget = match[2];
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
458
|
+
const isExternalOrAnchorLink = linkTarget.startsWith("http://") || linkTarget.startsWith("https://") || linkTarget.startsWith("#");
|
|
459
|
+
if (isExternalOrAnchorLink) {
|
|
460
|
+
match = linkPattern.exec(line);
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
if (linkTarget.startsWith("/")) {
|
|
464
|
+
violations.push({
|
|
465
|
+
file: filePath,
|
|
466
|
+
message: `Absolute link not allowed: "${linkTarget}" (use a relative path instead)`,
|
|
467
|
+
line: i + 1
|
|
468
|
+
});
|
|
469
|
+
match = linkPattern.exec(line);
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
const targetPath = linkTarget.split("#")[0];
|
|
473
|
+
const resolvedPath = resolve(fileDir, targetPath);
|
|
474
|
+
if (!fileSystem.exists(resolvedPath)) {
|
|
475
|
+
violations.push({
|
|
476
|
+
file: filePath,
|
|
477
|
+
message: `Broken link: "${linkTarget}"`,
|
|
478
|
+
line: i + 1
|
|
479
|
+
});
|
|
471
480
|
}
|
|
472
481
|
match = linkPattern.exec(line);
|
|
473
482
|
}
|
|
@@ -611,16 +620,21 @@ function extractPrincipleRelationships(filePath, content) {
|
|
|
611
620
|
let match = linkPattern.exec(line);
|
|
612
621
|
while (match) {
|
|
613
622
|
const linkTarget = match[2];
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
623
|
+
const isLocalLink = !linkTarget.startsWith("#") && !linkTarget.startsWith("http://") && !linkTarget.startsWith("https://");
|
|
624
|
+
if (!isLocalLink) {
|
|
625
|
+
match = linkPattern.exec(line);
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
const targetPath = linkTarget.split("#")[0];
|
|
629
|
+
const resolvedPath = resolve2(fileDir, targetPath);
|
|
630
|
+
if (!resolvedPath.includes("/.dust/principles/")) {
|
|
631
|
+
match = linkPattern.exec(line);
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
if (currentSection === "## Parent Principle") {
|
|
635
|
+
parentPrinciples.push(resolvedPath);
|
|
636
|
+
} else {
|
|
637
|
+
subPrinciples.push(resolvedPath);
|
|
624
638
|
}
|
|
625
639
|
match = linkPattern.exec(line);
|
|
626
640
|
}
|
|
@@ -768,7 +782,7 @@ var ALLOWED_ROOT_PATHS = [
|
|
|
768
782
|
].join(", ");
|
|
769
783
|
function validatePatchRootEntries(fileSystem, dustPath, patch) {
|
|
770
784
|
const violations = [];
|
|
771
|
-
const sortedPaths = Object.keys(patch.files).
|
|
785
|
+
const sortedPaths = Object.keys(patch.files).toSorted();
|
|
772
786
|
const reportedUnexpectedRootDirectories = new Set;
|
|
773
787
|
for (const relativePath of sortedPaths) {
|
|
774
788
|
const content = patch.files[relativePath];
|
|
@@ -798,7 +812,21 @@ function validatePatchRootEntries(fileSystem, dustPath, patch) {
|
|
|
798
812
|
}
|
|
799
813
|
return violations;
|
|
800
814
|
}
|
|
801
|
-
|
|
815
|
+
function relativizeViolationFilePath(filePath, cwd) {
|
|
816
|
+
const relativePath = relative(cwd, filePath);
|
|
817
|
+
if (relativePath === "" || relativePath === "." || relativePath === ".." || relativePath.startsWith("../") || relativePath.startsWith("..\\")) {
|
|
818
|
+
return filePath;
|
|
819
|
+
}
|
|
820
|
+
return relativePath;
|
|
821
|
+
}
|
|
822
|
+
function relativizeViolations(violations, cwd) {
|
|
823
|
+
return violations.map((violation) => ({
|
|
824
|
+
...violation,
|
|
825
|
+
file: relativizeViolationFilePath(violation.file, cwd)
|
|
826
|
+
}));
|
|
827
|
+
}
|
|
828
|
+
async function validatePatch(fileSystem, dustPath, patch, options = {}) {
|
|
829
|
+
const cwd = options.cwd ?? process.cwd();
|
|
802
830
|
const absolutePatchFiles = new Map;
|
|
803
831
|
const deletedPaths = new Set;
|
|
804
832
|
for (const [relativePath, content] of Object.entries(patch.files)) {
|
|
@@ -888,7 +916,7 @@ async function validatePatch(fileSystem, dustPath, patch) {
|
|
|
888
916
|
}
|
|
889
917
|
return {
|
|
890
918
|
valid: violations.length === 0,
|
|
891
|
-
violations
|
|
919
|
+
violations: relativizeViolations(violations, cwd)
|
|
892
920
|
};
|
|
893
921
|
}
|
|
894
922
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@joshski/dust",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.95",
|
|
4
4
|
"description": "Flow state for AI coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"istanbul-lib-report": "^3.0.1",
|
|
75
75
|
"knip": "^5.60.1",
|
|
76
76
|
"oxfmt": "^0.36.0",
|
|
77
|
-
"oxlint": "^1.
|
|
77
|
+
"oxlint": "^1.51.0",
|
|
78
78
|
"typescript": "^5.8.3",
|
|
79
79
|
"vitest": "^4.0.18"
|
|
80
80
|
}
|