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,135 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { SCHEMA_VERSION, validateProject } from '../config/schemas.js';
4
+ import { DevWorkspaceError } from '../errors/index.js';
5
+ import { gitTopLevel } from '../git/git.js';
6
+ import { detectIntegrations } from '../integrations/index.js';
7
+ import { atomicWriteFile, atomicWriteJson, ensureDir, pathExists } from '../utils/fs.js';
8
+ import { findWorkspaceRoot, LOCAL_GITIGNORE, loadProject, wsPaths, WORKSPACE_DIR } from '../core/workspace.js';
9
+ import { packageVersion } from '../core/version.js';
10
+ import { withLock } from '../utils/lock.js';
11
+ import { readPackageJson } from '../discovery/detect.js';
12
+ import { newProjectId } from './analysis.js';
13
+ import { integrationCtx, isoNow } from './common.js';
14
+ async function resolveRoot(o, notes) {
15
+ if (o.root)
16
+ return path.resolve(o.cwd, o.root);
17
+ const existing = await findWorkspaceRoot(o.cwd);
18
+ if (existing) {
19
+ if (path.resolve(existing) !== path.resolve(o.cwd))
20
+ notes.push(`Using the existing workspace root (${path.relative(o.cwd, existing) || '.'}).`);
21
+ return existing;
22
+ }
23
+ const top = await gitTopLevel(o.cwd);
24
+ if (top && path.resolve(top).toLowerCase() !== path.resolve(o.cwd).toLowerCase()) {
25
+ notes.push('Current directory is inside a Git repository; using the repository root. Pass --root to choose a different directory.');
26
+ return path.resolve(top);
27
+ }
28
+ return path.resolve(o.cwd);
29
+ }
30
+ /** Make sure each required line is present in .devworkspace/.gitignore without removing anything the user added. */
31
+ async function ensureGitignore(ws) {
32
+ if (!(await pathExists(ws.gitignore))) {
33
+ await atomicWriteFile(ws.gitignore, LOCAL_GITIGNORE);
34
+ return 'created';
35
+ }
36
+ const text = await fs.readFile(ws.gitignore, 'utf8');
37
+ const have = new Set(text.split(/\r?\n/).map((l) => l.trim()));
38
+ const missing = LOCAL_GITIGNORE.split('\n').filter((l) => l && !l.startsWith('#') && !have.has(l));
39
+ if (missing.length === 0)
40
+ return 'unchanged';
41
+ const eol = text.includes('\r\n') ? '\r\n' : '\n';
42
+ await atomicWriteFile(ws.gitignore, text + (text.endsWith('\n') ? '' : eol) + missing.join(eol) + eol);
43
+ return 'updated';
44
+ }
45
+ export async function initWorkspace(o) {
46
+ const notes = [];
47
+ const root = await resolveRoot(o, notes);
48
+ const st = await fs.stat(root).catch(() => null);
49
+ if (!st?.isDirectory()) {
50
+ throw new DevWorkspaceError('NOT_FOUND', `Project root does not exist: ${root}`, { hint: 'Run the command inside your project, or pass --root <dir>.' });
51
+ }
52
+ const ws = wsPaths(root);
53
+ const created = [];
54
+ const unchanged = [];
55
+ const rel = (p) => path.relative(root, p).replace(/\\/g, '/');
56
+ const alreadyInitialized = await pathExists(ws.projectJson);
57
+ const dirs = [ws.contextDir, ws.decisionsDir, ws.tasksActive, ws.tasksCompleted, ws.sessionsDir, ws.analysisDir, ws.stateDir];
58
+ const pkg = (await pathExists(path.join(root, 'package.json'))) ? await readPackageJson(root, 'package.json').catch(() => null) : null;
59
+ const name = pkg?.name ?? path.basename(root);
60
+ const now = isoNow(o);
61
+ const integrations = await detectIntegrations(integrationCtx(o, root));
62
+ const result = (extra) => ({
63
+ root,
64
+ dryRun: !!o.dryRun,
65
+ alreadyInitialized,
66
+ created,
67
+ unchanged,
68
+ gitignoreUpdated: false,
69
+ notes,
70
+ integrations,
71
+ nextSteps: ['Run: devworkspace analyze', 'Then paste .devworkspace/analysis/onboarding-prompt.md into Claude Code'],
72
+ ...extra,
73
+ });
74
+ if (o.dryRun) {
75
+ for (const d of dirs)
76
+ if (!(await pathExists(d)))
77
+ created.push(rel(d) + '/');
78
+ if (!alreadyInitialized)
79
+ created.push(rel(ws.projectJson));
80
+ if (!(await pathExists(ws.gitignore)))
81
+ created.push(rel(ws.gitignore));
82
+ return result({});
83
+ }
84
+ await ensureDir(ws.dir);
85
+ return withLock(ws.dir, async () => {
86
+ for (const d of dirs) {
87
+ if (await pathExists(d))
88
+ unchanged.push(rel(d) + '/');
89
+ else {
90
+ await ensureDir(d);
91
+ created.push(rel(d) + '/');
92
+ }
93
+ }
94
+ // Empty directories do not survive Git, so keep portable folders alive.
95
+ for (const d of [ws.decisionsDir, ws.tasksActive, ws.tasksCompleted, ws.sessionsDir]) {
96
+ const keep = path.join(d, '.gitkeep');
97
+ if (!(await pathExists(keep)))
98
+ await fs.writeFile(keep, '');
99
+ }
100
+ let project;
101
+ if (alreadyInitialized) {
102
+ project = await loadProject(ws);
103
+ unchanged.push(rel(ws.projectJson));
104
+ if (project.devworkspaceVersion !== packageVersion()) {
105
+ project = { ...project, devworkspaceVersion: packageVersion(), updatedAt: now };
106
+ await atomicWriteJson(ws.projectJson, validateProject(project));
107
+ notes.push(`Recorded DevWorkspace ${packageVersion()} in project.json.`);
108
+ }
109
+ }
110
+ else {
111
+ project = {
112
+ schemaVersion: SCHEMA_VERSION,
113
+ projectId: newProjectId(),
114
+ name,
115
+ createdAt: now,
116
+ updatedAt: now,
117
+ devworkspaceVersion: packageVersion(),
118
+ settings: { review: 'light', maxFiles: 50_000 },
119
+ managedFiles: [{ path: `${WORKSPACE_DIR}/.gitignore`, mode: 'owned' }],
120
+ };
121
+ await atomicWriteJson(ws.projectJson, validateProject(project));
122
+ created.push(rel(ws.projectJson));
123
+ }
124
+ const gi = await ensureGitignore(ws);
125
+ if (gi === 'created')
126
+ created.push(rel(ws.gitignore));
127
+ else if (gi === 'updated')
128
+ notes.push('Added missing ignore rules to .devworkspace/.gitignore.');
129
+ else
130
+ unchanged.push(rel(ws.gitignore));
131
+ await atomicWriteJson(ws.integrationsState, { schemaVersion: SCHEMA_VERSION, detectedAt: now, integrations });
132
+ return result({ gitignoreUpdated: gi === 'updated' });
133
+ });
134
+ }
135
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/services/init.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { SCHEMA_VERSION, validateProject, type ProjectConfig } from '../config/schemas.js';\nimport { DevWorkspaceError } from '../errors/index.js';\nimport { gitTopLevel } from '../git/git.js';\nimport { detectIntegrations } from '../integrations/index.js';\nimport { atomicWriteFile, atomicWriteJson, ensureDir, pathExists } from '../utils/fs.js';\nimport { findWorkspaceRoot, LOCAL_GITIGNORE, loadProject, wsPaths, WORKSPACE_DIR, type Ws } from '../core/workspace.js';\nimport { packageVersion } from '../core/version.js';\nimport { withLock } from '../utils/lock.js';\nimport { readPackageJson } from '../discovery/detect.js';\nimport { newProjectId } from './analysis.js';\nimport { integrationCtx, isoNow, type ServiceOptions } from './common.js';\n\nexport interface InitOptions extends ServiceOptions {\n /** Explicit project root; defaults to the Git top-level or the current directory. */\n root?: string;\n}\n\nexport interface InitResult {\n root: string;\n dryRun: boolean;\n alreadyInitialized: boolean;\n created: string[];\n unchanged: string[];\n gitignoreUpdated: boolean;\n notes: string[];\n integrations: Awaited<ReturnType<typeof detectIntegrations>>;\n nextSteps: string[];\n}\n\nasync function resolveRoot(o: InitOptions, notes: string[]): Promise<string> {\n if (o.root) return path.resolve(o.cwd, o.root);\n const existing = await findWorkspaceRoot(o.cwd);\n if (existing) {\n if (path.resolve(existing) !== path.resolve(o.cwd)) notes.push(`Using the existing workspace root (${path.relative(o.cwd, existing) || '.'}).`);\n return existing;\n }\n const top = await gitTopLevel(o.cwd);\n if (top && path.resolve(top).toLowerCase() !== path.resolve(o.cwd).toLowerCase()) {\n notes.push('Current directory is inside a Git repository; using the repository root. Pass --root to choose a different directory.');\n return path.resolve(top);\n }\n return path.resolve(o.cwd);\n}\n\n/** Make sure each required line is present in .devworkspace/.gitignore without removing anything the user added. */\nasync function ensureGitignore(ws: Ws): Promise<'created' | 'updated' | 'unchanged'> {\n if (!(await pathExists(ws.gitignore))) {\n await atomicWriteFile(ws.gitignore, LOCAL_GITIGNORE);\n return 'created';\n }\n const text = await fs.readFile(ws.gitignore, 'utf8');\n const have = new Set(text.split(/\\r?\\n/).map((l) => l.trim()));\n const missing = LOCAL_GITIGNORE.split('\\n').filter((l) => l && !l.startsWith('#') && !have.has(l));\n if (missing.length === 0) return 'unchanged';\n const eol = text.includes('\\r\\n') ? '\\r\\n' : '\\n';\n await atomicWriteFile(ws.gitignore, text + (text.endsWith('\\n') ? '' : eol) + missing.join(eol) + eol);\n return 'updated';\n}\n\nexport async function initWorkspace(o: InitOptions): Promise<InitResult> {\n const notes: string[] = [];\n const root = await resolveRoot(o, notes);\n const st = await fs.stat(root).catch(() => null);\n if (!st?.isDirectory()) {\n throw new DevWorkspaceError('NOT_FOUND', `Project root does not exist: ${root}`, { hint: 'Run the command inside your project, or pass --root <dir>.' });\n }\n const ws = wsPaths(root);\n const created: string[] = [];\n const unchanged: string[] = [];\n const rel = (p: string): string => path.relative(root, p).replace(/\\\\/g, '/');\n const alreadyInitialized = await pathExists(ws.projectJson);\n\n const dirs = [ws.contextDir, ws.decisionsDir, ws.tasksActive, ws.tasksCompleted, ws.sessionsDir, ws.analysisDir, ws.stateDir];\n const pkg = (await pathExists(path.join(root, 'package.json'))) ? await readPackageJson(root, 'package.json').catch(() => null) : null;\n const name = pkg?.name ?? path.basename(root);\n const now = isoNow(o);\n\n const integrations = await detectIntegrations(integrationCtx(o, root));\n\n const result = (extra: Partial<InitResult>): InitResult => ({\n root,\n dryRun: !!o.dryRun,\n alreadyInitialized,\n created,\n unchanged,\n gitignoreUpdated: false,\n notes,\n integrations,\n nextSteps: ['Run: devworkspace analyze', 'Then paste .devworkspace/analysis/onboarding-prompt.md into Claude Code'],\n ...extra,\n });\n\n if (o.dryRun) {\n for (const d of dirs) if (!(await pathExists(d))) created.push(rel(d) + '/');\n if (!alreadyInitialized) created.push(rel(ws.projectJson));\n if (!(await pathExists(ws.gitignore))) created.push(rel(ws.gitignore));\n return result({});\n }\n\n await ensureDir(ws.dir);\n return withLock(ws.dir, async () => {\n for (const d of dirs) {\n if (await pathExists(d)) unchanged.push(rel(d) + '/');\n else {\n await ensureDir(d);\n created.push(rel(d) + '/');\n }\n }\n // Empty directories do not survive Git, so keep portable folders alive.\n for (const d of [ws.decisionsDir, ws.tasksActive, ws.tasksCompleted, ws.sessionsDir]) {\n const keep = path.join(d, '.gitkeep');\n if (!(await pathExists(keep))) await fs.writeFile(keep, '');\n }\n\n let project: ProjectConfig;\n if (alreadyInitialized) {\n project = await loadProject(ws);\n unchanged.push(rel(ws.projectJson));\n if (project.devworkspaceVersion !== packageVersion()) {\n project = { ...project, devworkspaceVersion: packageVersion(), updatedAt: now };\n await atomicWriteJson(ws.projectJson, validateProject(project));\n notes.push(`Recorded DevWorkspace ${packageVersion()} in project.json.`);\n }\n } else {\n project = {\n schemaVersion: SCHEMA_VERSION,\n projectId: newProjectId(),\n name,\n createdAt: now,\n updatedAt: now,\n devworkspaceVersion: packageVersion(),\n settings: { review: 'light', maxFiles: 50_000 },\n managedFiles: [{ path: `${WORKSPACE_DIR}/.gitignore`, mode: 'owned' }],\n };\n await atomicWriteJson(ws.projectJson, validateProject(project));\n created.push(rel(ws.projectJson));\n }\n\n const gi = await ensureGitignore(ws);\n if (gi === 'created') created.push(rel(ws.gitignore));\n else if (gi === 'updated') notes.push('Added missing ignore rules to .devworkspace/.gitignore.');\n else unchanged.push(rel(ws.gitignore));\n\n await atomicWriteJson(ws.integrationsState, { schemaVersion: SCHEMA_VERSION, detectedAt: now, integrations });\n return result({ gitignoreUpdated: gi === 'updated' });\n });\n}\n"]}
@@ -0,0 +1,40 @@
1
+ import { type CommandEntry, type Fact } from '../config/schemas.js';
2
+ import { type FactInput } from '../knowledge/facts.js';
3
+ import { type ServiceOptions } from './common.js';
4
+ export declare function addFact(o: ServiceOptions, input: FactInput): Promise<{
5
+ fact: Fact;
6
+ action: string;
7
+ }>;
8
+ export declare function verifyFactByKey(o: ServiceOptions, key: string): Promise<Fact>;
9
+ export declare function removeFactByKey(o: ServiceOptions, key: string): Promise<boolean>;
10
+ export interface ContextResult {
11
+ project: {
12
+ name: string;
13
+ id: string;
14
+ };
15
+ topic: string | null;
16
+ facts: Fact[];
17
+ totalFacts: number;
18
+ testingContract: CommandEntry[];
19
+ decisions: {
20
+ number: number;
21
+ title: string;
22
+ status: string;
23
+ file: string;
24
+ }[];
25
+ activeTasks: {
26
+ id: string;
27
+ title: string;
28
+ status: string;
29
+ }[];
30
+ documents: {
31
+ name: string;
32
+ bytes: number;
33
+ }[];
34
+ content?: Record<string, string>;
35
+ }
36
+ /** What DevWorkspace currently believes about the project. Concise by default; --full adds the context documents. */
37
+ export declare function getContext(o: ServiceOptions, opts?: {
38
+ topic?: string;
39
+ full?: boolean;
40
+ }): Promise<ContextResult>;
@@ -0,0 +1,72 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { SCHEMA_VERSION, validateCommands } from '../config/schemas.js';
4
+ import { loadProject, requireWorkspace } from '../core/workspace.js';
5
+ import { readVersioned } from '../core/versioned.js';
6
+ import { listDecisions } from '../decisions/decision.js';
7
+ import { buildFact, loadFacts, removeFact, saveFacts, upsertFact, verifyFact } from '../knowledge/facts.js';
8
+ import { listTasks } from '../tasks/task.js';
9
+ import { pathExists, readTextBounded } from '../utils/fs.js';
10
+ import { isoNow, mutate } from './common.js';
11
+ export async function addFact(o, input) {
12
+ return mutate(o, async (ws) => {
13
+ const facts = await loadFacts(ws);
14
+ const existing = facts.facts.find((f) => f.key === input.key);
15
+ const fact = await buildFact(ws.root, input, isoNow(o), existing);
16
+ const action = upsertFact(facts, fact);
17
+ await saveFacts(ws, facts);
18
+ return { fact, action };
19
+ });
20
+ }
21
+ export async function verifyFactByKey(o, key) {
22
+ return mutate(o, async (ws) => {
23
+ const facts = await loadFacts(ws);
24
+ const fact = await verifyFact(ws.root, facts, key, isoNow(o));
25
+ await saveFacts(ws, facts);
26
+ return fact;
27
+ });
28
+ }
29
+ export async function removeFactByKey(o, key) {
30
+ return mutate(o, async (ws) => {
31
+ const facts = await loadFacts(ws);
32
+ const removed = removeFact(facts, key);
33
+ if (removed)
34
+ await saveFacts(ws, facts);
35
+ return removed;
36
+ });
37
+ }
38
+ /** What DevWorkspace currently believes about the project. Concise by default; --full adds the context documents. */
39
+ export async function getContext(o, opts = {}) {
40
+ const ws = await requireWorkspace(o.cwd);
41
+ const project = await loadProject(ws);
42
+ const all = await loadFacts(ws);
43
+ const topic = opts.topic?.toLowerCase() ?? null;
44
+ const facts = topic ? all.facts.filter((f) => f.key.startsWith(topic) || f.key.includes(topic)) : all.facts;
45
+ let testingContract = [];
46
+ if (await pathExists(ws.commands))
47
+ testingContract = (await readVersioned(ws.commands, 'commands', SCHEMA_VERSION, validateCommands)).data.checks;
48
+ const { decisions } = await listDecisions(ws);
49
+ const { tasks } = await listTasks(ws);
50
+ const documents = [];
51
+ const content = {};
52
+ if (await pathExists(ws.contextDir)) {
53
+ for (const name of (await fs.readdir(ws.contextDir)).filter((n) => n.endsWith('.md')).sort()) {
54
+ const full = path.join(ws.contextDir, name);
55
+ documents.push({ name, bytes: (await fs.stat(full)).size });
56
+ if (opts.full)
57
+ content[name] = (await readTextBounded(full, 256 * 1024)) ?? '';
58
+ }
59
+ }
60
+ return {
61
+ project: { name: project.name, id: project.projectId },
62
+ topic,
63
+ facts,
64
+ totalFacts: all.facts.length,
65
+ testingContract,
66
+ decisions: decisions.map((d) => ({ number: d.number, title: d.title, status: d.status, file: d.file })),
67
+ activeTasks: tasks.map((t) => ({ id: t.id, title: t.title, status: t.status })),
68
+ documents,
69
+ ...(opts.full ? { content } : {}),
70
+ };
71
+ }
72
+ //# sourceMappingURL=knowledge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"knowledge.js","sourceRoot":"","sources":["../../src/services/knowledge.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { SCHEMA_VERSION, validateCommands, type CommandEntry, type Fact } from '../config/schemas.js';\nimport { loadProject, requireWorkspace } from '../core/workspace.js';\nimport { readVersioned } from '../core/versioned.js';\nimport { listDecisions } from '../decisions/decision.js';\nimport { buildFact, loadFacts, removeFact, saveFacts, upsertFact, verifyFact, type FactInput } from '../knowledge/facts.js';\nimport { listTasks } from '../tasks/task.js';\nimport { pathExists, readTextBounded } from '../utils/fs.js';\nimport { isoNow, mutate, type ServiceOptions } from './common.js';\n\nexport async function addFact(o: ServiceOptions, input: FactInput): Promise<{ fact: Fact; action: string }> {\n return mutate(o, async (ws) => {\n const facts = await loadFacts(ws);\n const existing = facts.facts.find((f) => f.key === input.key);\n const fact = await buildFact(ws.root, input, isoNow(o), existing);\n const action = upsertFact(facts, fact);\n await saveFacts(ws, facts);\n return { fact, action };\n });\n}\n\nexport async function verifyFactByKey(o: ServiceOptions, key: string): Promise<Fact> {\n return mutate(o, async (ws) => {\n const facts = await loadFacts(ws);\n const fact = await verifyFact(ws.root, facts, key, isoNow(o));\n await saveFacts(ws, facts);\n return fact;\n });\n}\n\nexport async function removeFactByKey(o: ServiceOptions, key: string): Promise<boolean> {\n return mutate(o, async (ws) => {\n const facts = await loadFacts(ws);\n const removed = removeFact(facts, key);\n if (removed) await saveFacts(ws, facts);\n return removed;\n });\n}\n\nexport interface ContextResult {\n project: { name: string; id: string };\n topic: string | null;\n facts: Fact[];\n totalFacts: number;\n testingContract: CommandEntry[];\n decisions: { number: number; title: string; status: string; file: string }[];\n activeTasks: { id: string; title: string; status: string }[];\n documents: { name: string; bytes: number }[];\n content?: Record<string, string>;\n}\n\n/** What DevWorkspace currently believes about the project. Concise by default; --full adds the context documents. */\nexport async function getContext(o: ServiceOptions, opts: { topic?: string; full?: boolean } = {}): Promise<ContextResult> {\n const ws = await requireWorkspace(o.cwd);\n const project = await loadProject(ws);\n const all = await loadFacts(ws);\n const topic = opts.topic?.toLowerCase() ?? null;\n const facts = topic ? all.facts.filter((f) => f.key.startsWith(topic) || f.key.includes(topic)) : all.facts;\n let testingContract: CommandEntry[] = [];\n if (await pathExists(ws.commands)) testingContract = (await readVersioned(ws.commands, 'commands', SCHEMA_VERSION, validateCommands)).data.checks;\n const { decisions } = await listDecisions(ws);\n const { tasks } = await listTasks(ws);\n const documents: { name: string; bytes: number }[] = [];\n const content: Record<string, string> = {};\n if (await pathExists(ws.contextDir)) {\n for (const name of (await fs.readdir(ws.contextDir)).filter((n) => n.endsWith('.md')).sort()) {\n const full = path.join(ws.contextDir, name);\n documents.push({ name, bytes: (await fs.stat(full)).size });\n if (opts.full) content[name] = (await readTextBounded(full, 256 * 1024)) ?? '';\n }\n }\n return {\n project: { name: project.name, id: project.projectId },\n topic,\n facts,\n totalFacts: all.facts.length,\n testingContract,\n decisions: decisions.map((d) => ({ number: d.number, title: d.title, status: d.status, file: d.file })),\n activeTasks: tasks.map((t) => ({ id: t.id, title: t.title, status: t.status })),\n documents,\n ...(opts.full ? { content } : {}),\n };\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import { type Migration } from '../core/migrations.js';
2
+ import { type ServiceOptions } from './common.js';
3
+ export interface MigrateResult {
4
+ dryRun: boolean;
5
+ checked: number;
6
+ migrated: {
7
+ file: string;
8
+ from: number;
9
+ to: number;
10
+ }[];
11
+ }
12
+ /**
13
+ * Upgrade every schema-versioned file to the current schema. Each file is backed up under
14
+ * .devworkspace/local/backups/ before it is rewritten, and files from a newer schema abort the run untouched.
15
+ */
16
+ export declare function migrateWorkspace(o: ServiceOptions, registry?: Record<string, Migration[]>, target?: number): Promise<MigrateResult>;
@@ -0,0 +1,54 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { SCHEMA_VERSION, validateAnalysisState, validateCommands, validateFacts, validateProject } from '../config/schemas.js';
4
+ import { MIGRATIONS, migrateData } from '../core/migrations.js';
5
+ import { writeVersioned } from '../core/versioned.js';
6
+ import { validateSession } from '../sessions/session.js';
7
+ import { validateTask } from '../tasks/task.js';
8
+ import { pathExists, readJsonFile } from '../utils/fs.js';
9
+ import { mutate } from './common.js';
10
+ async function jsonIn(dir) {
11
+ if (!(await pathExists(dir)))
12
+ return [];
13
+ return (await fs.readdir(dir)).filter((n) => n.endsWith('.json')).map((n) => path.join(dir, n));
14
+ }
15
+ /**
16
+ * Upgrade every schema-versioned file to the current schema. Each file is backed up under
17
+ * .devworkspace/local/backups/ before it is rewritten, and files from a newer schema abort the run untouched.
18
+ */
19
+ export async function migrateWorkspace(o, registry = MIGRATIONS, target = SCHEMA_VERSION) {
20
+ return mutate(o, async (ws) => {
21
+ const targets = [
22
+ { file: ws.projectJson, kind: 'project', validate: validateProject },
23
+ { file: ws.facts, kind: 'facts', validate: validateFacts },
24
+ { file: ws.commands, kind: 'commands', validate: validateCommands },
25
+ { file: ws.analysisState, kind: 'analysis', validate: validateAnalysisState },
26
+ ...(await jsonIn(ws.tasksActive)).map((file) => ({ file, kind: 'task', validate: validateTask })),
27
+ ...(await jsonIn(ws.tasksCompleted)).map((file) => ({ file, kind: 'task', validate: validateTask })),
28
+ ...(await jsonIn(ws.sessionsDir)).map((file) => ({ file, kind: 'session', validate: validateSession })),
29
+ ];
30
+ // Plan first: nothing is written unless every file can be migrated.
31
+ const plan = [];
32
+ let checked = 0;
33
+ for (const t of targets) {
34
+ if (!(await pathExists(t.file)))
35
+ continue;
36
+ checked++;
37
+ const r = migrateData(await readJsonFile(t.file), target, registry[t.kind] ?? [], path.basename(t.file));
38
+ if (r.migrated) {
39
+ t.validate(r.data);
40
+ plan.push({ file: t.file, data: r.data, from: r.from });
41
+ }
42
+ }
43
+ const stamp = `migrate-${new Date().toISOString().replace(/[:.]/g, '-')}`;
44
+ if (!o.dryRun)
45
+ for (const p of plan)
46
+ await writeVersioned(ws, p.file, p.data, stamp);
47
+ return {
48
+ dryRun: !!o.dryRun,
49
+ checked,
50
+ migrated: plan.map((p) => ({ file: path.relative(ws.root, p.file).replace(/\\/g, '/'), from: p.from, to: target })),
51
+ };
52
+ });
53
+ }
54
+ //# sourceMappingURL=migrate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../src/services/migrate.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { SCHEMA_VERSION, validateAnalysisState, validateCommands, validateFacts, validateProject } from '../config/schemas.js';\nimport { MIGRATIONS, migrateData, type Migration } from '../core/migrations.js';\nimport { writeVersioned } from '../core/versioned.js';\nimport { type Ws } from '../core/workspace.js';\nimport { validateSession } from '../sessions/session.js';\nimport { validateTask } from '../tasks/task.js';\nimport { pathExists, readJsonFile } from '../utils/fs.js';\nimport { mutate, type ServiceOptions } from './common.js';\n\nexport interface MigrateResult {\n dryRun: boolean;\n checked: number;\n migrated: { file: string; from: number; to: number }[];\n}\n\ntype Validator = (v: unknown, file?: string) => unknown;\n\nasync function jsonIn(dir: string): Promise<string[]> {\n if (!(await pathExists(dir))) return [];\n return (await fs.readdir(dir)).filter((n) => n.endsWith('.json')).map((n) => path.join(dir, n));\n}\n\n/**\n * Upgrade every schema-versioned file to the current schema. Each file is backed up under\n * .devworkspace/local/backups/ before it is rewritten, and files from a newer schema abort the run untouched.\n */\nexport async function migrateWorkspace(o: ServiceOptions, registry: Record<string, Migration[]> = MIGRATIONS, target: number = SCHEMA_VERSION): Promise<MigrateResult> {\n return mutate(o, async (ws: Ws) => {\n const targets: { file: string; kind: string; validate: Validator }[] = [\n { file: ws.projectJson, kind: 'project', validate: validateProject },\n { file: ws.facts, kind: 'facts', validate: validateFacts },\n { file: ws.commands, kind: 'commands', validate: validateCommands },\n { file: ws.analysisState, kind: 'analysis', validate: validateAnalysisState },\n ...(await jsonIn(ws.tasksActive)).map((file) => ({ file, kind: 'task', validate: validateTask as Validator })),\n ...(await jsonIn(ws.tasksCompleted)).map((file) => ({ file, kind: 'task', validate: validateTask as Validator })),\n ...(await jsonIn(ws.sessionsDir)).map((file) => ({ file, kind: 'session', validate: validateSession as Validator })),\n ];\n // Plan first: nothing is written unless every file can be migrated.\n const plan: { file: string; data: { schemaVersion: number }; from: number }[] = [];\n let checked = 0;\n for (const t of targets) {\n if (!(await pathExists(t.file))) continue;\n checked++;\n const r = migrateData(await readJsonFile(t.file), target, registry[t.kind] ?? [], path.basename(t.file));\n if (r.migrated) {\n t.validate(r.data);\n plan.push({ file: t.file, data: r.data as { schemaVersion: number }, from: r.from });\n }\n }\n const stamp = `migrate-${new Date().toISOString().replace(/[:.]/g, '-')}`;\n if (!o.dryRun) for (const p of plan) await writeVersioned(ws, p.file, p.data, stamp);\n return {\n dryRun: !!o.dryRun,\n checked,\n migrated: plan.map((p) => ({ file: path.relative(ws.root, p.file).replace(/\\\\/g, '/'), from: p.from, to: target })),\n };\n });\n}\n"]}
@@ -0,0 +1,27 @@
1
+ import { type Ws } from '../core/workspace.js';
2
+ import { type ServiceOptions } from './common.js';
3
+ export interface ExportResult {
4
+ file: string | null;
5
+ dryRun: boolean;
6
+ fileCount: number;
7
+ bytes: number;
8
+ files: string[];
9
+ }
10
+ export declare function exportWorkspace(o: ServiceOptions, outPath?: string): Promise<ExportResult>;
11
+ export interface ImportResult {
12
+ dryRun: boolean;
13
+ created: string[];
14
+ updated: string[];
15
+ unchanged: string[];
16
+ merged: string[];
17
+ conflicts: {
18
+ path: string;
19
+ reason: string;
20
+ }[];
21
+ }
22
+ export interface ImportOptions extends ServiceOptions {
23
+ overwrite?: boolean;
24
+ allowDifferentProject?: boolean;
25
+ }
26
+ export declare function importWorkspace(o: ImportOptions, file: string): Promise<ImportResult>;
27
+ export type { Ws };
@@ -0,0 +1,155 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { SCHEMA_VERSION, validateFacts } from '../config/schemas.js';
4
+ import { DevWorkspaceError } from '../errors/index.js';
5
+ import { loadProject, requireWorkspace } from '../core/workspace.js';
6
+ import { packageVersion } from '../core/version.js';
7
+ import { authorityRank } from '../knowledge/facts.js';
8
+ import { BUNDLE_FORMAT, MAX_BUNDLE_BYTES, isExportable, parseBundle } from '../portability/bundle.js';
9
+ import { findSecrets } from '../security/redact.js';
10
+ import { atomicWriteFile, ensureDir, listFilesRecursive, pathExists, writeTextIfChanged } from '../utils/fs.js';
11
+ import { assertNoSymlinkEscape, resolveInside, toPosix } from '../utils/paths.js';
12
+ import { withLock } from '../utils/lock.js';
13
+ import { isoNow } from './common.js';
14
+ export async function exportWorkspace(o, outPath) {
15
+ const ws = await requireWorkspace(o.cwd);
16
+ const project = await loadProject(ws);
17
+ const files = {};
18
+ let bytes = 0;
19
+ for (const abs of await listFilesRecursive(ws.dir, 5000)) {
20
+ const rel = toPosix(path.relative(ws.dir, abs));
21
+ if (!isExportable(rel))
22
+ continue;
23
+ const text = await fs.readFile(abs, 'utf8');
24
+ if (findSecrets(text).length > 0) {
25
+ throw new DevWorkspaceError('SECURITY', `Refusing to export: ${rel} contains secret-like values.`, {
26
+ hint: 'Remove the value (and rotate the credential if it was real), then export again.',
27
+ });
28
+ }
29
+ files[rel] = text;
30
+ bytes += text.length;
31
+ if (bytes > MAX_BUNDLE_BYTES)
32
+ throw new DevWorkspaceError('SECURITY', 'Workspace is too large to export.');
33
+ }
34
+ const names = Object.keys(files).sort();
35
+ if (o.dryRun)
36
+ return { file: null, dryRun: true, fileCount: names.length, bytes, files: names };
37
+ const target = path.resolve(o.cwd, outPath ?? `devworkspace-export-${isoNow(o).slice(0, 10)}.json`);
38
+ const insideWorkspace = !path.relative(ws.dir, target).startsWith('..') && !path.isAbsolute(path.relative(ws.dir, target));
39
+ if (insideWorkspace)
40
+ throw new DevWorkspaceError('USAGE', 'Choose an export location outside .devworkspace/.');
41
+ const bundle = {
42
+ format: BUNDLE_FORMAT,
43
+ schemaVersion: SCHEMA_VERSION,
44
+ exportedAt: isoNow(o),
45
+ devworkspaceVersion: packageVersion(),
46
+ projectId: project.projectId,
47
+ projectName: project.name,
48
+ files: Object.fromEntries(names.map((n) => [n, files[n]])),
49
+ };
50
+ await atomicWriteFile(target, JSON.stringify(bundle, null, 2) + '\n');
51
+ return { file: target, dryRun: false, fileCount: names.length, bytes, files: names };
52
+ }
53
+ function mergeFacts(local, incoming) {
54
+ const out = { schemaVersion: SCHEMA_VERSION, facts: [...local.facts] };
55
+ let changed = 0;
56
+ for (const inc of incoming.facts) {
57
+ const i = out.facts.findIndex((f) => f.key === inc.key);
58
+ if (i === -1) {
59
+ out.facts.push(inc);
60
+ changed++;
61
+ continue;
62
+ }
63
+ const cur = out.facts[i];
64
+ const incomingWins = inc.updatedAt > cur.updatedAt && (authorityRank(inc) >= authorityRank(cur) || cur.status === 'STALE');
65
+ if (incomingWins && JSON.stringify(inc) !== JSON.stringify(cur)) {
66
+ out.facts[i] = inc;
67
+ changed++;
68
+ }
69
+ }
70
+ out.facts.sort((a, b) => a.key.localeCompare(b.key));
71
+ return { merged: out, changed };
72
+ }
73
+ export async function importWorkspace(o, file) {
74
+ const ws = await requireWorkspace(o.cwd);
75
+ const abs = path.resolve(o.cwd, file);
76
+ const stat = await fs.stat(abs).catch(() => null);
77
+ if (!stat?.isFile())
78
+ throw new DevWorkspaceError('NOT_FOUND', `Import file not found: ${file}`);
79
+ if (stat.size > MAX_BUNDLE_BYTES)
80
+ throw new DevWorkspaceError('SECURITY', 'Import file is too large.');
81
+ const bundle = parseBundle(await fs.readFile(abs, 'utf8')); // validates everything before any write
82
+ const project = await loadProject(ws);
83
+ if (bundle.projectId !== project.projectId && !o.allowDifferentProject) {
84
+ throw new DevWorkspaceError('CONFLICT', 'This export belongs to a different project (projectId differs).', {
85
+ hint: 'If you are sure, re-run with --allow-different-project.',
86
+ });
87
+ }
88
+ const result = { dryRun: !!o.dryRun, created: [], updated: [], unchanged: [], merged: [], conflicts: [] };
89
+ const apply = async () => {
90
+ for (const rel of Object.keys(bundle.files).sort()) {
91
+ const content = bundle.files[rel];
92
+ if (rel === 'project.json') {
93
+ result.unchanged.push(rel); // local identity is never replaced by an import
94
+ continue;
95
+ }
96
+ const target = resolveInside(ws.dir, rel);
97
+ await assertNoSymlinkEscape(ws.dir, target);
98
+ const exists = await pathExists(target);
99
+ if (!exists) {
100
+ if (!o.dryRun) {
101
+ await ensureDir(path.dirname(target));
102
+ await atomicWriteFile(target, content);
103
+ }
104
+ result.created.push(rel);
105
+ continue;
106
+ }
107
+ const local = await fs.readFile(target, 'utf8');
108
+ if (local === content) {
109
+ result.unchanged.push(rel);
110
+ continue;
111
+ }
112
+ if (rel === 'context/facts.json') {
113
+ const { merged, changed } = mergeFacts(validateFacts(JSON.parse(local)), validateFacts(JSON.parse(content)));
114
+ if (changed > 0) {
115
+ if (!o.dryRun)
116
+ await writeTextIfChanged(target, JSON.stringify(merged, null, 2) + '\n');
117
+ result.merged.push(`${rel} (${changed} fact(s))`);
118
+ }
119
+ else
120
+ result.unchanged.push(rel);
121
+ continue;
122
+ }
123
+ if (rel.startsWith('tasks/')) {
124
+ const l = JSON.parse(local);
125
+ const i = JSON.parse(content);
126
+ if ((i.updatedAt ?? '') > (l.updatedAt ?? '')) {
127
+ if (!o.dryRun)
128
+ await atomicWriteFile(target, content);
129
+ result.updated.push(rel);
130
+ }
131
+ else
132
+ result.conflicts.push({ path: rel, reason: 'local task is newer or equal; kept local' });
133
+ continue;
134
+ }
135
+ if (o.overwrite) {
136
+ if (!o.dryRun) {
137
+ const backup = path.join(ws.backupsDir, `import-${isoNow(o).replace(/[:.]/g, '-')}`, rel);
138
+ await ensureDir(path.dirname(backup));
139
+ await fs.copyFile(target, backup);
140
+ await atomicWriteFile(target, content);
141
+ }
142
+ result.updated.push(rel);
143
+ }
144
+ else {
145
+ result.conflicts.push({ path: rel, reason: 'differs from the local file; kept local (use --overwrite to replace, a backup is kept)' });
146
+ }
147
+ }
148
+ };
149
+ if (o.dryRun)
150
+ await apply();
151
+ else
152
+ await withLock(ws.dir, apply);
153
+ return result;
154
+ }
155
+ //# sourceMappingURL=portability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portability.js","sourceRoot":"","sources":["../../src/services/portability.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { SCHEMA_VERSION, validateFacts, type Fact, type FactsFile } from '../config/schemas.js';\nimport { DevWorkspaceError } from '../errors/index.js';\nimport { loadProject, requireWorkspace, type Ws } from '../core/workspace.js';\nimport { packageVersion } from '../core/version.js';\nimport { authorityRank } from '../knowledge/facts.js';\nimport { BUNDLE_FORMAT, MAX_BUNDLE_BYTES, isExportable, parseBundle, type Bundle } from '../portability/bundle.js';\nimport { findSecrets } from '../security/redact.js';\nimport { atomicWriteFile, ensureDir, listFilesRecursive, pathExists, writeTextIfChanged } from '../utils/fs.js';\nimport { assertNoSymlinkEscape, resolveInside, toPosix } from '../utils/paths.js';\nimport { withLock } from '../utils/lock.js';\nimport { isoNow, type ServiceOptions } from './common.js';\n\nexport interface ExportResult {\n file: string | null;\n dryRun: boolean;\n fileCount: number;\n bytes: number;\n files: string[];\n}\n\nexport async function exportWorkspace(o: ServiceOptions, outPath?: string): Promise<ExportResult> {\n const ws = await requireWorkspace(o.cwd);\n const project = await loadProject(ws);\n const files: Record<string, string> = {};\n let bytes = 0;\n for (const abs of await listFilesRecursive(ws.dir, 5000)) {\n const rel = toPosix(path.relative(ws.dir, abs));\n if (!isExportable(rel)) continue;\n const text = await fs.readFile(abs, 'utf8');\n if (findSecrets(text).length > 0) {\n throw new DevWorkspaceError('SECURITY', `Refusing to export: ${rel} contains secret-like values.`, {\n hint: 'Remove the value (and rotate the credential if it was real), then export again.',\n });\n }\n files[rel] = text;\n bytes += text.length;\n if (bytes > MAX_BUNDLE_BYTES) throw new DevWorkspaceError('SECURITY', 'Workspace is too large to export.');\n }\n const names = Object.keys(files).sort();\n if (o.dryRun) return { file: null, dryRun: true, fileCount: names.length, bytes, files: names };\n\n const target = path.resolve(o.cwd, outPath ?? `devworkspace-export-${isoNow(o).slice(0, 10)}.json`);\n const insideWorkspace = !path.relative(ws.dir, target).startsWith('..') && !path.isAbsolute(path.relative(ws.dir, target));\n if (insideWorkspace) throw new DevWorkspaceError('USAGE', 'Choose an export location outside .devworkspace/.');\n const bundle: Bundle = {\n format: BUNDLE_FORMAT,\n schemaVersion: SCHEMA_VERSION,\n exportedAt: isoNow(o),\n devworkspaceVersion: packageVersion(),\n projectId: project.projectId,\n projectName: project.name,\n files: Object.fromEntries(names.map((n) => [n, files[n]!])),\n };\n await atomicWriteFile(target, JSON.stringify(bundle, null, 2) + '\\n');\n return { file: target, dryRun: false, fileCount: names.length, bytes, files: names };\n}\n\nexport interface ImportResult {\n dryRun: boolean;\n created: string[];\n updated: string[];\n unchanged: string[];\n merged: string[];\n conflicts: { path: string; reason: string }[];\n}\n\nexport interface ImportOptions extends ServiceOptions {\n overwrite?: boolean;\n allowDifferentProject?: boolean;\n}\n\nfunction mergeFacts(local: FactsFile, incoming: FactsFile): { merged: FactsFile; changed: number } {\n const out: FactsFile = { schemaVersion: SCHEMA_VERSION, facts: [...local.facts] };\n let changed = 0;\n for (const inc of incoming.facts) {\n const i = out.facts.findIndex((f) => f.key === inc.key);\n if (i === -1) {\n out.facts.push(inc);\n changed++;\n continue;\n }\n const cur = out.facts[i]!;\n const incomingWins = inc.updatedAt > cur.updatedAt && (authorityRank(inc) >= authorityRank(cur) || cur.status === 'STALE');\n if (incomingWins && JSON.stringify(inc) !== JSON.stringify(cur)) {\n out.facts[i] = inc as Fact;\n changed++;\n }\n }\n out.facts.sort((a, b) => a.key.localeCompare(b.key));\n return { merged: out, changed };\n}\n\nexport async function importWorkspace(o: ImportOptions, file: string): Promise<ImportResult> {\n const ws = await requireWorkspace(o.cwd);\n const abs = path.resolve(o.cwd, file);\n const stat = await fs.stat(abs).catch(() => null);\n if (!stat?.isFile()) throw new DevWorkspaceError('NOT_FOUND', `Import file not found: ${file}`);\n if (stat.size > MAX_BUNDLE_BYTES) throw new DevWorkspaceError('SECURITY', 'Import file is too large.');\n const bundle = parseBundle(await fs.readFile(abs, 'utf8')); // validates everything before any write\n const project = await loadProject(ws);\n if (bundle.projectId !== project.projectId && !o.allowDifferentProject) {\n throw new DevWorkspaceError('CONFLICT', 'This export belongs to a different project (projectId differs).', {\n hint: 'If you are sure, re-run with --allow-different-project.',\n });\n }\n\n const result: ImportResult = { dryRun: !!o.dryRun, created: [], updated: [], unchanged: [], merged: [], conflicts: [] };\n const apply = async (): Promise<void> => {\n for (const rel of Object.keys(bundle.files).sort()) {\n const content = bundle.files[rel]!;\n if (rel === 'project.json') {\n result.unchanged.push(rel); // local identity is never replaced by an import\n continue;\n }\n const target = resolveInside(ws.dir, rel);\n await assertNoSymlinkEscape(ws.dir, target);\n const exists = await pathExists(target);\n if (!exists) {\n if (!o.dryRun) {\n await ensureDir(path.dirname(target));\n await atomicWriteFile(target, content);\n }\n result.created.push(rel);\n continue;\n }\n const local = await fs.readFile(target, 'utf8');\n if (local === content) {\n result.unchanged.push(rel);\n continue;\n }\n if (rel === 'context/facts.json') {\n const { merged, changed } = mergeFacts(validateFacts(JSON.parse(local)), validateFacts(JSON.parse(content)));\n if (changed > 0) {\n if (!o.dryRun) await writeTextIfChanged(target, JSON.stringify(merged, null, 2) + '\\n');\n result.merged.push(`${rel} (${changed} fact(s))`);\n } else result.unchanged.push(rel);\n continue;\n }\n if (rel.startsWith('tasks/')) {\n const l = JSON.parse(local) as { updatedAt?: string };\n const i = JSON.parse(content) as { updatedAt?: string };\n if ((i.updatedAt ?? '') > (l.updatedAt ?? '')) {\n if (!o.dryRun) await atomicWriteFile(target, content);\n result.updated.push(rel);\n } else result.conflicts.push({ path: rel, reason: 'local task is newer or equal; kept local' });\n continue;\n }\n if (o.overwrite) {\n if (!o.dryRun) {\n const backup = path.join(ws.backupsDir, `import-${isoNow(o).replace(/[:.]/g, '-')}`, rel);\n await ensureDir(path.dirname(backup));\n await fs.copyFile(target, backup);\n await atomicWriteFile(target, content);\n }\n result.updated.push(rel);\n } else {\n result.conflicts.push({ path: rel, reason: 'differs from the local file; kept local (use --overwrite to replace, a backup is kept)' });\n }\n }\n };\n if (o.dryRun) await apply();\n else await withLock(ws.dir, apply);\n return result;\n}\n\nexport type { Ws };\n"]}
@@ -0,0 +1,8 @@
1
+ import { type Decision, type DecisionInput } from '../decisions/decision.js';
2
+ import { type Session, type SessionInput } from '../sessions/session.js';
3
+ import { type ServiceOptions } from './common.js';
4
+ export declare function recordDecision(o: ServiceOptions, input: DecisionInput): Promise<Decision>;
5
+ export declare function getDecisions(o: ServiceOptions): Promise<Decision[]>;
6
+ export declare function getDecision(o: ServiceOptions, number: number): Promise<Decision>;
7
+ export declare function recordSession(o: ServiceOptions, input: SessionInput): Promise<Session>;
8
+ export declare function getSessions(o: ServiceOptions, limit?: number): Promise<Session[]>;
@@ -0,0 +1,26 @@
1
+ import { requireWorkspace } from '../core/workspace.js';
2
+ import { addDecision, listDecisions } from '../decisions/decision.js';
3
+ import { listSessions, saveSession } from '../sessions/session.js';
4
+ import { DevWorkspaceError } from '../errors/index.js';
5
+ import { isoNow, mutate } from './common.js';
6
+ export async function recordDecision(o, input) {
7
+ return mutate(o, (ws) => addDecision(ws, input, isoNow(o)));
8
+ }
9
+ export async function getDecisions(o) {
10
+ const ws = await requireWorkspace(o.cwd);
11
+ return (await listDecisions(ws)).decisions;
12
+ }
13
+ export async function getDecision(o, number) {
14
+ const d = (await getDecisions(o)).find((x) => x.number === number);
15
+ if (!d)
16
+ throw new DevWorkspaceError('NOT_FOUND', `No decision number ${number}.`);
17
+ return d;
18
+ }
19
+ export async function recordSession(o, input) {
20
+ return mutate(o, (ws) => saveSession(ws, input, isoNow(o)));
21
+ }
22
+ export async function getSessions(o, limit = 10) {
23
+ const ws = await requireWorkspace(o.cwd);
24
+ return listSessions(ws, limit);
25
+ }
26
+ //# sourceMappingURL=records.js.map