@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.
Files changed (73) hide show
  1. package/CHANGELOG.md +216 -0
  2. package/LICENSE +190 -0
  3. package/NOTICE +4 -0
  4. package/README.md +200 -0
  5. package/dist/cli.js +464 -0
  6. package/dist/commands/__tests__/report.test.js +202 -0
  7. package/dist/commands/compare.js +168 -0
  8. package/dist/commands/doctor.js +124 -0
  9. package/dist/commands/follow.js +251 -0
  10. package/dist/commands/gc.js +161 -0
  11. package/dist/commands/guards-only.js +89 -0
  12. package/dist/commands/metrics.js +441 -0
  13. package/dist/commands/orchestrate.js +800 -0
  14. package/dist/commands/paths.js +31 -0
  15. package/dist/commands/preflight.js +152 -0
  16. package/dist/commands/report.js +478 -0
  17. package/dist/commands/resume.js +149 -0
  18. package/dist/commands/run.js +538 -0
  19. package/dist/commands/status.js +189 -0
  20. package/dist/commands/summarize.js +220 -0
  21. package/dist/commands/version.js +82 -0
  22. package/dist/commands/wait.js +170 -0
  23. package/dist/config/__tests__/presets.test.js +104 -0
  24. package/dist/config/load.js +66 -0
  25. package/dist/config/schema.js +160 -0
  26. package/dist/context/__tests__/artifact.test.js +130 -0
  27. package/dist/context/__tests__/pack.test.js +191 -0
  28. package/dist/context/artifact.js +67 -0
  29. package/dist/context/index.js +2 -0
  30. package/dist/context/pack.js +273 -0
  31. package/dist/diagnosis/analyzer.js +678 -0
  32. package/dist/diagnosis/formatter.js +136 -0
  33. package/dist/diagnosis/index.js +6 -0
  34. package/dist/diagnosis/types.js +7 -0
  35. package/dist/env/__tests__/fingerprint.test.js +116 -0
  36. package/dist/env/fingerprint.js +111 -0
  37. package/dist/orchestrator/__tests__/policy.test.js +185 -0
  38. package/dist/orchestrator/__tests__/schema-version.test.js +65 -0
  39. package/dist/orchestrator/artifacts.js +405 -0
  40. package/dist/orchestrator/state-machine.js +646 -0
  41. package/dist/orchestrator/types.js +88 -0
  42. package/dist/ownership/normalize.js +45 -0
  43. package/dist/repo/context.js +90 -0
  44. package/dist/repo/git.js +13 -0
  45. package/dist/repo/worktree.js +239 -0
  46. package/dist/store/run-store.js +107 -0
  47. package/dist/store/run-utils.js +69 -0
  48. package/dist/store/runs-root.js +126 -0
  49. package/dist/supervisor/__tests__/evidence-gate.test.js +111 -0
  50. package/dist/supervisor/__tests__/ownership.test.js +103 -0
  51. package/dist/supervisor/__tests__/state-machine.test.js +290 -0
  52. package/dist/supervisor/collision.js +240 -0
  53. package/dist/supervisor/evidence-gate.js +98 -0
  54. package/dist/supervisor/planner.js +18 -0
  55. package/dist/supervisor/runner.js +1562 -0
  56. package/dist/supervisor/scope-guard.js +55 -0
  57. package/dist/supervisor/state-machine.js +121 -0
  58. package/dist/supervisor/verification-policy.js +64 -0
  59. package/dist/tasks/task-metadata.js +72 -0
  60. package/dist/types/schemas.js +1 -0
  61. package/dist/verification/engine.js +49 -0
  62. package/dist/workers/__tests__/claude.test.js +88 -0
  63. package/dist/workers/__tests__/codex.test.js +81 -0
  64. package/dist/workers/claude.js +119 -0
  65. package/dist/workers/codex.js +162 -0
  66. package/dist/workers/json.js +22 -0
  67. package/dist/workers/mock.js +193 -0
  68. package/dist/workers/prompts.js +98 -0
  69. package/dist/workers/schemas.js +39 -0
  70. package/package.json +47 -0
  71. package/templates/prompts/implementer.md +70 -0
  72. package/templates/prompts/planner.md +62 -0
  73. 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,2 @@
1
+ export { buildContextPack, formatContextPackForPrompt, estimatePackTokens } from './pack.js';
2
+ export { writeContextPackArtifact, readContextPackArtifact, formatContextPackStatus } from './artifact.js';
@@ -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
+ }