devworkspace 0.0.0-stage → 0.1.1

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 (190) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/LICENSE +21 -0
  3. package/README.md +128 -3
  4. package/dist/agents/claude/adapter.d.ts +15 -0
  5. package/dist/agents/claude/adapter.js +107 -0
  6. package/dist/agents/claude/adapter.js.map +1 -0
  7. package/dist/agents/claude/content.d.ts +7 -0
  8. package/dist/agents/claude/content.js +136 -0
  9. package/dist/agents/claude/content.js.map +1 -0
  10. package/dist/agents/types.d.ts +26 -0
  11. package/dist/agents/types.js +2 -0
  12. package/dist/agents/types.js.map +1 -0
  13. package/dist/cli/args.d.ts +197 -0
  14. package/dist/cli/args.js +94 -0
  15. package/dist/cli/args.js.map +1 -0
  16. package/dist/cli/help.d.ts +10 -0
  17. package/dist/cli/help.js +181 -0
  18. package/dist/cli/help.js.map +1 -0
  19. package/dist/cli/main.d.ts +2 -0
  20. package/dist/cli/main.js +19 -0
  21. package/dist/cli/main.js.map +1 -0
  22. package/dist/cli/render.d.ts +41 -0
  23. package/dist/cli/render.js +220 -0
  24. package/dist/cli/render.js.map +1 -0
  25. package/dist/cli/run.d.ts +10 -0
  26. package/dist/cli/run.js +339 -0
  27. package/dist/cli/run.js.map +1 -0
  28. package/dist/config/schemas.d.ts +76 -0
  29. package/dist/config/schemas.js +83 -0
  30. package/dist/config/schemas.js.map +1 -0
  31. package/dist/config/validate.d.ts +22 -0
  32. package/dist/config/validate.js +76 -0
  33. package/dist/config/validate.js.map +1 -0
  34. package/dist/core/migrations.d.ts +15 -0
  35. package/dist/core/migrations.js +34 -0
  36. package/dist/core/migrations.js.map +1 -0
  37. package/dist/core/version.d.ts +2 -0
  38. package/dist/core/version.js +12 -0
  39. package/dist/core/version.js.map +1 -0
  40. package/dist/core/versioned.d.ts +13 -0
  41. package/dist/core/versioned.js +16 -0
  42. package/dist/core/versioned.js.map +1 -0
  43. package/dist/core/workspace.d.ts +34 -0
  44. package/dist/core/workspace.js +92 -0
  45. package/dist/core/workspace.js.map +1 -0
  46. package/dist/decisions/decision.d.ts +31 -0
  47. package/dist/decisions/decision.js +123 -0
  48. package/dist/decisions/decision.js.map +1 -0
  49. package/dist/discovery/commands.d.ts +12 -0
  50. package/dist/discovery/commands.js +92 -0
  51. package/dist/discovery/commands.js.map +1 -0
  52. package/dist/discovery/detect.d.ts +72 -0
  53. package/dist/discovery/detect.js +421 -0
  54. package/dist/discovery/detect.js.map +1 -0
  55. package/dist/discovery/ignore.d.ts +6 -0
  56. package/dist/discovery/ignore.js +76 -0
  57. package/dist/discovery/ignore.js.map +1 -0
  58. package/dist/discovery/scan.d.ts +24 -0
  59. package/dist/discovery/scan.js +132 -0
  60. package/dist/discovery/scan.js.map +1 -0
  61. package/dist/errors/index.d.ts +25 -0
  62. package/dist/errors/index.js +39 -0
  63. package/dist/errors/index.js.map +1 -0
  64. package/dist/git/git.d.ts +27 -0
  65. package/dist/git/git.js +97 -0
  66. package/dist/git/git.js.map +1 -0
  67. package/dist/index.d.ts +26 -0
  68. package/dist/index.js +21 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/integrations/agent-skills.d.ts +10 -0
  71. package/dist/integrations/agent-skills.js +45 -0
  72. package/dist/integrations/agent-skills.js.map +1 -0
  73. package/dist/integrations/graphify.d.ts +7 -0
  74. package/dist/integrations/graphify.js +54 -0
  75. package/dist/integrations/graphify.js.map +1 -0
  76. package/dist/integrations/index.d.ts +6 -0
  77. package/dist/integrations/index.js +30 -0
  78. package/dist/integrations/index.js.map +1 -0
  79. package/dist/integrations/omniroute.d.ts +7 -0
  80. package/dist/integrations/omniroute.js +24 -0
  81. package/dist/integrations/omniroute.js.map +1 -0
  82. package/dist/integrations/ponytail.d.ts +10 -0
  83. package/dist/integrations/ponytail.js +58 -0
  84. package/dist/integrations/ponytail.js.map +1 -0
  85. package/dist/integrations/probe.d.ts +6 -0
  86. package/dist/integrations/probe.js +41 -0
  87. package/dist/integrations/probe.js.map +1 -0
  88. package/dist/integrations/types.d.ts +25 -0
  89. package/dist/integrations/types.js +2 -0
  90. package/dist/integrations/types.js.map +1 -0
  91. package/dist/knowledge/facts.d.ts +43 -0
  92. package/dist/knowledge/facts.js +195 -0
  93. package/dist/knowledge/facts.js.map +1 -0
  94. package/dist/portability/bundle.d.ts +22 -0
  95. package/dist/portability/bundle.js +102 -0
  96. package/dist/portability/bundle.js.map +1 -0
  97. package/dist/prompts/onboarding.d.ts +19 -0
  98. package/dist/prompts/onboarding.js +53 -0
  99. package/dist/prompts/onboarding.js.map +1 -0
  100. package/dist/prompts/refresh.d.ts +15 -0
  101. package/dist/prompts/refresh.js +33 -0
  102. package/dist/prompts/refresh.js.map +1 -0
  103. package/dist/prompts/requirements.d.ts +9 -0
  104. package/dist/prompts/requirements.js +34 -0
  105. package/dist/prompts/requirements.js.map +1 -0
  106. package/dist/security/files.d.ts +5 -0
  107. package/dist/security/files.js +44 -0
  108. package/dist/security/files.js.map +1 -0
  109. package/dist/security/redact.d.ts +13 -0
  110. package/dist/security/redact.js +43 -0
  111. package/dist/security/redact.js.map +1 -0
  112. package/dist/services/agent.d.ts +11 -0
  113. package/dist/services/agent.js +42 -0
  114. package/dist/services/agent.js.map +1 -0
  115. package/dist/services/analysis.d.ts +45 -0
  116. package/dist/services/analysis.js +151 -0
  117. package/dist/services/analysis.js.map +1 -0
  118. package/dist/services/common.d.ts +18 -0
  119. package/dist/services/common.js +18 -0
  120. package/dist/services/common.js.map +1 -0
  121. package/dist/services/doctor.d.ts +27 -0
  122. package/dist/services/doctor.js +223 -0
  123. package/dist/services/doctor.js.map +1 -0
  124. package/dist/services/init.d.ts +18 -0
  125. package/dist/services/init.js +135 -0
  126. package/dist/services/init.js.map +1 -0
  127. package/dist/services/knowledge.d.ts +40 -0
  128. package/dist/services/knowledge.js +72 -0
  129. package/dist/services/knowledge.js.map +1 -0
  130. package/dist/services/migrate.d.ts +16 -0
  131. package/dist/services/migrate.js +54 -0
  132. package/dist/services/migrate.js.map +1 -0
  133. package/dist/services/portability.d.ts +27 -0
  134. package/dist/services/portability.js +155 -0
  135. package/dist/services/portability.js.map +1 -0
  136. package/dist/services/records.d.ts +8 -0
  137. package/dist/services/records.js +26 -0
  138. package/dist/services/records.js.map +1 -0
  139. package/dist/services/refresh.d.ts +22 -0
  140. package/dist/services/refresh.js +64 -0
  141. package/dist/services/refresh.js.map +1 -0
  142. package/dist/services/reset.d.ts +23 -0
  143. package/dist/services/reset.js +96 -0
  144. package/dist/services/reset.js.map +1 -0
  145. package/dist/services/status.d.ts +57 -0
  146. package/dist/services/status.js +111 -0
  147. package/dist/services/status.js.map +1 -0
  148. package/dist/services/tasks.d.ts +44 -0
  149. package/dist/services/tasks.js +136 -0
  150. package/dist/services/tasks.js.map +1 -0
  151. package/dist/sessions/session.d.ts +19 -0
  152. package/dist/sessions/session.js +53 -0
  153. package/dist/sessions/session.js.map +1 -0
  154. package/dist/tasks/routing.d.ts +27 -0
  155. package/dist/tasks/routing.js +206 -0
  156. package/dist/tasks/routing.js.map +1 -0
  157. package/dist/tasks/task.d.ts +90 -0
  158. package/dist/tasks/task.js +275 -0
  159. package/dist/tasks/task.js.map +1 -0
  160. package/dist/utils/cleanup.d.ts +3 -0
  161. package/dist/utils/cleanup.js +18 -0
  162. package/dist/utils/cleanup.js.map +1 -0
  163. package/dist/utils/fs.d.ts +17 -0
  164. package/dist/utils/fs.js +164 -0
  165. package/dist/utils/fs.js.map +1 -0
  166. package/dist/utils/lock.d.ts +6 -0
  167. package/dist/utils/lock.js +77 -0
  168. package/dist/utils/lock.js.map +1 -0
  169. package/dist/utils/logger.d.ts +11 -0
  170. package/dist/utils/logger.js +18 -0
  171. package/dist/utils/logger.js.map +1 -0
  172. package/dist/utils/paths.d.ts +18 -0
  173. package/dist/utils/paths.js +95 -0
  174. package/dist/utils/paths.js.map +1 -0
  175. package/dist/utils/proc.d.ts +22 -0
  176. package/dist/utils/proc.js +74 -0
  177. package/dist/utils/proc.js.map +1 -0
  178. package/docs/architecture.md +53 -0
  179. package/docs/benchmarking.md +33 -0
  180. package/docs/claude.md +46 -0
  181. package/docs/contributing.md +64 -0
  182. package/docs/getting-started.md +79 -0
  183. package/docs/integrations.md +51 -0
  184. package/docs/knowledge.md +66 -0
  185. package/docs/portability.md +40 -0
  186. package/docs/routing.md +51 -0
  187. package/docs/security.md +44 -0
  188. package/docs/testing.md +58 -0
  189. package/docs/troubleshooting.md +27 -0
  190. package/package.json +68 -4
@@ -0,0 +1,275 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { DevWorkspaceError } from '../errors/index.js';
4
+ import { Check } from '../config/validate.js';
5
+ import { SCHEMA_VERSION } from '../config/schemas.js';
6
+ import { atomicWriteJson, ensureDir, pathExists, randomId } from '../utils/fs.js';
7
+ import { readVersioned } from '../core/versioned.js';
8
+ import { backupIfVersionChanges } from '../core/workspace.js';
9
+ import { containsSecret } from '../security/redact.js';
10
+ export const TASK_TYPES = [
11
+ 'QUESTION',
12
+ 'BUG',
13
+ 'FEATURE',
14
+ 'REFACTOR',
15
+ 'DOCUMENTATION',
16
+ 'TESTING',
17
+ 'PERFORMANCE',
18
+ 'SECURITY',
19
+ 'ARCHITECTURE',
20
+ 'RELEASE',
21
+ 'MAINTENANCE',
22
+ ];
23
+ export const TASK_STATUSES = ['IDEA', 'SPECIFIED', 'PLANNED', 'IN_PROGRESS', 'TESTING', 'REVIEW', 'BLOCKED', 'FAILED', 'COMPLETED'];
24
+ export const VERIFY_STATUSES = ['PENDING', 'PASS', 'FAIL', 'SKIPPED'];
25
+ const TRANSITIONS = {
26
+ IDEA: ['SPECIFIED', 'PLANNED', 'IN_PROGRESS', 'BLOCKED', 'FAILED'],
27
+ SPECIFIED: ['PLANNED', 'IN_PROGRESS', 'BLOCKED', 'IDEA'],
28
+ PLANNED: ['IN_PROGRESS', 'SPECIFIED', 'BLOCKED', 'IDEA'],
29
+ IN_PROGRESS: ['TESTING', 'REVIEW', 'COMPLETED', 'BLOCKED', 'FAILED', 'PLANNED'],
30
+ TESTING: ['IN_PROGRESS', 'REVIEW', 'COMPLETED', 'BLOCKED', 'FAILED'],
31
+ REVIEW: ['IN_PROGRESS', 'TESTING', 'COMPLETED', 'BLOCKED', 'FAILED'],
32
+ BLOCKED: ['IN_PROGRESS', 'PLANNED', 'SPECIFIED', 'IDEA', 'FAILED'],
33
+ FAILED: ['IN_PROGRESS', 'IDEA'],
34
+ COMPLETED: ['IN_PROGRESS'],
35
+ };
36
+ export function allowedTransitions(from) {
37
+ return TRANSITIONS[from];
38
+ }
39
+ const CODE_CHANGING = ['BUG', 'FEATURE', 'REFACTOR', 'PERFORMANCE', 'SECURITY', 'ARCHITECTURE', 'MAINTENANCE', 'TESTING', 'RELEASE'];
40
+ const CHECKS_BY_TYPE = {
41
+ QUESTION: [],
42
+ DOCUMENTATION: [],
43
+ BUG: ['unit', 'typecheck', 'lint', 'build'],
44
+ FEATURE: ['unit', 'typecheck', 'lint', 'build', 'review'],
45
+ REFACTOR: ['unit', 'typecheck', 'lint', 'build', 'review'],
46
+ TESTING: ['unit', 'typecheck', 'lint'],
47
+ PERFORMANCE: ['unit', 'build', 'review'],
48
+ SECURITY: ['unit', 'typecheck', 'lint', 'build', 'review'],
49
+ ARCHITECTURE: ['unit', 'typecheck', 'lint', 'build', 'review'],
50
+ RELEASE: ['unit', 'typecheck', 'lint', 'build', 'integration', 'e2e'],
51
+ MAINTENANCE: ['lint', 'build'],
52
+ };
53
+ export function isCodeChanging(type) {
54
+ return CODE_CHANGING.includes(type);
55
+ }
56
+ /** Verification a task of this type needs, given what the project actually offers. Unavailable checks are never demanded. */
57
+ export function requiredChecksFor(type, contract, reviewLevel = 'light') {
58
+ const available = new Set(contract.filter((c) => c.available).map((c) => c.id));
59
+ const items = [];
60
+ for (const check of CHECKS_BY_TYPE[type]) {
61
+ if (check === 'review') {
62
+ if (reviewLevel === 'off')
63
+ continue;
64
+ }
65
+ else if (!available.has(check))
66
+ continue;
67
+ items.push({ check, required: true, status: 'PENDING' });
68
+ }
69
+ return items;
70
+ }
71
+ export function newTaskId(now = new Date()) {
72
+ const d = now.toISOString().slice(0, 10).replace(/-/g, '');
73
+ return `T-${d}-${randomId(2)}`;
74
+ }
75
+ export function createTaskObject(input, now, id = newTaskId()) {
76
+ const title = input.title.trim().slice(0, 200);
77
+ if (!title)
78
+ throw new DevWorkspaceError('USAGE', 'Task title must not be empty.');
79
+ const task = {
80
+ schemaVersion: SCHEMA_VERSION,
81
+ id,
82
+ title,
83
+ type: input.type,
84
+ status: 'IDEA',
85
+ createdAt: now,
86
+ updatedAt: now,
87
+ description: (input.description ?? '').trim().slice(0, 4000),
88
+ acceptanceCriteria: [],
89
+ plan: [],
90
+ affectedAreas: [],
91
+ verification: input.verification,
92
+ tests: {},
93
+ attestations: { userWorkIntact: false, knowledgeReviewed: false, finalStateInspected: false },
94
+ decisions: [],
95
+ blockers: [],
96
+ constraints: input.constraints ?? [],
97
+ history: [{ at: now, from: null, to: 'IDEA' }],
98
+ };
99
+ assertNoSecrets(task);
100
+ return task;
101
+ }
102
+ export function assertNoSecrets(task) {
103
+ if (containsSecret(JSON.stringify(task))) {
104
+ throw new DevWorkspaceError('SECURITY', 'Task content looks like it contains a secret; refusing to store it.', {
105
+ hint: 'Remove credential values from titles, plans and notes.',
106
+ });
107
+ }
108
+ }
109
+ export function validateTask(v, file = 'task') {
110
+ const c = new Check(file);
111
+ const o = c.obj(v);
112
+ c.at_('schemaVersion').int(o.schemaVersion, 1);
113
+ c.at_('id').str(o.id, { min: 3, max: 64 });
114
+ c.at_('title').str(o.title, { min: 1, max: 300 });
115
+ c.at_('type').oneOf(o.type, TASK_TYPES);
116
+ c.at_('status').oneOf(o.status, TASK_STATUSES);
117
+ c.at_('createdAt').iso(o.createdAt);
118
+ c.at_('updatedAt').iso(o.updatedAt);
119
+ for (const k of ['acceptanceCriteria', 'plan', 'affectedAreas', 'decisions', 'blockers', 'constraints'])
120
+ c.at_(k).strArr(o[k]);
121
+ c.at_('verification')
122
+ .arr(o.verification, 100)
123
+ .forEach((item, i) => {
124
+ const ic = c.at_(`verification.${i}`);
125
+ const io = ic.obj(item);
126
+ ic.at_('check').str(io.check, { max: 50 });
127
+ ic.at_('required').bool(io.required);
128
+ ic.at_('status').oneOf(io.status, VERIFY_STATUSES);
129
+ });
130
+ const a = c.at_('attestations').obj(o.attestations);
131
+ c.at_('attestations.userWorkIntact').bool(a.userWorkIntact);
132
+ c.at_('attestations.knowledgeReviewed').bool(a.knowledgeReviewed);
133
+ c.at_('attestations.finalStateInspected').bool(a.finalStateInspected);
134
+ c.at_('tests').obj(o.tests);
135
+ c.at_('history').arr(o.history, 500);
136
+ return o;
137
+ }
138
+ /** The completion gate. A task is only COMPLETED when every applicable requirement holds. */
139
+ export function evaluateGate(task) {
140
+ const failures = [];
141
+ const warnings = [];
142
+ for (const v of task.verification) {
143
+ if (!v.required)
144
+ continue;
145
+ if (v.status === 'FAIL')
146
+ failures.push(`${v.check}: FAIL`);
147
+ else if (v.status === 'PENDING')
148
+ failures.push(`${v.check}: not run yet`);
149
+ else if (v.status === 'SKIPPED') {
150
+ if (v.reason === 'user-override')
151
+ warnings.push(`${v.check}: skipped at the user's request; completion is less certain`);
152
+ else
153
+ failures.push(`${v.check}: skipped without a user override`);
154
+ }
155
+ }
156
+ const { baseline, after } = task.tests;
157
+ if (baseline && !after)
158
+ failures.push('baseline test results recorded, but no results after the change');
159
+ if (baseline && after) {
160
+ if (after.failed > baseline.failed)
161
+ failures.push(`regression: failures rose from ${baseline.failed} to ${after.failed}`);
162
+ if (after.passed < baseline.passed)
163
+ failures.push(`regression: passing tests fell from ${baseline.passed} to ${after.passed}`);
164
+ }
165
+ if (isCodeChanging(task.type)) {
166
+ if (!task.attestations.userWorkIntact)
167
+ failures.push('not confirmed: no user work was overwritten');
168
+ if (!task.attestations.knowledgeReviewed)
169
+ failures.push('not confirmed: project knowledge was reviewed for updates');
170
+ if (!task.attestations.finalStateInspected)
171
+ failures.push('not confirmed: final repository state was inspected');
172
+ }
173
+ if ((isCodeChanging(task.type) || task.type === 'DOCUMENTATION') && !(task.completionSummary ?? '').trim()) {
174
+ failures.push('completion summary is missing');
175
+ }
176
+ return { ok: failures.length === 0, failures, warnings };
177
+ }
178
+ export function transitionTask(task, to, now, note) {
179
+ if (task.status === to)
180
+ return null;
181
+ if (!TRANSITIONS[task.status].includes(to)) {
182
+ throw new DevWorkspaceError('GATE', `Cannot move a task from ${task.status} to ${to}.`, {
183
+ hint: `Allowed from ${task.status}: ${TRANSITIONS[task.status].join(', ')}.`,
184
+ });
185
+ }
186
+ let gate = null;
187
+ if (to === 'COMPLETED') {
188
+ gate = evaluateGate(task);
189
+ if (!gate.ok) {
190
+ throw new DevWorkspaceError('GATE', `Task ${task.id} cannot be completed:\n - ${gate.failures.join('\n - ')}`, {
191
+ hint: 'Resolve the items above (or mark the task BLOCKED/FAILED). A task is never complete while verification is failing.',
192
+ });
193
+ }
194
+ }
195
+ task.history.push({ at: now, from: task.status, to, ...(note ? { note: note.slice(0, 300) } : {}) });
196
+ task.status = to;
197
+ task.updatedAt = now;
198
+ return gate;
199
+ }
200
+ export function nextRequiredStep(task) {
201
+ switch (task.status) {
202
+ case 'COMPLETED':
203
+ return 'None. The task is complete.';
204
+ case 'FAILED':
205
+ return 'Review the failure, then move the task back to IN_PROGRESS or close the idea.';
206
+ case 'BLOCKED':
207
+ return task.blockers.length > 0 ? `Resolve blocker: ${task.blockers[task.blockers.length - 1]}` : 'Identify and record what blocks this task.';
208
+ case 'IDEA':
209
+ return 'Clarify acceptance criteria, then plan.';
210
+ case 'SPECIFIED':
211
+ return 'Write a plan.';
212
+ case 'PLANNED':
213
+ return 'Start implementation (baseline verification first for meaningful changes).';
214
+ default: {
215
+ const pending = task.verification.filter((v) => v.required && (v.status === 'PENDING' || v.status === 'FAIL'));
216
+ if (pending.length > 0)
217
+ return `Run/fix verification: ${pending.map((p) => `${p.check} (${p.status.toLowerCase()})`).join(', ')}.`;
218
+ const g = evaluateGate(task);
219
+ return g.ok ? 'All gates pass. Mark the task COMPLETED.' : `Satisfy the completion gate: ${g.failures[0]}`;
220
+ }
221
+ }
222
+ }
223
+ // ---------- storage ----------
224
+ function taskFile(ws, task) {
225
+ return path.join(task.status === 'COMPLETED' ? ws.tasksCompleted : ws.tasksActive, `${task.id}.json`);
226
+ }
227
+ export async function saveTask(ws, task) {
228
+ validateTask(task);
229
+ assertNoSecrets(task);
230
+ const dest = taskFile(ws, task);
231
+ await backupIfVersionChanges(ws, dest, task.schemaVersion);
232
+ await ensureDir(path.dirname(dest));
233
+ await atomicWriteJson(dest, task);
234
+ const other = path.join(task.status === 'COMPLETED' ? ws.tasksActive : ws.tasksCompleted, `${task.id}.json`);
235
+ await fs.rm(other, { force: true });
236
+ }
237
+ export async function loadTask(ws, idOrPrefix) {
238
+ for (const dir of [ws.tasksActive, ws.tasksCompleted]) {
239
+ if (!(await pathExists(dir)))
240
+ continue;
241
+ const names = (await fs.readdir(dir)).filter((n) => n.endsWith('.json'));
242
+ const hits = names.filter((n) => n === `${idOrPrefix}.json` || n.startsWith(idOrPrefix));
243
+ if (hits.length > 1) {
244
+ const exact = hits.find((n) => n === `${idOrPrefix}.json`);
245
+ if (!exact)
246
+ throw new DevWorkspaceError('USAGE', `Task id "${idOrPrefix}" is ambiguous.`, { hint: 'Use more characters of the id.' });
247
+ hits.splice(0, hits.length, exact);
248
+ }
249
+ if (hits.length === 1) {
250
+ const file = path.join(dir, hits[0]);
251
+ return (await readVersioned(file, 'task', SCHEMA_VERSION, validateTask)).data;
252
+ }
253
+ }
254
+ throw new DevWorkspaceError('NOT_FOUND', `No task matching "${idOrPrefix}".`, { hint: 'List tasks with "devworkspace task list --all".' });
255
+ }
256
+ export async function listTasks(ws, opts = {}) {
257
+ const tasks = [];
258
+ const corrupt = [];
259
+ const dirs = opts.includeCompleted ? [ws.tasksActive, ws.tasksCompleted] : [ws.tasksActive];
260
+ for (const dir of dirs) {
261
+ if (!(await pathExists(dir)))
262
+ continue;
263
+ for (const name of (await fs.readdir(dir)).filter((n) => n.endsWith('.json')).sort()) {
264
+ try {
265
+ tasks.push((await readVersioned(path.join(dir, name), 'task', SCHEMA_VERSION, validateTask)).data);
266
+ }
267
+ catch (e) {
268
+ corrupt.push({ file: `${path.basename(dir)}/${name}`, error: e.message });
269
+ }
270
+ }
271
+ }
272
+ tasks.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
273
+ return { tasks, corrupt };
274
+ }
275
+ //# sourceMappingURL=task.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task.js","sourceRoot":"","sources":["../../src/tasks/task.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { DevWorkspaceError } from '../errors/index.js';\nimport { Check } from '../config/validate.js';\nimport { SCHEMA_VERSION, type CommandEntry } from '../config/schemas.js';\nimport { atomicWriteJson, ensureDir, pathExists, randomId } from '../utils/fs.js';\nimport { readVersioned } from '../core/versioned.js';\nimport { backupIfVersionChanges, type Ws } from '../core/workspace.js';\nimport { containsSecret } from '../security/redact.js';\n\nexport const TASK_TYPES = [\n 'QUESTION',\n 'BUG',\n 'FEATURE',\n 'REFACTOR',\n 'DOCUMENTATION',\n 'TESTING',\n 'PERFORMANCE',\n 'SECURITY',\n 'ARCHITECTURE',\n 'RELEASE',\n 'MAINTENANCE',\n] as const;\nexport type TaskType = (typeof TASK_TYPES)[number];\n\nexport const TASK_STATUSES = ['IDEA', 'SPECIFIED', 'PLANNED', 'IN_PROGRESS', 'TESTING', 'REVIEW', 'BLOCKED', 'FAILED', 'COMPLETED'] as const;\nexport type TaskStatus = (typeof TASK_STATUSES)[number];\n\nexport const VERIFY_STATUSES = ['PENDING', 'PASS', 'FAIL', 'SKIPPED'] as const;\nexport type VerifyStatus = (typeof VERIFY_STATUSES)[number];\n\nexport interface VerificationItem {\n check: string;\n required: boolean;\n status: VerifyStatus;\n reason?: string;\n detail?: string;\n updatedAt?: string;\n}\n\nexport interface TestCounts {\n passed: number;\n failed: number;\n}\n\nexport interface Attestations {\n userWorkIntact: boolean;\n knowledgeReviewed: boolean;\n finalStateInspected: boolean;\n}\n\nexport interface Task {\n schemaVersion: number;\n id: string;\n title: string;\n type: TaskType;\n status: TaskStatus;\n createdAt: string;\n updatedAt: string;\n description: string;\n acceptanceCriteria: string[];\n plan: string[];\n affectedAreas: string[];\n verification: VerificationItem[];\n tests: { baseline?: TestCounts; after?: TestCounts };\n attestations: Attestations;\n decisions: string[];\n blockers: string[];\n constraints: string[];\n completionSummary?: string;\n history: { at: string; from: TaskStatus | null; to: TaskStatus; note?: string }[];\n}\n\nconst TRANSITIONS: Record<TaskStatus, TaskStatus[]> = {\n IDEA: ['SPECIFIED', 'PLANNED', 'IN_PROGRESS', 'BLOCKED', 'FAILED'],\n SPECIFIED: ['PLANNED', 'IN_PROGRESS', 'BLOCKED', 'IDEA'],\n PLANNED: ['IN_PROGRESS', 'SPECIFIED', 'BLOCKED', 'IDEA'],\n IN_PROGRESS: ['TESTING', 'REVIEW', 'COMPLETED', 'BLOCKED', 'FAILED', 'PLANNED'],\n TESTING: ['IN_PROGRESS', 'REVIEW', 'COMPLETED', 'BLOCKED', 'FAILED'],\n REVIEW: ['IN_PROGRESS', 'TESTING', 'COMPLETED', 'BLOCKED', 'FAILED'],\n BLOCKED: ['IN_PROGRESS', 'PLANNED', 'SPECIFIED', 'IDEA', 'FAILED'],\n FAILED: ['IN_PROGRESS', 'IDEA'],\n COMPLETED: ['IN_PROGRESS'],\n};\n\nexport function allowedTransitions(from: TaskStatus): TaskStatus[] {\n return TRANSITIONS[from];\n}\n\nconst CODE_CHANGING: TaskType[] = ['BUG', 'FEATURE', 'REFACTOR', 'PERFORMANCE', 'SECURITY', 'ARCHITECTURE', 'MAINTENANCE', 'TESTING', 'RELEASE'];\n\nconst CHECKS_BY_TYPE: Record<TaskType, string[]> = {\n QUESTION: [],\n DOCUMENTATION: [],\n BUG: ['unit', 'typecheck', 'lint', 'build'],\n FEATURE: ['unit', 'typecheck', 'lint', 'build', 'review'],\n REFACTOR: ['unit', 'typecheck', 'lint', 'build', 'review'],\n TESTING: ['unit', 'typecheck', 'lint'],\n PERFORMANCE: ['unit', 'build', 'review'],\n SECURITY: ['unit', 'typecheck', 'lint', 'build', 'review'],\n ARCHITECTURE: ['unit', 'typecheck', 'lint', 'build', 'review'],\n RELEASE: ['unit', 'typecheck', 'lint', 'build', 'integration', 'e2e'],\n MAINTENANCE: ['lint', 'build'],\n};\n\nexport function isCodeChanging(type: TaskType): boolean {\n return CODE_CHANGING.includes(type);\n}\n\n/** Verification a task of this type needs, given what the project actually offers. Unavailable checks are never demanded. */\nexport function requiredChecksFor(type: TaskType, contract: CommandEntry[], reviewLevel: 'off' | 'light' | 'strict' = 'light'): VerificationItem[] {\n const available = new Set(contract.filter((c) => c.available).map((c) => c.id as string));\n const items: VerificationItem[] = [];\n for (const check of CHECKS_BY_TYPE[type]) {\n if (check === 'review') {\n if (reviewLevel === 'off') continue;\n } else if (!available.has(check)) continue;\n items.push({ check, required: true, status: 'PENDING' });\n }\n return items;\n}\n\nexport function newTaskId(now: Date = new Date()): string {\n const d = now.toISOString().slice(0, 10).replace(/-/g, '');\n return `T-${d}-${randomId(2)}`;\n}\n\nexport interface NewTaskInput {\n title: string;\n type: TaskType;\n description?: string;\n verification: VerificationItem[];\n constraints?: string[];\n}\n\nexport function createTaskObject(input: NewTaskInput, now: string, id = newTaskId()): Task {\n const title = input.title.trim().slice(0, 200);\n if (!title) throw new DevWorkspaceError('USAGE', 'Task title must not be empty.');\n const task: Task = {\n schemaVersion: SCHEMA_VERSION,\n id,\n title,\n type: input.type,\n status: 'IDEA',\n createdAt: now,\n updatedAt: now,\n description: (input.description ?? '').trim().slice(0, 4000),\n acceptanceCriteria: [],\n plan: [],\n affectedAreas: [],\n verification: input.verification,\n tests: {},\n attestations: { userWorkIntact: false, knowledgeReviewed: false, finalStateInspected: false },\n decisions: [],\n blockers: [],\n constraints: input.constraints ?? [],\n history: [{ at: now, from: null, to: 'IDEA' }],\n };\n assertNoSecrets(task);\n return task;\n}\n\nexport function assertNoSecrets(task: Task): void {\n if (containsSecret(JSON.stringify(task))) {\n throw new DevWorkspaceError('SECURITY', 'Task content looks like it contains a secret; refusing to store it.', {\n hint: 'Remove credential values from titles, plans and notes.',\n });\n }\n}\n\nexport function validateTask(v: unknown, file = 'task'): Task {\n const c = new Check(file);\n const o = c.obj(v);\n c.at_('schemaVersion').int(o.schemaVersion, 1);\n c.at_('id').str(o.id, { min: 3, max: 64 });\n c.at_('title').str(o.title, { min: 1, max: 300 });\n c.at_('type').oneOf(o.type, TASK_TYPES);\n c.at_('status').oneOf(o.status, TASK_STATUSES);\n c.at_('createdAt').iso(o.createdAt);\n c.at_('updatedAt').iso(o.updatedAt);\n for (const k of ['acceptanceCriteria', 'plan', 'affectedAreas', 'decisions', 'blockers', 'constraints'] as const) c.at_(k).strArr(o[k]);\n c.at_('verification')\n .arr(o.verification, 100)\n .forEach((item, i) => {\n const ic = c.at_(`verification.${i}`);\n const io = ic.obj(item);\n ic.at_('check').str(io.check, { max: 50 });\n ic.at_('required').bool(io.required);\n ic.at_('status').oneOf(io.status, VERIFY_STATUSES);\n });\n const a = c.at_('attestations').obj(o.attestations);\n c.at_('attestations.userWorkIntact').bool(a.userWorkIntact);\n c.at_('attestations.knowledgeReviewed').bool(a.knowledgeReviewed);\n c.at_('attestations.finalStateInspected').bool(a.finalStateInspected);\n c.at_('tests').obj(o.tests);\n c.at_('history').arr(o.history, 500);\n return o as unknown as Task;\n}\n\nexport interface GateResult {\n ok: boolean;\n failures: string[];\n warnings: string[];\n}\n\n/** The completion gate. A task is only COMPLETED when every applicable requirement holds. */\nexport function evaluateGate(task: Task): GateResult {\n const failures: string[] = [];\n const warnings: string[] = [];\n for (const v of task.verification) {\n if (!v.required) continue;\n if (v.status === 'FAIL') failures.push(`${v.check}: FAIL`);\n else if (v.status === 'PENDING') failures.push(`${v.check}: not run yet`);\n else if (v.status === 'SKIPPED') {\n if (v.reason === 'user-override') warnings.push(`${v.check}: skipped at the user's request; completion is less certain`);\n else failures.push(`${v.check}: skipped without a user override`);\n }\n }\n const { baseline, after } = task.tests;\n if (baseline && !after) failures.push('baseline test results recorded, but no results after the change');\n if (baseline && after) {\n if (after.failed > baseline.failed) failures.push(`regression: failures rose from ${baseline.failed} to ${after.failed}`);\n if (after.passed < baseline.passed) failures.push(`regression: passing tests fell from ${baseline.passed} to ${after.passed}`);\n }\n if (isCodeChanging(task.type)) {\n if (!task.attestations.userWorkIntact) failures.push('not confirmed: no user work was overwritten');\n if (!task.attestations.knowledgeReviewed) failures.push('not confirmed: project knowledge was reviewed for updates');\n if (!task.attestations.finalStateInspected) failures.push('not confirmed: final repository state was inspected');\n }\n if ((isCodeChanging(task.type) || task.type === 'DOCUMENTATION') && !(task.completionSummary ?? '').trim()) {\n failures.push('completion summary is missing');\n }\n return { ok: failures.length === 0, failures, warnings };\n}\n\nexport function transitionTask(task: Task, to: TaskStatus, now: string, note?: string): GateResult | null {\n if (task.status === to) return null;\n if (!TRANSITIONS[task.status].includes(to)) {\n throw new DevWorkspaceError('GATE', `Cannot move a task from ${task.status} to ${to}.`, {\n hint: `Allowed from ${task.status}: ${TRANSITIONS[task.status].join(', ')}.`,\n });\n }\n let gate: GateResult | null = null;\n if (to === 'COMPLETED') {\n gate = evaluateGate(task);\n if (!gate.ok) {\n throw new DevWorkspaceError('GATE', `Task ${task.id} cannot be completed:\\n - ${gate.failures.join('\\n - ')}`, {\n hint: 'Resolve the items above (or mark the task BLOCKED/FAILED). A task is never complete while verification is failing.',\n });\n }\n }\n task.history.push({ at: now, from: task.status, to, ...(note ? { note: note.slice(0, 300) } : {}) });\n task.status = to;\n task.updatedAt = now;\n return gate;\n}\n\nexport function nextRequiredStep(task: Task): string {\n switch (task.status) {\n case 'COMPLETED':\n return 'None. The task is complete.';\n case 'FAILED':\n return 'Review the failure, then move the task back to IN_PROGRESS or close the idea.';\n case 'BLOCKED':\n return task.blockers.length > 0 ? `Resolve blocker: ${task.blockers[task.blockers.length - 1]}` : 'Identify and record what blocks this task.';\n case 'IDEA':\n return 'Clarify acceptance criteria, then plan.';\n case 'SPECIFIED':\n return 'Write a plan.';\n case 'PLANNED':\n return 'Start implementation (baseline verification first for meaningful changes).';\n default: {\n const pending = task.verification.filter((v) => v.required && (v.status === 'PENDING' || v.status === 'FAIL'));\n if (pending.length > 0) return `Run/fix verification: ${pending.map((p) => `${p.check} (${p.status.toLowerCase()})`).join(', ')}.`;\n const g = evaluateGate(task);\n return g.ok ? 'All gates pass. Mark the task COMPLETED.' : `Satisfy the completion gate: ${g.failures[0]}`;\n }\n }\n}\n\n// ---------- storage ----------\n\nfunction taskFile(ws: Ws, task: Pick<Task, 'id' | 'status'>): string {\n return path.join(task.status === 'COMPLETED' ? ws.tasksCompleted : ws.tasksActive, `${task.id}.json`);\n}\n\nexport async function saveTask(ws: Ws, task: Task): Promise<void> {\n validateTask(task);\n assertNoSecrets(task);\n const dest = taskFile(ws, task);\n await backupIfVersionChanges(ws, dest, task.schemaVersion);\n await ensureDir(path.dirname(dest));\n await atomicWriteJson(dest, task);\n const other = path.join(task.status === 'COMPLETED' ? ws.tasksActive : ws.tasksCompleted, `${task.id}.json`);\n await fs.rm(other, { force: true });\n}\n\nexport async function loadTask(ws: Ws, idOrPrefix: string): Promise<Task> {\n for (const dir of [ws.tasksActive, ws.tasksCompleted]) {\n if (!(await pathExists(dir))) continue;\n const names = (await fs.readdir(dir)).filter((n) => n.endsWith('.json'));\n const hits = names.filter((n) => n === `${idOrPrefix}.json` || n.startsWith(idOrPrefix));\n if (hits.length > 1) {\n const exact = hits.find((n) => n === `${idOrPrefix}.json`);\n if (!exact) throw new DevWorkspaceError('USAGE', `Task id \"${idOrPrefix}\" is ambiguous.`, { hint: 'Use more characters of the id.' });\n hits.splice(0, hits.length, exact);\n }\n if (hits.length === 1) {\n const file = path.join(dir, hits[0]!);\n return (await readVersioned(file, 'task', SCHEMA_VERSION, validateTask)).data;\n }\n }\n throw new DevWorkspaceError('NOT_FOUND', `No task matching \"${idOrPrefix}\".`, { hint: 'List tasks with \"devworkspace task list --all\".' });\n}\n\nexport interface TaskListing {\n tasks: Task[];\n corrupt: { file: string; error: string }[];\n}\n\nexport async function listTasks(ws: Ws, opts: { includeCompleted?: boolean } = {}): Promise<TaskListing> {\n const tasks: Task[] = [];\n const corrupt: { file: string; error: string }[] = [];\n const dirs = opts.includeCompleted ? [ws.tasksActive, ws.tasksCompleted] : [ws.tasksActive];\n for (const dir of dirs) {\n if (!(await pathExists(dir))) continue;\n for (const name of (await fs.readdir(dir)).filter((n) => n.endsWith('.json')).sort()) {\n try {\n tasks.push((await readVersioned(path.join(dir, name), 'task', SCHEMA_VERSION, validateTask)).data);\n } catch (e) {\n corrupt.push({ file: `${path.basename(dir)}/${name}`, error: (e as Error).message });\n }\n }\n }\n tasks.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));\n return { tasks, corrupt };\n}\n"]}
@@ -0,0 +1,3 @@
1
+ /** Register a synchronous cleanup (e.g. release a lock). Returns a disposer. */
2
+ export declare function onCleanup(fn: () => void): () => void;
3
+ export declare function runCleanups(): void;
@@ -0,0 +1,18 @@
1
+ const callbacks = new Set();
2
+ /** Register a synchronous cleanup (e.g. release a lock). Returns a disposer. */
3
+ export function onCleanup(fn) {
4
+ callbacks.add(fn);
5
+ return () => callbacks.delete(fn);
6
+ }
7
+ export function runCleanups() {
8
+ for (const fn of [...callbacks]) {
9
+ try {
10
+ fn();
11
+ }
12
+ catch {
13
+ /* best effort */
14
+ }
15
+ }
16
+ callbacks.clear();
17
+ }
18
+ //# sourceMappingURL=cleanup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../src/utils/cleanup.ts"],"names":[],"mappings":"AAAA,MAAM,SAAS,GAAG,IAAI,GAAG,EAAc,CAAC;AAExC,gFAAgF;AAChF,MAAM,UAAU,SAAS,CAAC,EAAc;IACtC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClB,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,KAAK,MAAM,EAAE,IAAI,CAAC,GAAG,SAAS,CAAC,EAAE,CAAC;QAChC,IAAI,CAAC;YACH,EAAE,EAAE,CAAC;QACP,CAAC;QAAC,MAAM,CAAC;YACP,iBAAiB;QACnB,CAAC;IACH,CAAC;IACD,SAAS,CAAC,KAAK,EAAE,CAAC;AACpB,CAAC","sourcesContent":["const callbacks = new Set<() => void>();\n\n/** Register a synchronous cleanup (e.g. release a lock). Returns a disposer. */\nexport function onCleanup(fn: () => void): () => void {\n callbacks.add(fn);\n return () => callbacks.delete(fn);\n}\n\nexport function runCleanups(): void {\n for (const fn of [...callbacks]) {\n try {\n fn();\n } catch {\n /* best effort */\n }\n }\n callbacks.clear();\n}\n"]}
@@ -0,0 +1,17 @@
1
+ export declare const MAX_JSON_BYTES: number;
2
+ export declare function pathExists(p: string): Promise<boolean>;
3
+ export declare function ensureDir(p: string): Promise<void>;
4
+ /** Write to a temp file in the same directory, flush it, then atomically rename into place. */
5
+ export declare function atomicWriteFile(file: string, data: string | Uint8Array, validate?: (data: string) => void): Promise<void>;
6
+ export declare function atomicWriteJson(file: string, value: unknown): Promise<void>;
7
+ export declare function readJsonFile(file: string, maxBytes?: number): Promise<unknown>;
8
+ export declare function sha256File(file: string): Promise<string>;
9
+ export declare function sha256Text(text: string): string;
10
+ export declare function looksBinary(buf: Uint8Array): boolean;
11
+ /** Reads a text file; returns null if binary or larger than maxBytes. */
12
+ export declare function readTextBounded(file: string, maxBytes?: number): Promise<string | null>;
13
+ export declare function randomId(bytes?: number): string;
14
+ export declare function listFilesRecursive(dir: string, maxFiles?: number): Promise<string[]>;
15
+ /** Atomically write text only when it differs from what is on disk. Returns true if a write happened. */
16
+ export declare function writeTextIfChanged(file: string, text: string): Promise<boolean>;
17
+ export declare function writeJsonIfChanged(file: string, value: unknown): Promise<boolean>;
@@ -0,0 +1,164 @@
1
+ import { promises as fs, createReadStream } from 'node:fs';
2
+ import { createHash, randomBytes } from 'node:crypto';
3
+ import path from 'node:path';
4
+ import { DevWorkspaceError } from '../errors/index.js';
5
+ export const MAX_JSON_BYTES = 5 * 1024 * 1024;
6
+ export async function pathExists(p) {
7
+ try {
8
+ await fs.access(p);
9
+ return true;
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ export async function ensureDir(p) {
16
+ await fs.mkdir(p, { recursive: true });
17
+ }
18
+ async function renameWithRetry(from, to) {
19
+ let lastErr;
20
+ // Windows briefly denies renames while another process (reader, indexer, editor file watcher) holds the target.
21
+ for (let i = 0; i < 20; i++) {
22
+ try {
23
+ await fs.rename(from, to);
24
+ return;
25
+ }
26
+ catch (e) {
27
+ lastErr = e;
28
+ const code = e.code;
29
+ if (code !== 'EPERM' && code !== 'EBUSY' && code !== 'EACCES')
30
+ break;
31
+ await new Promise((r) => setTimeout(r, Math.min(25 * (i + 1), 150)));
32
+ }
33
+ }
34
+ throw lastErr;
35
+ }
36
+ /** Write to a temp file in the same directory, flush it, then atomically rename into place. */
37
+ export async function atomicWriteFile(file, data, validate) {
38
+ await ensureDir(path.dirname(file));
39
+ if (validate && typeof data === 'string')
40
+ validate(data);
41
+ const tmp = `${file}.${process.pid}.${randomBytes(4).toString('hex')}.tmp`;
42
+ let fh;
43
+ try {
44
+ fh = await fs.open(tmp, 'wx');
45
+ await fh.writeFile(data);
46
+ await fh.sync();
47
+ await fh.close();
48
+ fh = undefined;
49
+ await renameWithRetry(tmp, file);
50
+ }
51
+ catch (e) {
52
+ if (fh)
53
+ await fh.close().catch(() => undefined);
54
+ await fs.rm(tmp, { force: true }).catch(() => undefined);
55
+ throw new DevWorkspaceError('IO', `Could not write ${path.basename(file)}.`, {
56
+ cause: e,
57
+ hint: 'Check file permissions and free disk space.',
58
+ });
59
+ }
60
+ }
61
+ export async function atomicWriteJson(file, value) {
62
+ const text = JSON.stringify(value, null, 2) + '\n';
63
+ await atomicWriteFile(file, text, (t) => {
64
+ JSON.parse(t);
65
+ });
66
+ }
67
+ export async function readJsonFile(file, maxBytes = MAX_JSON_BYTES) {
68
+ let stat;
69
+ try {
70
+ stat = await fs.stat(file);
71
+ }
72
+ catch (e) {
73
+ throw new DevWorkspaceError('NOT_FOUND', `File not found: ${path.basename(file)}`, { cause: e });
74
+ }
75
+ if (stat.size > maxBytes) {
76
+ throw new DevWorkspaceError('SCHEMA', `${path.basename(file)} is unreasonably large (${stat.size} bytes).`, {
77
+ hint: 'Restore it from version control or a backup in .devworkspace/local/backups.',
78
+ });
79
+ }
80
+ const text = await fs.readFile(file, 'utf8');
81
+ try {
82
+ return JSON.parse(text.replace(/^\uFEFF/, ''));
83
+ }
84
+ catch (e) {
85
+ throw new DevWorkspaceError('SCHEMA', `${path.basename(file)} contains malformed JSON.`, {
86
+ cause: e,
87
+ hint: 'Restore the file from version control (git checkout -- <file>) or from .devworkspace/local/backups.',
88
+ });
89
+ }
90
+ }
91
+ export async function sha256File(file) {
92
+ const hash = createHash('sha256');
93
+ await new Promise((resolve, reject) => {
94
+ const s = createReadStream(file);
95
+ s.on('data', (c) => hash.update(c));
96
+ s.on('error', reject);
97
+ s.on('end', () => resolve());
98
+ });
99
+ return hash.digest('hex');
100
+ }
101
+ export function sha256Text(text) {
102
+ return createHash('sha256').update(text).digest('hex');
103
+ }
104
+ export function looksBinary(buf) {
105
+ const n = Math.min(buf.length, 8000);
106
+ for (let i = 0; i < n; i++)
107
+ if (buf[i] === 0)
108
+ return true;
109
+ return false;
110
+ }
111
+ /** Reads a text file; returns null if binary or larger than maxBytes. */
112
+ export async function readTextBounded(file, maxBytes = 512 * 1024) {
113
+ const stat = await fs.stat(file);
114
+ if (stat.size > maxBytes)
115
+ return null;
116
+ const buf = await fs.readFile(file);
117
+ if (looksBinary(buf))
118
+ return null;
119
+ return buf.toString('utf8').replace(/^\uFEFF/, '');
120
+ }
121
+ export function randomId(bytes = 2) {
122
+ return randomBytes(bytes).toString('hex');
123
+ }
124
+ export async function listFilesRecursive(dir, maxFiles = 5000) {
125
+ const out = [];
126
+ async function walk(d) {
127
+ let entries;
128
+ try {
129
+ entries = await fs.readdir(d, { withFileTypes: true });
130
+ }
131
+ catch {
132
+ return;
133
+ }
134
+ for (const e of entries) {
135
+ if (out.length >= maxFiles)
136
+ return;
137
+ const full = path.join(d, e.name);
138
+ if (e.isDirectory())
139
+ await walk(full);
140
+ else if (e.isFile())
141
+ out.push(full);
142
+ }
143
+ }
144
+ await walk(dir);
145
+ return out;
146
+ }
147
+ /** Atomically write text only when it differs from what is on disk. Returns true if a write happened. */
148
+ export async function writeTextIfChanged(file, text) {
149
+ try {
150
+ if ((await fs.readFile(file, 'utf8')) === text)
151
+ return false;
152
+ }
153
+ catch {
154
+ /* missing: write below */
155
+ }
156
+ await atomicWriteFile(file, text);
157
+ return true;
158
+ }
159
+ export async function writeJsonIfChanged(file, value) {
160
+ const text = JSON.stringify(value, null, 2) + '\n';
161
+ JSON.parse(text);
162
+ return writeTextIfChanged(file, text);
163
+ }
164
+ //# sourceMappingURL=fs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/utils/fs.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs, createReadStream } from 'node:fs';\nimport { createHash, randomBytes } from 'node:crypto';\nimport path from 'node:path';\nimport { DevWorkspaceError } from '../errors/index.js';\n\nexport const MAX_JSON_BYTES = 5 * 1024 * 1024;\n\nexport async function pathExists(p: string): Promise<boolean> {\n try {\n await fs.access(p);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function ensureDir(p: string): Promise<void> {\n await fs.mkdir(p, { recursive: true });\n}\n\nasync function renameWithRetry(from: string, to: string): Promise<void> {\n let lastErr: unknown;\n // Windows briefly denies renames while another process (reader, indexer, editor file watcher) holds the target.\n for (let i = 0; i < 20; i++) {\n try {\n await fs.rename(from, to);\n return;\n } catch (e) {\n lastErr = e;\n const code = (e as NodeJS.ErrnoException).code;\n if (code !== 'EPERM' && code !== 'EBUSY' && code !== 'EACCES') break;\n await new Promise((r) => setTimeout(r, Math.min(25 * (i + 1), 150)));\n }\n }\n throw lastErr;\n}\n\n/** Write to a temp file in the same directory, flush it, then atomically rename into place. */\nexport async function atomicWriteFile(file: string, data: string | Uint8Array, validate?: (data: string) => void): Promise<void> {\n await ensureDir(path.dirname(file));\n if (validate && typeof data === 'string') validate(data);\n const tmp = `${file}.${process.pid}.${randomBytes(4).toString('hex')}.tmp`;\n let fh;\n try {\n fh = await fs.open(tmp, 'wx');\n await fh.writeFile(data);\n await fh.sync();\n await fh.close();\n fh = undefined;\n await renameWithRetry(tmp, file);\n } catch (e) {\n if (fh) await fh.close().catch(() => undefined);\n await fs.rm(tmp, { force: true }).catch(() => undefined);\n throw new DevWorkspaceError('IO', `Could not write ${path.basename(file)}.`, {\n cause: e,\n hint: 'Check file permissions and free disk space.',\n });\n }\n}\n\nexport async function atomicWriteJson(file: string, value: unknown): Promise<void> {\n const text = JSON.stringify(value, null, 2) + '\\n';\n await atomicWriteFile(file, text, (t) => {\n JSON.parse(t);\n });\n}\n\nexport async function readJsonFile(file: string, maxBytes = MAX_JSON_BYTES): Promise<unknown> {\n let stat;\n try {\n stat = await fs.stat(file);\n } catch (e) {\n throw new DevWorkspaceError('NOT_FOUND', `File not found: ${path.basename(file)}`, { cause: e });\n }\n if (stat.size > maxBytes) {\n throw new DevWorkspaceError('SCHEMA', `${path.basename(file)} is unreasonably large (${stat.size} bytes).`, {\n hint: 'Restore it from version control or a backup in .devworkspace/local/backups.',\n });\n }\n const text = await fs.readFile(file, 'utf8');\n try {\n return JSON.parse(text.replace(/^\\uFEFF/, ''));\n } catch (e) {\n throw new DevWorkspaceError('SCHEMA', `${path.basename(file)} contains malformed JSON.`, {\n cause: e,\n hint: 'Restore the file from version control (git checkout -- <file>) or from .devworkspace/local/backups.',\n });\n }\n}\n\nexport async function sha256File(file: string): Promise<string> {\n const hash = createHash('sha256');\n await new Promise<void>((resolve, reject) => {\n const s = createReadStream(file);\n s.on('data', (c) => hash.update(c));\n s.on('error', reject);\n s.on('end', () => resolve());\n });\n return hash.digest('hex');\n}\n\nexport function sha256Text(text: string): string {\n return createHash('sha256').update(text).digest('hex');\n}\n\nexport function looksBinary(buf: Uint8Array): boolean {\n const n = Math.min(buf.length, 8000);\n for (let i = 0; i < n; i++) if (buf[i] === 0) return true;\n return false;\n}\n\n/** Reads a text file; returns null if binary or larger than maxBytes. */\nexport async function readTextBounded(file: string, maxBytes = 512 * 1024): Promise<string | null> {\n const stat = await fs.stat(file);\n if (stat.size > maxBytes) return null;\n const buf = await fs.readFile(file);\n if (looksBinary(buf)) return null;\n return buf.toString('utf8').replace(/^\\uFEFF/, '');\n}\n\nexport function randomId(bytes = 2): string {\n return randomBytes(bytes).toString('hex');\n}\n\nexport async function listFilesRecursive(dir: string, maxFiles = 5000): Promise<string[]> {\n const out: string[] = [];\n async function walk(d: string): Promise<void> {\n let entries;\n try {\n entries = await fs.readdir(d, { withFileTypes: true });\n } catch {\n return;\n }\n for (const e of entries) {\n if (out.length >= maxFiles) return;\n const full = path.join(d, e.name);\n if (e.isDirectory()) await walk(full);\n else if (e.isFile()) out.push(full);\n }\n }\n await walk(dir);\n return out;\n}\n\n/** Atomically write text only when it differs from what is on disk. Returns true if a write happened. */\nexport async function writeTextIfChanged(file: string, text: string): Promise<boolean> {\n try {\n if ((await fs.readFile(file, 'utf8')) === text) return false;\n } catch {\n /* missing: write below */\n }\n await atomicWriteFile(file, text);\n return true;\n}\n\nexport async function writeJsonIfChanged(file: string, value: unknown): Promise<boolean> {\n const text = JSON.stringify(value, null, 2) + '\\n';\n JSON.parse(text);\n return writeTextIfChanged(file, text);\n}\n"]}
@@ -0,0 +1,6 @@
1
+ export interface LockOptions {
2
+ timeoutMs?: number;
3
+ staleMs?: number;
4
+ }
5
+ /** Exclusive advisory lock stored under .devworkspace/local/. Stale locks (dead pid or too old) are broken. */
6
+ export declare function withLock<T>(workspaceDir: string, fn: () => Promise<T>, opts?: LockOptions): Promise<T>;