@bevel-software/platform-shared 0.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.
- package/LICENSE +202 -0
- package/dist/auth/types.d.ts +15 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +2 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/chat/types.d.ts +39 -0
- package/dist/chat/types.d.ts.map +1 -0
- package/dist/chat/types.js +13 -0
- package/dist/chat/types.js.map +1 -0
- package/dist/git/branchAuthor.d.ts +28 -0
- package/dist/git/branchAuthor.d.ts.map +1 -0
- package/dist/git/branchAuthor.js +45 -0
- package/dist/git/branchAuthor.js.map +1 -0
- package/dist/git/pr.types.d.ts +264 -0
- package/dist/git/pr.types.d.ts.map +1 -0
- package/dist/git/pr.types.js +2 -0
- package/dist/git/pr.types.js.map +1 -0
- package/dist/git/protected.d.ts +52 -0
- package/dist/git/protected.d.ts.map +1 -0
- package/dist/git/protected.js +92 -0
- package/dist/git/protected.js.map +1 -0
- package/dist/git/review.types.d.ts +49 -0
- package/dist/git/review.types.d.ts.map +1 -0
- package/dist/git/review.types.js +2 -0
- package/dist/git/review.types.js.map +1 -0
- package/dist/git/types.d.ts +107 -0
- package/dist/git/types.d.ts.map +1 -0
- package/dist/git/types.js +2 -0
- package/dist/git/types.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/workflow/events.d.ts +218 -0
- package/dist/workflow/events.d.ts.map +1 -0
- package/dist/workflow/events.js +48 -0
- package/dist/workflow/events.js.map +1 -0
- package/dist/workflow/interface.d.ts +233 -0
- package/dist/workflow/interface.d.ts.map +1 -0
- package/dist/workflow/interface.js +20 -0
- package/dist/workflow/interface.js.map +1 -0
- package/dist/workflow/types.d.ts +131 -0
- package/dist/workflow/types.d.ts.map +1 -0
- package/dist/workflow/types.js +17 -0
- package/dist/workflow/types.js.map +1 -0
- package/dist/workspace/filename.d.ts +33 -0
- package/dist/workspace/filename.d.ts.map +1 -0
- package/dist/workspace/filename.js +98 -0
- package/dist/workspace/filename.js.map +1 -0
- package/dist/workspace/frontmatter.d.ts +23 -0
- package/dist/workspace/frontmatter.d.ts.map +1 -0
- package/dist/workspace/frontmatter.js +48 -0
- package/dist/workspace/frontmatter.js.map +1 -0
- package/dist/workspace/kb-layout.d.ts +66 -0
- package/dist/workspace/kb-layout.d.ts.map +1 -0
- package/dist/workspace/kb-layout.js +63 -0
- package/dist/workspace/kb-layout.js.map +1 -0
- package/dist/workspace/types.d.ts +57 -0
- package/dist/workspace/types.d.ts.map +1 -0
- package/dist/workspace/types.js +2 -0
- package/dist/workspace/types.js.map +1 -0
- package/package.json +41 -0
- package/src/auth/types.ts +16 -0
- package/src/chat/types.ts +53 -0
- package/src/git/branchAuthor.ts +46 -0
- package/src/git/pr.types.ts +274 -0
- package/src/git/protected.ts +121 -0
- package/src/git/review.types.ts +51 -0
- package/src/git/types.ts +170 -0
- package/src/index.ts +23 -0
- package/src/workflow/events.ts +271 -0
- package/src/workflow/interface.ts +357 -0
- package/src/workflow/types.ts +156 -0
- package/src/workspace/filename.ts +102 -0
- package/src/workspace/frontmatter.ts +45 -0
- package/src/workspace/kb-layout.ts +81 -0
- package/src/workspace/types.ts +59 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow event bus types — what the backend pushes over SSE to keep the
|
|
3
|
+
* frontend in sync with state changes, including those triggered by other
|
|
4
|
+
* users on the same branch or by the agent acting server-side.
|
|
5
|
+
*
|
|
6
|
+
* **Scope model** — events have one of three scopes, set by the variant:
|
|
7
|
+
*
|
|
8
|
+
* - **Workspace-scoped** (`workspaceId` field). Pushed to every connected
|
|
9
|
+
* session whose current focus is `workspaceId`. Examples: a file changed
|
|
10
|
+
* on this branch, someone took the lock on a file. Anyone editing this
|
|
11
|
+
* branch cares.
|
|
12
|
+
*
|
|
13
|
+
* - **User-scoped** (`forUserId` field, no `workspaceId`). Pushed only to
|
|
14
|
+
* sessions opened by `forUserId`, regardless of their current focus.
|
|
15
|
+
* Examples: an agent turn called a tool on your behalf, your view should
|
|
16
|
+
* navigate to a different branch. Other users on the same branch don't
|
|
17
|
+
* care.
|
|
18
|
+
*
|
|
19
|
+
* - **Global** (no `workspaceId`, no `forUserId`). Pushed to every
|
|
20
|
+
* authenticated session. Examples: change requests opening / merging /
|
|
21
|
+
* being rejected — any user with access to the repo might care.
|
|
22
|
+
*
|
|
23
|
+
* **Identity + replay** — every event carries an `id` (monotonic int) and an
|
|
24
|
+
* ISO `ts`. Clients reconnecting with `Last-Event-ID: <id>` get the buffered
|
|
25
|
+
* tail replayed (server's ring buffer is finite — see `WorkflowEventBus`).
|
|
26
|
+
* If `id` is older than what the buffer still has, the server sends a
|
|
27
|
+
* `{ kind: 'resync' }` instead so the client refetches the world cleanly.
|
|
28
|
+
*
|
|
29
|
+
* **Idempotency over exactly-once** — clients tolerate duplicates and
|
|
30
|
+
* out-of-order delivery: every payload carries enough state for the UI to
|
|
31
|
+
* reach the right end state regardless of how many times it sees the event
|
|
32
|
+
* (e.g. `file-changed` includes the new SHA — UI only refreshes if the open
|
|
33
|
+
* tab's SHA differs).
|
|
34
|
+
*/
|
|
35
|
+
/** True for events scoped to a workspace — the focus filter applies. */
|
|
36
|
+
export function isWorkspaceScoped(e) {
|
|
37
|
+
return (e.kind === 'file-changed' ||
|
|
38
|
+
e.kind === 'lock-acquired' ||
|
|
39
|
+
e.kind === 'lock-released' ||
|
|
40
|
+
e.kind === 'fs-tree-changed');
|
|
41
|
+
}
|
|
42
|
+
/** True for events scoped to a specific user — the user filter applies. */
|
|
43
|
+
export function isUserScoped(e) {
|
|
44
|
+
return (e.kind === 'agent-tool-call' ||
|
|
45
|
+
e.kind === 'branch-switched' ||
|
|
46
|
+
e.kind === 'change-request-merge-failed');
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/workflow/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAwNH,wEAAwE;AACxE,MAAM,UAAU,iBAAiB,CAC/B,CAAuB;IAEvB,OAAO,CACL,CAAC,CAAC,IAAI,KAAK,cAAc;QACzB,CAAC,CAAC,IAAI,KAAK,eAAe;QAC1B,CAAC,CAAC,IAAI,KAAK,eAAe;QAC1B,CAAC,CAAC,IAAI,KAAK,iBAAiB,CAC7B,CAAC;AACJ,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,YAAY,CAC1B,CAAuB;IAEvB,OAAO,CACL,CAAC,CAAC,IAAI,KAAK,iBAAiB;QAC5B,CAAC,CAAC,IAAI,KAAK,iBAAiB;QAC5B,CAAC,CAAC,IAAI,KAAK,6BAA6B,CACzC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `IWorkflowService` — the abstraction the rest of the app talks to instead
|
|
3
|
+
* of raw git/gh. See `bevel-platform/PLAN.md` for the motivation and the spec the
|
|
4
|
+
* methods here encode (protected branches, atomic per-file changes, file
|
|
5
|
+
* locks, change requests as branch-pair reconciliations).
|
|
6
|
+
*
|
|
7
|
+
* Naming convention: methods speak workflow vocabulary (`commitChange`,
|
|
8
|
+
* `openChangeRequest`, `acquireLock`), never git vocabulary (`commit`,
|
|
9
|
+
* `createPr`, no analogue for locks). Implementations may delegate to the
|
|
10
|
+
* existing git/PR/review-workflow services until those are folded into
|
|
11
|
+
* `modules/workflow`; callers should not depend on that fact.
|
|
12
|
+
*
|
|
13
|
+
* Some methods on this interface have no backing service yet — file locks,
|
|
14
|
+
* `openChangeRequest`, `updateFromTarget`. The facade implementation throws
|
|
15
|
+
* `NotImplementedWorkflowError` from those until the underlying support
|
|
16
|
+
* lands. The interface defines them up-front so consumers can plan against
|
|
17
|
+
* the final surface area.
|
|
18
|
+
*/
|
|
19
|
+
import type { AuthUser } from '../auth/types.js';
|
|
20
|
+
import type { AcquireLockResult, Branch, BranchWorkspaceStatus, CancelChangeRequestResult, Change, ChangeInput, ChangeRequest, ChangeRequestComment, ChangeRequestDetail, ChangeRequestState, ChangedFile, FileApproval, FileLock, MergeChangeRequestOutcome, OpenChangeRequestInput, PostChangeRequestCommentInput } from './types.js';
|
|
21
|
+
export interface IWorkflowService {
|
|
22
|
+
listBranches(workspaceId: string, opts?: {
|
|
23
|
+
freshFetch?: boolean;
|
|
24
|
+
}): Promise<Branch[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Create a new unprotected branch. Protected branches cannot be created
|
|
27
|
+
* via the workflow — the protected set is bootstrapped from the KB repo's
|
|
28
|
+
* initial state and never grown at runtime.
|
|
29
|
+
*/
|
|
30
|
+
createBranch(workspaceId: string, name: string, fromBase?: string): Promise<Branch>;
|
|
31
|
+
/**
|
|
32
|
+
* Delete a branch. Removes both local and origin refs in one call. The
|
|
33
|
+
* branch's author (per `<email-localpart>/...` naming) OR an admin (per the
|
|
34
|
+
* workspace's `roles.yaml`) can delete it. Admins can additionally remove
|
|
35
|
+
* unprefixed CLI-created branches that have no recognisable author.
|
|
36
|
+
*
|
|
37
|
+
* `onlyIfNoRemote: true` is the legacy orphan-cleanup path: bypasses the
|
|
38
|
+
* author/admin check (callers prune any orphan they encounter) and refuses
|
|
39
|
+
* if origin still has the ref. Protected and current branches always reject.
|
|
40
|
+
*/
|
|
41
|
+
deleteBranch(workspaceId: string, name: string, user: AuthUser, opts?: {
|
|
42
|
+
onlyIfNoRemote?: boolean;
|
|
43
|
+
}): Promise<void>;
|
|
44
|
+
/** Current branch's workspace status — currently just the branch name + upstream sync state. */
|
|
45
|
+
branchStatus(workspaceId: string): Promise<BranchWorkspaceStatus>;
|
|
46
|
+
/**
|
|
47
|
+
* Push the current branch upstream so other users (and change requests)
|
|
48
|
+
* can see the commits on it. Workflow term: "share current branch". The
|
|
49
|
+
* underlying implementation is `git push -u origin <branch>`.
|
|
50
|
+
*/
|
|
51
|
+
shareCurrentBranch(workspaceId: string, user: AuthUser): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Number of commits on `branch` that aren't on `baseBranch` — exactly
|
|
54
|
+
* what a CR from `branch` to `baseBranch` would propose for review.
|
|
55
|
+
* Implemented as `git rev-list --count <baseBranch>..<branch>` against
|
|
56
|
+
* the workspace's clone; returns 0 when the branch has no unmerged
|
|
57
|
+
* commits (a fresh fork that nobody wrote to) and when either ref
|
|
58
|
+
* cannot be resolved. Background runs use this to decide whether a
|
|
59
|
+
* routine branch deserves a change request — without it, a run where
|
|
60
|
+
* the agent commits files but forgets to call `open_change_request`
|
|
61
|
+
* would silently lose its work to the no-changes cleanup path.
|
|
62
|
+
*/
|
|
63
|
+
countCommitsAhead(workspaceId: string, branch: string, baseBranch: string): Promise<number>;
|
|
64
|
+
/** Fetch remote refs without modifying the working tree. */
|
|
65
|
+
refreshRemotes(workspaceId: string): Promise<void>;
|
|
66
|
+
/** Pull the latest remote state into the current branch (rebase strategy). */
|
|
67
|
+
updateFromRemote(workspaceId: string): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Hard-reset the workspace's checked-out branch to `origin/<branch>` (fetch
|
|
70
|
+
* first), discarding any local divergence. Break-glass primitive for
|
|
71
|
+
* roles.yaml recovery so the restoring commit can fast-forward on push.
|
|
72
|
+
*/
|
|
73
|
+
resetToRemote(workspaceId: string, branch: string): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Best-fit protected branch that `branch` was forked from — picks the
|
|
76
|
+
* protected branch on origin yielding the smallest "ahead" count.
|
|
77
|
+
* Returns null if no protected branch can be reached.
|
|
78
|
+
*/
|
|
79
|
+
resolveForkBase(workspaceId: string, branch: string): Promise<string | null>;
|
|
80
|
+
/**
|
|
81
|
+
* Paths that the next `commitChange` would include — what `git add -A`
|
|
82
|
+
* would stage: modified + deleted + renamed + untracked (honouring
|
|
83
|
+
* `.gitignore`). Returns only paths.
|
|
84
|
+
*/
|
|
85
|
+
listPendingChangePaths(workspaceId: string): Promise<string[]>;
|
|
86
|
+
/**
|
|
87
|
+
* Commit one atomic change. The current backing implementation packs every
|
|
88
|
+
* dirty path in the working tree into the commit; the workflow spec
|
|
89
|
+
* eventually constrains this to one file per change, enforced at the
|
|
90
|
+
* commit boundary. Until that lands, callers should already commit
|
|
91
|
+
* one-file-at-a-time by convention.
|
|
92
|
+
*/
|
|
93
|
+
commitChange(workspaceId: string, user: AuthUser, input: ChangeInput): Promise<Change>;
|
|
94
|
+
/**
|
|
95
|
+
* Commit whatever the caller has already written to the working tree as ONE
|
|
96
|
+
* atomic change, then push. Unlike `commitChange` (one file at a time, guarded
|
|
97
|
+
* at the commit boundary) this commits the whole dirty set together — for the
|
|
98
|
+
* rare case where a group of files must land together or not at all (e.g. a
|
|
99
|
+
* role rename rewriting `roles.yaml` + every reference). Does NOT write
|
|
100
|
+
* content: the caller writes the files first (e.g. via the lock-aware
|
|
101
|
+
* filesystem) and is responsible for any validation. Returns null on a no-op
|
|
102
|
+
* (clean tree).
|
|
103
|
+
*/
|
|
104
|
+
commitChanges(workspaceId: string, user: AuthUser, summary: string): Promise<Change | null>;
|
|
105
|
+
/** Per-file history. Newest first; clamps to ≤ 100 entries. */
|
|
106
|
+
listChangesForFile(workspaceId: string, path: string, limit?: number): Promise<Change[]>;
|
|
107
|
+
/**
|
|
108
|
+
* Revert a single change by creating a new change that undoes it.
|
|
109
|
+
* Refused on protected branches; refused without write permission on the
|
|
110
|
+
* touched paths.
|
|
111
|
+
*/
|
|
112
|
+
revertChange(workspaceId: string, user: AuthUser, sha: string): Promise<Change>;
|
|
113
|
+
/**
|
|
114
|
+
* Diff of one file between two branches. Both names must resolve to a
|
|
115
|
+
* known branch on this workspace (local head or remote-tracking). Returns
|
|
116
|
+
* the raw unified diff body — empty when identical, includes the
|
|
117
|
+
* "new file" / "deleted file" headers when the file only exists on one
|
|
118
|
+
* side.
|
|
119
|
+
*/
|
|
120
|
+
compareFile(workspaceId: string, path: string, fromBranch: string, toBranch: string): Promise<string>;
|
|
121
|
+
/**
|
|
122
|
+
* Diff of one file at a specific change (sha) — i.e. `git show <sha> -- <path>`.
|
|
123
|
+
* Used by the File viewer's History tab.
|
|
124
|
+
*/
|
|
125
|
+
showFileAtChange(workspaceId: string, path: string, sha: string): Promise<string>;
|
|
126
|
+
/**
|
|
127
|
+
* Try to acquire an edit lock on `(branch, path)` for the caller. Returns
|
|
128
|
+
* `{ acquired: true, lock }` on success; `{ acquired: false, lock }` if
|
|
129
|
+
* someone else already holds it (the existing lock is included so the UI
|
|
130
|
+
* can render "Locked by X" without a follow-up call).
|
|
131
|
+
*
|
|
132
|
+
* Pessimistic per the spec. Auto-released when the lock expires past TTL
|
|
133
|
+
* without a heartbeat — stale locks must not block forever.
|
|
134
|
+
*/
|
|
135
|
+
acquireLock(workspaceId: string, branch: string, path: string, user: AuthUser): Promise<AcquireLockResult>;
|
|
136
|
+
/** Heartbeat to keep an acquired lock alive past its current TTL. */
|
|
137
|
+
heartbeatLock(workspaceId: string, branch: string, path: string, user: AuthUser): Promise<FileLock>;
|
|
138
|
+
/**
|
|
139
|
+
* Commit the lock-held file's accumulated edits as a change *without*
|
|
140
|
+
* releasing the lock. Used by the client-side autosave checkpoint timer
|
|
141
|
+
* so the lock stays exclusively the caller's across many interim
|
|
142
|
+
* commits — releasing + reacquiring around every checkpoint would let
|
|
143
|
+
* another user grab the lock in the gap. Returns `null` when the file
|
|
144
|
+
* has no pending edits (idempotent). Refused when the lock isn't held
|
|
145
|
+
* by `user`.
|
|
146
|
+
*/
|
|
147
|
+
commitFileWhileLocked(workspaceId: string, branch: string, path: string, user: AuthUser, summary?: string): Promise<Change | null>;
|
|
148
|
+
/**
|
|
149
|
+
* Release a held lock. The "I'm done editing this file" terminal signal.
|
|
150
|
+
*
|
|
151
|
+
* **Does not commit or push synchronously.** This method enqueues a row in
|
|
152
|
+
* `pending_commits` and drops the lock, then returns `void`. The background
|
|
153
|
+
* `PendingCommitsWorker` claims the row, synthesises the commit subject
|
|
154
|
+
* (callers do *not* supply one — the worker derives a default from the path
|
|
155
|
+
* and any worker-side metadata), runs `commitFile` against whatever bytes
|
|
156
|
+
* are on disk, and pushes the result. Decoupling the user-visible lock
|
|
157
|
+
* release from git means a transient push failure no longer wedges the
|
|
158
|
+
* editor, and a process crash mid-commit becomes resumable instead of
|
|
159
|
+
* leaving an orphan on disk. See `lock-decoupling-plan.md` for the full
|
|
160
|
+
* design.
|
|
161
|
+
*
|
|
162
|
+
* The autosave-checkpoint timer and explicit `commitChange` calls land
|
|
163
|
+
* commits directly (no queue); `releaseLock` is queue-only.
|
|
164
|
+
*/
|
|
165
|
+
releaseLock(workspaceId: string, branch: string, path: string, user: AuthUser): Promise<void>;
|
|
166
|
+
/**
|
|
167
|
+
* Drop the lock **without committing**. Used on the failure path of a
|
|
168
|
+
* lock-aware write — when the underlying op threw, we don't want the
|
|
169
|
+
* normal `releaseLock` to commit + push whatever partial state happens
|
|
170
|
+
* to be on disk. The lock row goes away (so the next caller can edit
|
|
171
|
+
* the file); the working tree is left as-is. Idempotent like
|
|
172
|
+
* `releaseLock`: a no-op when the caller doesn't hold the lock.
|
|
173
|
+
*/
|
|
174
|
+
releaseLockNoCommit(workspaceId: string, branch: string, path: string, user: AuthUser): Promise<void>;
|
|
175
|
+
/** Current lock holder for `(branch, path)`, or null when nobody holds it. */
|
|
176
|
+
getLock(workspaceId: string, branch: string, path: string): Promise<FileLock | null>;
|
|
177
|
+
listChangeRequests(opts?: {
|
|
178
|
+
fresh?: boolean;
|
|
179
|
+
}): Promise<ChangeRequest[]>;
|
|
180
|
+
/** Change requests authored by the caller (hash-matched against the body marker). */
|
|
181
|
+
listChangeRequestsAuthoredBy(emailOrLogin: string): Promise<ChangeRequest[]>;
|
|
182
|
+
/** Change requests touching files the user has write permission on. */
|
|
183
|
+
listChangeRequestsForUser(workspaceId: string, email: string, opts?: {
|
|
184
|
+
fresh?: boolean;
|
|
185
|
+
}): Promise<ChangeRequest[]>;
|
|
186
|
+
getChangeRequest(number: number): Promise<ChangeRequest | null>;
|
|
187
|
+
getChangeRequestDetail(number: number, opts?: {
|
|
188
|
+
fresh?: boolean;
|
|
189
|
+
workspaceId?: string;
|
|
190
|
+
viewerEmail?: string;
|
|
191
|
+
}): Promise<ChangeRequestDetail | null>;
|
|
192
|
+
/**
|
|
193
|
+
* Open a new change request. Per spec, auto-merges `targetBranch` into
|
|
194
|
+
* `sourceBranch` and surfaces the resulting diff as the changes to review.
|
|
195
|
+
* Throws `NotImplementedWorkflowError` until the backing create-PR path
|
|
196
|
+
* lands in the workflow module (today the agent shells `gh pr create`
|
|
197
|
+
* directly).
|
|
198
|
+
*/
|
|
199
|
+
openChangeRequest(workspaceId: string, user: AuthUser, input: OpenChangeRequestInput): Promise<ChangeRequestDetail>;
|
|
200
|
+
/**
|
|
201
|
+
* Re-run `targetBranch → sourceBranch` merge on an existing change request.
|
|
202
|
+
* Used when the target has advanced since the change request was opened.
|
|
203
|
+
* Throws `NotImplementedWorkflowError` until the backing merge path lands.
|
|
204
|
+
*/
|
|
205
|
+
updateFromTarget(workspaceId: string, user: AuthUser, number: number): Promise<ChangeRequestDetail>;
|
|
206
|
+
listComments(number: number): Promise<ChangeRequestComment[]>;
|
|
207
|
+
postComment(number: number, user: AuthUser, input: PostChangeRequestCommentInput, headSha: string): Promise<ChangeRequestComment>;
|
|
208
|
+
editComment(commentId: string, number: number, user: AuthUser, body: string): Promise<ChangeRequestComment>;
|
|
209
|
+
deleteComment(commentId: string, number: number, user: AuthUser): Promise<void>;
|
|
210
|
+
approveFile(number: number, path: string, user: AuthUser, files: ChangedFile[], headSha: string, baseBranch: string, authorIdHash: string | null, workspaceId: string): Promise<FileApproval[]>;
|
|
211
|
+
unapproveFile(number: number, path: string, user: AuthUser, files: ChangedFile[], headSha: string, baseBranch: string, authorIdHash: string | null, workspaceId: string): Promise<FileApproval[]>;
|
|
212
|
+
/**
|
|
213
|
+
* Reject a change request without merging. Authorized for the author or
|
|
214
|
+
* for users with write permission to every changed file on the target
|
|
215
|
+
* branch.
|
|
216
|
+
*/
|
|
217
|
+
rejectChangeRequest(number: number, user: AuthUser, state: ChangeRequestState, authorIdHash: string | null, baseBranch: string, workspaceId: string): Promise<CancelChangeRequestResult>;
|
|
218
|
+
/**
|
|
219
|
+
* Merge a change request. The workflow tries a direct merge first; on
|
|
220
|
+
* conflicts it returns a `conflicts-need-resolution` outcome and the
|
|
221
|
+
* caller (typically the agent) is responsible for resolving them as new
|
|
222
|
+
* changes on the source branch. Once the source contains target cleanly,
|
|
223
|
+
* a subsequent `mergeChangeRequest` call lands the merge.
|
|
224
|
+
*
|
|
225
|
+
* The hard merge invariant — only this method may invoke `gh pr merge` —
|
|
226
|
+
* is preserved through the facade by delegating to
|
|
227
|
+
* `IReviewWorkflowService.mergePr`.
|
|
228
|
+
*/
|
|
229
|
+
mergeChangeRequest(number: number, user: AuthUser, headSha: string, approvals: FileApproval[], state: ChangeRequestState, title: string, baseBranch: string, workspaceId: string, opts?: {
|
|
230
|
+
bypass?: boolean;
|
|
231
|
+
}): Promise<MergeChangeRequestOutcome>;
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/workflow/interface.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EACV,iBAAiB,EACjB,MAAM,EACN,qBAAqB,EACrB,yBAAyB,EACzB,MAAM,EACN,WAAW,EACX,aAAa,EACb,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,yBAAyB,EACzB,sBAAsB,EACtB,6BAA6B,EAC9B,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,gBAAgB;IAG/B,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACtF;;;;OAIG;IACH,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACpF;;;;;;;;;OASG;IACH,YAAY,CACV,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,QAAQ,EACd,IAAI,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,OAAO,CAAA;KAAE,GAClC,OAAO,CAAC,IAAI,CAAC,CAAC;IAQjB,gGAAgG;IAChG,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAGlE;;;;OAIG;IACH,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE;;;;;;;;;;OAUG;IACH,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5F,4DAA4D;IAC5D,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,8EAA8E;IAC9E,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD;;;;OAIG;IACH,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE;;;;OAIG;IACH,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7E;;;;OAIG;IACH,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAQ/D;;;;;;OAMG;IACH,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACvF;;;;;;;;;OASG;IACH,aAAa,CACX,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1B,+DAA+D;IAC/D,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACzF;;;;OAIG;IACH,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAChF;;;;;;OAMG;IACH,WAAW,CACT,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB;;;OAGG;IACH,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAIlF;;;;;;;;OAQG;IACH,WAAW,CACT,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,QAAQ,GACb,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC9B,qEAAqE;IACrE,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACpG;;;;;;;;OAQG;IACH,qBAAqB,CACnB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,QAAQ,EACd,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAE1B;;;;;;;;;;;;;;;;OAgBG;IACH,WAAW,CACT,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,QAAQ,GACb,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB;;;;;;;OAOG;IACH,mBAAmB,CACjB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,QAAQ,GACb,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,8EAA8E;IAC9E,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;IAIrF,kBAAkB,CAAC,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IACzE,qFAAqF;IACrF,4BAA4B,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IAC7E,uEAAuE;IACvE,yBAAyB,CACvB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GACzB,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IAC5B,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IAChE,sBAAsB,CACpB,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GACrE,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;IAEvC;;;;;;OAMG;IACH,iBAAiB,CACf,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,sBAAsB,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAEhC;;;;OAIG;IACH,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAGpG,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAAC;IAC9D,WAAW,CACT,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,6BAA6B,EACpC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACjC,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC5G,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAIhF,WAAW,CACT,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,WAAW,EAAE,EACpB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IAC3B,aAAa,CACX,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,WAAW,EAAE,EACpB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IAE3B;;;;OAIG;IACH,mBAAmB,CACjB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,kBAAkB,EACzB,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,yBAAyB,CAAC,CAAC;IAEtC;;;;;;;;;;OAUG;IACH,kBAAkB,CAChB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,YAAY,EAAE,EACzB,KAAK,EAAE,kBAAkB,EACzB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAC1B,OAAO,CAAC,yBAAyB,CAAC,CAAC;CACvC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `IWorkflowService` — the abstraction the rest of the app talks to instead
|
|
3
|
+
* of raw git/gh. See `bevel-platform/PLAN.md` for the motivation and the spec the
|
|
4
|
+
* methods here encode (protected branches, atomic per-file changes, file
|
|
5
|
+
* locks, change requests as branch-pair reconciliations).
|
|
6
|
+
*
|
|
7
|
+
* Naming convention: methods speak workflow vocabulary (`commitChange`,
|
|
8
|
+
* `openChangeRequest`, `acquireLock`), never git vocabulary (`commit`,
|
|
9
|
+
* `createPr`, no analogue for locks). Implementations may delegate to the
|
|
10
|
+
* existing git/PR/review-workflow services until those are folded into
|
|
11
|
+
* `modules/workflow`; callers should not depend on that fact.
|
|
12
|
+
*
|
|
13
|
+
* Some methods on this interface have no backing service yet — file locks,
|
|
14
|
+
* `openChangeRequest`, `updateFromTarget`. The facade implementation throws
|
|
15
|
+
* `NotImplementedWorkflowError` from those until the underlying support
|
|
16
|
+
* lands. The interface defines them up-front so consumers can plan against
|
|
17
|
+
* the final surface area.
|
|
18
|
+
*/
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=interface.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interface.js","sourceRoot":"","sources":["../../src/workflow/interface.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow vocabulary — the abstraction our app speaks instead of raw git/gh.
|
|
3
|
+
*
|
|
4
|
+
* Most types here alias existing engineering types in `@bevel-software/platform-shared/git/*` so
|
|
5
|
+
* the workflow layer is a single source of truth without duplicating the
|
|
6
|
+
* payload shapes our backend already produces. The point of the aliases is the
|
|
7
|
+
* *name*: callers depend on `Change` / `ChangeRequest` / `Branch`, never on
|
|
8
|
+
* `Commit` / `PullRequest` / `Branch` from the git module. When we eventually
|
|
9
|
+
* collapse `modules/git` and `modules/review-workflow` into `modules/workflow`,
|
|
10
|
+
* the alias targets move with the implementation; the consumer-facing names
|
|
11
|
+
* don't need to.
|
|
12
|
+
*
|
|
13
|
+
* New types that have no git counterpart (file locks, change-request open
|
|
14
|
+
* inputs, conflict-resolution payloads) live here directly.
|
|
15
|
+
*/
|
|
16
|
+
import type { BranchInfo, CommitAttribution, ShareChangesRequest, WorkingTreeStatus } from '../git/types.js';
|
|
17
|
+
import type { CancelPrResult, FileApprovalState, MergePrResult, PullRequestDetail, PullRequestState, PullRequestSummary, PostPrCommentInput, PrReviewComment, PullRequestFile } from '../git/pr.types.js';
|
|
18
|
+
/** A variation of the ontology. Protected or not. */
|
|
19
|
+
export type Branch = BranchInfo;
|
|
20
|
+
/**
|
|
21
|
+
* Snapshot of the workspace tied to a branch — currently-checked-out branch,
|
|
22
|
+
* dirty bit, unshared commit count. Mirrors `WorkingTreeStatus` from the git
|
|
23
|
+
* module. Renames to `BranchWorkspaceStatus` in the workflow domain to signal
|
|
24
|
+
* "this describes the branch's workspace state", not raw git plumbing.
|
|
25
|
+
*/
|
|
26
|
+
export type BranchWorkspaceStatus = WorkingTreeStatus;
|
|
27
|
+
/**
|
|
28
|
+
* A change is the atomic unit of history. Each change affects exactly one file.
|
|
29
|
+
* Creating an empty folder is modeled as a change creating a `.gitkeep` in it.
|
|
30
|
+
* Renames are a single change touching one path with the old path removed in
|
|
31
|
+
* the same atomic operation. Aliases `CommitAttribution` because that's
|
|
32
|
+
* exactly the shape the backend already returns for a single commit.
|
|
33
|
+
*/
|
|
34
|
+
export type Change = CommitAttribution;
|
|
35
|
+
/**
|
|
36
|
+
* Input shape for committing a change. `summary` is required and single-line;
|
|
37
|
+
* `description` is optional and supports a multi-line body. Aliases
|
|
38
|
+
* `ShareChangesRequest` for now — once the workflow module owns the validator
|
|
39
|
+
* gate end-to-end, this shape may grow workflow-specific fields (lock token,
|
|
40
|
+
* forced single-path scope).
|
|
41
|
+
*/
|
|
42
|
+
export type ChangeInput = ShareChangesRequest;
|
|
43
|
+
/**
|
|
44
|
+
* A live edit lock on one (branch, path). Per the workflow spec:
|
|
45
|
+
* - acquired on first edit in edit mode, or on agent write
|
|
46
|
+
* - per-(branch, path); the same file on another branch is independently lockable
|
|
47
|
+
* - autosave checkpoint timer extends the lock and produces intermediate changes
|
|
48
|
+
* - stale locks (no heartbeat past TTL) auto-release so a disconnected client
|
|
49
|
+
* can't hold a file hostage
|
|
50
|
+
*
|
|
51
|
+
* Holder identity carries both `userId` (the join key against the users table)
|
|
52
|
+
* and `holderName` (display name, denormalised so consumers can render a
|
|
53
|
+
* "locked by Alice" badge without an extra round-trip). For agent-driven
|
|
54
|
+
* writes the holder is the user driving the agent.
|
|
55
|
+
*/
|
|
56
|
+
export interface FileLock {
|
|
57
|
+
branch: string;
|
|
58
|
+
path: string;
|
|
59
|
+
holderUserId: string;
|
|
60
|
+
holderName: string;
|
|
61
|
+
/** ISO timestamp when the lock was first acquired. */
|
|
62
|
+
acquiredAt: string;
|
|
63
|
+
/** ISO timestamp of the most recent heartbeat — the autosave/keepalive ping. */
|
|
64
|
+
lastHeartbeatAt: string;
|
|
65
|
+
/** ISO timestamp after which a non-heartbeated lock is considered stale and may be reclaimed. */
|
|
66
|
+
expiresAt: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Result of attempting to acquire a lock. `acquired: false` carries the
|
|
70
|
+
* current holder so the UI can render "Locked by X" without a follow-up call.
|
|
71
|
+
*/
|
|
72
|
+
export type AcquireLockResult = {
|
|
73
|
+
acquired: true;
|
|
74
|
+
lock: FileLock;
|
|
75
|
+
} | {
|
|
76
|
+
acquired: false;
|
|
77
|
+
lock: FileLock;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* A change request is the reconciliation of two branches. In the current
|
|
81
|
+
* implementation this is backed by a GitHub pull request; the alias hides
|
|
82
|
+
* that from callers. `state` values map 1:1 (`open`/`merged`/`closed`).
|
|
83
|
+
*
|
|
84
|
+
* Opening a change request is defined in the spec as auto-merging target →
|
|
85
|
+
* source first; that part isn't yet implemented in the backing services, so
|
|
86
|
+
* `openChangeRequest` on `IWorkflowService` currently throws
|
|
87
|
+
* `NotImplementedWorkflowError`.
|
|
88
|
+
*/
|
|
89
|
+
export type ChangeRequest = PullRequestSummary;
|
|
90
|
+
export type ChangeRequestDetail = PullRequestDetail;
|
|
91
|
+
export type ChangeRequestState = PullRequestState;
|
|
92
|
+
export type ChangedFile = PullRequestFile;
|
|
93
|
+
export type FileApproval = FileApprovalState;
|
|
94
|
+
export type MergeChangeRequestResult = MergePrResult;
|
|
95
|
+
export type CancelChangeRequestResult = CancelPrResult;
|
|
96
|
+
export type ChangeRequestComment = PrReviewComment;
|
|
97
|
+
export type PostChangeRequestCommentInput = PostPrCommentInput;
|
|
98
|
+
/**
|
|
99
|
+
* Input for opening a new change request. The workflow auto-merges
|
|
100
|
+
* `targetBranch` into `sourceBranch` as part of opening, so callers don't
|
|
101
|
+
* pass a head SHA — the workflow resolves the head itself.
|
|
102
|
+
*
|
|
103
|
+
* `title` + `description` are the change request's metadata; the description
|
|
104
|
+
* is plain text (no GitHub-flavoured markdown affordances exposed at this
|
|
105
|
+
* layer). The implementation may still render this as the PR body when the
|
|
106
|
+
* underlying GitHub PR is created.
|
|
107
|
+
*/
|
|
108
|
+
export interface OpenChangeRequestInput {
|
|
109
|
+
sourceBranch: string;
|
|
110
|
+
targetBranch: string;
|
|
111
|
+
title: string;
|
|
112
|
+
description?: string;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Result of asking the workflow to merge a change request. Mirrors
|
|
116
|
+
* `MergePrResult` — caller learns the resulting change (commit) sha and the
|
|
117
|
+
* timestamp the merge landed.
|
|
118
|
+
*
|
|
119
|
+
* When the merge attempt hits conflicts the workflow falls back to an
|
|
120
|
+
* agent-driven resolution path; the caller does NOT get a `MergeChangeRequestResult`
|
|
121
|
+
* back in that case — they get a `ConflictPending` shape instead so the UI
|
|
122
|
+
* can route into the resolution flow.
|
|
123
|
+
*/
|
|
124
|
+
export type MergeChangeRequestOutcome = {
|
|
125
|
+
kind: 'merged';
|
|
126
|
+
result: MergeChangeRequestResult;
|
|
127
|
+
} | {
|
|
128
|
+
kind: 'conflicts-need-resolution';
|
|
129
|
+
conflictedPaths: string[];
|
|
130
|
+
};
|
|
131
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/workflow/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EACV,UAAU,EACV,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EACf,eAAe,EAChB,MAAM,oBAAoB,CAAC;AAI5B,qDAAqD;AACrD,MAAM,MAAM,MAAM,GAAG,UAAU,CAAC;AAEhC;;;;;GAKG;AACH,MAAM,MAAM,qBAAqB,GAAG,iBAAiB,CAAC;AAItD;;;;;;GAMG;AACH,MAAM,MAAM,MAAM,GAAG,iBAAiB,CAAC;AAEvC;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG,mBAAmB,CAAC;AAI9C;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,UAAU,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,eAAe,EAAE,MAAM,CAAC;IACxB,iGAAiG;IACjG,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GACzB;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClC;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC;AAIxC;;;;;;;;;GASG;AACH,MAAM,MAAM,aAAa,GAAG,kBAAkB,CAAC;AAC/C,MAAM,MAAM,mBAAmB,GAAG,iBAAiB,CAAC;AACpD,MAAM,MAAM,kBAAkB,GAAG,gBAAgB,CAAC;AAClD,MAAM,MAAM,WAAW,GAAG,eAAe,CAAC;AAE1C,MAAM,MAAM,YAAY,GAAG,iBAAiB,CAAC;AAC7C,MAAM,MAAM,wBAAwB,GAAG,aAAa,CAAC;AACrD,MAAM,MAAM,yBAAyB,GAAG,cAAc,CAAC;AACvD,MAAM,MAAM,oBAAoB,GAAG,eAAe,CAAC;AACnD,MAAM,MAAM,6BAA6B,GAAG,kBAAkB,CAAC;AAE/D;;;;;;;;;GASG;AACH,MAAM,WAAW,sBAAsB;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,yBAAyB,GACjC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,wBAAwB,CAAA;CAAE,GACpD;IAAE,IAAI,EAAE,2BAA2B,CAAC;IAAC,eAAe,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow vocabulary — the abstraction our app speaks instead of raw git/gh.
|
|
3
|
+
*
|
|
4
|
+
* Most types here alias existing engineering types in `@bevel-software/platform-shared/git/*` so
|
|
5
|
+
* the workflow layer is a single source of truth without duplicating the
|
|
6
|
+
* payload shapes our backend already produces. The point of the aliases is the
|
|
7
|
+
* *name*: callers depend on `Change` / `ChangeRequest` / `Branch`, never on
|
|
8
|
+
* `Commit` / `PullRequest` / `Branch` from the git module. When we eventually
|
|
9
|
+
* collapse `modules/git` and `modules/review-workflow` into `modules/workflow`,
|
|
10
|
+
* the alias targets move with the implementation; the consumer-facing names
|
|
11
|
+
* don't need to.
|
|
12
|
+
*
|
|
13
|
+
* New types that have no git counterpart (file locks, change-request open
|
|
14
|
+
* inputs, conflict-resolution payloads) live here directly.
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/workflow/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform filename validation. A name that passes here must be writable
|
|
3
|
+
* as-is on Windows (NTFS), macOS (APFS), and Linux (ext4) — so the same
|
|
4
|
+
* workspace can round-trip between dev machines, the backend container, and
|
|
5
|
+
* any user's local clone without `git checkout` failing on a forbidden name.
|
|
6
|
+
*
|
|
7
|
+
* Used by:
|
|
8
|
+
* - frontend: gate the create / rename inputs in FileExplorer
|
|
9
|
+
* - backend: reject API requests at the workspace service boundary
|
|
10
|
+
* - agent prompt: the same rules are described to the LLM so it picks valid
|
|
11
|
+
* names when authoring KB files
|
|
12
|
+
*
|
|
13
|
+
* Keep this file dependency-free — both bundle targets import it directly.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Validate a single path component (file or folder name — no `/`, no `\`).
|
|
17
|
+
* Returns `null` if valid, or a short reason string suitable for surfacing
|
|
18
|
+
* in a UI error or API 400 body.
|
|
19
|
+
*/
|
|
20
|
+
export declare function validateFilename(name: string): string | null;
|
|
21
|
+
/**
|
|
22
|
+
* Validate a workspace-relative path: a `/`-separated string of components.
|
|
23
|
+
* Each component is checked with {@link validateFilename}. Backslashes are
|
|
24
|
+
* rejected outright — the workspace API uses `/` everywhere and accepting
|
|
25
|
+
* `\` would round-trip differently on Windows vs the Linux container.
|
|
26
|
+
*/
|
|
27
|
+
export declare function validateRelativePath(relativePath: string): string | null;
|
|
28
|
+
/**
|
|
29
|
+
* Throwing wrapper for the backend service layer — keeps call sites a single
|
|
30
|
+
* line and produces a clear `Error` the route handler can surface as a 400.
|
|
31
|
+
*/
|
|
32
|
+
export declare function assertValidRelativePath(relativePath: string): void;
|
|
33
|
+
//# sourceMappingURL=filename.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filename.d.ts","sourceRoot":"","sources":["../../src/workspace/filename.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAsBH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA2B5D;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiBxE;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAGlE"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform filename validation. A name that passes here must be writable
|
|
3
|
+
* as-is on Windows (NTFS), macOS (APFS), and Linux (ext4) — so the same
|
|
4
|
+
* workspace can round-trip between dev machines, the backend container, and
|
|
5
|
+
* any user's local clone without `git checkout` failing on a forbidden name.
|
|
6
|
+
*
|
|
7
|
+
* Used by:
|
|
8
|
+
* - frontend: gate the create / rename inputs in FileExplorer
|
|
9
|
+
* - backend: reject API requests at the workspace service boundary
|
|
10
|
+
* - agent prompt: the same rules are described to the LLM so it picks valid
|
|
11
|
+
* names when authoring KB files
|
|
12
|
+
*
|
|
13
|
+
* Keep this file dependency-free — both bundle targets import it directly.
|
|
14
|
+
*/
|
|
15
|
+
/** Windows-reserved device names. Case-insensitive; the bare name AND
|
|
16
|
+
* `<name>.<ext>` are both reserved (e.g. `NUL.txt` is invalid on Windows). */
|
|
17
|
+
const WINDOWS_RESERVED_NAMES = new Set([
|
|
18
|
+
'CON', 'PRN', 'AUX', 'NUL',
|
|
19
|
+
'COM0', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',
|
|
20
|
+
'LPT0', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9',
|
|
21
|
+
]);
|
|
22
|
+
/** Characters Windows forbids in any filename component. `/` is the path
|
|
23
|
+
* separator on Unix, included for the same reason. `\` is also a path
|
|
24
|
+
* separator on Windows. Control chars (0x00–0x1F) are rejected separately. */
|
|
25
|
+
// eslint-disable-next-line no-control-regex
|
|
26
|
+
const FORBIDDEN_CHARS = /[<>:"/\\|?*\x00-\x1F]/;
|
|
27
|
+
/** Per-component byte limit: NTFS = 255 UTF-16 units, ext4 = 255 bytes,
|
|
28
|
+
* APFS = 255 UTF-8 bytes. Use UTF-8 bytes — the strictest of the three. */
|
|
29
|
+
const MAX_NAME_BYTES = 255;
|
|
30
|
+
const utf8 = new TextEncoder();
|
|
31
|
+
/**
|
|
32
|
+
* Validate a single path component (file or folder name — no `/`, no `\`).
|
|
33
|
+
* Returns `null` if valid, or a short reason string suitable for surfacing
|
|
34
|
+
* in a UI error or API 400 body.
|
|
35
|
+
*/
|
|
36
|
+
export function validateFilename(name) {
|
|
37
|
+
if (typeof name !== 'string')
|
|
38
|
+
return 'Name is required';
|
|
39
|
+
if (name.length === 0)
|
|
40
|
+
return 'Name cannot be empty';
|
|
41
|
+
if (name !== name.trim())
|
|
42
|
+
return 'Name cannot start or end with whitespace';
|
|
43
|
+
if (name === '.' || name === '..')
|
|
44
|
+
return 'Name cannot be "." or ".."';
|
|
45
|
+
if (FORBIDDEN_CHARS.test(name)) {
|
|
46
|
+
return 'Name cannot contain any of these characters: < > : " / \\ | ? *';
|
|
47
|
+
}
|
|
48
|
+
// Windows trims trailing dots and spaces silently — a name ending in either
|
|
49
|
+
// becomes a different filename on disk, breaking sync between OSes.
|
|
50
|
+
if (name.endsWith('.') || name.endsWith(' ')) {
|
|
51
|
+
return 'Name cannot end with a "." or a space';
|
|
52
|
+
}
|
|
53
|
+
// Windows reserved device names — match `NAME` and `NAME.ext`, case-insensitive.
|
|
54
|
+
const stem = name.split('.')[0].toUpperCase();
|
|
55
|
+
if (WINDOWS_RESERVED_NAMES.has(stem)) {
|
|
56
|
+
return `"${name}" is a reserved system name on Windows`;
|
|
57
|
+
}
|
|
58
|
+
if (utf8.encode(name).length > MAX_NAME_BYTES) {
|
|
59
|
+
return `Name is too long (max ${MAX_NAME_BYTES} bytes)`;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Validate a workspace-relative path: a `/`-separated string of components.
|
|
65
|
+
* Each component is checked with {@link validateFilename}. Backslashes are
|
|
66
|
+
* rejected outright — the workspace API uses `/` everywhere and accepting
|
|
67
|
+
* `\` would round-trip differently on Windows vs the Linux container.
|
|
68
|
+
*/
|
|
69
|
+
export function validateRelativePath(relativePath) {
|
|
70
|
+
if (typeof relativePath !== 'string' || relativePath.length === 0) {
|
|
71
|
+
return 'Path is required';
|
|
72
|
+
}
|
|
73
|
+
if (relativePath.includes('\\')) {
|
|
74
|
+
return 'Path cannot contain "\\" — use "/" as the separator';
|
|
75
|
+
}
|
|
76
|
+
// Strip a single leading `./` so callers that build paths from a base dir
|
|
77
|
+
// (`./<name>`) don't get a spurious "." segment rejection.
|
|
78
|
+
const normalized = relativePath.replace(/^\.\//, '');
|
|
79
|
+
const segments = normalized.split('/').filter((s) => s.length > 0);
|
|
80
|
+
if (segments.length === 0)
|
|
81
|
+
return 'Path is required';
|
|
82
|
+
for (const segment of segments) {
|
|
83
|
+
const reason = validateFilename(segment);
|
|
84
|
+
if (reason)
|
|
85
|
+
return `Invalid path segment "${segment}": ${reason}`;
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Throwing wrapper for the backend service layer — keeps call sites a single
|
|
91
|
+
* line and produces a clear `Error` the route handler can surface as a 400.
|
|
92
|
+
*/
|
|
93
|
+
export function assertValidRelativePath(relativePath) {
|
|
94
|
+
const reason = validateRelativePath(relativePath);
|
|
95
|
+
if (reason)
|
|
96
|
+
throw new Error(`Invalid path: ${reason}`);
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=filename.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filename.js","sourceRoot":"","sources":["../../src/workspace/filename.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE `---` frontmatter splitter, shared by backend and frontend so no file
|
|
3
|
+
* type grows its own regex. Splits a leading `---`-fenced YAML block from the
|
|
4
|
+
* body; null when the text doesn't open with a fence. Parsing the YAML inside is
|
|
5
|
+
* the caller's concern (the access-control resolver deliberately keeps its own
|
|
6
|
+
* hardened line-based reader — see `modules/access/access-splice.ts`).
|
|
7
|
+
*/
|
|
8
|
+
export declare function extractFrontmatter(text: string): {
|
|
9
|
+
frontmatter: string;
|
|
10
|
+
body: string;
|
|
11
|
+
} | null;
|
|
12
|
+
/**
|
|
13
|
+
* Set a top-level scalar key in a file's `---` frontmatter, LINE-based: replace an
|
|
14
|
+
* existing `<key>:` line's value, else insert `<key>: <value>` at the top of the
|
|
15
|
+
* block (creating a `---` block if the file has none). Deliberately NOT a
|
|
16
|
+
* parse-and-re-serialize — a KB node's `nodeType:` quoted-markdown-link and access
|
|
17
|
+
* lines are read by a hand-rolled regex parser that a full YAML re-emit would
|
|
18
|
+
* corrupt, so every other line is preserved byte-for-byte. `value` is emitted as a
|
|
19
|
+
* bare scalar; callers pass simple id/name strings (no quoting/escaping needed for
|
|
20
|
+
* the `[a-z0-9_-]` id grammar).
|
|
21
|
+
*/
|
|
22
|
+
export declare function setFrontmatterField(text: string, key: string, value: string): string;
|
|
23
|
+
//# sourceMappingURL=frontmatter.d.ts.map
|