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

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 (37) 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/bin.js +80 -80
  7. package/src/bridge/bin/commit-boundary.js +64 -64
  8. package/src/bridge/connectors/capture-guard.js +24 -24
  9. package/src/bridge/connectors/claude-code/hook-entry.js +45 -45
  10. package/src/bridge/connectors/claude-code/notify.js +57 -57
  11. package/src/bridge/connectors/codex/cursor-store.js +83 -83
  12. package/src/bridge/connectors/codex/hook-entry.js +215 -215
  13. package/src/bridge/connectors/codex/session-parser.js +100 -100
  14. package/src/bridge/connectors/opencode/hook-entry.js +55 -55
  15. package/src/bridge/core/bridge.js +249 -246
  16. package/src/bridge/core/commit-projection.js +288 -288
  17. package/src/bridge/core/compaction.js +23 -23
  18. package/src/bridge/core/consumer-registry.js +111 -111
  19. package/src/bridge/core/event-schema.js +127 -127
  20. package/src/bridge/core/fs-utils.js +81 -81
  21. package/src/bridge/core/journal-router.js +39 -39
  22. package/src/bridge/core/journal.js +507 -507
  23. package/src/bridge/core/lock.js +96 -96
  24. package/src/bridge/core/normalizer.js +114 -114
  25. package/src/bridge/core/paths.js +12 -12
  26. package/src/bridge/core/project-registry.js +176 -176
  27. package/src/bridge/core/repo-context.js +191 -191
  28. package/src/bridge/core/runtime-home.js +189 -189
  29. package/src/bridge/core/semver.js +23 -23
  30. package/src/bridge/core/turn-identity.js +127 -127
  31. package/src/bridge/index.js +86 -86
  32. package/src/bridge/installers/claude-code/installer.js +181 -181
  33. package/src/bridge/installers/codex/installer.js +114 -114
  34. package/src/bridge/installers/opencode/installer.js +96 -96
  35. package/src/bridge/installers/zcode/installer.js +200 -200
  36. package/src/bridge/query/conversation-query.js +194 -194
  37. 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
+ };