@sanqianx/ai-coding-event-bridge-console 0.7.2 → 0.7.4

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 (36) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +81 -81
  3. package/console/index.html +953 -953
  4. package/package.json +26 -26
  5. package/src/agent-detect.js +128 -128
  6. package/src/bridge/bin/commit-boundary.js +64 -64
  7. package/src/bridge/connectors/capture-guard.js +24 -24
  8. package/src/bridge/connectors/claude-code/hook-entry.js +45 -45
  9. package/src/bridge/connectors/claude-code/notify.js +57 -57
  10. package/src/bridge/connectors/codex/cursor-store.js +83 -83
  11. package/src/bridge/connectors/codex/hook-entry.js +215 -215
  12. package/src/bridge/connectors/codex/session-parser.js +100 -100
  13. package/src/bridge/connectors/opencode/hook-entry.js +55 -55
  14. package/src/bridge/core/bridge.js +249 -246
  15. package/src/bridge/core/commit-projection.js +288 -288
  16. package/src/bridge/core/compaction.js +23 -23
  17. package/src/bridge/core/consumer-registry.js +111 -111
  18. package/src/bridge/core/event-schema.js +127 -127
  19. package/src/bridge/core/fs-utils.js +81 -81
  20. package/src/bridge/core/journal-router.js +39 -39
  21. package/src/bridge/core/journal.js +507 -507
  22. package/src/bridge/core/lock.js +96 -96
  23. package/src/bridge/core/normalizer.js +114 -114
  24. package/src/bridge/core/paths.js +12 -12
  25. package/src/bridge/core/project-registry.js +176 -176
  26. package/src/bridge/core/repo-context.js +191 -191
  27. package/src/bridge/core/runtime-home.js +189 -189
  28. package/src/bridge/core/semver.js +23 -23
  29. package/src/bridge/core/turn-identity.js +127 -127
  30. package/src/bridge/index.js +86 -86
  31. package/src/bridge/installers/claude-code/installer.js +181 -181
  32. package/src/bridge/installers/codex/installer.js +114 -114
  33. package/src/bridge/installers/opencode/installer.js +96 -96
  34. package/src/bridge/installers/zcode/installer.js +200 -200
  35. package/src/bridge/query/conversation-query.js +194 -194
  36. package/src/server.js +825 -825
@@ -1,176 +1,176 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const { writeJsonAtomicSync, readJsonIfExists, ensureDirSync } = require('./fs-utils');
6
- const { CrossProcessLock } = require('./lock');
7
- const { resolveRepoContext, comparisonForm, repoIdentityKey } = require('./repo-context');
8
-
9
- const REGISTRY_VERSION = 1;
10
-
11
- /**
12
- * Imported-project registry, persisted at `<home>/projects.json`. A registered
13
- * project owns a durable journal at `<store>/journal`; capture routes events
14
- * whose repoIdentity.workspaceId matches the project id there instead of the
15
- * global journal. Removing a project only unregisters it — its data on disk is
16
- * never touched.
17
- */
18
-
19
- function registryFile(home) {
20
- return path.join(home, 'projects.json');
21
- }
22
-
23
- function registryLockFile(home) {
24
- return path.join(home, 'locks', 'projects.lock');
25
- }
26
-
27
- function loadRegistry(home) {
28
- const raw = readJsonIfExists(registryFile(home));
29
- if (!raw || !Array.isArray(raw.projects)) {
30
- return { version: REGISTRY_VERSION, projects: [] };
31
- }
32
- return {
33
- version: raw.version || REGISTRY_VERSION,
34
- projects: raw.projects.filter(
35
- (project) => project && typeof project === 'object' && typeof project.id === 'string' && typeof project.store === 'string'
36
- )
37
- };
38
- }
39
-
40
- function sameComparisonPath(a, b) {
41
- return comparisonForm(a) === comparisonForm(b);
42
- }
43
-
44
- function defaultStoreFor(home, name) {
45
- return path.join(home, 'projects', name);
46
- }
47
-
48
- async function addProject({ home, path: projectDir, store, resolveRepo = resolveRepoContext } = {}) {
49
- if (typeof home !== 'string' || !home) {
50
- throw new Error('addProject requires home');
51
- }
52
- if (typeof projectDir !== 'string' || !projectDir.trim()) {
53
- const err = new Error('addProject requires a project directory');
54
- err.code = 'PROJECT_PATH_REQUIRED';
55
- throw err;
56
- }
57
- let stat = null;
58
- try {
59
- stat = fs.statSync(projectDir);
60
- } catch (_) {
61
- stat = null;
62
- }
63
- if (!stat || !stat.isDirectory()) {
64
- const err = new Error(`project directory does not exist: ${projectDir}`);
65
- err.code = 'PROJECT_DIR_INVALID';
66
- throw err;
67
- }
68
-
69
- // The registry only accepts Git work trees: event identity (workspaceId) is
70
- // derived from the Git toplevel, so a non-Git folder could never match the
71
- // events captured inside it. Importing a subdirectory resolves to its repo
72
- // root, which is the identity events actually carry.
73
- const repo = await resolveRepo(projectDir);
74
- if (!repo || !repo.repoIdentity) {
75
- const err = new Error(`not a Git work tree (no repo identity): ${projectDir}`);
76
- err.code = 'PROJECT_NOT_GIT';
77
- throw err;
78
- }
79
-
80
- const name = path.basename(repo.projectPath) || repo.projectPath;
81
- const entry = {
82
- id: repo.repoIdentity.workspaceId,
83
- name,
84
- path: repo.projectPath,
85
- remote: repo.repoIdentity.remote || null,
86
- store: typeof store === 'string' && store.trim() ? path.resolve(store) : defaultStoreFor(home, name),
87
- createdAt: new Date().toISOString()
88
- };
89
-
90
- const lock = new CrossProcessLock(registryLockFile(home));
91
- return lock.withLock(async () => {
92
- const registry = loadRegistry(home);
93
- if (registry.projects.some((project) => project.id === entry.id)) {
94
- const err = new Error(`project already imported: ${entry.path}`);
95
- err.code = 'PROJECT_DUPLICATE';
96
- throw err;
97
- }
98
- if (registry.projects.some((project) => sameComparisonPath(project.path, entry.path))) {
99
- const err = new Error(`project path already imported: ${entry.path}`);
100
- err.code = 'PROJECT_DUPLICATE';
101
- throw err;
102
- }
103
- if (registry.projects.some((project) => sameComparisonPath(project.store, entry.store))) {
104
- const err = new Error(`store location already used by another project: ${entry.store}`);
105
- err.code = 'PROJECT_STORE_CONFLICT';
106
- throw err;
107
- }
108
- try {
109
- ensureDirSync(path.join(entry.store, 'journal'));
110
- } catch (_) {
111
- const err = new Error(`cannot create journal directory at: ${entry.store}`);
112
- err.code = 'PROJECT_STORE_INVALID';
113
- throw err;
114
- }
115
- writeJsonAtomicSync(registryFile(home), {
116
- version: REGISTRY_VERSION,
117
- projects: registry.projects.concat([entry])
118
- });
119
- return { ...entry };
120
- });
121
- }
122
-
123
- async function removeProject({ home, id } = {}) {
124
- if (typeof home !== 'string' || !home) {
125
- throw new Error('removeProject requires home');
126
- }
127
- if (typeof id !== 'string' || !id) {
128
- const err = new Error('removeProject requires a project id');
129
- err.code = 'PROJECT_ID_REQUIRED';
130
- throw err;
131
- }
132
- const lock = new CrossProcessLock(registryLockFile(home));
133
- return lock.withLock(async () => {
134
- const registry = loadRegistry(home);
135
- let removed = null;
136
- const projects = [];
137
- for (const project of registry.projects) {
138
- if (project.id === id) {
139
- removed = project;
140
- } else {
141
- projects.push(project);
142
- }
143
- }
144
- if (!removed) {
145
- const err = new Error(`project not registered: ${id}`);
146
- err.code = 'PROJECT_NOT_FOUND';
147
- throw err;
148
- }
149
- writeJsonAtomicSync(registryFile(home), { version: REGISTRY_VERSION, projects });
150
- return { ...removed };
151
- });
152
- }
153
-
154
- function listProjects(home) {
155
- return loadRegistry(home).projects.map((project) => ({ ...project }));
156
- }
157
-
158
- // Registry lookup by capture identity (repoIdentity.workspaceId, or the
159
- // identity string itself for legacy records): the same rule journal routing
160
- // uses to pick a project's journal.
161
- function findProject(home, repoIdentity) {
162
- const key = repoIdentityKey(repoIdentity);
163
- if (!key) return null;
164
- const project = loadRegistry(home).projects.find((candidate) => candidate.id === key);
165
- return project ? { ...project } : null;
166
- }
167
-
168
- module.exports = {
169
- REGISTRY_VERSION,
170
- registryFile,
171
- loadRegistry,
172
- addProject,
173
- removeProject,
174
- listProjects,
175
- findProject
176
- };
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { writeJsonAtomicSync, readJsonIfExists, ensureDirSync } = require('./fs-utils');
6
+ const { CrossProcessLock } = require('./lock');
7
+ const { resolveRepoContext, comparisonForm, repoIdentityKey } = require('./repo-context');
8
+
9
+ const REGISTRY_VERSION = 1;
10
+
11
+ /**
12
+ * Imported-project registry, persisted at `<home>/projects.json`. A registered
13
+ * project owns a durable journal at `<store>/journal`; capture routes events
14
+ * whose repoIdentity.workspaceId matches the project id there instead of the
15
+ * global journal. Removing a project only unregisters it — its data on disk is
16
+ * never touched.
17
+ */
18
+
19
+ function registryFile(home) {
20
+ return path.join(home, 'projects.json');
21
+ }
22
+
23
+ function registryLockFile(home) {
24
+ return path.join(home, 'locks', 'projects.lock');
25
+ }
26
+
27
+ function loadRegistry(home) {
28
+ const raw = readJsonIfExists(registryFile(home));
29
+ if (!raw || !Array.isArray(raw.projects)) {
30
+ return { version: REGISTRY_VERSION, projects: [] };
31
+ }
32
+ return {
33
+ version: raw.version || REGISTRY_VERSION,
34
+ projects: raw.projects.filter(
35
+ (project) => project && typeof project === 'object' && typeof project.id === 'string' && typeof project.store === 'string'
36
+ )
37
+ };
38
+ }
39
+
40
+ function sameComparisonPath(a, b) {
41
+ return comparisonForm(a) === comparisonForm(b);
42
+ }
43
+
44
+ function defaultStoreFor(home, name) {
45
+ return path.join(home, 'projects', name);
46
+ }
47
+
48
+ async function addProject({ home, path: projectDir, store, resolveRepo = resolveRepoContext } = {}) {
49
+ if (typeof home !== 'string' || !home) {
50
+ throw new Error('addProject requires home');
51
+ }
52
+ if (typeof projectDir !== 'string' || !projectDir.trim()) {
53
+ const err = new Error('addProject requires a project directory');
54
+ err.code = 'PROJECT_PATH_REQUIRED';
55
+ throw err;
56
+ }
57
+ let stat = null;
58
+ try {
59
+ stat = fs.statSync(projectDir);
60
+ } catch (_) {
61
+ stat = null;
62
+ }
63
+ if (!stat || !stat.isDirectory()) {
64
+ const err = new Error(`project directory does not exist: ${projectDir}`);
65
+ err.code = 'PROJECT_DIR_INVALID';
66
+ throw err;
67
+ }
68
+
69
+ // The registry only accepts Git work trees: event identity (workspaceId) is
70
+ // derived from the Git toplevel, so a non-Git folder could never match the
71
+ // events captured inside it. Importing a subdirectory resolves to its repo
72
+ // root, which is the identity events actually carry.
73
+ const repo = await resolveRepo(projectDir);
74
+ if (!repo || !repo.repoIdentity) {
75
+ const err = new Error(`not a Git work tree (no repo identity): ${projectDir}`);
76
+ err.code = 'PROJECT_NOT_GIT';
77
+ throw err;
78
+ }
79
+
80
+ const name = path.basename(repo.projectPath) || repo.projectPath;
81
+ const entry = {
82
+ id: repo.repoIdentity.workspaceId,
83
+ name,
84
+ path: repo.projectPath,
85
+ remote: repo.repoIdentity.remote || null,
86
+ store: typeof store === 'string' && store.trim() ? path.resolve(store) : defaultStoreFor(home, name),
87
+ createdAt: new Date().toISOString()
88
+ };
89
+
90
+ const lock = new CrossProcessLock(registryLockFile(home));
91
+ return lock.withLock(async () => {
92
+ const registry = loadRegistry(home);
93
+ if (registry.projects.some((project) => project.id === entry.id)) {
94
+ const err = new Error(`project already imported: ${entry.path}`);
95
+ err.code = 'PROJECT_DUPLICATE';
96
+ throw err;
97
+ }
98
+ if (registry.projects.some((project) => sameComparisonPath(project.path, entry.path))) {
99
+ const err = new Error(`project path already imported: ${entry.path}`);
100
+ err.code = 'PROJECT_DUPLICATE';
101
+ throw err;
102
+ }
103
+ if (registry.projects.some((project) => sameComparisonPath(project.store, entry.store))) {
104
+ const err = new Error(`store location already used by another project: ${entry.store}`);
105
+ err.code = 'PROJECT_STORE_CONFLICT';
106
+ throw err;
107
+ }
108
+ try {
109
+ ensureDirSync(path.join(entry.store, 'journal'));
110
+ } catch (_) {
111
+ const err = new Error(`cannot create journal directory at: ${entry.store}`);
112
+ err.code = 'PROJECT_STORE_INVALID';
113
+ throw err;
114
+ }
115
+ writeJsonAtomicSync(registryFile(home), {
116
+ version: REGISTRY_VERSION,
117
+ projects: registry.projects.concat([entry])
118
+ });
119
+ return { ...entry };
120
+ });
121
+ }
122
+
123
+ async function removeProject({ home, id } = {}) {
124
+ if (typeof home !== 'string' || !home) {
125
+ throw new Error('removeProject requires home');
126
+ }
127
+ if (typeof id !== 'string' || !id) {
128
+ const err = new Error('removeProject requires a project id');
129
+ err.code = 'PROJECT_ID_REQUIRED';
130
+ throw err;
131
+ }
132
+ const lock = new CrossProcessLock(registryLockFile(home));
133
+ return lock.withLock(async () => {
134
+ const registry = loadRegistry(home);
135
+ let removed = null;
136
+ const projects = [];
137
+ for (const project of registry.projects) {
138
+ if (project.id === id) {
139
+ removed = project;
140
+ } else {
141
+ projects.push(project);
142
+ }
143
+ }
144
+ if (!removed) {
145
+ const err = new Error(`project not registered: ${id}`);
146
+ err.code = 'PROJECT_NOT_FOUND';
147
+ throw err;
148
+ }
149
+ writeJsonAtomicSync(registryFile(home), { version: REGISTRY_VERSION, projects });
150
+ return { ...removed };
151
+ });
152
+ }
153
+
154
+ function listProjects(home) {
155
+ return loadRegistry(home).projects.map((project) => ({ ...project }));
156
+ }
157
+
158
+ // Registry lookup by capture identity (repoIdentity.workspaceId, or the
159
+ // identity string itself for legacy records): the same rule journal routing
160
+ // uses to pick a project's journal.
161
+ function findProject(home, repoIdentity) {
162
+ const key = repoIdentityKey(repoIdentity);
163
+ if (!key) return null;
164
+ const project = loadRegistry(home).projects.find((candidate) => candidate.id === key);
165
+ return project ? { ...project } : null;
166
+ }
167
+
168
+ module.exports = {
169
+ REGISTRY_VERSION,
170
+ registryFile,
171
+ loadRegistry,
172
+ addProject,
173
+ removeProject,
174
+ listProjects,
175
+ findProject
176
+ };