@ttfw/envoi 1.0.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/README.md +238 -0
- package/dist/commands/app.d.ts +2 -0
- package/dist/commands/app.d.ts.map +1 -0
- package/dist/commands/app.js +31 -0
- package/dist/commands/app.js.map +1 -0
- package/dist/commands/autonomy.d.ts +6 -0
- package/dist/commands/autonomy.d.ts.map +1 -0
- package/dist/commands/autonomy.js +89 -0
- package/dist/commands/autonomy.js.map +1 -0
- package/dist/commands/builder.d.ts +13 -0
- package/dist/commands/builder.d.ts.map +1 -0
- package/dist/commands/builder.js +142 -0
- package/dist/commands/builder.js.map +1 -0
- package/dist/commands/idea.d.ts +12 -0
- package/dist/commands/idea.d.ts.map +1 -0
- package/dist/commands/idea.js +79 -0
- package/dist/commands/idea.js.map +1 -0
- package/dist/commands/init.d.ts +18 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +423 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/mode.d.ts +13 -0
- package/dist/commands/mode.d.ts.map +1 -0
- package/dist/commands/mode.js +96 -0
- package/dist/commands/mode.js.map +1 -0
- package/dist/commands/onboard.d.ts +37 -0
- package/dist/commands/onboard.d.ts.map +1 -0
- package/dist/commands/onboard.js +743 -0
- package/dist/commands/onboard.js.map +1 -0
- package/dist/commands/pr-note.d.ts +8 -0
- package/dist/commands/pr-note.d.ts.map +1 -0
- package/dist/commands/pr-note.js +27 -0
- package/dist/commands/pr-note.js.map +1 -0
- package/dist/commands/undo.d.ts +7 -0
- package/dist/commands/undo.d.ts.map +1 -0
- package/dist/commands/undo.js +59 -0
- package/dist/commands/undo.js.map +1 -0
- package/dist/commands/update.d.ts +24 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +248 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/constants/report_codes.d.ts +29 -0
- package/dist/constants/report_codes.d.ts.map +1 -0
- package/dist/constants/report_codes.js +69 -0
- package/dist/constants/report_codes.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +675 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/autonomy.d.ts +16 -0
- package/dist/lib/autonomy.d.ts.map +1 -0
- package/dist/lib/autonomy.js +38 -0
- package/dist/lib/autonomy.js.map +1 -0
- package/dist/lib/blocked.d.ts +87 -0
- package/dist/lib/blocked.d.ts.map +1 -0
- package/dist/lib/blocked.js +134 -0
- package/dist/lib/blocked.js.map +1 -0
- package/dist/lib/branding.d.ts +13 -0
- package/dist/lib/branding.d.ts.map +1 -0
- package/dist/lib/branding.js +19 -0
- package/dist/lib/branding.js.map +1 -0
- package/dist/lib/claude.d.ts +42 -0
- package/dist/lib/claude.d.ts.map +1 -0
- package/dist/lib/claude.js +291 -0
- package/dist/lib/claude.js.map +1 -0
- package/dist/lib/config.d.ts +71 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +410 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/diff.d.ts +150 -0
- package/dist/lib/diff.d.ts.map +1 -0
- package/dist/lib/diff.js +257 -0
- package/dist/lib/diff.js.map +1 -0
- package/dist/lib/doctor.d.ts +67 -0
- package/dist/lib/doctor.d.ts.map +1 -0
- package/dist/lib/doctor.js +211 -0
- package/dist/lib/doctor.js.map +1 -0
- package/dist/lib/fingerprint.d.ts +27 -0
- package/dist/lib/fingerprint.d.ts.map +1 -0
- package/dist/lib/fingerprint.js +116 -0
- package/dist/lib/fingerprint.js.map +1 -0
- package/dist/lib/fs.d.ts +93 -0
- package/dist/lib/fs.d.ts.map +1 -0
- package/dist/lib/fs.js +179 -0
- package/dist/lib/fs.js.map +1 -0
- package/dist/lib/git.d.ts +177 -0
- package/dist/lib/git.d.ts.map +1 -0
- package/dist/lib/git.js +355 -0
- package/dist/lib/git.js.map +1 -0
- package/dist/lib/git_branching.d.ts +84 -0
- package/dist/lib/git_branching.d.ts.map +1 -0
- package/dist/lib/git_branching.js +327 -0
- package/dist/lib/git_branching.js.map +1 -0
- package/dist/lib/gitignore.d.ts +26 -0
- package/dist/lib/gitignore.d.ts.map +1 -0
- package/dist/lib/gitignore.js +119 -0
- package/dist/lib/gitignore.js.map +1 -0
- package/dist/lib/guardrails.d.ts +232 -0
- package/dist/lib/guardrails.d.ts.map +1 -0
- package/dist/lib/guardrails.js +323 -0
- package/dist/lib/guardrails.js.map +1 -0
- package/dist/lib/history.d.ts +110 -0
- package/dist/lib/history.d.ts.map +1 -0
- package/dist/lib/history.js +236 -0
- package/dist/lib/history.js.map +1 -0
- package/dist/lib/index.d.ts +29 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +29 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/json-extract.d.ts +42 -0
- package/dist/lib/json-extract.d.ts.map +1 -0
- package/dist/lib/json-extract.js +201 -0
- package/dist/lib/json-extract.js.map +1 -0
- package/dist/lib/judge.d.ts +237 -0
- package/dist/lib/judge.d.ts.map +1 -0
- package/dist/lib/judge.js +501 -0
- package/dist/lib/judge.js.map +1 -0
- package/dist/lib/lock.d.ts +79 -0
- package/dist/lib/lock.d.ts.map +1 -0
- package/dist/lib/lock.js +254 -0
- package/dist/lib/lock.js.map +1 -0
- package/dist/lib/migration.d.ts +9 -0
- package/dist/lib/migration.d.ts.map +1 -0
- package/dist/lib/migration.js +74 -0
- package/dist/lib/migration.js.map +1 -0
- package/dist/lib/paths.d.ts +18 -0
- package/dist/lib/paths.d.ts.map +1 -0
- package/dist/lib/paths.js +27 -0
- package/dist/lib/paths.js.map +1 -0
- package/dist/lib/preflight.d.ts +33 -0
- package/dist/lib/preflight.d.ts.map +1 -0
- package/dist/lib/preflight.js +177 -0
- package/dist/lib/preflight.js.map +1 -0
- package/dist/lib/prompt_budget.d.ts +18 -0
- package/dist/lib/prompt_budget.d.ts.map +1 -0
- package/dist/lib/prompt_budget.js +36 -0
- package/dist/lib/prompt_budget.js.map +1 -0
- package/dist/lib/report.d.ts +102 -0
- package/dist/lib/report.d.ts.map +1 -0
- package/dist/lib/report.js +347 -0
- package/dist/lib/report.js.map +1 -0
- package/dist/lib/reviewer-flow.d.ts +80 -0
- package/dist/lib/reviewer-flow.d.ts.map +1 -0
- package/dist/lib/reviewer-flow.js +138 -0
- package/dist/lib/reviewer-flow.js.map +1 -0
- package/dist/lib/reviewer.d.ts +53 -0
- package/dist/lib/reviewer.d.ts.map +1 -0
- package/dist/lib/reviewer.js +199 -0
- package/dist/lib/reviewer.js.map +1 -0
- package/dist/lib/risk.d.ts +127 -0
- package/dist/lib/risk.d.ts.map +1 -0
- package/dist/lib/risk.js +192 -0
- package/dist/lib/risk.js.map +1 -0
- package/dist/lib/rollback.d.ts +143 -0
- package/dist/lib/rollback.d.ts.map +1 -0
- package/dist/lib/rollback.js +244 -0
- package/dist/lib/rollback.js.map +1 -0
- package/dist/lib/schema.d.ts +47 -0
- package/dist/lib/schema.d.ts.map +1 -0
- package/dist/lib/schema.js +91 -0
- package/dist/lib/schema.js.map +1 -0
- package/dist/lib/scope.d.ts +89 -0
- package/dist/lib/scope.d.ts.map +1 -0
- package/dist/lib/scope.js +135 -0
- package/dist/lib/scope.js.map +1 -0
- package/dist/lib/self_update.d.ts +13 -0
- package/dist/lib/self_update.d.ts.map +1 -0
- package/dist/lib/self_update.js +172 -0
- package/dist/lib/self_update.js.map +1 -0
- package/dist/lib/state.d.ts +143 -0
- package/dist/lib/state.d.ts.map +1 -0
- package/dist/lib/state.js +258 -0
- package/dist/lib/state.js.map +1 -0
- package/dist/lib/tick.d.ts +310 -0
- package/dist/lib/tick.d.ts.map +1 -0
- package/dist/lib/tick.js +424 -0
- package/dist/lib/tick.js.map +1 -0
- package/dist/lib/transport.d.ts +145 -0
- package/dist/lib/transport.d.ts.map +1 -0
- package/dist/lib/transport.js +237 -0
- package/dist/lib/transport.js.map +1 -0
- package/dist/lib/verdict_labels.d.ts +5 -0
- package/dist/lib/verdict_labels.d.ts.map +1 -0
- package/dist/lib/verdict_labels.js +25 -0
- package/dist/lib/verdict_labels.js.map +1 -0
- package/dist/lib/verify-safety.d.ts +63 -0
- package/dist/lib/verify-safety.d.ts.map +1 -0
- package/dist/lib/verify-safety.js +123 -0
- package/dist/lib/verify-safety.js.map +1 -0
- package/dist/lib/verify.d.ts +139 -0
- package/dist/lib/verify.d.ts.map +1 -0
- package/dist/lib/verify.js +311 -0
- package/dist/lib/verify.js.map +1 -0
- package/dist/lib/workspace_state.d.ts +79 -0
- package/dist/lib/workspace_state.d.ts.map +1 -0
- package/dist/lib/workspace_state.js +283 -0
- package/dist/lib/workspace_state.js.map +1 -0
- package/dist/runner/builder.d.ts +58 -0
- package/dist/runner/builder.d.ts.map +1 -0
- package/dist/runner/builder.js +775 -0
- package/dist/runner/builder.js.map +1 -0
- package/dist/runner/builder_parse.d.ts +37 -0
- package/dist/runner/builder_parse.d.ts.map +1 -0
- package/dist/runner/builder_parse.js +76 -0
- package/dist/runner/builder_parse.js.map +1 -0
- package/dist/runner/index.d.ts +9 -0
- package/dist/runner/index.d.ts.map +1 -0
- package/dist/runner/index.js +7 -0
- package/dist/runner/index.js.map +1 -0
- package/dist/runner/loop.d.ts +51 -0
- package/dist/runner/loop.d.ts.map +1 -0
- package/dist/runner/loop.js +221 -0
- package/dist/runner/loop.js.map +1 -0
- package/dist/runner/orchestrator.d.ts +67 -0
- package/dist/runner/orchestrator.d.ts.map +1 -0
- package/dist/runner/orchestrator.js +376 -0
- package/dist/runner/orchestrator.js.map +1 -0
- package/dist/runner/tick.d.ts +10 -0
- package/dist/runner/tick.d.ts.map +1 -0
- package/dist/runner/tick.js +1639 -0
- package/dist/runner/tick.js.map +1 -0
- package/dist/types/blocked.d.ts +52 -0
- package/dist/types/blocked.d.ts.map +1 -0
- package/dist/types/blocked.js +8 -0
- package/dist/types/blocked.js.map +1 -0
- package/dist/types/builder.d.ts +25 -0
- package/dist/types/builder.d.ts.map +1 -0
- package/dist/types/builder.js +7 -0
- package/dist/types/builder.js.map +1 -0
- package/dist/types/claude.d.ts +86 -0
- package/dist/types/claude.d.ts.map +1 -0
- package/dist/types/claude.js +48 -0
- package/dist/types/claude.js.map +1 -0
- package/dist/types/config.d.ts +384 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +7 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/index.d.ts +18 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +8 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/lock.d.ts +21 -0
- package/dist/types/lock.d.ts.map +1 -0
- package/dist/types/lock.js +8 -0
- package/dist/types/lock.js.map +1 -0
- package/dist/types/preflight.d.ts +49 -0
- package/dist/types/preflight.d.ts.map +1 -0
- package/dist/types/preflight.js +8 -0
- package/dist/types/preflight.js.map +1 -0
- package/dist/types/report.d.ts +161 -0
- package/dist/types/report.d.ts.map +1 -0
- package/dist/types/report.js +8 -0
- package/dist/types/report.js.map +1 -0
- package/dist/types/reviewer.d.ts +66 -0
- package/dist/types/reviewer.d.ts.map +1 -0
- package/dist/types/reviewer.js +5 -0
- package/dist/types/reviewer.js.map +1 -0
- package/dist/types/state.d.ts +124 -0
- package/dist/types/state.d.ts.map +1 -0
- package/dist/types/state.js +20 -0
- package/dist/types/state.js.map +1 -0
- package/dist/types/task.d.ts +117 -0
- package/dist/types/task.d.ts.map +1 -0
- package/dist/types/task.js +7 -0
- package/dist/types/task.js.map +1 -0
- package/dist/types/workspace_state.d.ts +125 -0
- package/dist/types/workspace_state.d.ts.map +1 -0
- package/dist/types/workspace_state.js +10 -0
- package/dist/types/workspace_state.js.map +1 -0
- package/envoi.config.json +191 -0
- package/package.json +52 -0
- package/relais/prompts/.gitkeep +0 -0
- package/relais/prompts/builder.system.txt +13 -0
- package/relais/prompts/builder.user.txt +15 -0
- package/relais/prompts/orchestrator.system.txt +37 -0
- package/relais/prompts/orchestrator.user.txt +34 -0
- package/relais/prompts/reviewer.system.txt +33 -0
- package/relais/prompts/reviewer.user.txt +35 -0
- package/relais/schemas/.gitkeep +0 -0
- package/relais/schemas/builder_result.schema.json +29 -0
- package/relais/schemas/report.schema.json +195 -0
- package/relais/schemas/reviewer_result.schema.json +70 -0
- package/relais/schemas/task.schema.json +155 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verification command execution module.
|
|
3
|
+
*
|
|
4
|
+
* Provides functions to execute verification commands safely using argv arrays
|
|
5
|
+
* (no shell) with parameter interpolation and timeout handling.
|
|
6
|
+
*/
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
8
|
+
/**
|
|
9
|
+
* Validates a single parameter value against security constraints.
|
|
10
|
+
*
|
|
11
|
+
* Checks for:
|
|
12
|
+
* - Length exceeding max_param_len
|
|
13
|
+
* - Whitespace (if reject_whitespace_in_params is true)
|
|
14
|
+
* - Path traversal '..' (if reject_dotdot is true)
|
|
15
|
+
* - Shell metacharacters (using reject_metachars_regex)
|
|
16
|
+
*
|
|
17
|
+
* @param paramName - Name of the parameter being validated
|
|
18
|
+
* @param value - The parameter value to validate (converted to string)
|
|
19
|
+
* @param config - Verification configuration with validation rules
|
|
20
|
+
* @returns ParamValidationError if validation fails, null if valid
|
|
21
|
+
*/
|
|
22
|
+
export function validateParam(paramName, value, config) {
|
|
23
|
+
// Convert value to string for validation
|
|
24
|
+
const strValue = value === null ? '' : String(value);
|
|
25
|
+
// Check length
|
|
26
|
+
if (strValue.length > config.max_param_len) {
|
|
27
|
+
return {
|
|
28
|
+
param_name: paramName,
|
|
29
|
+
value: strValue,
|
|
30
|
+
reason: 'too_long',
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// Check for whitespace if enabled
|
|
34
|
+
if (config.reject_whitespace_in_params && /\s/.test(strValue)) {
|
|
35
|
+
return {
|
|
36
|
+
param_name: paramName,
|
|
37
|
+
value: strValue,
|
|
38
|
+
reason: 'whitespace',
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// Check for path traversal '..' if enabled
|
|
42
|
+
if (config.reject_dotdot) {
|
|
43
|
+
// Match: ../, ^..$, /..$
|
|
44
|
+
if (/\.\.\/|^\.\.$|\/\.\.$/.test(strValue)) {
|
|
45
|
+
return {
|
|
46
|
+
param_name: paramName,
|
|
47
|
+
value: strValue,
|
|
48
|
+
reason: 'dotdot',
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Check for shell metacharacters
|
|
53
|
+
try {
|
|
54
|
+
const metacharRegex = new RegExp(config.reject_metachars_regex);
|
|
55
|
+
if (metacharRegex.test(strValue)) {
|
|
56
|
+
return {
|
|
57
|
+
param_name: paramName,
|
|
58
|
+
value: strValue,
|
|
59
|
+
reason: 'metachar',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
// If regex is invalid, treat as validation failure
|
|
65
|
+
// This shouldn't happen with valid config, but be defensive
|
|
66
|
+
return {
|
|
67
|
+
param_name: paramName,
|
|
68
|
+
value: strValue,
|
|
69
|
+
reason: 'metachar',
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Validates all verification parameters for a task.
|
|
76
|
+
*
|
|
77
|
+
* Validates all parameters in task.verification.params against the security
|
|
78
|
+
* constraints defined in the verification config.
|
|
79
|
+
*
|
|
80
|
+
* @param task - Task containing verification configuration and parameters
|
|
81
|
+
* @param config - Verification configuration with validation rules
|
|
82
|
+
* @returns ParamValidationResult with ok=true if all params valid, errors otherwise
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* const result = validateVerificationParams(task, config.verification);
|
|
87
|
+
* if (!result.ok) {
|
|
88
|
+
* // Handle validation errors
|
|
89
|
+
* console.error('Invalid parameters:', result.errors);
|
|
90
|
+
* }
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export function validateVerificationParams(task, config) {
|
|
94
|
+
const errors = [];
|
|
95
|
+
const verification = task.verification;
|
|
96
|
+
const params = verification.params ?? {};
|
|
97
|
+
// Validate all parameters for all templates
|
|
98
|
+
for (const [templateId, templateParams] of Object.entries(params)) {
|
|
99
|
+
for (const [paramName, paramValue] of Object.entries(templateParams)) {
|
|
100
|
+
const error = validateParam(paramName, paramValue, config);
|
|
101
|
+
if (error) {
|
|
102
|
+
// Prefix param name with template ID for clarity
|
|
103
|
+
errors.push({
|
|
104
|
+
...error,
|
|
105
|
+
param_name: `${templateId}.${error.param_name}`,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
ok: errors.length === 0,
|
|
112
|
+
errors,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Interpolates parameter placeholders in command arguments.
|
|
117
|
+
*
|
|
118
|
+
* Replaces {{param_name}} placeholders with values from the params object.
|
|
119
|
+
*
|
|
120
|
+
* @param args - Array of command arguments that may contain {{param}} placeholders
|
|
121
|
+
* @param params - Object mapping parameter names to values
|
|
122
|
+
* @returns Array of arguments with placeholders replaced
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```typescript
|
|
126
|
+
* interpolateArgs(['--filter', '{{pkg}}'], { pkg: 'my-package' })
|
|
127
|
+
* // Returns: ['--filter', 'my-package']
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
export function interpolateArgs(args, params) {
|
|
131
|
+
return args.map((arg) => {
|
|
132
|
+
// Replace {{param_name}} placeholders
|
|
133
|
+
return arg.replace(/\{\{(\w+)\}\}/g, (match, paramName) => {
|
|
134
|
+
const value = params[paramName];
|
|
135
|
+
if (value === undefined) {
|
|
136
|
+
throw new Error(`Missing parameter '${paramName}' for template argument '${arg}'`);
|
|
137
|
+
}
|
|
138
|
+
// Convert to string, handling null explicitly
|
|
139
|
+
return value === null ? '' : String(value);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Executes a single verification command.
|
|
145
|
+
*
|
|
146
|
+
* Runs the command using spawn with shell:false for security. Captures stdout
|
|
147
|
+
* and stderr, enforces timeout, and returns execution results.
|
|
148
|
+
*
|
|
149
|
+
* @param template - Verification template containing cmd and args
|
|
150
|
+
* @param params - Parameters for template argument interpolation
|
|
151
|
+
* @param timeoutMs - Timeout in milliseconds (0 means no timeout)
|
|
152
|
+
* @returns Promise resolving to VerificationRun result
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```typescript
|
|
156
|
+
* const result = await executeVerification(
|
|
157
|
+
* { id: 'lint', cmd: 'pnpm', args: ['-w', 'lint'] },
|
|
158
|
+
* {},
|
|
159
|
+
* 90000
|
|
160
|
+
* );
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
export async function executeVerification(template, params, timeoutMs) {
|
|
164
|
+
const startTime = Date.now();
|
|
165
|
+
let stdout = '';
|
|
166
|
+
let stderr = '';
|
|
167
|
+
// Interpolate parameters in args
|
|
168
|
+
const args = interpolateArgs(template.args, params);
|
|
169
|
+
return new Promise((resolve, reject) => {
|
|
170
|
+
let timeoutId = null;
|
|
171
|
+
let child = null;
|
|
172
|
+
// Set up timeout handler
|
|
173
|
+
const handleTimeout = () => {
|
|
174
|
+
if (child) {
|
|
175
|
+
child.kill('SIGTERM');
|
|
176
|
+
// Give it a moment to terminate gracefully, then force kill
|
|
177
|
+
setTimeout(() => {
|
|
178
|
+
if (child && !child.killed) {
|
|
179
|
+
child.kill('SIGKILL');
|
|
180
|
+
}
|
|
181
|
+
}, 1000);
|
|
182
|
+
}
|
|
183
|
+
const durationMs = Date.now() - startTime;
|
|
184
|
+
resolve({
|
|
185
|
+
template_id: template.id,
|
|
186
|
+
exit_code: 124, // Standard timeout exit code
|
|
187
|
+
stdout,
|
|
188
|
+
stderr: stderr + '\n[Command timed out]',
|
|
189
|
+
duration_ms: durationMs,
|
|
190
|
+
success: false,
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
// Set up timeout if specified
|
|
194
|
+
if (timeoutMs > 0) {
|
|
195
|
+
timeoutId = setTimeout(handleTimeout, timeoutMs);
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
// Spawn the process with shell:false for security
|
|
199
|
+
child = spawn(template.cmd, args, {
|
|
200
|
+
shell: false,
|
|
201
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
202
|
+
});
|
|
203
|
+
// Capture stdout
|
|
204
|
+
child.stdout?.on('data', (data) => {
|
|
205
|
+
stdout += data.toString();
|
|
206
|
+
});
|
|
207
|
+
// Capture stderr
|
|
208
|
+
child.stderr?.on('data', (data) => {
|
|
209
|
+
stderr += data.toString();
|
|
210
|
+
});
|
|
211
|
+
// Handle process completion
|
|
212
|
+
child.on('close', (code) => {
|
|
213
|
+
if (timeoutId) {
|
|
214
|
+
clearTimeout(timeoutId);
|
|
215
|
+
}
|
|
216
|
+
const durationMs = Date.now() - startTime;
|
|
217
|
+
const exitCode = code ?? 0;
|
|
218
|
+
// If process was killed by timeout, result was already resolved
|
|
219
|
+
if (code === null && timeoutId) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
resolve({
|
|
223
|
+
template_id: template.id,
|
|
224
|
+
exit_code: exitCode,
|
|
225
|
+
stdout,
|
|
226
|
+
stderr,
|
|
227
|
+
duration_ms: durationMs,
|
|
228
|
+
success: exitCode === 0,
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
// Handle process errors
|
|
232
|
+
child.on('error', (error) => {
|
|
233
|
+
if (timeoutId) {
|
|
234
|
+
clearTimeout(timeoutId);
|
|
235
|
+
}
|
|
236
|
+
const durationMs = Date.now() - startTime;
|
|
237
|
+
resolve({
|
|
238
|
+
template_id: template.id,
|
|
239
|
+
exit_code: 1,
|
|
240
|
+
stdout,
|
|
241
|
+
stderr: stderr + `\n[Process error: ${error.message}]`,
|
|
242
|
+
duration_ms: durationMs,
|
|
243
|
+
success: false,
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
if (timeoutId) {
|
|
249
|
+
clearTimeout(timeoutId);
|
|
250
|
+
}
|
|
251
|
+
const durationMs = Date.now() - startTime;
|
|
252
|
+
resolve({
|
|
253
|
+
template_id: template.id,
|
|
254
|
+
exit_code: 1,
|
|
255
|
+
stdout,
|
|
256
|
+
stderr: stderr + `\n[Failed to spawn process: ${error instanceof Error ? error.message : String(error)}]`,
|
|
257
|
+
duration_ms: durationMs,
|
|
258
|
+
success: false,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Runs verification commands for a task.
|
|
265
|
+
*
|
|
266
|
+
* Executes fast verifications first, then slow verifications. Each verification
|
|
267
|
+
* uses the appropriate timeout from config. Parameters are taken from the task's
|
|
268
|
+
* verification.params object, keyed by template ID.
|
|
269
|
+
*
|
|
270
|
+
* @param templates - Map of template ID to VerificationTemplate
|
|
271
|
+
* @param task - Task containing verification configuration
|
|
272
|
+
* @param config - Verification configuration with timeouts
|
|
273
|
+
* @returns Promise resolving to array of VerificationRun results
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* ```typescript
|
|
277
|
+
* const templates = new Map([
|
|
278
|
+
* ['lint', { id: 'lint', cmd: 'pnpm', args: ['-w', 'lint'] }]
|
|
279
|
+
* ]);
|
|
280
|
+
* const runs = await runVerifications(templates, task, config.verification);
|
|
281
|
+
* ```
|
|
282
|
+
*/
|
|
283
|
+
export async function runVerifications(templates, task, config) {
|
|
284
|
+
const results = [];
|
|
285
|
+
const verification = task.verification;
|
|
286
|
+
const params = verification.params ?? {};
|
|
287
|
+
// Run fast verifications first
|
|
288
|
+
for (const templateId of verification.fast) {
|
|
289
|
+
const template = templates.get(templateId);
|
|
290
|
+
if (!template) {
|
|
291
|
+
throw new Error(`Verification template '${templateId}' not found in config`);
|
|
292
|
+
}
|
|
293
|
+
const templateParams = params[templateId] ?? {};
|
|
294
|
+
const timeoutMs = config.timeout_fast_seconds * 1000;
|
|
295
|
+
const result = await executeVerification(template, templateParams, timeoutMs);
|
|
296
|
+
results.push(result);
|
|
297
|
+
}
|
|
298
|
+
// Then run slow verifications
|
|
299
|
+
for (const templateId of verification.slow) {
|
|
300
|
+
const template = templates.get(templateId);
|
|
301
|
+
if (!template) {
|
|
302
|
+
throw new Error(`Verification template '${templateId}' not found in config`);
|
|
303
|
+
}
|
|
304
|
+
const templateParams = params[templateId] ?? {};
|
|
305
|
+
const timeoutMs = config.timeout_slow_seconds * 1000;
|
|
306
|
+
const result = await executeVerification(template, templateParams, timeoutMs);
|
|
307
|
+
results.push(result);
|
|
308
|
+
}
|
|
309
|
+
return results;
|
|
310
|
+
}
|
|
311
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/lib/verify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace state persistence library.
|
|
3
|
+
*
|
|
4
|
+
* Manages reading and writing STATE.json to track milestone progress
|
|
5
|
+
* and budget consumption across restarts.
|
|
6
|
+
*
|
|
7
|
+
* @see docs/NEW-PLAN.md lines 48-59
|
|
8
|
+
*/
|
|
9
|
+
import type { WorkspaceState, BudgetDeltas, IdeaTestabilityNeed } from '../types/workspace_state.js';
|
|
10
|
+
import type { PerMilestoneBudgets } from '../types/config.js';
|
|
11
|
+
/**
|
|
12
|
+
* Creates a default empty WorkspaceState.
|
|
13
|
+
*
|
|
14
|
+
* @returns Default state with null milestone_id, zero budgets, false budget_warning
|
|
15
|
+
*/
|
|
16
|
+
export declare function createDefaultState(): WorkspaceState;
|
|
17
|
+
/**
|
|
18
|
+
* Reads and parses STATE.json from workspaceDir.
|
|
19
|
+
*
|
|
20
|
+
* Returns default state if file doesn't exist.
|
|
21
|
+
*
|
|
22
|
+
* @param workspaceDir - The workspace directory containing STATE.json
|
|
23
|
+
* @returns Parsed WorkspaceState or default if file doesn't exist
|
|
24
|
+
*/
|
|
25
|
+
export declare function readWorkspaceState(workspaceDir: string): Promise<WorkspaceState>;
|
|
26
|
+
/**
|
|
27
|
+
* Writes WorkspaceState to STATE.json atomically.
|
|
28
|
+
*
|
|
29
|
+
* Uses atomic write pattern for crash safety.
|
|
30
|
+
*
|
|
31
|
+
* @param workspaceDir - The workspace directory to write STATE.json to
|
|
32
|
+
* @param state - The WorkspaceState to persist
|
|
33
|
+
*/
|
|
34
|
+
export declare function writeWorkspaceState(workspaceDir: string, state: WorkspaceState): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Ensures state has the given milestone_id.
|
|
37
|
+
*
|
|
38
|
+
* If milestone changes, resets budgets to zero.
|
|
39
|
+
*
|
|
40
|
+
* @param state - Current WorkspaceState
|
|
41
|
+
* @param milestoneId - Milestone ID to ensure
|
|
42
|
+
* @returns Updated state and whether milestone changed
|
|
43
|
+
*/
|
|
44
|
+
export declare function ensureMilestone(state: WorkspaceState, milestoneId: string): {
|
|
45
|
+
state: WorkspaceState;
|
|
46
|
+
changed: boolean;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Applies incremental budget changes to state.
|
|
50
|
+
*
|
|
51
|
+
* Creates new state with updated budgets (immutable update).
|
|
52
|
+
*
|
|
53
|
+
* @param state - Current WorkspaceState
|
|
54
|
+
* @param deltas - Partial budget counts to add
|
|
55
|
+
* @returns New state with updated budgets
|
|
56
|
+
*/
|
|
57
|
+
export declare function applyDeltas(state: WorkspaceState, deltas: BudgetDeltas): WorkspaceState;
|
|
58
|
+
export interface NewIdeaInput {
|
|
59
|
+
text: string;
|
|
60
|
+
source: 'interactive' | 'cli' | 'api';
|
|
61
|
+
target_by?: string | null;
|
|
62
|
+
testability_need?: IdeaTestabilityNeed;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Appends a new idea entry to workspace state.
|
|
66
|
+
*/
|
|
67
|
+
export declare function appendIdeaEntry(state: WorkspaceState, input: NewIdeaInput): WorkspaceState;
|
|
68
|
+
/**
|
|
69
|
+
* Checks if any budget is approaching its limit.
|
|
70
|
+
*
|
|
71
|
+
* Returns true if any budget count >= max * warnAtFraction.
|
|
72
|
+
*
|
|
73
|
+
* @param state - Current WorkspaceState
|
|
74
|
+
* @param perMilestone - Per-milestone budget limits
|
|
75
|
+
* @param warnAtFraction - Fraction (0-1) at which to warn
|
|
76
|
+
* @returns True if any budget is at warning level
|
|
77
|
+
*/
|
|
78
|
+
export declare function computeBudgetWarning(state: WorkspaceState, perMilestone: PerMilestoneBudgets, warnAtFraction: number): boolean;
|
|
79
|
+
//# sourceMappingURL=workspace_state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace_state.d.ts","sourceRoot":"","sources":["../../src/lib/workspace_state.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EACV,cAAc,EAEd,YAAY,EAGZ,mBAAmB,EAEpB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,cAAc,CAgBnD;AA2GD;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CA4BtF;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,cAAc,GACpB,OAAO,CAAC,IAAI,CAAC,CAGf;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,cAAc,EACrB,WAAW,EAAE,MAAM,GAClB;IAAE,KAAK,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAmB7C;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,GAAG,cAAc,CAYvF;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,aAAa,GAAG,KAAK,GAAG,KAAK,CAAC;IACtC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,gBAAgB,CAAC,EAAE,mBAAmB,CAAC;CACxC;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,GAAG,cAAc,CAqB1F;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,cAAc,EACrB,YAAY,EAAE,mBAAmB,EACjC,cAAc,EAAE,MAAM,GACrB,OAAO,CAsBT"}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace state persistence library.
|
|
3
|
+
*
|
|
4
|
+
* Manages reading and writing STATE.json to track milestone progress
|
|
5
|
+
* and budget consumption across restarts.
|
|
6
|
+
*
|
|
7
|
+
* @see docs/NEW-PLAN.md lines 48-59
|
|
8
|
+
*/
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { atomicReadJson, atomicWriteJson, AtomicFsError } from './fs.js';
|
|
11
|
+
/**
|
|
12
|
+
* Creates a default empty WorkspaceState.
|
|
13
|
+
*
|
|
14
|
+
* @returns Default state with null milestone_id, zero budgets, false budget_warning
|
|
15
|
+
*/
|
|
16
|
+
export function createDefaultState() {
|
|
17
|
+
return {
|
|
18
|
+
milestone_id: null,
|
|
19
|
+
budgets: {
|
|
20
|
+
ticks: 0,
|
|
21
|
+
orchestrator_calls: 0,
|
|
22
|
+
builder_calls: 0,
|
|
23
|
+
verify_runs: 0,
|
|
24
|
+
},
|
|
25
|
+
budget_warning: false,
|
|
26
|
+
last_run_id: null,
|
|
27
|
+
last_verdict: null,
|
|
28
|
+
idea_inbox: [],
|
|
29
|
+
planning_digest: null,
|
|
30
|
+
open_product_questions: [],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Runtime shape guard for idea status values.
|
|
35
|
+
*/
|
|
36
|
+
function isIdeaStatus(value) {
|
|
37
|
+
return value === 'new' || value === 'triaged' || value === 'scheduled' || value === 'deferred' || value === 'done';
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Runtime shape guard for idea testability values.
|
|
41
|
+
*/
|
|
42
|
+
function isIdeaTestabilityNeed(value) {
|
|
43
|
+
return value === 'soon' || value === 'later' || value === 'unknown';
|
|
44
|
+
}
|
|
45
|
+
function normalizeIdeaInboxEntry(value) {
|
|
46
|
+
if (typeof value !== 'object' || value === null)
|
|
47
|
+
return null;
|
|
48
|
+
const candidate = value;
|
|
49
|
+
if (typeof candidate.id !== 'string' || candidate.id.length === 0)
|
|
50
|
+
return null;
|
|
51
|
+
if (typeof candidate.text !== 'string' || candidate.text.length === 0)
|
|
52
|
+
return null;
|
|
53
|
+
if (typeof candidate.submitted_at !== 'string' || candidate.submitted_at.length === 0)
|
|
54
|
+
return null;
|
|
55
|
+
if (candidate.source !== 'interactive' && candidate.source !== 'cli' && candidate.source !== 'api')
|
|
56
|
+
return null;
|
|
57
|
+
if (!isIdeaStatus(candidate.status))
|
|
58
|
+
return null;
|
|
59
|
+
return {
|
|
60
|
+
id: candidate.id,
|
|
61
|
+
text: candidate.text,
|
|
62
|
+
submitted_at: candidate.submitted_at,
|
|
63
|
+
source: candidate.source,
|
|
64
|
+
status: candidate.status,
|
|
65
|
+
target_by: typeof candidate.target_by === 'string' ? candidate.target_by : null,
|
|
66
|
+
testability_need: isIdeaTestabilityNeed(candidate.testability_need) ? candidate.testability_need : undefined,
|
|
67
|
+
triaged_by_task_id: typeof candidate.triaged_by_task_id === 'string' ? candidate.triaged_by_task_id : undefined,
|
|
68
|
+
triaged_at: typeof candidate.triaged_at === 'string' ? candidate.triaged_at : undefined,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function normalizeOpenProductQuestion(value) {
|
|
72
|
+
if (typeof value !== 'object' || value === null)
|
|
73
|
+
return null;
|
|
74
|
+
const candidate = value;
|
|
75
|
+
if (typeof candidate.id !== 'string' || candidate.id.length === 0)
|
|
76
|
+
return null;
|
|
77
|
+
if (typeof candidate.prompt !== 'string' || candidate.prompt.length === 0)
|
|
78
|
+
return null;
|
|
79
|
+
if (typeof candidate.created_at !== 'string' || candidate.created_at.length === 0)
|
|
80
|
+
return null;
|
|
81
|
+
if (typeof candidate.resolved !== 'boolean')
|
|
82
|
+
return null;
|
|
83
|
+
const choices = Array.isArray(candidate.choices)
|
|
84
|
+
? candidate.choices.filter((entry) => typeof entry === 'string' && entry.length > 0)
|
|
85
|
+
: undefined;
|
|
86
|
+
return {
|
|
87
|
+
id: candidate.id,
|
|
88
|
+
prompt: candidate.prompt,
|
|
89
|
+
choices,
|
|
90
|
+
created_at: candidate.created_at,
|
|
91
|
+
resolved: candidate.resolved,
|
|
92
|
+
resolved_at: typeof candidate.resolved_at === 'string' ? candidate.resolved_at : undefined,
|
|
93
|
+
resolution: typeof candidate.resolution === 'string' ? candidate.resolution : undefined,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Normalizes persisted state to include optional modern planning fields while
|
|
98
|
+
* preserving legacy fields present in older STATE.json files.
|
|
99
|
+
*/
|
|
100
|
+
function normalizeWorkspaceState(rawState) {
|
|
101
|
+
const source = rawState;
|
|
102
|
+
const ideaInbox = Array.isArray(source.idea_inbox)
|
|
103
|
+
? source.idea_inbox
|
|
104
|
+
.map((entry) => normalizeIdeaInboxEntry(entry))
|
|
105
|
+
.filter((entry) => entry !== null)
|
|
106
|
+
: [];
|
|
107
|
+
const openQuestions = Array.isArray(source.open_product_questions)
|
|
108
|
+
? source.open_product_questions
|
|
109
|
+
.map((entry) => normalizeOpenProductQuestion(entry))
|
|
110
|
+
.filter((entry) => entry !== null)
|
|
111
|
+
: [];
|
|
112
|
+
const planningDigestRaw = source.planning_digest;
|
|
113
|
+
const planningDigest = typeof planningDigestRaw === 'object' &&
|
|
114
|
+
planningDigestRaw !== null &&
|
|
115
|
+
typeof planningDigestRaw.updated_at === 'string' &&
|
|
116
|
+
typeof planningDigestRaw.summary === 'string'
|
|
117
|
+
? {
|
|
118
|
+
updated_at: String(planningDigestRaw.updated_at),
|
|
119
|
+
summary: String(planningDigestRaw.summary),
|
|
120
|
+
last_task_id: typeof planningDigestRaw.last_task_id === 'string'
|
|
121
|
+
? String(planningDigestRaw.last_task_id)
|
|
122
|
+
: undefined,
|
|
123
|
+
suggested_milestone: typeof planningDigestRaw.suggested_milestone === 'string'
|
|
124
|
+
? String(planningDigestRaw.suggested_milestone)
|
|
125
|
+
: undefined,
|
|
126
|
+
}
|
|
127
|
+
: null;
|
|
128
|
+
return {
|
|
129
|
+
...source,
|
|
130
|
+
idea_inbox: ideaInbox,
|
|
131
|
+
planning_digest: planningDigest,
|
|
132
|
+
open_product_questions: openQuestions,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Reads and parses STATE.json from workspaceDir.
|
|
137
|
+
*
|
|
138
|
+
* Returns default state if file doesn't exist.
|
|
139
|
+
*
|
|
140
|
+
* @param workspaceDir - The workspace directory containing STATE.json
|
|
141
|
+
* @returns Parsed WorkspaceState or default if file doesn't exist
|
|
142
|
+
*/
|
|
143
|
+
export async function readWorkspaceState(workspaceDir) {
|
|
144
|
+
const filePath = join(workspaceDir, 'STATE.json');
|
|
145
|
+
try {
|
|
146
|
+
const state = await atomicReadJson(filePath);
|
|
147
|
+
// Validate shape matches WorkspaceState interface
|
|
148
|
+
// TypeScript will catch type mismatches, but we ensure required fields exist
|
|
149
|
+
if (typeof state === 'object' &&
|
|
150
|
+
state !== null &&
|
|
151
|
+
'milestone_id' in state &&
|
|
152
|
+
'budgets' in state &&
|
|
153
|
+
'budget_warning' in state &&
|
|
154
|
+
'last_run_id' in state &&
|
|
155
|
+
'last_verdict' in state) {
|
|
156
|
+
return normalizeWorkspaceState(state);
|
|
157
|
+
}
|
|
158
|
+
// If shape doesn't match, return default
|
|
159
|
+
return createDefaultState();
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
// If file doesn't exist or can't be read, return default state
|
|
163
|
+
if (error instanceof AtomicFsError) {
|
|
164
|
+
return createDefaultState();
|
|
165
|
+
}
|
|
166
|
+
// Re-throw unexpected errors
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Writes WorkspaceState to STATE.json atomically.
|
|
172
|
+
*
|
|
173
|
+
* Uses atomic write pattern for crash safety.
|
|
174
|
+
*
|
|
175
|
+
* @param workspaceDir - The workspace directory to write STATE.json to
|
|
176
|
+
* @param state - The WorkspaceState to persist
|
|
177
|
+
*/
|
|
178
|
+
export async function writeWorkspaceState(workspaceDir, state) {
|
|
179
|
+
const filePath = join(workspaceDir, 'STATE.json');
|
|
180
|
+
await atomicWriteJson(filePath, state);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Ensures state has the given milestone_id.
|
|
184
|
+
*
|
|
185
|
+
* If milestone changes, resets budgets to zero.
|
|
186
|
+
*
|
|
187
|
+
* @param state - Current WorkspaceState
|
|
188
|
+
* @param milestoneId - Milestone ID to ensure
|
|
189
|
+
* @returns Updated state and whether milestone changed
|
|
190
|
+
*/
|
|
191
|
+
export function ensureMilestone(state, milestoneId) {
|
|
192
|
+
if (state.milestone_id === milestoneId) {
|
|
193
|
+
return { state, changed: false };
|
|
194
|
+
}
|
|
195
|
+
// Milestone is different or null - create new state with reset budgets
|
|
196
|
+
const newState = {
|
|
197
|
+
...state,
|
|
198
|
+
milestone_id: milestoneId,
|
|
199
|
+
budgets: {
|
|
200
|
+
ticks: 0,
|
|
201
|
+
orchestrator_calls: 0,
|
|
202
|
+
builder_calls: 0,
|
|
203
|
+
verify_runs: 0,
|
|
204
|
+
},
|
|
205
|
+
budget_warning: false,
|
|
206
|
+
};
|
|
207
|
+
return { state: newState, changed: true };
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Applies incremental budget changes to state.
|
|
211
|
+
*
|
|
212
|
+
* Creates new state with updated budgets (immutable update).
|
|
213
|
+
*
|
|
214
|
+
* @param state - Current WorkspaceState
|
|
215
|
+
* @param deltas - Partial budget counts to add
|
|
216
|
+
* @returns New state with updated budgets
|
|
217
|
+
*/
|
|
218
|
+
export function applyDeltas(state, deltas) {
|
|
219
|
+
const newBudgets = {
|
|
220
|
+
ticks: state.budgets.ticks + (deltas.ticks ?? 0),
|
|
221
|
+
orchestrator_calls: state.budgets.orchestrator_calls + (deltas.orchestrator_calls ?? 0),
|
|
222
|
+
builder_calls: state.budgets.builder_calls + (deltas.builder_calls ?? 0),
|
|
223
|
+
verify_runs: state.budgets.verify_runs + (deltas.verify_runs ?? 0),
|
|
224
|
+
};
|
|
225
|
+
return {
|
|
226
|
+
...state,
|
|
227
|
+
budgets: newBudgets,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Appends a new idea entry to workspace state.
|
|
232
|
+
*/
|
|
233
|
+
export function appendIdeaEntry(state, input) {
|
|
234
|
+
const trimmedText = input.text.trim();
|
|
235
|
+
if (!trimmedText)
|
|
236
|
+
return state;
|
|
237
|
+
const now = new Date().toISOString();
|
|
238
|
+
const nextId = `idea-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
239
|
+
const entry = {
|
|
240
|
+
id: nextId,
|
|
241
|
+
text: trimmedText,
|
|
242
|
+
submitted_at: now,
|
|
243
|
+
source: input.source,
|
|
244
|
+
status: 'new',
|
|
245
|
+
target_by: input.target_by ?? null,
|
|
246
|
+
testability_need: input.testability_need ?? 'unknown',
|
|
247
|
+
};
|
|
248
|
+
const inbox = [...(state.idea_inbox ?? []), entry];
|
|
249
|
+
return {
|
|
250
|
+
...state,
|
|
251
|
+
idea_inbox: inbox,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Checks if any budget is approaching its limit.
|
|
256
|
+
*
|
|
257
|
+
* Returns true if any budget count >= max * warnAtFraction.
|
|
258
|
+
*
|
|
259
|
+
* @param state - Current WorkspaceState
|
|
260
|
+
* @param perMilestone - Per-milestone budget limits
|
|
261
|
+
* @param warnAtFraction - Fraction (0-1) at which to warn
|
|
262
|
+
* @returns True if any budget is at warning level
|
|
263
|
+
*/
|
|
264
|
+
export function computeBudgetWarning(state, perMilestone, warnAtFraction) {
|
|
265
|
+
// Check ticks
|
|
266
|
+
if (state.budgets.ticks >= perMilestone.max_ticks * warnAtFraction) {
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
// Check orchestrator_calls
|
|
270
|
+
if (state.budgets.orchestrator_calls >= perMilestone.max_orchestrator_calls * warnAtFraction) {
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
// Check builder_calls
|
|
274
|
+
if (state.budgets.builder_calls >= perMilestone.max_builder_calls * warnAtFraction) {
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
// Check verify_runs
|
|
278
|
+
if (state.budgets.verify_runs >= perMilestone.max_verify_runs * warnAtFraction) {
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
//# sourceMappingURL=workspace_state.js.map
|