@ericsanchezok/synergy-workbench 0.0.0-stage → 3.1.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 (153) hide show
  1. package/AGENTS.md +30 -0
  2. package/README.md +16 -2
  3. package/dist/agents.js +11 -0
  4. package/dist/cli-adapter.js +8 -0
  5. package/dist/component.js +65 -0
  6. package/dist/config-schema.js +265 -0
  7. package/dist/execution/activity.js +78 -0
  8. package/dist/execution/content.js +184 -0
  9. package/dist/execution/json-sections.js +231 -0
  10. package/dist/execution/presentation.js +79 -0
  11. package/dist/execution/preview.js +13 -0
  12. package/dist/execution/process.js +125 -0
  13. package/dist/execution/projection.js +327 -0
  14. package/dist/execution/query.js +18 -0
  15. package/dist/execution/routes.js +189 -0
  16. package/dist/execution/schema.js +322 -0
  17. package/dist/execution/service.js +1993 -0
  18. package/dist/features.js +22 -0
  19. package/dist/http.js +65 -0
  20. package/dist/performance/analysis.js +330 -0
  21. package/dist/performance/catalog.js +391 -0
  22. package/dist/performance/dashboard.js +483 -0
  23. package/dist/performance/error.js +15 -0
  24. package/dist/performance/index.js +6 -0
  25. package/dist/performance/inflight.js +43 -0
  26. package/dist/performance/projection.js +138 -0
  27. package/dist/performance/prompt/base.txt +38 -0
  28. package/dist/performance/prompt/builder.js +21 -0
  29. package/dist/performance/routes/performance-route.js +439 -0
  30. package/dist/performance/schema.js +628 -0
  31. package/dist/performance/timeline.js +98 -0
  32. package/dist/performance/trace-detail.js +91 -0
  33. package/dist/presentation/service.js +270 -0
  34. package/dist/presentation/tools/workbench-view.js +65 -0
  35. package/dist/presentation/tools.js +10 -0
  36. package/dist/project/directories.js +363 -0
  37. package/dist/project/git-health.js +498 -0
  38. package/dist/project/ide.js +66 -0
  39. package/dist/project/migration.js +22 -0
  40. package/dist/project/routes/directories.js +89 -0
  41. package/dist/project/routes/git.js +30 -0
  42. package/dist/project/routes/task-defaults.js +42 -0
  43. package/dist/project/session-health.js +9 -0
  44. package/dist/project/startup.js +28 -0
  45. package/dist/project/task-defaults.js +68 -0
  46. package/dist/project/tools/session-control.js +664 -0
  47. package/dist/project/tools/session-control.txt +60 -0
  48. package/dist/project/tools/worktree-archive.js +76 -0
  49. package/dist/project/tools/worktree-enter.js +175 -0
  50. package/dist/project/tools/worktree-leave.js +119 -0
  51. package/dist/project/tools/worktree-list.js +52 -0
  52. package/dist/project/tools.js +26 -0
  53. package/dist/project/vcs-branch-watcher.js +76 -0
  54. package/dist/project/vcs.js +114 -0
  55. package/dist/project/worktree-janitor.js +140 -0
  56. package/dist/project/worktrees.js +98 -0
  57. package/dist/push/bridge.js +171 -0
  58. package/dist/push/index.js +4 -0
  59. package/dist/push/routes/push.js +224 -0
  60. package/dist/push/service.js +93 -0
  61. package/dist/push/store.js +121 -0
  62. package/dist/push/types.js +58 -0
  63. package/dist/review/git.js +226 -0
  64. package/dist/review/routes.js +45 -0
  65. package/dist/review/schema.js +32 -0
  66. package/dist/review/state.js +79 -0
  67. package/dist/stats/aggregator.js +252 -0
  68. package/dist/stats/canonical.js +89 -0
  69. package/dist/stats/cli/stats.js +302 -0
  70. package/dist/stats/engine.js +176 -0
  71. package/dist/stats/index.js +5 -0
  72. package/dist/stats/rollup.js +460 -0
  73. package/dist/stats/routes/stats.js +94 -0
  74. package/dist/stats/routes/usage.js +83 -0
  75. package/dist/stats/storage.js +86 -0
  76. package/dist/stats/types.js +267 -0
  77. package/dist/worker.js +6 -0
  78. package/package.json +232 -4
  79. package/src/agents.ts +15 -0
  80. package/src/cli-adapter.ts +11 -0
  81. package/src/component.ts +73 -0
  82. package/src/config-schema.ts +289 -0
  83. package/src/execution/activity.ts +83 -0
  84. package/src/execution/content.ts +189 -0
  85. package/src/execution/json-sections.ts +230 -0
  86. package/src/execution/presentation.ts +91 -0
  87. package/src/execution/preview.ts +12 -0
  88. package/src/execution/process.ts +118 -0
  89. package/src/execution/projection.ts +330 -0
  90. package/src/execution/query.ts +19 -0
  91. package/src/execution/routes.ts +324 -0
  92. package/src/execution/schema.ts +336 -0
  93. package/src/execution/service.ts +2149 -0
  94. package/src/features.ts +22 -0
  95. package/src/http.ts +75 -0
  96. package/src/performance/analysis.ts +356 -0
  97. package/src/performance/catalog.ts +902 -0
  98. package/src/performance/dashboard.ts +521 -0
  99. package/src/performance/error.ts +32 -0
  100. package/src/performance/index.ts +6 -0
  101. package/src/performance/inflight.ts +46 -0
  102. package/src/performance/projection.ts +143 -0
  103. package/src/performance/prompt/base.txt +38 -0
  104. package/src/performance/prompt/builder.ts +24 -0
  105. package/src/performance/routes/performance-route.ts +541 -0
  106. package/src/performance/schema.ts +695 -0
  107. package/src/performance/timeline.ts +100 -0
  108. package/src/performance/trace-detail.ts +97 -0
  109. package/src/presentation/service.ts +303 -0
  110. package/src/presentation/tools/workbench-view.ts +75 -0
  111. package/src/presentation/tools.ts +11 -0
  112. package/src/project/directories.ts +391 -0
  113. package/src/project/git-health.ts +577 -0
  114. package/src/project/ide.ts +80 -0
  115. package/src/project/migration.ts +24 -0
  116. package/src/project/routes/directories.ts +124 -0
  117. package/src/project/routes/git.ts +42 -0
  118. package/src/project/routes/task-defaults.ts +50 -0
  119. package/src/project/session-health.ts +10 -0
  120. package/src/project/startup.ts +28 -0
  121. package/src/project/task-defaults.ts +70 -0
  122. package/src/project/tools/session-control.ts +725 -0
  123. package/src/project/tools/session-control.txt +60 -0
  124. package/src/project/tools/worktree-archive.ts +92 -0
  125. package/src/project/tools/worktree-enter.ts +227 -0
  126. package/src/project/tools/worktree-leave.ts +140 -0
  127. package/src/project/tools/worktree-list.ts +68 -0
  128. package/src/project/tools.ts +29 -0
  129. package/src/project/vcs-branch-watcher.ts +81 -0
  130. package/src/project/vcs.ts +124 -0
  131. package/src/project/worktree-janitor.ts +153 -0
  132. package/src/project/worktrees.ts +110 -0
  133. package/src/push/bridge.ts +182 -0
  134. package/src/push/index.ts +4 -0
  135. package/src/push/routes/push.ts +267 -0
  136. package/src/push/service.ts +108 -0
  137. package/src/push/store.ts +120 -0
  138. package/src/push/types.ts +67 -0
  139. package/src/review/git.ts +221 -0
  140. package/src/review/routes.ts +65 -0
  141. package/src/review/schema.ts +34 -0
  142. package/src/review/state.ts +77 -0
  143. package/src/stats/aggregator.ts +277 -0
  144. package/src/stats/canonical.ts +93 -0
  145. package/src/stats/cli/stats.ts +348 -0
  146. package/src/stats/engine.ts +197 -0
  147. package/src/stats/index.ts +5 -0
  148. package/src/stats/rollup.ts +522 -0
  149. package/src/stats/routes/stats.ts +107 -0
  150. package/src/stats/routes/usage.ts +103 -0
  151. package/src/stats/storage.ts +78 -0
  152. package/src/stats/types.ts +351 -0
  153. package/src/worker.ts +7 -0
@@ -0,0 +1,124 @@
1
+ import { Hono } from "hono"
2
+ import { describeRoute, resolver, validator } from "hono-openapi"
3
+ import { z } from "zod"
4
+ import { Scope } from "@ericsanchezok/synergy-harness/scope"
5
+ import { ScopeContext } from "@ericsanchezok/synergy-harness/scope/context"
6
+ import { ProjectWorktrees } from "../worktrees"
7
+ import { WorkspaceAccess } from "@ericsanchezok/synergy-harness/workspace/access"
8
+ import { Worktree } from "@ericsanchezok/synergy-local-runtime/workspace/worktree"
9
+ import { ProjectDirectories } from "../directories"
10
+
11
+ const params = z.object({ scopeID: z.string().min(1) })
12
+ const response = (schema: z.ZodType) => ({
13
+ description: "Project folders",
14
+ content: { "application/json": { schema: resolver(schema) } },
15
+ })
16
+ const failures = {
17
+ 400: response(ProjectDirectories.Invalid.Schema),
18
+ 409: response(ProjectDirectories.Conflict.Schema),
19
+ }
20
+ async function result(c: import("hono").Context, action: () => Promise<unknown>) {
21
+ try {
22
+ return c.json(await action())
23
+ } catch (error) {
24
+ if (error instanceof ProjectDirectories.Invalid) return c.json(error.toObject(), 400)
25
+ if (error instanceof ProjectDirectories.Conflict) return c.json(error.toObject(), 409)
26
+ if (error instanceof WorkspaceAccess.BusyError)
27
+ return c.json(
28
+ {
29
+ name: "ProjectDirectoriesConflict",
30
+ data: { message: "Project folders are in use. Keep your changes and retry when tasks are idle." },
31
+ },
32
+ 409,
33
+ )
34
+ throw error
35
+ }
36
+ }
37
+ export function ProjectDirectoriesRoute() {
38
+ return new Hono()
39
+ .post(
40
+ "/",
41
+ describeRoute({
42
+ summary: "Create a project from folders",
43
+ operationId: "project.create",
44
+ responses: { 200: response(ProjectDirectories.Created), ...failures },
45
+ }),
46
+ validator("json", ProjectDirectories.CreateInput),
47
+ (c) => result(c, () => ProjectDirectories.create(c.req.valid("json"))),
48
+ )
49
+ .get(
50
+ "/:scopeID/directories",
51
+ describeRoute({
52
+ summary: "Get project folders",
53
+ operationId: "project.directories",
54
+ responses: { 200: response(ProjectDirectories.Result) },
55
+ }),
56
+ validator("param", params),
57
+ (c) => result(c, () => ProjectDirectories.get(c.req.valid("param").scopeID)),
58
+ )
59
+ .patch(
60
+ "/:scopeID/directories",
61
+ describeRoute({
62
+ summary: "Save main and additional project folders",
63
+ operationId: "project.updateDirectories",
64
+ responses: { 200: response(ProjectDirectories.Result), ...failures },
65
+ }),
66
+ validator("param", params),
67
+ validator("json", ProjectDirectories.UpdateInput),
68
+ (c) => result(c, () => ProjectDirectories.update(c.req.valid("param").scopeID, c.req.valid("json"))),
69
+ )
70
+ .get(
71
+ "/:scopeID/worktree-inventory",
72
+ describeRoute({
73
+ summary: "List Worktree identities without computing status or disk usage",
74
+ operationId: "project.worktreeInventory",
75
+ responses: { 200: response(ProjectWorktrees.Inventory) },
76
+ }),
77
+ validator("param", params),
78
+ async (c) => {
79
+ const scope = await Scope.resolve(c.req.valid("param"))
80
+ const inventory = await ScopeContext.provide({
81
+ scope,
82
+ workspace: null,
83
+ fn: () => ProjectWorktrees.inventory(scope.id),
84
+ })
85
+ c.header("x-synergy-epoch", inventory.sync.epoch)
86
+ c.header("x-synergy-seq", String(inventory.sync.seq))
87
+ return c.json(inventory)
88
+ },
89
+ )
90
+ .get(
91
+ "/:scopeID/worktree-details",
92
+ describeRoute({
93
+ summary: "Compute status, disk usage and cleanup protection for one Worktree",
94
+ operationId: "project.worktreeDetails",
95
+ responses: { 200: response(Worktree.Details), ...failures },
96
+ }),
97
+ validator("param", params),
98
+ validator("query", z.object({ target: z.string().min(1), sourceWorkspaceID: z.string().optional() })),
99
+ async (c) => {
100
+ const scope = await Scope.resolve(c.req.valid("param"))
101
+ const input = c.req.valid("query")
102
+ return c.json(
103
+ await ScopeContext.provide({
104
+ scope,
105
+ workspace: null,
106
+ fn: () => Worktree.withSource(input.sourceWorkspaceID, () => Worktree.details(input)),
107
+ }),
108
+ )
109
+ },
110
+ )
111
+ .get(
112
+ "/:scopeID/worktrees",
113
+ describeRoute({
114
+ summary: "List project Worktrees with their original repositories",
115
+ operationId: "project.worktrees",
116
+ responses: { 200: response(Worktree.Info.array()) },
117
+ }),
118
+ validator("param", params),
119
+ async (c) => {
120
+ const scope = await Scope.resolve(c.req.valid("param"))
121
+ return c.json(await ScopeContext.provide({ scope, workspace: null, fn: () => ProjectWorktrees.list(scope.id) }))
122
+ },
123
+ )
124
+ }
@@ -0,0 +1,42 @@
1
+ import { Hono } from "hono"
2
+ import { describeRoute, validator } from "hono-openapi"
3
+ import { resolver } from "hono-openapi"
4
+ import z from "zod"
5
+ import { Vcs } from "@ericsanchezok/synergy-workbench/project/vcs"
6
+ import { errors } from "@ericsanchezok/synergy-server/server/error"
7
+
8
+ export const GitRoute = () =>
9
+ new Hono().post(
10
+ "/init",
11
+ describeRoute({
12
+ summary: "Initialize git repository",
13
+ description: "Initialize a git repository in the specified directory if one does not already exist.",
14
+ operationId: "global.git.init",
15
+ responses: {
16
+ 200: {
17
+ description: "Git initialization result",
18
+ content: {
19
+ "application/json": {
20
+ schema: resolver(
21
+ z.object({
22
+ initialized: z.boolean().meta({ description: "Whether a new git repository was initialized" }),
23
+ }),
24
+ ),
25
+ },
26
+ },
27
+ },
28
+ ...errors(400),
29
+ },
30
+ }),
31
+ validator(
32
+ "json",
33
+ z.object({
34
+ directory: z.string().meta({ description: "Directory path to initialize git in" }),
35
+ }),
36
+ ),
37
+ async (c) => {
38
+ const { directory } = c.req.valid("json")
39
+ const initialized = await Vcs.initIfNeeded(directory)
40
+ return c.json({ initialized })
41
+ },
42
+ )
@@ -0,0 +1,50 @@
1
+ import { Hono } from "hono"
2
+ import { describeRoute, resolver, validator } from "hono-openapi"
3
+ import { ProjectTaskDefaults } from "../task-defaults"
4
+
5
+ export function ProjectTaskDefaultsRoute() {
6
+ const success = {
7
+ description: "Project task defaults",
8
+ content: { "application/json": { schema: resolver(ProjectTaskDefaults.Result) } },
9
+ }
10
+ return new Hono()
11
+ .get(
12
+ "/",
13
+ describeRoute({
14
+ summary: "Get project new-task defaults",
15
+ operationId: "project.taskDefaults.get",
16
+ responses: { 200: success },
17
+ }),
18
+ async (c) => c.json(await ProjectTaskDefaults.get()),
19
+ )
20
+ .patch(
21
+ "/",
22
+ describeRoute({
23
+ summary: "Save project new-task defaults",
24
+ description:
25
+ "Updates only Web and Desktop new-task defaults. Existing sessions and runtime creation rules are unchanged.",
26
+ operationId: "project.taskDefaults.update",
27
+ responses: {
28
+ 200: success,
29
+ 400: {
30
+ description: "Invalid defaults",
31
+ content: { "application/json": { schema: resolver(ProjectTaskDefaults.Invalid.Schema) } },
32
+ },
33
+ 409: {
34
+ description: "Concurrent project edit",
35
+ content: { "application/json": { schema: resolver(ProjectTaskDefaults.Conflict.Schema) } },
36
+ },
37
+ },
38
+ }),
39
+ validator("json", ProjectTaskDefaults.Input),
40
+ async (c) => {
41
+ try {
42
+ return c.json(await ProjectTaskDefaults.update(c.req.valid("json")))
43
+ } catch (error) {
44
+ if (error instanceof ProjectTaskDefaults.Conflict) return c.json(error.toObject(), 409)
45
+ if (error instanceof ProjectTaskDefaults.Invalid) return c.json(error.toObject(), 400)
46
+ throw error
47
+ }
48
+ },
49
+ )
50
+ }
@@ -0,0 +1,10 @@
1
+ import { SessionProjectHealth } from "@ericsanchezok/synergy-harness/session/project-health"
2
+ import { GitHealth } from "./git-health"
3
+
4
+ export function registerProjectSessionHealth() {
5
+ SessionProjectHealth.register({
6
+ isGitRepo: (cwd) => GitHealth.isGitRepo(cwd),
7
+ observeGitHealth: (cwd) => GitHealth.observeCached(cwd),
8
+ invalidateGitHealth: (cwd) => GitHealth.invalidate(cwd),
9
+ })
10
+ }
@@ -0,0 +1,28 @@
1
+ import { ScopeStartup } from "@ericsanchezok/synergy-harness/scope/startup"
2
+ import { Vcs } from "./vcs"
3
+ import { startWorktreeJanitor, stopWorktreeJanitor } from "./worktree-janitor"
4
+
5
+ /**
6
+ * H5 project startup contribution: Vcs.init moves out of scope/runtime.ts.
7
+ * It runs after the file watcher, matching the historical startup sequence.
8
+ */
9
+ export function registerProjectStartup() {
10
+ ScopeStartup.register({
11
+ name: "vcs-init",
12
+ owner: "workspace",
13
+ phase: "surface",
14
+ after: ["file-watcher"],
15
+ init: () => Vcs.init().then(() => {}),
16
+ })
17
+ // The janitor only schedules itself here; its first scan is deferred with an
18
+ // unref'd timer so neither startup latency nor process lifetime depends on it.
19
+ ScopeStartup.register({
20
+ name: "worktree-janitor",
21
+ phase: "surface",
22
+ after: ["command-watcher"],
23
+ init: (scope) => {
24
+ if (scope.type === "project") startWorktreeJanitor(scope)
25
+ },
26
+ dispose: (scopeID) => stopWorktreeJanitor(scopeID),
27
+ })
28
+ }
@@ -0,0 +1,70 @@
1
+ import { ProjectDirectories } from "./directories"
2
+ import path from "node:path"
3
+ import { z } from "zod"
4
+ import { NamedError } from "@ericsanchezok/synergy-util/error"
5
+ import { Config } from "@ericsanchezok/synergy-harness/config/config"
6
+ import { GlobalBus } from "@ericsanchezok/synergy-harness/bus/global"
7
+ import { ScopeContext } from "@ericsanchezok/synergy-harness/scope/context"
8
+ import { ResourceProfiles } from "@ericsanchezok/synergy-local-runtime/environment/profiles"
9
+
10
+ export namespace ProjectTaskDefaults {
11
+ export const Defaults = z
12
+ .object({
13
+ defaultSessionWorkspace: z.enum(["main", "worktree"]).optional(),
14
+ defaultSessionEnvironmentProfile: z.string().min(1).nullable().optional(),
15
+ })
16
+ .meta({ ref: "ProjectTaskDefaults" })
17
+ export const Result = z
18
+ .object({ defaults: Defaults, effective: Defaults, editable: z.boolean() })
19
+ .meta({ ref: "ProjectTaskDefaultsResult" })
20
+ export const Input = z.object({ defaults: Defaults, expected: Defaults }).meta({ ref: "ProjectTaskDefaultsInput" })
21
+ export const Conflict = NamedError.create("ProjectTaskDefaultsConflict", z.object({ message: z.string() }))
22
+ export const Invalid = NamedError.create("ProjectTaskDefaultsInvalid", z.object({ message: z.string() }))
23
+ function root() {
24
+ const scope = ScopeContext.current.scope
25
+ return scope.type === "project" && scope.local ? path.join(scope.local.directory, ".synergy") : undefined
26
+ }
27
+ export async function get(): Promise<z.infer<typeof Result>> {
28
+ const directory = root()
29
+ return {
30
+ defaults: directory ? Defaults.parse(await Config.domainGet("general", directory)) : {},
31
+ effective: Defaults.parse(await Config.current()),
32
+ editable: !!directory,
33
+ }
34
+ }
35
+ export async function update(input: z.infer<typeof Input>) {
36
+ const parsed = Input.parse(input)
37
+ const directory = root()
38
+ if (!directory)
39
+ throw new Invalid({
40
+ message: "This project inherits global settings because it has no project configuration directory.",
41
+ })
42
+ if (parsed.defaults.defaultSessionWorkspace === "worktree") {
43
+ const folders = await ProjectDirectories.get(ScopeContext.current.scope.id)
44
+ if (!folders.folders.some((folder) => folder.workspaceID === folders.mainWorkspaceID && folder.git))
45
+ throw new Invalid({ message: "Worktree requires a Git main folder." })
46
+ }
47
+ const profile = parsed.defaults.defaultSessionEnvironmentProfile
48
+ if (profile && !(await ResourceProfiles.list()).environments.some((item) => item.name === profile))
49
+ throw new Invalid({ message: "The selected execution profile is unavailable. Choose a configured location." })
50
+ const { change } = await Config.domainMutateWithChange(
51
+ "general",
52
+ (current) => {
53
+ if (JSON.stringify(Defaults.parse(current)) !== JSON.stringify(parsed.expected))
54
+ throw new Conflict({ message: "Project defaults changed. Reload them before saving again." })
55
+ delete current.defaultSessionWorkspace
56
+ delete current.defaultSessionEnvironmentProfile
57
+ return { ...current, ...parsed.defaults }
58
+ },
59
+ { root: directory, mode: "replace-domain" },
60
+ )
61
+ GlobalBus().emit("event", {
62
+ scopeID: ScopeContext.current.scope.id,
63
+ payload: {
64
+ type: Config.Event.Updated.type,
65
+ properties: { scope: "project", changedFields: change.changedFields },
66
+ },
67
+ })
68
+ return get()
69
+ }
70
+ }