devworkspace 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +15 -2
  3. package/dist/agents/claude/content.js +3 -3
  4. package/dist/agents/claude/content.js.map +1 -1
  5. package/dist/cli/args.d.ts +19 -0
  6. package/dist/cli/args.js +6 -0
  7. package/dist/cli/args.js.map +1 -1
  8. package/dist/cli/help.js +40 -5
  9. package/dist/cli/help.js.map +1 -1
  10. package/dist/cli/render.d.ts +7 -0
  11. package/dist/cli/render.js +96 -13
  12. package/dist/cli/render.js.map +1 -1
  13. package/dist/cli/run.d.ts +6 -0
  14. package/dist/cli/run.js +65 -3
  15. package/dist/cli/run.js.map +1 -1
  16. package/dist/core/workspace.js +2 -1
  17. package/dist/core/workspace.js.map +1 -1
  18. package/dist/index.d.ts +3 -1
  19. package/dist/index.js +2 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/integrations/adapters/agent-skills.d.ts +8 -0
  22. package/dist/integrations/adapters/agent-skills.js +25 -0
  23. package/dist/integrations/adapters/agent-skills.js.map +1 -0
  24. package/dist/integrations/adapters/claudePlugin.d.ts +41 -0
  25. package/dist/integrations/adapters/claudePlugin.js +129 -0
  26. package/dist/integrations/adapters/claudePlugin.js.map +1 -0
  27. package/dist/integrations/adapters/graphify.d.ts +2 -0
  28. package/dist/integrations/adapters/graphify.js +239 -0
  29. package/dist/integrations/adapters/graphify.js.map +1 -0
  30. package/dist/integrations/adapters/omniroute.d.ts +6 -0
  31. package/dist/integrations/adapters/omniroute.js +32 -0
  32. package/dist/integrations/adapters/omniroute.js.map +1 -0
  33. package/dist/integrations/adapters/pluginSkill.d.ts +28 -0
  34. package/dist/integrations/adapters/pluginSkill.js +195 -0
  35. package/dist/integrations/adapters/pluginSkill.js.map +1 -0
  36. package/dist/integrations/adapters/ponytail.d.ts +8 -0
  37. package/dist/integrations/adapters/ponytail.js +33 -0
  38. package/dist/integrations/adapters/ponytail.js.map +1 -0
  39. package/dist/integrations/exec.d.ts +26 -0
  40. package/dist/integrations/exec.js +167 -0
  41. package/dist/integrations/exec.js.map +1 -0
  42. package/dist/integrations/index.d.ts +5 -6
  43. package/dist/integrations/index.js +3 -29
  44. package/dist/integrations/index.js.map +1 -1
  45. package/dist/integrations/manager.d.ts +62 -0
  46. package/dist/integrations/manager.js +248 -0
  47. package/dist/integrations/manager.js.map +1 -0
  48. package/dist/integrations/notices.d.ts +13 -0
  49. package/dist/integrations/notices.js +68 -0
  50. package/dist/integrations/notices.js.map +1 -0
  51. package/dist/integrations/protect.d.ts +20 -0
  52. package/dist/integrations/protect.js +131 -0
  53. package/dist/integrations/protect.js.map +1 -0
  54. package/dist/integrations/state.d.ts +10 -0
  55. package/dist/integrations/state.js +81 -0
  56. package/dist/integrations/state.js.map +1 -0
  57. package/dist/integrations/types.d.ts +133 -16
  58. package/dist/integrations/types.js +13 -1
  59. package/dist/integrations/types.js.map +1 -1
  60. package/dist/prompts/onboarding.d.ts +3 -3
  61. package/dist/prompts/onboarding.js +5 -4
  62. package/dist/prompts/onboarding.js.map +1 -1
  63. package/dist/services/analysis.d.ts +2 -2
  64. package/dist/services/analysis.js +4 -5
  65. package/dist/services/analysis.js.map +1 -1
  66. package/dist/services/common.d.ts +8 -5
  67. package/dist/services/common.js +2 -5
  68. package/dist/services/common.js.map +1 -1
  69. package/dist/services/doctor.d.ts +10 -0
  70. package/dist/services/doctor.js +9 -15
  71. package/dist/services/doctor.js.map +1 -1
  72. package/dist/services/init.d.ts +9 -2
  73. package/dist/services/init.js +10 -7
  74. package/dist/services/init.js.map +1 -1
  75. package/dist/services/integrations.d.ts +26 -0
  76. package/dist/services/integrations.js +35 -0
  77. package/dist/services/integrations.js.map +1 -0
  78. package/dist/services/status.d.ts +3 -2
  79. package/dist/services/status.js +3 -3
  80. package/dist/services/status.js.map +1 -1
  81. package/dist/utils/lock.d.ts +2 -0
  82. package/dist/utils/lock.js +2 -2
  83. package/dist/utils/lock.js.map +1 -1
  84. package/dist/utils/proc.d.ts +8 -1
  85. package/dist/utils/proc.js +31 -8
  86. package/dist/utils/proc.js.map +1 -1
  87. package/docs/architecture.md +1 -1
  88. package/docs/claude.md +4 -0
  89. package/docs/getting-started.md +2 -2
  90. package/docs/integrations.md +98 -33
  91. package/docs/security.md +5 -1
  92. package/docs/testing.md +2 -0
  93. package/docs/troubleshooting.md +8 -1
  94. package/package.json +1 -1
  95. package/dist/integrations/agent-skills.d.ts +0 -10
  96. package/dist/integrations/agent-skills.js +0 -45
  97. package/dist/integrations/agent-skills.js.map +0 -1
  98. package/dist/integrations/graphify.d.ts +0 -7
  99. package/dist/integrations/graphify.js +0 -54
  100. package/dist/integrations/graphify.js.map +0 -1
  101. package/dist/integrations/omniroute.d.ts +0 -7
  102. package/dist/integrations/omniroute.js +0 -24
  103. package/dist/integrations/omniroute.js.map +0 -1
  104. package/dist/integrations/ponytail.d.ts +0 -10
  105. package/dist/integrations/ponytail.js +0 -58
  106. package/dist/integrations/ponytail.js.map +0 -1
  107. package/dist/integrations/probe.d.ts +0 -6
  108. package/dist/integrations/probe.js +0 -41
  109. package/dist/integrations/probe.js.map +0 -1
@@ -3,14 +3,14 @@ import path from 'node:path';
3
3
  import { SCHEMA_VERSION, validateProject } from '../config/schemas.js';
4
4
  import { DevWorkspaceError } from '../errors/index.js';
5
5
  import { gitTopLevel } from '../git/git.js';
6
- import { detectIntegrations } from '../integrations/index.js';
6
+ import { inspectAll, runSetup } from '../integrations/index.js';
7
7
  import { atomicWriteFile, atomicWriteJson, ensureDir, pathExists } from '../utils/fs.js';
8
8
  import { findWorkspaceRoot, LOCAL_GITIGNORE, loadProject, wsPaths, WORKSPACE_DIR } from '../core/workspace.js';
9
9
  import { packageVersion } from '../core/version.js';
10
10
  import { withLock } from '../utils/lock.js';
11
11
  import { readPackageJson } from '../discovery/detect.js';
12
12
  import { newProjectId } from './analysis.js';
13
- import { integrationCtx, isoNow } from './common.js';
13
+ import { isoNow, managerEnv } from './common.js';
14
14
  async function resolveRoot(o, notes) {
15
15
  if (o.root)
16
16
  return path.resolve(o.cwd, o.root);
@@ -58,7 +58,6 @@ export async function initWorkspace(o) {
58
58
  const pkg = (await pathExists(path.join(root, 'package.json'))) ? await readPackageJson(root, 'package.json').catch(() => null) : null;
59
59
  const name = pkg?.name ?? path.basename(root);
60
60
  const now = isoNow(o);
61
- const integrations = await detectIntegrations(integrationCtx(o, root));
62
61
  const result = (extra) => ({
63
62
  root,
64
63
  dryRun: !!o.dryRun,
@@ -67,10 +66,11 @@ export async function initWorkspace(o) {
67
66
  unchanged,
68
67
  gitignoreUpdated: false,
69
68
  notes,
70
- integrations,
69
+ integrations: [],
71
70
  nextSteps: ['Run: devworkspace analyze', 'Then paste .devworkspace/analysis/onboarding-prompt.md into Claude Code'],
72
71
  ...extra,
73
72
  });
73
+ const shallow = async () => inspectAll(ws, managerEnv(o, root), { deep: false });
74
74
  if (o.dryRun) {
75
75
  for (const d of dirs)
76
76
  if (!(await pathExists(d)))
@@ -79,7 +79,7 @@ export async function initWorkspace(o) {
79
79
  created.push(rel(ws.projectJson));
80
80
  if (!(await pathExists(ws.gitignore)))
81
81
  created.push(rel(ws.gitignore));
82
- return result({});
82
+ return result({ integrations: await shallow() });
83
83
  }
84
84
  await ensureDir(ws.dir);
85
85
  return withLock(ws.dir, async () => {
@@ -128,8 +128,11 @@ export async function initWorkspace(o) {
128
128
  notes.push('Added missing ignore rules to .devworkspace/.gitignore.');
129
129
  else
130
130
  unchanged.push(rel(ws.gitignore));
131
- await atomicWriteJson(ws.integrationsState, { schemaVersion: SCHEMA_VERSION, detectedAt: now, integrations });
132
- return result({ gitignoreUpdated: gi === 'updated' });
131
+ let setup;
132
+ if (o.withIntegrations) {
133
+ setup = await runSetup(ws, managerEnv(o, root), { scope: o.withIntegrations.scope, yes: o.withIntegrations.yes, dryRun: false, repair: false });
134
+ }
135
+ return result({ gitignoreUpdated: gi === 'updated', integrations: setup ? setup.outcomes.map((x) => x.report) : await shallow(), ...(setup ? { setup } : {}) });
133
136
  });
134
137
  }
135
138
  //# sourceMappingURL=init.js.map
@@ -1 +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"]}
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 { inspectAll, runSetup, type IntegrationReport, type SetupResult } 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 { isoNow, managerEnv, 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 /** Also run the integration manager (same code path as `devworkspace setup --integrations`). */\n withIntegrations?: { yes: boolean; scope: 'project' | 'user' };\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: IntegrationReport[];\n /** Present when withIntegrations was requested. */\n setup?: SetupResult;\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\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 const shallow = async (): Promise<IntegrationReport[]> => inspectAll(ws, managerEnv(o, root), { deep: false });\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({ integrations: await shallow() });\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 let setup: SetupResult | undefined;\n if (o.withIntegrations) {\n setup = await runSetup(ws, managerEnv(o, root), { scope: o.withIntegrations.scope, yes: o.withIntegrations.yes, dryRun: false, repair: false });\n }\n return result({ gitignoreUpdated: gi === 'updated', integrations: setup ? setup.outcomes.map((x) => x.report) : await shallow(), ...(setup ? { setup } : {}) });\n });\n}\n"]}
@@ -0,0 +1,26 @@
1
+ import { type IntegrationId, type IntegrationReport, type InstallScope, type ProgressEvent, type SetupResult } from '../integrations/index.js';
2
+ import { type ServiceOptions } from './common.js';
3
+ export interface SetupRequest {
4
+ yes?: boolean;
5
+ scope?: InstallScope;
6
+ only?: string[];
7
+ repair?: boolean;
8
+ }
9
+ export declare function parseOnly(only: string[] | undefined): IntegrationId[] | undefined;
10
+ /** Install and verify the integrations. Without `yes`, only project-local, non-executing steps may run. */
11
+ export declare function setupIntegrations(o: ServiceOptions, req?: SetupRequest, onEvent?: (e: ProgressEvent) => void): Promise<SetupResult>;
12
+ export interface IntegrationsStatus {
13
+ deep: boolean;
14
+ reports: IntegrationReport[];
15
+ summary: {
16
+ complete: boolean;
17
+ required: number;
18
+ verified: number;
19
+ };
20
+ acknowledged?: string[];
21
+ }
22
+ /** Observed state (never the saved file alone). `deep` runs the tools; shallow only looks at files and PATH. */
23
+ export declare function integrationsStatus(o: ServiceOptions, opts?: {
24
+ deep?: boolean;
25
+ restarted?: boolean;
26
+ }): Promise<IntegrationsStatus>;
@@ -0,0 +1,35 @@
1
+ import { DevWorkspaceError } from '../errors/index.js';
2
+ import { requireWorkspace } from '../core/workspace.js';
3
+ import { INTEGRATION_IDS, acknowledgeRestart, inspectAll, runSetup, summarize, } from '../integrations/index.js';
4
+ import { isoNow, managerEnv } from './common.js';
5
+ export function parseOnly(only) {
6
+ if (!only || only.length === 0)
7
+ return undefined;
8
+ const ids = only.flatMap((s) => s.split(',')).map((s) => s.trim()).filter(Boolean);
9
+ for (const id of ids) {
10
+ if (!INTEGRATION_IDS.includes(id)) {
11
+ throw new DevWorkspaceError('USAGE', `Unknown integration "${id}".`, { hint: `Use: ${INTEGRATION_IDS.join(', ')}.` });
12
+ }
13
+ }
14
+ return ids;
15
+ }
16
+ /** Install and verify the integrations. Without `yes`, only project-local, non-executing steps may run. */
17
+ export async function setupIntegrations(o, req = {}, onEvent) {
18
+ const ws = await requireWorkspace(o.cwd);
19
+ return runSetup(ws, managerEnv(o, ws.root), { scope: req.scope ?? 'project', yes: !!req.yes, dryRun: !!o.dryRun, repair: !!req.repair, only: parseOnly(req.only) }, onEvent);
20
+ }
21
+ /** Observed state (never the saved file alone). `deep` runs the tools; shallow only looks at files and PATH. */
22
+ export async function integrationsStatus(o, opts = {}) {
23
+ const ws = await requireWorkspace(o.cwd);
24
+ const env = managerEnv(o, ws.root);
25
+ let acknowledged;
26
+ if (opts.restarted) {
27
+ const before = await inspectAll(ws, env, { deep: false });
28
+ acknowledged = before.filter((r) => r.status === 'RESTART_REQUIRED').map((r) => r.id);
29
+ await acknowledgeRestart(ws, acknowledged, isoNow(o));
30
+ }
31
+ const reports = await inspectAll(ws, env, { deep: opts.deep ?? true });
32
+ const s = summarize(reports);
33
+ return { deep: opts.deep ?? true, reports, summary: { complete: s.complete, required: s.required, verified: s.verified }, ...(acknowledged ? { acknowledged } : {}) };
34
+ }
35
+ //# sourceMappingURL=integrations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integrations.js","sourceRoot":"","sources":["../../src/services/integrations.ts"],"names":[],"mappings":"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","sourcesContent":["import { DevWorkspaceError } from '../errors/index.js';\nimport { requireWorkspace } from '../core/workspace.js';\nimport {\n INTEGRATION_IDS,\n acknowledgeRestart,\n inspectAll,\n runSetup,\n summarize,\n type IntegrationId,\n type IntegrationReport,\n type InstallScope,\n type ProgressEvent,\n type SetupResult,\n} from '../integrations/index.js';\nimport { isoNow, managerEnv, type ServiceOptions } from './common.js';\n\nexport interface SetupRequest {\n yes?: boolean;\n scope?: InstallScope;\n only?: string[];\n repair?: boolean;\n}\n\nexport function parseOnly(only: string[] | undefined): IntegrationId[] | undefined {\n if (!only || only.length === 0) return undefined;\n const ids = only.flatMap((s) => s.split(',')).map((s) => s.trim()).filter(Boolean);\n for (const id of ids) {\n if (!(INTEGRATION_IDS as readonly string[]).includes(id)) {\n throw new DevWorkspaceError('USAGE', `Unknown integration \"${id}\".`, { hint: `Use: ${INTEGRATION_IDS.join(', ')}.` });\n }\n }\n return ids as IntegrationId[];\n}\n\n/** Install and verify the integrations. Without `yes`, only project-local, non-executing steps may run. */\nexport async function setupIntegrations(o: ServiceOptions, req: SetupRequest = {}, onEvent?: (e: ProgressEvent) => void): Promise<SetupResult> {\n const ws = await requireWorkspace(o.cwd);\n return runSetup(\n ws,\n managerEnv(o, ws.root),\n { scope: req.scope ?? 'project', yes: !!req.yes, dryRun: !!o.dryRun, repair: !!req.repair, only: parseOnly(req.only) },\n onEvent,\n );\n}\n\nexport interface IntegrationsStatus {\n deep: boolean;\n reports: IntegrationReport[];\n summary: { complete: boolean; required: number; verified: number };\n acknowledged?: string[];\n}\n\n/** Observed state (never the saved file alone). `deep` runs the tools; shallow only looks at files and PATH. */\nexport async function integrationsStatus(o: ServiceOptions, opts: { deep?: boolean; restarted?: boolean } = {}): Promise<IntegrationsStatus> {\n const ws = await requireWorkspace(o.cwd);\n const env = managerEnv(o, ws.root);\n let acknowledged: string[] | undefined;\n if (opts.restarted) {\n const before = await inspectAll(ws, env, { deep: false });\n acknowledged = before.filter((r) => r.status === 'RESTART_REQUIRED').map((r) => r.id);\n await acknowledgeRestart(ws, acknowledged as IntegrationId[], isoNow(o));\n }\n const reports = await inspectAll(ws, env, { deep: opts.deep ?? true });\n const s = summarize(reports);\n return { deep: opts.deep ?? true, reports, summary: { complete: s.complete, required: s.required, verified: s.verified }, ...(acknowledged ? { acknowledged } : {}) };\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { type Ws } from '../core/workspace.js';
2
- import { type IntegrationStatus } from '../integrations/index.js';
2
+ import { type IntegrationReport } from '../integrations/index.js';
3
3
  import { type ServiceOptions } from './common.js';
4
4
  export interface TaskBrief {
5
5
  id: string;
@@ -36,7 +36,8 @@ export interface StatusResult {
36
36
  knowledgeWritten: boolean;
37
37
  ready: boolean;
38
38
  };
39
- integrations: IntegrationStatus[];
39
+ /** Shallow observation (files and PATH only; never VERIFIED). Run doctor or `integrations status` to verify. */
40
+ integrations: IntegrationReport[];
40
41
  activeTasks: TaskBrief[];
41
42
  verification: {
42
43
  id: string;
@@ -4,14 +4,14 @@ import { SCHEMA_VERSION, validateAnalysisState, validateCommands } from '../conf
4
4
  import { findWorkspaceRoot, loadProject, wsPaths } from '../core/workspace.js';
5
5
  import { readVersioned } from '../core/versioned.js';
6
6
  import { getGitState } from '../git/git.js';
7
- import { detectIntegrations } from '../integrations/index.js';
7
+ import { inspectAll } from '../integrations/index.js';
8
8
  import { loadFacts } from '../knowledge/facts.js';
9
9
  import { listSessions } from '../sessions/session.js';
10
10
  import { listTasks, nextRequiredStep } from '../tasks/task.js';
11
11
  import { pathExists } from '../utils/fs.js';
12
12
  import { SECTION_END } from '../agents/claude/content.js';
13
13
  import { SKILL_PATH } from '../agents/claude/content.js';
14
- import { integrationCtx } from './common.js';
14
+ import { managerEnv } from './common.js';
15
15
  const brief = (t) => ({ id: t.id, title: t.title, type: t.type, status: t.status, updatedAt: t.updatedAt, next: nextRequiredStep(t) });
16
16
  export async function instructionsInstalled(ws) {
17
17
  for (const rel of ['CLAUDE.md', '.claude/CLAUDE.md']) {
@@ -102,7 +102,7 @@ export async function getStatus(o) {
102
102
  const sessions = await listSessions(ws, 1);
103
103
  if (sessions[0])
104
104
  base.lastSession = { id: sessions[0].id, goal: sessions[0].goal, next: sessions[0].next };
105
- base.integrations = await detectIntegrations(integrationCtx(o, root));
105
+ base.integrations = await inspectAll(ws, managerEnv(o, root), { deep: false });
106
106
  base.onboarding.instructionsInstalled = await instructionsInstalled(ws);
107
107
  base.onboarding.knowledgeWritten = await knowledgeWritten(ws);
108
108
  base.onboarding.ready = base.onboarding.instructionsInstalled && base.onboarding.knowledgeWritten && base.knowledge.state !== 'Empty';
@@ -1 +1 @@
1
- {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/services/status.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, type CommandEntry } from '../config/schemas.js';\nimport { findWorkspaceRoot, loadProject, wsPaths, type Ws } from '../core/workspace.js';\nimport { readVersioned } from '../core/versioned.js';\nimport { getGitState } from '../git/git.js';\nimport { detectIntegrations, type IntegrationStatus } from '../integrations/index.js';\nimport { loadFacts } from '../knowledge/facts.js';\nimport { listSessions } from '../sessions/session.js';\nimport { listTasks, nextRequiredStep, type Task } from '../tasks/task.js';\nimport { pathExists } from '../utils/fs.js';\nimport { SECTION_END } from '../agents/claude/content.js';\nimport { SKILL_PATH } from '../agents/claude/content.js';\nimport { integrationCtx, type ServiceOptions } from './common.js';\n\nexport interface TaskBrief {\n id: string;\n title: string;\n type: string;\n status: string;\n updatedAt: string;\n next: string;\n}\n\nexport interface StatusResult {\n initialized: boolean;\n root: string | null;\n project: { name: string; id: string } | null;\n workspace: 'Healthy' | 'Needs attention' | 'Not initialized' | 'Unreadable';\n workspaceProblem?: string;\n git: { available: boolean; isRepo: boolean; branch: string | null; workingTree: 'Clean' | 'Modified' | 'Unknown'; changes: number };\n knowledge: { state: 'Current' | 'Stale' | 'Empty' | 'Unknown'; facts: number; stale: number; analyzedAt: string | null };\n onboarding: { instructionsInstalled: boolean; knowledgeWritten: boolean; ready: boolean };\n integrations: IntegrationStatus[];\n activeTasks: TaskBrief[];\n verification: { id: string; available: boolean; command: string | null; required: string }[];\n lastSession: { id: string; goal: string; next: string } | null;\n corruptTasks: number;\n}\n\nconst brief = (t: Task): TaskBrief => ({ id: t.id, title: t.title, type: t.type, status: t.status, updatedAt: t.updatedAt, next: nextRequiredStep(t) });\n\nexport async function instructionsInstalled(ws: Ws): Promise<boolean> {\n for (const rel of ['CLAUDE.md', '.claude/CLAUDE.md']) {\n try {\n const text = await fs.readFile(path.join(ws.root, ...rel.split('/')), 'utf8');\n if (text.includes('<!-- devworkspace:start') && text.includes(SECTION_END) && (await pathExists(path.join(ws.root, ...SKILL_PATH.split('/'))))) return true;\n } catch {\n /* try next */\n }\n }\n return false;\n}\n\nexport async function knowledgeWritten(ws: Ws): Promise<boolean> {\n try {\n return (await fs.stat(path.join(ws.contextDir, 'architecture.md'))).size >= 200;\n } catch {\n return false;\n }\n}\n\n/** Cheap overview: reads small workspace files and Git status only. Never hashes or scans source files. */\nexport async function getStatus(o: ServiceOptions): Promise<StatusResult> {\n const root = await findWorkspaceRoot(o.cwd);\n const git = await getGitState(root ?? o.cwd);\n const gitBrief: StatusResult['git'] = {\n available: git.available,\n isRepo: git.isRepo,\n branch: git.branch,\n workingTree: git.isRepo ? (git.dirty ? 'Modified' : 'Clean') : 'Unknown',\n changes: git.entries.length,\n };\n const base: StatusResult = {\n initialized: false,\n root,\n project: null,\n workspace: 'Not initialized',\n git: gitBrief,\n knowledge: { state: 'Unknown', facts: 0, stale: 0, analyzedAt: null },\n onboarding: { instructionsInstalled: false, knowledgeWritten: false, ready: false },\n integrations: [],\n activeTasks: [],\n verification: [],\n lastSession: null,\n corruptTasks: 0,\n };\n if (!root) return base;\n\n const ws = wsPaths(root);\n base.initialized = true;\n try {\n const project = await loadProject(ws);\n base.project = { name: project.name, id: project.projectId };\n base.workspace = 'Healthy';\n } catch (e) {\n return { ...base, workspace: 'Unreadable', workspaceProblem: (e as Error).message };\n }\n\n try {\n const facts = await loadFacts(ws);\n const stale = facts.facts.filter((f) => f.status === 'STALE').length;\n let analyzedAt: string | null = null;\n if (await pathExists(ws.analysisState)) {\n analyzedAt = (await readVersioned(ws.analysisState, 'analysis', SCHEMA_VERSION, validateAnalysisState)).data.analyzedAt;\n }\n base.knowledge = { facts: facts.facts.length, stale, analyzedAt, state: facts.facts.length === 0 ? 'Empty' : stale > 0 ? 'Stale' : 'Current' };\n } catch (e) {\n base.workspace = 'Needs attention';\n base.workspaceProblem = (e as Error).message;\n }\n\n if (await pathExists(ws.commands)) {\n try {\n const commands = (await readVersioned(ws.commands, 'commands', SCHEMA_VERSION, validateCommands)).data;\n base.verification = commands.checks.map((c: CommandEntry) => ({ id: c.id, available: c.available, command: c.command, required: c.required }));\n } catch (e) {\n base.workspace = 'Needs attention';\n base.workspaceProblem ??= (e as Error).message;\n }\n }\n\n const { tasks, corrupt } = await listTasks(ws);\n base.activeTasks = tasks.map(brief);\n base.corruptTasks = corrupt.length;\n if (corrupt.length > 0) base.workspace = 'Needs attention';\n const sessions = await listSessions(ws, 1);\n if (sessions[0]) base.lastSession = { id: sessions[0].id, goal: sessions[0].goal, next: sessions[0].next };\n\n base.integrations = await detectIntegrations(integrationCtx(o, root));\n base.onboarding.instructionsInstalled = await instructionsInstalled(ws);\n base.onboarding.knowledgeWritten = await knowledgeWritten(ws);\n base.onboarding.ready = base.onboarding.instructionsInstalled && base.onboarding.knowledgeWritten && base.knowledge.state !== 'Empty';\n return base;\n}\n"]}
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/services/status.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, type CommandEntry } from '../config/schemas.js';\nimport { findWorkspaceRoot, loadProject, wsPaths, type Ws } from '../core/workspace.js';\nimport { readVersioned } from '../core/versioned.js';\nimport { getGitState } from '../git/git.js';\nimport { inspectAll, type IntegrationReport } from '../integrations/index.js';\nimport { loadFacts } from '../knowledge/facts.js';\nimport { listSessions } from '../sessions/session.js';\nimport { listTasks, nextRequiredStep, type Task } from '../tasks/task.js';\nimport { pathExists } from '../utils/fs.js';\nimport { SECTION_END } from '../agents/claude/content.js';\nimport { SKILL_PATH } from '../agents/claude/content.js';\nimport { managerEnv, type ServiceOptions } from './common.js';\n\nexport interface TaskBrief {\n id: string;\n title: string;\n type: string;\n status: string;\n updatedAt: string;\n next: string;\n}\n\nexport interface StatusResult {\n initialized: boolean;\n root: string | null;\n project: { name: string; id: string } | null;\n workspace: 'Healthy' | 'Needs attention' | 'Not initialized' | 'Unreadable';\n workspaceProblem?: string;\n git: { available: boolean; isRepo: boolean; branch: string | null; workingTree: 'Clean' | 'Modified' | 'Unknown'; changes: number };\n knowledge: { state: 'Current' | 'Stale' | 'Empty' | 'Unknown'; facts: number; stale: number; analyzedAt: string | null };\n onboarding: { instructionsInstalled: boolean; knowledgeWritten: boolean; ready: boolean };\n /** Shallow observation (files and PATH only; never VERIFIED). Run doctor or `integrations status` to verify. */\n integrations: IntegrationReport[];\n activeTasks: TaskBrief[];\n verification: { id: string; available: boolean; command: string | null; required: string }[];\n lastSession: { id: string; goal: string; next: string } | null;\n corruptTasks: number;\n}\n\nconst brief = (t: Task): TaskBrief => ({ id: t.id, title: t.title, type: t.type, status: t.status, updatedAt: t.updatedAt, next: nextRequiredStep(t) });\n\nexport async function instructionsInstalled(ws: Ws): Promise<boolean> {\n for (const rel of ['CLAUDE.md', '.claude/CLAUDE.md']) {\n try {\n const text = await fs.readFile(path.join(ws.root, ...rel.split('/')), 'utf8');\n if (text.includes('<!-- devworkspace:start') && text.includes(SECTION_END) && (await pathExists(path.join(ws.root, ...SKILL_PATH.split('/'))))) return true;\n } catch {\n /* try next */\n }\n }\n return false;\n}\n\nexport async function knowledgeWritten(ws: Ws): Promise<boolean> {\n try {\n return (await fs.stat(path.join(ws.contextDir, 'architecture.md'))).size >= 200;\n } catch {\n return false;\n }\n}\n\n/** Cheap overview: reads small workspace files and Git status only. Never hashes or scans source files. */\nexport async function getStatus(o: ServiceOptions): Promise<StatusResult> {\n const root = await findWorkspaceRoot(o.cwd);\n const git = await getGitState(root ?? o.cwd);\n const gitBrief: StatusResult['git'] = {\n available: git.available,\n isRepo: git.isRepo,\n branch: git.branch,\n workingTree: git.isRepo ? (git.dirty ? 'Modified' : 'Clean') : 'Unknown',\n changes: git.entries.length,\n };\n const base: StatusResult = {\n initialized: false,\n root,\n project: null,\n workspace: 'Not initialized',\n git: gitBrief,\n knowledge: { state: 'Unknown', facts: 0, stale: 0, analyzedAt: null },\n onboarding: { instructionsInstalled: false, knowledgeWritten: false, ready: false },\n integrations: [],\n activeTasks: [],\n verification: [],\n lastSession: null,\n corruptTasks: 0,\n };\n if (!root) return base;\n\n const ws = wsPaths(root);\n base.initialized = true;\n try {\n const project = await loadProject(ws);\n base.project = { name: project.name, id: project.projectId };\n base.workspace = 'Healthy';\n } catch (e) {\n return { ...base, workspace: 'Unreadable', workspaceProblem: (e as Error).message };\n }\n\n try {\n const facts = await loadFacts(ws);\n const stale = facts.facts.filter((f) => f.status === 'STALE').length;\n let analyzedAt: string | null = null;\n if (await pathExists(ws.analysisState)) {\n analyzedAt = (await readVersioned(ws.analysisState, 'analysis', SCHEMA_VERSION, validateAnalysisState)).data.analyzedAt;\n }\n base.knowledge = { facts: facts.facts.length, stale, analyzedAt, state: facts.facts.length === 0 ? 'Empty' : stale > 0 ? 'Stale' : 'Current' };\n } catch (e) {\n base.workspace = 'Needs attention';\n base.workspaceProblem = (e as Error).message;\n }\n\n if (await pathExists(ws.commands)) {\n try {\n const commands = (await readVersioned(ws.commands, 'commands', SCHEMA_VERSION, validateCommands)).data;\n base.verification = commands.checks.map((c: CommandEntry) => ({ id: c.id, available: c.available, command: c.command, required: c.required }));\n } catch (e) {\n base.workspace = 'Needs attention';\n base.workspaceProblem ??= (e as Error).message;\n }\n }\n\n const { tasks, corrupt } = await listTasks(ws);\n base.activeTasks = tasks.map(brief);\n base.corruptTasks = corrupt.length;\n if (corrupt.length > 0) base.workspace = 'Needs attention';\n const sessions = await listSessions(ws, 1);\n if (sessions[0]) base.lastSession = { id: sessions[0].id, goal: sessions[0].goal, next: sessions[0].next };\n\n base.integrations = await inspectAll(ws, managerEnv(o, root), { deep: false });\n base.onboarding.instructionsInstalled = await instructionsInstalled(ws);\n base.onboarding.knowledgeWritten = await knowledgeWritten(ws);\n base.onboarding.ready = base.onboarding.instructionsInstalled && base.onboarding.knowledgeWritten && base.knowledge.state !== 'Empty';\n return base;\n}\n"]}
@@ -1,6 +1,8 @@
1
1
  export interface LockOptions {
2
2
  timeoutMs?: number;
3
3
  staleMs?: number;
4
+ /** Lock file name under .devworkspace/local/. Long-running operations use their own lock. */
5
+ name?: string;
4
6
  }
5
7
  /** Exclusive advisory lock stored under .devworkspace/local/. Stale locks (dead pid or too old) are broken. */
6
8
  export declare function withLock<T>(workspaceDir: string, fn: () => Promise<T>, opts?: LockOptions): Promise<T>;
@@ -17,7 +17,7 @@ export async function withLock(workspaceDir, fn, opts = {}) {
17
17
  const timeoutMs = opts.timeoutMs ?? 5000;
18
18
  const staleMs = opts.staleMs ?? 60_000;
19
19
  const dir = path.join(workspaceDir, 'local');
20
- const lockPath = path.join(dir, 'workspace.lock');
20
+ const lockPath = path.join(dir, opts.name ?? 'workspace.lock');
21
21
  await ensureDir(dir);
22
22
  const started = Date.now();
23
23
  for (;;) {
@@ -52,7 +52,7 @@ export async function withLock(workspaceDir, fn, opts = {}) {
52
52
  }
53
53
  if (Date.now() - started > timeoutMs) {
54
54
  throw new DevWorkspaceError('LOCKED', 'Another DevWorkspace process is modifying this workspace.', {
55
- hint: `Wait for it to finish, or delete ${path.join('.devworkspace', 'local', 'workspace.lock')} if no process is running.`,
55
+ hint: `Wait for it to finish, or delete ${path.join('.devworkspace', 'local', opts.name ?? 'workspace.lock')} if no process is running.`,
56
56
  });
57
57
  }
58
58
  await new Promise((r) => setTimeout(r, 50));
@@ -1 +1 @@
1
- {"version":3,"file":"lock.js","sourceRoot":"","sources":["../../src/utils/lock.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs, unlinkSync } from 'node:fs';\nimport path from 'node:path';\nimport { DevWorkspaceError } from '../errors/index.js';\nimport { ensureDir } from './fs.js';\nimport { onCleanup } from './cleanup.js';\n\nexport interface LockOptions {\n timeoutMs?: number;\n staleMs?: number;\n}\n\nfunction pidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (e) {\n return (e as NodeJS.ErrnoException).code === 'EPERM';\n }\n}\n\n/** Exclusive advisory lock stored under .devworkspace/local/. Stale locks (dead pid or too old) are broken. */\nexport async function withLock<T>(workspaceDir: string, fn: () => Promise<T>, opts: LockOptions = {}): Promise<T> {\n const timeoutMs = opts.timeoutMs ?? 5000;\n const staleMs = opts.staleMs ?? 60_000;\n const dir = path.join(workspaceDir, 'local');\n const lockPath = path.join(dir, 'workspace.lock');\n await ensureDir(dir);\n const started = Date.now();\n for (;;) {\n try {\n const fh = await fs.open(lockPath, 'wx');\n await fh.writeFile(JSON.stringify({ pid: process.pid, at: Date.now() }));\n await fh.close();\n break;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== 'EEXIST') {\n throw new DevWorkspaceError('IO', 'Could not acquire the workspace lock.', { cause: e });\n }\n let stale: boolean;\n try {\n const info = JSON.parse(await fs.readFile(lockPath, 'utf8')) as { pid?: number; at?: number };\n const age = Date.now() - (info.at ?? 0);\n stale = age > staleMs || (typeof info.pid === 'number' && !pidAlive(info.pid));\n } catch {\n // Unreadable lock: another process may be mid-write, so only break it once it is old.\n try {\n stale = Date.now() - (await fs.stat(lockPath)).mtimeMs > 2000;\n } catch {\n stale = true;\n }\n }\n if (stale) {\n await fs.rm(lockPath, { force: true });\n continue;\n }\n if (Date.now() - started > timeoutMs) {\n throw new DevWorkspaceError('LOCKED', 'Another DevWorkspace process is modifying this workspace.', {\n hint: `Wait for it to finish, or delete ${path.join('.devworkspace', 'local', 'workspace.lock')} if no process is running.`,\n });\n }\n await new Promise((r) => setTimeout(r, 50));\n }\n }\n const dispose = onCleanup(() => {\n try {\n unlinkSync(lockPath);\n } catch {\n /* already gone */\n }\n });\n try {\n return await fn();\n } finally {\n dispose();\n await fs.rm(lockPath, { force: true }).catch(() => undefined);\n }\n}\n"]}
1
+ {"version":3,"file":"lock.js","sourceRoot":"","sources":["../../src/utils/lock.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs, unlinkSync } from 'node:fs';\nimport path from 'node:path';\nimport { DevWorkspaceError } from '../errors/index.js';\nimport { ensureDir } from './fs.js';\nimport { onCleanup } from './cleanup.js';\n\nexport interface LockOptions {\n timeoutMs?: number;\n staleMs?: number;\n /** Lock file name under .devworkspace/local/. Long-running operations use their own lock. */\n name?: string;\n}\n\nfunction pidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (e) {\n return (e as NodeJS.ErrnoException).code === 'EPERM';\n }\n}\n\n/** Exclusive advisory lock stored under .devworkspace/local/. Stale locks (dead pid or too old) are broken. */\nexport async function withLock<T>(workspaceDir: string, fn: () => Promise<T>, opts: LockOptions = {}): Promise<T> {\n const timeoutMs = opts.timeoutMs ?? 5000;\n const staleMs = opts.staleMs ?? 60_000;\n const dir = path.join(workspaceDir, 'local');\n const lockPath = path.join(dir, opts.name ?? 'workspace.lock');\n await ensureDir(dir);\n const started = Date.now();\n for (;;) {\n try {\n const fh = await fs.open(lockPath, 'wx');\n await fh.writeFile(JSON.stringify({ pid: process.pid, at: Date.now() }));\n await fh.close();\n break;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== 'EEXIST') {\n throw new DevWorkspaceError('IO', 'Could not acquire the workspace lock.', { cause: e });\n }\n let stale: boolean;\n try {\n const info = JSON.parse(await fs.readFile(lockPath, 'utf8')) as { pid?: number; at?: number };\n const age = Date.now() - (info.at ?? 0);\n stale = age > staleMs || (typeof info.pid === 'number' && !pidAlive(info.pid));\n } catch {\n // Unreadable lock: another process may be mid-write, so only break it once it is old.\n try {\n stale = Date.now() - (await fs.stat(lockPath)).mtimeMs > 2000;\n } catch {\n stale = true;\n }\n }\n if (stale) {\n await fs.rm(lockPath, { force: true });\n continue;\n }\n if (Date.now() - started > timeoutMs) {\n throw new DevWorkspaceError('LOCKED', 'Another DevWorkspace process is modifying this workspace.', {\n hint: `Wait for it to finish, or delete ${path.join('.devworkspace', 'local', opts.name ?? 'workspace.lock')} if no process is running.`,\n });\n }\n await new Promise((r) => setTimeout(r, 50));\n }\n }\n const dispose = onCleanup(() => {\n try {\n unlinkSync(lockPath);\n } catch {\n /* already gone */\n }\n });\n try {\n return await fn();\n } finally {\n dispose();\n await fs.rm(lockPath, { force: true }).catch(() => undefined);\n }\n}\n"]}
@@ -4,12 +4,19 @@ export interface RunResult {
4
4
  stdout: string;
5
5
  stderr: string;
6
6
  timedOut: boolean;
7
+ aborted?: boolean;
7
8
  error?: string;
8
9
  }
9
10
  export interface RunOptions {
10
11
  cwd?: string;
11
12
  timeoutMs?: number;
12
13
  maxBytes?: number;
14
+ /** Extra environment variables merged over process.env (undefined values unset the variable). */
15
+ env?: Record<string, string | undefined>;
16
+ /** Abort (kill) the process when the signal fires. */
17
+ signal?: AbortSignal;
18
+ /** Windows only: pass the argument string through unmodified (used for cmd.exe /c with pre-validated arguments). */
19
+ windowsVerbatimArguments?: boolean;
13
20
  }
14
21
  /**
15
22
  * Run an executable with an argument array. Never uses a shell, so project- or
@@ -18,5 +25,5 @@ export interface RunOptions {
18
25
  export declare function run(command: string, args: string[], opts?: RunOptions): Promise<RunResult>;
19
26
  /** Locate an executable on PATH (honouring PATHEXT on Windows) without executing anything. */
20
27
  export declare function which(name: string, env?: NodeJS.ProcessEnv): Promise<string | null>;
21
- /** .cmd/.bat shims need a shell on Windows; we refuse to execute them. */
28
+ /** .cmd/.bat shims need a shell on Windows. They are only run through the validated path in integrations/exec.ts. */
22
29
  export declare function isShellShim(file: string): boolean;
@@ -12,39 +12,62 @@ export function run(command, args, opts = {}) {
12
12
  let stdout = '';
13
13
  let stderr = '';
14
14
  let timedOut = false;
15
+ let aborted = false;
15
16
  let settled = false;
16
17
  // eslint-disable-next-line prefer-const -- assigned after spawn; finish() may run first if spawn throws
17
18
  let timer;
19
+ const onAbort = () => {
20
+ aborted = true;
21
+ child?.kill();
22
+ };
18
23
  const finish = (r) => {
19
24
  if (settled)
20
25
  return;
21
26
  settled = true;
22
27
  if (timer)
23
28
  clearTimeout(timer);
24
- resolve(r);
29
+ opts.signal?.removeEventListener('abort', onAbort);
30
+ resolve(aborted ? { ...r, ok: false, aborted: true } : r);
25
31
  };
26
32
  let child;
33
+ if (opts.signal?.aborted) {
34
+ finish({ ok: false, code: null, stdout, stderr, timedOut: false, aborted: true, error: 'aborted' });
35
+ return;
36
+ }
27
37
  try {
28
- child = spawn(command, args, { cwd: opts.cwd, shell: false, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] });
38
+ const env = opts.env ? { ...process.env, ...opts.env } : process.env;
39
+ for (const [k, v] of Object.entries(env))
40
+ if (v === undefined)
41
+ delete env[k];
42
+ child = spawn(command, args, {
43
+ cwd: opts.cwd,
44
+ env,
45
+ shell: false,
46
+ windowsHide: true,
47
+ windowsVerbatimArguments: opts.windowsVerbatimArguments,
48
+ stdio: ['ignore', 'pipe', 'pipe'],
49
+ });
29
50
  }
30
51
  catch (e) {
31
52
  finish({ ok: false, code: null, stdout, stderr, timedOut: false, error: e.message });
32
53
  return;
33
54
  }
55
+ const c = child;
34
56
  timer = setTimeout(() => {
35
57
  timedOut = true;
36
- child.kill();
58
+ c.kill();
37
59
  }, timeoutMs);
38
- child.stdout.on('data', (d) => {
60
+ opts.signal?.addEventListener('abort', onAbort, { once: true });
61
+ c.stdout?.on('data', (d) => {
39
62
  if (stdout.length < maxBytes)
40
63
  stdout += d.toString('utf8');
41
64
  });
42
- child.stderr.on('data', (d) => {
65
+ c.stderr?.on('data', (d) => {
43
66
  if (stderr.length < maxBytes)
44
67
  stderr += d.toString('utf8');
45
68
  });
46
- child.on('error', (e) => finish({ ok: false, code: null, stdout, stderr, timedOut, error: e.message }));
47
- child.on('close', (code) => finish({ ok: code === 0 && !timedOut, code, stdout, stderr, timedOut }));
69
+ c.on('error', (e) => finish({ ok: false, code: null, stdout, stderr, timedOut, error: e.message }));
70
+ c.on('close', (code) => finish({ ok: code === 0 && !timedOut && !aborted, code, stdout, stderr, timedOut }));
48
71
  });
49
72
  }
50
73
  /** Locate an executable on PATH (honouring PATHEXT on Windows) without executing anything. */
@@ -67,7 +90,7 @@ export async function which(name, env = process.env) {
67
90
  }
68
91
  return null;
69
92
  }
70
- /** .cmd/.bat shims need a shell on Windows; we refuse to execute them. */
93
+ /** .cmd/.bat shims need a shell on Windows. They are only run through the validated path in integrations/exec.ts. */
71
94
  export function isShellShim(file) {
72
95
  return /\.(cmd|bat)$/i.test(file);
73
96
  }
@@ -1 +1 @@
1
- {"version":3,"file":"proc.js","sourceRoot":"","sources":["../../src/utils/proc.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from 'node:child_process';\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\n\nexport interface RunResult {\n ok: boolean;\n code: number | null;\n stdout: string;\n stderr: string;\n timedOut: boolean;\n error?: string;\n}\n\nexport interface RunOptions {\n cwd?: string;\n timeoutMs?: number;\n maxBytes?: number;\n}\n\n/**\n * Run an executable with an argument array. Never uses a shell, so project- or\n * user-supplied values cannot be interpreted as shell syntax.\n */\nexport function run(command: string, args: string[], opts: RunOptions = {}): Promise<RunResult> {\n const timeoutMs = opts.timeoutMs ?? 15_000;\n const maxBytes = opts.maxBytes ?? 4 * 1024 * 1024;\n return new Promise((resolve) => {\n let stdout = '';\n let stderr = '';\n let timedOut = false;\n let settled = false;\n // eslint-disable-next-line prefer-const -- assigned after spawn; finish() may run first if spawn throws\n let timer: NodeJS.Timeout | undefined;\n const finish = (r: RunResult): void => {\n if (settled) return;\n settled = true;\n if (timer) clearTimeout(timer);\n resolve(r);\n };\n let child;\n try {\n child = spawn(command, args, { cwd: opts.cwd, shell: false, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] });\n } catch (e) {\n finish({ ok: false, code: null, stdout, stderr, timedOut: false, error: (e as Error).message });\n return;\n }\n timer = setTimeout(() => {\n timedOut = true;\n child.kill();\n }, timeoutMs);\n child.stdout.on('data', (d: Buffer) => {\n if (stdout.length < maxBytes) stdout += d.toString('utf8');\n });\n child.stderr.on('data', (d: Buffer) => {\n if (stderr.length < maxBytes) stderr += d.toString('utf8');\n });\n child.on('error', (e) => finish({ ok: false, code: null, stdout, stderr, timedOut, error: e.message }));\n child.on('close', (code) => finish({ ok: code === 0 && !timedOut, code, stdout, stderr, timedOut }));\n });\n}\n\n/** Locate an executable on PATH (honouring PATHEXT on Windows) without executing anything. */\nexport async function which(name: string, env: NodeJS.ProcessEnv = process.env): Promise<string | null> {\n const isWin = process.platform === 'win32';\n const pathVar = env.PATH ?? env.Path ?? '';\n const exts = isWin ? (env.PATHEXT ?? '.EXE;.CMD;.BAT;.COM').split(';').filter(Boolean) : [''];\n for (const dir of pathVar.split(path.delimiter).filter(Boolean)) {\n for (const ext of isWin ? ['', ...exts] : exts) {\n const candidate = path.join(dir, name + ext);\n try {\n const st = await fs.stat(candidate);\n if (st.isFile()) return candidate;\n } catch {\n /* keep looking */\n }\n }\n }\n return null;\n}\n\n/** .cmd/.bat shims need a shell on Windows; we refuse to execute them. */\nexport function isShellShim(file: string): boolean {\n return /\\.(cmd|bat)$/i.test(file);\n}\n"]}
1
+ {"version":3,"file":"proc.js","sourceRoot":"","sources":["../../src/utils/proc.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from 'node:child_process';\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\n\nexport interface RunResult {\n ok: boolean;\n code: number | null;\n stdout: string;\n stderr: string;\n timedOut: boolean;\n aborted?: boolean;\n error?: string;\n}\n\nexport interface RunOptions {\n cwd?: string;\n timeoutMs?: number;\n maxBytes?: number;\n /** Extra environment variables merged over process.env (undefined values unset the variable). */\n env?: Record<string, string | undefined>;\n /** Abort (kill) the process when the signal fires. */\n signal?: AbortSignal;\n /** Windows only: pass the argument string through unmodified (used for cmd.exe /c with pre-validated arguments). */\n windowsVerbatimArguments?: boolean;\n}\n\n/**\n * Run an executable with an argument array. Never uses a shell, so project- or\n * user-supplied values cannot be interpreted as shell syntax.\n */\nexport function run(command: string, args: string[], opts: RunOptions = {}): Promise<RunResult> {\n const timeoutMs = opts.timeoutMs ?? 15_000;\n const maxBytes = opts.maxBytes ?? 4 * 1024 * 1024;\n return new Promise((resolve) => {\n let stdout = '';\n let stderr = '';\n let timedOut = false;\n let aborted = false;\n let settled = false;\n // eslint-disable-next-line prefer-const -- assigned after spawn; finish() may run first if spawn throws\n let timer: NodeJS.Timeout | undefined;\n const onAbort = (): void => {\n aborted = true;\n child?.kill();\n };\n const finish = (r: RunResult): void => {\n if (settled) return;\n settled = true;\n if (timer) clearTimeout(timer);\n opts.signal?.removeEventListener('abort', onAbort);\n resolve(aborted ? { ...r, ok: false, aborted: true } : r);\n };\n let child: ReturnType<typeof spawn> | undefined;\n if (opts.signal?.aborted) {\n finish({ ok: false, code: null, stdout, stderr, timedOut: false, aborted: true, error: 'aborted' });\n return;\n }\n try {\n const env = opts.env ? { ...process.env, ...opts.env } : process.env;\n for (const [k, v] of Object.entries(env)) if (v === undefined) delete env[k];\n child = spawn(command, args, {\n cwd: opts.cwd,\n env,\n shell: false,\n windowsHide: true,\n windowsVerbatimArguments: opts.windowsVerbatimArguments,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n } catch (e) {\n finish({ ok: false, code: null, stdout, stderr, timedOut: false, error: (e as Error).message });\n return;\n }\n const c = child;\n timer = setTimeout(() => {\n timedOut = true;\n c.kill();\n }, timeoutMs);\n opts.signal?.addEventListener('abort', onAbort, { once: true });\n c.stdout?.on('data', (d: Buffer) => {\n if (stdout.length < maxBytes) stdout += d.toString('utf8');\n });\n c.stderr?.on('data', (d: Buffer) => {\n if (stderr.length < maxBytes) stderr += d.toString('utf8');\n });\n c.on('error', (e) => finish({ ok: false, code: null, stdout, stderr, timedOut, error: e.message }));\n c.on('close', (code) => finish({ ok: code === 0 && !timedOut && !aborted, code, stdout, stderr, timedOut }));\n });\n}\n\n/** Locate an executable on PATH (honouring PATHEXT on Windows) without executing anything. */\nexport async function which(name: string, env: NodeJS.ProcessEnv = process.env): Promise<string | null> {\n const isWin = process.platform === 'win32';\n const pathVar = env.PATH ?? env.Path ?? '';\n const exts = isWin ? (env.PATHEXT ?? '.EXE;.CMD;.BAT;.COM').split(';').filter(Boolean) : [''];\n for (const dir of pathVar.split(path.delimiter).filter(Boolean)) {\n for (const ext of isWin ? ['', ...exts] : exts) {\n const candidate = path.join(dir, name + ext);\n try {\n const st = await fs.stat(candidate);\n if (st.isFile()) return candidate;\n } catch {\n /* keep looking */\n }\n }\n }\n return null;\n}\n\n/** .cmd/.bat shims need a shell on Windows. They are only run through the validated path in integrations/exec.ts. */\nexport function isShellShim(file: string): boolean {\n return /\\.(cmd|bat)$/i.test(file);\n}\n"]}
@@ -26,7 +26,7 @@ The CLI contains no business logic. `src/index.ts` exports the services so a fut
26
26
  | `decisions/`, `sessions/` | Append-only decision records; short session summaries |
27
27
  | `security/` | Sensitive-file rules, secret detection/redaction, safe reading |
28
28
  | `git/` | Porcelain parsing and state (no mutating Git commands anywhere) |
29
- | `integrations/` | `Integration` interface + Graphify, Agent Skills, Ponytail, OmniRoute detectors |
29
+ | `integrations/` | Integration manager: one `IntegrationAdapter` per tool (observe, plan, verify), consent levels, file protection, state, runner abstraction (Windows shims, npx) |
30
30
  | `agents/` | `AgentAdapter` interface + the Claude adapter (managed `CLAUDE.md` block, skill) |
31
31
  | `prompts/` | Onboarding/refresh prompt builders and the behavioural requirement checker |
32
32
  | `portability/` | Export bundle format and hostile-input validation |
package/docs/claude.md CHANGED
@@ -41,6 +41,10 @@ DevWorkspace records what it manages in `project.json` → `managedFiles` (`owne
41
41
  - Never claim completion while required checks fail; report `STATUS: BLOCKED` plainly.
42
42
  - After meaningful work, record only durable knowledge.
43
43
 
44
+ ## Integrations
45
+
46
+ Onboarding tells Claude to run `devworkspace setup --integrations` (after asking you once) rather than installing tools by hand, so installs are previewed, consent-gated, protected and verified the same way whether you or Claude run them. See [integrations.md](integrations.md).
47
+
44
48
  ## Keeping Claude-specific code isolated
45
49
 
46
50
  Everything Claude-specific lives in `src/agents/claude/`. Core code talks to the `AgentAdapter` interface (`detectExisting`, `plan`), so a Cursor/Codex/Gemini adapter is a new directory implementing the same interface, not a change to core.
@@ -41,7 +41,7 @@ Open `.devworkspace/analysis/onboarding-prompt.md` and paste it into Claude Code
41
41
  3. run `devworkspace agent install` (adds a small managed block to `CLAUDE.md` and installs the `devworkspace` skill, preserving anything you wrote),
42
42
  4. write concise knowledge into `.devworkspace/context/`,
43
43
  5. check the testing contract and record a baseline,
44
- 6. offer to install and verify Graphify, Agent Skills and Ponytail (it asks once; nothing is installed without your yes),
44
+ 6. run the integration manager (`devworkspace setup --integrations`) after asking you once; it installs and verifies Graphify, Agent Skills and Ponytail and reports precise statuses,
45
45
  7. run `devworkspace doctor` and report unknowns and warnings.
46
46
 
47
47
  It is told not to modify application source during onboarding.
@@ -52,7 +52,7 @@ It is told not to modify application source during onboarding.
52
52
  npx devworkspace doctor
53
53
  ```
54
54
 
55
- `READY` means the workspace is healthy, instructions are installed and architecture knowledge exists. Warnings (for example *Graphify: not detected*) tell you exactly what to do next and do not fail the command.
55
+ `Core workspace: READY` means the workspace is healthy, instructions are installed and architecture knowledge exists. Integrations are listed separately with their observed status; anything not `VERIFIED` comes with the exact next step and does not fail the command. Run `npx devworkspace setup --integrations --dry-run` to see what setup would do, and `--yes` to do it.
56
56
 
57
57
  ## 5. Work normally
58
58