@weldr/runr 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +216 -0
- package/LICENSE +190 -0
- package/NOTICE +4 -0
- package/README.md +200 -0
- package/dist/cli.js +464 -0
- package/dist/commands/__tests__/report.test.js +202 -0
- package/dist/commands/compare.js +168 -0
- package/dist/commands/doctor.js +124 -0
- package/dist/commands/follow.js +251 -0
- package/dist/commands/gc.js +161 -0
- package/dist/commands/guards-only.js +89 -0
- package/dist/commands/metrics.js +441 -0
- package/dist/commands/orchestrate.js +800 -0
- package/dist/commands/paths.js +31 -0
- package/dist/commands/preflight.js +152 -0
- package/dist/commands/report.js +478 -0
- package/dist/commands/resume.js +149 -0
- package/dist/commands/run.js +538 -0
- package/dist/commands/status.js +189 -0
- package/dist/commands/summarize.js +220 -0
- package/dist/commands/version.js +82 -0
- package/dist/commands/wait.js +170 -0
- package/dist/config/__tests__/presets.test.js +104 -0
- package/dist/config/load.js +66 -0
- package/dist/config/schema.js +160 -0
- package/dist/context/__tests__/artifact.test.js +130 -0
- package/dist/context/__tests__/pack.test.js +191 -0
- package/dist/context/artifact.js +67 -0
- package/dist/context/index.js +2 -0
- package/dist/context/pack.js +273 -0
- package/dist/diagnosis/analyzer.js +678 -0
- package/dist/diagnosis/formatter.js +136 -0
- package/dist/diagnosis/index.js +6 -0
- package/dist/diagnosis/types.js +7 -0
- package/dist/env/__tests__/fingerprint.test.js +116 -0
- package/dist/env/fingerprint.js +111 -0
- package/dist/orchestrator/__tests__/policy.test.js +185 -0
- package/dist/orchestrator/__tests__/schema-version.test.js +65 -0
- package/dist/orchestrator/artifacts.js +405 -0
- package/dist/orchestrator/state-machine.js +646 -0
- package/dist/orchestrator/types.js +88 -0
- package/dist/ownership/normalize.js +45 -0
- package/dist/repo/context.js +90 -0
- package/dist/repo/git.js +13 -0
- package/dist/repo/worktree.js +239 -0
- package/dist/store/run-store.js +107 -0
- package/dist/store/run-utils.js +69 -0
- package/dist/store/runs-root.js +126 -0
- package/dist/supervisor/__tests__/evidence-gate.test.js +111 -0
- package/dist/supervisor/__tests__/ownership.test.js +103 -0
- package/dist/supervisor/__tests__/state-machine.test.js +290 -0
- package/dist/supervisor/collision.js +240 -0
- package/dist/supervisor/evidence-gate.js +98 -0
- package/dist/supervisor/planner.js +18 -0
- package/dist/supervisor/runner.js +1562 -0
- package/dist/supervisor/scope-guard.js +55 -0
- package/dist/supervisor/state-machine.js +121 -0
- package/dist/supervisor/verification-policy.js +64 -0
- package/dist/tasks/task-metadata.js +72 -0
- package/dist/types/schemas.js +1 -0
- package/dist/verification/engine.js +49 -0
- package/dist/workers/__tests__/claude.test.js +88 -0
- package/dist/workers/__tests__/codex.test.js +81 -0
- package/dist/workers/claude.js +119 -0
- package/dist/workers/codex.js +162 -0
- package/dist/workers/json.js +22 -0
- package/dist/workers/mock.js +193 -0
- package/dist/workers/prompts.js +98 -0
- package/dist/workers/schemas.js +39 -0
- package/package.json +47 -0
- package/templates/prompts/implementer.md +70 -0
- package/templates/prompts/planner.md +62 -0
- package/templates/prompts/reviewer.md +77 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { estimatePackTokens } from './pack.js';
|
|
4
|
+
/**
|
|
5
|
+
* Write context pack artifact to run directory.
|
|
6
|
+
* When pack is null, writes a disabled stub.
|
|
7
|
+
*/
|
|
8
|
+
export function writeContextPackArtifact(runDir, pack) {
|
|
9
|
+
const artifactsDir = path.join(runDir, 'artifacts');
|
|
10
|
+
// Ensure artifacts directory exists
|
|
11
|
+
if (!fs.existsSync(artifactsDir)) {
|
|
12
|
+
fs.mkdirSync(artifactsDir, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
const artifactPath = path.join(artifactsDir, 'context-pack.json');
|
|
15
|
+
if (pack === null) {
|
|
16
|
+
// Write disabled stub
|
|
17
|
+
const stub = {
|
|
18
|
+
enabled: false,
|
|
19
|
+
pack_version: 1,
|
|
20
|
+
generated_at: new Date().toISOString()
|
|
21
|
+
};
|
|
22
|
+
fs.writeFileSync(artifactPath, JSON.stringify(stub, null, 2));
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
// Write full pack
|
|
26
|
+
const artifact = {
|
|
27
|
+
enabled: true,
|
|
28
|
+
pack_version: 1,
|
|
29
|
+
generated_at: pack.generated_at,
|
|
30
|
+
estimated_tokens: estimatePackTokens(pack),
|
|
31
|
+
verification: pack.verification,
|
|
32
|
+
reference_files: pack.reference_files,
|
|
33
|
+
scope: pack.scope,
|
|
34
|
+
patterns: pack.patterns
|
|
35
|
+
};
|
|
36
|
+
fs.writeFileSync(artifactPath, JSON.stringify(artifact, null, 2));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Read context pack artifact from run directory.
|
|
41
|
+
* Returns null if file doesn't exist (older runs).
|
|
42
|
+
*/
|
|
43
|
+
export function readContextPackArtifact(runDir) {
|
|
44
|
+
const artifactPath = path.join(runDir, 'artifacts', 'context-pack.json');
|
|
45
|
+
if (!fs.existsSync(artifactPath)) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const content = fs.readFileSync(artifactPath, 'utf-8');
|
|
50
|
+
return JSON.parse(content);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Format context pack status for report output.
|
|
58
|
+
*/
|
|
59
|
+
export function formatContextPackStatus(artifact) {
|
|
60
|
+
if (artifact === null) {
|
|
61
|
+
return 'context_pack: (not found)';
|
|
62
|
+
}
|
|
63
|
+
if (!artifact.enabled) {
|
|
64
|
+
return 'context_pack: disabled';
|
|
65
|
+
}
|
|
66
|
+
return `context_pack: present (${artifact.estimated_tokens ?? '?'} tokens)`;
|
|
67
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
// Known reference patterns - maps task descriptions to actual files
|
|
4
|
+
const KNOWN_PATTERNS = {
|
|
5
|
+
'rng': {
|
|
6
|
+
paths: ['apps/deckbuilder/src/engine/rng.ts'],
|
|
7
|
+
reason: 'Deterministic RNG pattern (LCG algorithm)'
|
|
8
|
+
},
|
|
9
|
+
'rng pattern': {
|
|
10
|
+
paths: ['apps/deckbuilder/src/engine/rng.ts'],
|
|
11
|
+
reason: 'Deterministic RNG pattern (LCG algorithm)'
|
|
12
|
+
},
|
|
13
|
+
'deckbuilder rng': {
|
|
14
|
+
paths: ['apps/deckbuilder/src/engine/rng.ts'],
|
|
15
|
+
reason: 'Deterministic RNG pattern from deckbuilder'
|
|
16
|
+
},
|
|
17
|
+
'types pattern': {
|
|
18
|
+
paths: ['apps/deckbuilder/src/engine/types.ts'],
|
|
19
|
+
reason: 'Type definitions pattern'
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
export function buildContextPack(options) {
|
|
23
|
+
const { repoRoot, targetRoot, config, references, allowDeps } = options;
|
|
24
|
+
// 1. Extract verification commands from config (source of truth)
|
|
25
|
+
const verification = {
|
|
26
|
+
tier0: config.verification?.tier0 ?? [],
|
|
27
|
+
tier1: config.verification?.tier1 ?? [],
|
|
28
|
+
tier2: config.verification?.tier2 ?? [],
|
|
29
|
+
cwd: config.verification?.cwd
|
|
30
|
+
};
|
|
31
|
+
// 2. Resolve reference files
|
|
32
|
+
const reference_files = resolveReferences(repoRoot, references ?? []);
|
|
33
|
+
// 3. Extract scope from config
|
|
34
|
+
const scope = {
|
|
35
|
+
allowlist: config.scope?.allowlist ?? [],
|
|
36
|
+
denylist: config.scope?.denylist ?? []
|
|
37
|
+
};
|
|
38
|
+
// 4. Find nearest config patterns
|
|
39
|
+
const patterns = findNearestPatterns(repoRoot, targetRoot);
|
|
40
|
+
// 5. Build blockers guidance
|
|
41
|
+
const blockers = buildBlockersGuidance(config, allowDeps);
|
|
42
|
+
return {
|
|
43
|
+
version: 1,
|
|
44
|
+
generated_at: new Date().toISOString(),
|
|
45
|
+
verification,
|
|
46
|
+
reference_files,
|
|
47
|
+
scope,
|
|
48
|
+
patterns,
|
|
49
|
+
blockers
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function buildBlockersGuidance(config, allowDeps) {
|
|
53
|
+
const scope_violations = [];
|
|
54
|
+
const common_errors = [];
|
|
55
|
+
// Generate scope violation warnings
|
|
56
|
+
if (config.scope?.denylist?.length) {
|
|
57
|
+
for (const pattern of config.scope.denylist) {
|
|
58
|
+
if (pattern.includes('node_modules')) {
|
|
59
|
+
scope_violations.push(`Cannot modify ${pattern} - use existing dependencies only`);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
scope_violations.push(`Files matching ${pattern} are outside scope`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Lockfile restrictions
|
|
67
|
+
const lockfileRestrictions = !allowDeps && (config.lockfiles?.length ?? 0) > 0;
|
|
68
|
+
if (lockfileRestrictions) {
|
|
69
|
+
common_errors.push('npm install/pnpm install may fail if it modifies lockfiles - dependencies should already be installed');
|
|
70
|
+
}
|
|
71
|
+
// Common error patterns based on typical failures
|
|
72
|
+
common_errors.push('If verification commands fail at root, check if they should run in a subdirectory (see verification.cwd)', 'If tests fail due to missing dependencies, check if node_modules exists and is symlinked correctly', 'If lint/typecheck commands are not found, ensure package.json scripts are defined');
|
|
73
|
+
return {
|
|
74
|
+
scope_violations,
|
|
75
|
+
lockfile_restrictions: lockfileRestrictions,
|
|
76
|
+
common_errors
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function resolveReferences(repoRoot, references) {
|
|
80
|
+
const results = [];
|
|
81
|
+
const seen = new Set();
|
|
82
|
+
for (const ref of references) {
|
|
83
|
+
// Try explicit hint first
|
|
84
|
+
if (ref.hint) {
|
|
85
|
+
const fullPath = path.join(repoRoot, ref.hint);
|
|
86
|
+
if (fs.existsSync(fullPath) && !seen.has(ref.hint)) {
|
|
87
|
+
seen.add(ref.hint);
|
|
88
|
+
results.push({
|
|
89
|
+
path: ref.hint,
|
|
90
|
+
reason: ref.pattern,
|
|
91
|
+
content: readSnippet(fullPath)
|
|
92
|
+
});
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Try known patterns
|
|
97
|
+
const key = ref.pattern.toLowerCase();
|
|
98
|
+
for (const [patternKey, patternDef] of Object.entries(KNOWN_PATTERNS)) {
|
|
99
|
+
if (key.includes(patternKey)) {
|
|
100
|
+
for (const p of patternDef.paths) {
|
|
101
|
+
if (seen.has(p))
|
|
102
|
+
continue;
|
|
103
|
+
const fullPath = path.join(repoRoot, p);
|
|
104
|
+
if (fs.existsSync(fullPath)) {
|
|
105
|
+
seen.add(p);
|
|
106
|
+
results.push({
|
|
107
|
+
path: p,
|
|
108
|
+
reason: patternDef.reason,
|
|
109
|
+
content: readSnippet(fullPath)
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return results;
|
|
118
|
+
}
|
|
119
|
+
function findNearestPatterns(repoRoot, targetRoot) {
|
|
120
|
+
const targetAbs = path.isAbsolute(targetRoot)
|
|
121
|
+
? targetRoot
|
|
122
|
+
: path.join(repoRoot, targetRoot);
|
|
123
|
+
return {
|
|
124
|
+
tsconfig: findNearestFile(repoRoot, targetAbs, ['tsconfig.json']),
|
|
125
|
+
eslint: findNearestFile(repoRoot, targetAbs, [
|
|
126
|
+
'eslint.config.cjs',
|
|
127
|
+
'eslint.config.js',
|
|
128
|
+
'eslint.config.mjs',
|
|
129
|
+
'.eslintrc.cjs',
|
|
130
|
+
'.eslintrc.js',
|
|
131
|
+
'.eslintrc.json'
|
|
132
|
+
]),
|
|
133
|
+
package_json: findNearestFile(repoRoot, targetAbs, ['package.json'])
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function findNearestFile(repoRoot, startDir, fileNames) {
|
|
137
|
+
// First: search upward from target directory
|
|
138
|
+
let current = startDir;
|
|
139
|
+
while (current.startsWith(repoRoot) || current === repoRoot) {
|
|
140
|
+
for (const fileName of fileNames) {
|
|
141
|
+
const candidate = path.join(current, fileName);
|
|
142
|
+
if (fs.existsSync(candidate)) {
|
|
143
|
+
const relativePath = path.relative(repoRoot, candidate);
|
|
144
|
+
return {
|
|
145
|
+
path: relativePath,
|
|
146
|
+
content: readSnippet(candidate)
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const parent = path.dirname(current);
|
|
151
|
+
if (parent === current)
|
|
152
|
+
break;
|
|
153
|
+
current = parent;
|
|
154
|
+
}
|
|
155
|
+
// Fallback: check known-good locations (deckbuilder)
|
|
156
|
+
const fallbacks = [
|
|
157
|
+
'apps/deckbuilder'
|
|
158
|
+
];
|
|
159
|
+
for (const fallback of fallbacks) {
|
|
160
|
+
for (const fileName of fileNames) {
|
|
161
|
+
const candidate = path.join(repoRoot, fallback, fileName);
|
|
162
|
+
if (fs.existsSync(candidate)) {
|
|
163
|
+
const relativePath = path.relative(repoRoot, candidate);
|
|
164
|
+
return {
|
|
165
|
+
path: relativePath,
|
|
166
|
+
content: readSnippet(candidate)
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
function readSnippet(filePath, maxLines = 150) {
|
|
174
|
+
try {
|
|
175
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
176
|
+
const lines = content.split('\n');
|
|
177
|
+
if (lines.length <= maxLines) {
|
|
178
|
+
return content;
|
|
179
|
+
}
|
|
180
|
+
// Return first maxLines with truncation notice
|
|
181
|
+
return lines.slice(0, maxLines).join('\n') + `\n// ... truncated (${lines.length - maxLines} more lines)`;
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return '// Error reading file';
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// Format pack as a compact string for prompt injection
|
|
188
|
+
export function formatContextPackForPrompt(pack) {
|
|
189
|
+
const sections = [];
|
|
190
|
+
// Verification commands (most important - put first)
|
|
191
|
+
sections.push('## Verification Commands (must pass)');
|
|
192
|
+
if (pack.verification.cwd) {
|
|
193
|
+
sections.push(`Working directory: ${pack.verification.cwd}`);
|
|
194
|
+
}
|
|
195
|
+
if (pack.verification.tier0.length > 0) {
|
|
196
|
+
const prefix = pack.verification.cwd ? `cd ${pack.verification.cwd} && ` : '';
|
|
197
|
+
sections.push(`tier0: ${prefix}${pack.verification.tier0.join(' && ')}`);
|
|
198
|
+
}
|
|
199
|
+
if (pack.verification.tier1.length > 0) {
|
|
200
|
+
const prefix = pack.verification.cwd ? `cd ${pack.verification.cwd} && ` : '';
|
|
201
|
+
sections.push(`tier1: ${prefix}${pack.verification.tier1.join(' && ')}`);
|
|
202
|
+
}
|
|
203
|
+
// Scope
|
|
204
|
+
sections.push('\n## Scope Constraints');
|
|
205
|
+
sections.push(`allowlist: ${pack.scope.allowlist.join(', ') || '(none)'}`);
|
|
206
|
+
sections.push(`denylist: ${pack.scope.denylist.join(', ') || '(none)'}`);
|
|
207
|
+
// Blockers guidance (critical for avoiding common failures)
|
|
208
|
+
if (pack.blockers) {
|
|
209
|
+
sections.push('\n## Known Blockers & Constraints');
|
|
210
|
+
if (pack.blockers.lockfile_restrictions) {
|
|
211
|
+
sections.push('- **Lockfiles protected**: Cannot modify package-lock.json or similar');
|
|
212
|
+
}
|
|
213
|
+
for (const violation of pack.blockers.scope_violations) {
|
|
214
|
+
sections.push(`- ${violation}`);
|
|
215
|
+
}
|
|
216
|
+
if (pack.blockers.common_errors.length > 0) {
|
|
217
|
+
sections.push('\nCommon error patterns:');
|
|
218
|
+
for (const err of pack.blockers.common_errors) {
|
|
219
|
+
sections.push(`- ${err}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// Reference files
|
|
224
|
+
if (pack.reference_files.length > 0) {
|
|
225
|
+
sections.push('\n## Reference Files');
|
|
226
|
+
for (const ref of pack.reference_files) {
|
|
227
|
+
sections.push(`\n### ${ref.path}`);
|
|
228
|
+
sections.push(`Reason: ${ref.reason}`);
|
|
229
|
+
sections.push('```typescript');
|
|
230
|
+
sections.push(ref.content);
|
|
231
|
+
sections.push('```');
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// Config patterns
|
|
235
|
+
sections.push('\n## Config Patterns (use as templates)');
|
|
236
|
+
if (pack.patterns.tsconfig) {
|
|
237
|
+
sections.push(`\n### tsconfig.json (from ${pack.patterns.tsconfig.path})`);
|
|
238
|
+
sections.push('```json');
|
|
239
|
+
sections.push(pack.patterns.tsconfig.content);
|
|
240
|
+
sections.push('```');
|
|
241
|
+
}
|
|
242
|
+
if (pack.patterns.eslint) {
|
|
243
|
+
sections.push(`\n### eslint config (from ${pack.patterns.eslint.path})`);
|
|
244
|
+
sections.push('```javascript');
|
|
245
|
+
sections.push(pack.patterns.eslint.content);
|
|
246
|
+
sections.push('```');
|
|
247
|
+
}
|
|
248
|
+
if (pack.patterns.package_json) {
|
|
249
|
+
sections.push(`\n### package.json scripts (from ${pack.patterns.package_json.path})`);
|
|
250
|
+
// Extract just the scripts section for brevity
|
|
251
|
+
try {
|
|
252
|
+
const pkg = JSON.parse(pack.patterns.package_json.content);
|
|
253
|
+
const relevant = {
|
|
254
|
+
scripts: pkg.scripts,
|
|
255
|
+
devDependencies: pkg.devDependencies
|
|
256
|
+
};
|
|
257
|
+
sections.push('```json');
|
|
258
|
+
sections.push(JSON.stringify(relevant, null, 2));
|
|
259
|
+
sections.push('```');
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
sections.push('```json');
|
|
263
|
+
sections.push(pack.patterns.package_json.content);
|
|
264
|
+
sections.push('```');
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return sections.join('\n');
|
|
268
|
+
}
|
|
269
|
+
// Estimate token count (rough: chars/4)
|
|
270
|
+
export function estimatePackTokens(pack) {
|
|
271
|
+
const formatted = formatContextPackForPrompt(pack);
|
|
272
|
+
return Math.ceil(formatted.length / 4);
|
|
273
|
+
}
|