@bevel-software/platform-shared 0.15.1 → 0.20.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/dist/auth/types.d.ts +12 -0
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/git/pr.types.d.ts +75 -11
- package/dist/git/pr.types.d.ts.map +1 -1
- package/dist/git/types.d.ts +28 -1
- package/dist/git/types.d.ts.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/workflow/events.d.ts +32 -4
- package/dist/workflow/events.d.ts.map +1 -1
- package/dist/workflow/events.js.map +1 -1
- package/dist/workflow/interface.d.ts +103 -4
- package/dist/workflow/interface.d.ts.map +1 -1
- package/dist/workflow/types.d.ts +49 -0
- package/dist/workflow/types.d.ts.map +1 -1
- package/dist/workspace/access-verbs.d.ts +83 -0
- package/dist/workspace/access-verbs.d.ts.map +1 -0
- package/dist/workspace/access-verbs.js +110 -0
- package/dist/workspace/access-verbs.js.map +1 -0
- package/dist/workspace/agent-preamble.d.ts +33 -0
- package/dist/workspace/agent-preamble.d.ts.map +1 -0
- package/dist/workspace/agent-preamble.js +45 -0
- package/dist/workspace/agent-preamble.js.map +1 -0
- package/dist/workspace/entry-exists.d.ts +27 -0
- package/dist/workspace/entry-exists.d.ts.map +1 -0
- package/dist/workspace/entry-exists.js +33 -0
- package/dist/workspace/entry-exists.js.map +1 -0
- package/dist/workspace/filename.d.ts +15 -0
- package/dist/workspace/filename.d.ts.map +1 -1
- package/dist/workspace/filename.js +30 -0
- package/dist/workspace/filename.js.map +1 -1
- package/dist/workspace/frontmatter-carriers.d.ts +44 -0
- package/dist/workspace/frontmatter-carriers.d.ts.map +1 -0
- package/dist/workspace/frontmatter-carriers.js +52 -0
- package/dist/workspace/frontmatter-carriers.js.map +1 -0
- package/dist/workspace/frontmatter.d.ts +23 -4
- package/dist/workspace/frontmatter.d.ts.map +1 -1
- package/dist/workspace/frontmatter.js +59 -9
- package/dist/workspace/frontmatter.js.map +1 -1
- package/dist/workspace/kb-layout.d.ts +226 -19
- package/dist/workspace/kb-layout.d.ts.map +1 -1
- package/dist/workspace/kb-layout.js +353 -23
- package/dist/workspace/kb-layout.js.map +1 -1
- package/dist/workspace/placeholder.d.ts +21 -0
- package/dist/workspace/placeholder.d.ts.map +1 -0
- package/dist/workspace/placeholder.js +28 -0
- package/dist/workspace/placeholder.js.map +1 -0
- package/dist/workspace/platform-files.d.ts +105 -0
- package/dist/workspace/platform-files.d.ts.map +1 -0
- package/dist/workspace/platform-files.js +147 -0
- package/dist/workspace/platform-files.js.map +1 -0
- package/dist/workspace/types.d.ts +8 -0
- package/dist/workspace/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/auth/types.ts +12 -0
- package/src/git/pr.types.ts +77 -11
- package/src/git/types.ts +35 -1
- package/src/index.ts +6 -0
- package/src/workflow/events.ts +33 -3
- package/src/workflow/interface.ts +127 -4
- package/src/workflow/types.ts +43 -0
- package/src/workspace/access-verbs.ts +124 -0
- package/src/workspace/agent-preamble.ts +47 -0
- package/src/workspace/entry-exists.ts +36 -0
- package/src/workspace/filename.ts +31 -0
- package/src/workspace/frontmatter-carriers.ts +57 -0
- package/src/workspace/frontmatter.ts +57 -8
- package/src/workspace/kb-layout.ts +388 -25
- package/src/workspace/placeholder.ts +29 -0
- package/src/workspace/platform-files.ts +188 -0
- package/src/workspace/types.ts +8 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { type KbLayout } from './kb-layout.js';
|
|
2
|
+
/**
|
|
3
|
+
* The files the platform reads as configuration, not content. `access.md`
|
|
4
|
+
* governs the folder it sits in and `.bevelignore` layers like `.gitignore`,
|
|
5
|
+
* so both count at any depth; `roles.yaml` and the agent guide are read from
|
|
6
|
+
* the repository root only, so a nested file of either name is ordinary
|
|
7
|
+
* content. Moving one changes what the platform enforces, so moves refuse them.
|
|
8
|
+
*
|
|
9
|
+
* A FUNCTION, not a constant, and that is the whole of the configurable-guide
|
|
10
|
+
* change on this side: the guide's name is a deployment setting, so the fourth
|
|
11
|
+
* platform file is `HEXIS.md` on one deployment and `AGENTS.md` on the next —
|
|
12
|
+
* and on the first, a root `AGENTS.md` is the CUSTOMER'S own conventions file,
|
|
13
|
+
* which has to move and delete like any page. Every gate asks this rather than
|
|
14
|
+
* reading a list captured at module load.
|
|
15
|
+
*/
|
|
16
|
+
export declare function platformFileNames(layout?: KbLayout): readonly string[];
|
|
17
|
+
/**
|
|
18
|
+
* The platform file names under the DEFAULT layout — what they were before the
|
|
19
|
+
* guide could be renamed. Kept for callers that want the default answer rather
|
|
20
|
+
* than this deployment's; anything judging a real path asks
|
|
21
|
+
* {@link platformFileNames}, which knows what this deployment called its guide.
|
|
22
|
+
*/
|
|
23
|
+
export declare const PLATFORM_FILE_NAMES: readonly string[];
|
|
24
|
+
/**
|
|
25
|
+
* Whether the repository-relative `repoRelativePath` is a platform file.
|
|
26
|
+
* Exact spelling, as the platform reads it: `Access.md` is content. A caller
|
|
27
|
+
* on a case-insensitive disk passes the path's on-disk spelling.
|
|
28
|
+
*/
|
|
29
|
+
export declare function isPlatformFile(repoRelativePath: string): boolean;
|
|
30
|
+
/** The one sentence every surface refuses a platform-file move with. */
|
|
31
|
+
export declare function platformFileRefusal(pathOrName: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* The sentence a move is refused with when the DESTINATION would be a platform
|
|
34
|
+
* file — a note renamed to `access.md`, or dragged onto the one that is there.
|
|
35
|
+
* The other refusal keeps a platform file where the platform reads it; this one
|
|
36
|
+
* keeps everything else from becoming one, which a rename on disk would
|
|
37
|
+
* otherwise do silently: the folder would come back governed by rules nobody
|
|
38
|
+
* wrote as rules.
|
|
39
|
+
*/
|
|
40
|
+
export declare function platformFileCreationRefusal(pathOrName: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Whether `repoRelativeDir` is a folder the platform owns rather than content:
|
|
43
|
+
* the repository root itself, or one of its reserved top-level folders
|
|
44
|
+
* (`KnowledgeBase/`, `Skills/`, `Plugins/`, `Data/`, …). Deleting or moving
|
|
45
|
+
* one takes a whole section of the knowledge base with it. Exact spelling; a
|
|
46
|
+
* caller on a case-insensitive disk passes the path's on-disk spelling.
|
|
47
|
+
*/
|
|
48
|
+
export declare function isPlatformFolder(repoRelativeDir: string): boolean;
|
|
49
|
+
/** The sentence a delete or move of a platform folder is refused with. */
|
|
50
|
+
export declare function platformFolderRefusal(repoRelativeDir: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* Whether the platform file at `repoRelativePath` sits directly in the
|
|
53
|
+
* repository root — the copy every one of the four is read from there, and so
|
|
54
|
+
* never the misplaced one: it is the copy a restore puts back. A nested
|
|
55
|
+
* `access.md` or `.bevelignore` is a platform file too, but it layers on top
|
|
56
|
+
* of the root's rather than standing in for it, which is why moving the
|
|
57
|
+
* ROOT's copy into a folder is a move out and not a restore.
|
|
58
|
+
*/
|
|
59
|
+
export declare function isRootPlatformFile(repoRelativePath: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* The place a misplaced platform file is allowed to be put back, when
|
|
62
|
+
* `repoRelativeDestination` names one, and null when it does not.
|
|
63
|
+
*
|
|
64
|
+
* `roles.yaml` and the agent guide are read from the repository root and
|
|
65
|
+
* nowhere else, so their one required location is the root. A nested `.bevelignore` is
|
|
66
|
+
* read too (it layers, see `BevelIgnoreStack`), yet a restore of one lands at
|
|
67
|
+
* the root only — a deliberate narrowing of the exception, not a claim about
|
|
68
|
+
* where the file is read: the root's copy is the one whose absence breaks the
|
|
69
|
+
* workspace, and a folder that never had a `.bevelignore` is not missing one.
|
|
70
|
+
* `access.md` governs whatever folder it sits in, so a folder that has none
|
|
71
|
+
* is a place one is missing from — WHETHER the folder has one is a fact about
|
|
72
|
+
* the disk, which this pure predicate does not know and the caller checks
|
|
73
|
+
* (see `AccessControlService.canRestorePlatformFile`).
|
|
74
|
+
*/
|
|
75
|
+
export type PlatformRestoreDestination = {
|
|
76
|
+
name: string;
|
|
77
|
+
kind: 'root';
|
|
78
|
+
} | {
|
|
79
|
+
name: string;
|
|
80
|
+
kind: 'folder-without-access-md';
|
|
81
|
+
dir: string;
|
|
82
|
+
};
|
|
83
|
+
export declare function platformRestoreDestination(repoRelativeDestination: string): PlatformRestoreDestination | null;
|
|
84
|
+
/**
|
|
85
|
+
* Whether moving `repoRelativeSource` to `repoRelativeDestination` has the
|
|
86
|
+
* SHAPE of a platform-file restore — an admin putting a misplaced copy back.
|
|
87
|
+
* Who is asking is not part of the shape; `canRestorePlatformFile` answers
|
|
88
|
+
* that, and the state of the disk with it.
|
|
89
|
+
*
|
|
90
|
+
* Three things make the shape, and all three are about the move rather than
|
|
91
|
+
* about the source's current standing:
|
|
92
|
+
*
|
|
93
|
+
* - the source is NAMED like a platform file. A nested `roles.yaml`, or a
|
|
94
|
+
* nested copy of the agent guide, is ordinary content where it sits
|
|
95
|
+
* (`isPlatformFile` says so, and moving it needs no exception), but it is
|
|
96
|
+
* still the copy a restore carries back to the root — judging the shape on
|
|
97
|
+
* `isPlatformFile` would skip the exception for exactly the two files the
|
|
98
|
+
* root can lose;
|
|
99
|
+
* - the source is not the root's own copy, which is the copy a restore puts
|
|
100
|
+
* back, never the one it takes out;
|
|
101
|
+
* - the destination is a required location for that same name, so the file
|
|
102
|
+
* lands under the name the platform reads rather than beside it.
|
|
103
|
+
*/
|
|
104
|
+
export declare function isPlatformRestoreShape(repoRelativeSource: string, repoRelativeDestination: string): boolean;
|
|
105
|
+
//# sourceMappingURL=platform-files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform-files.d.ts","sourceRoot":"","sources":["../../src/workspace/platform-files.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,QAAQ,EACd,MAAM,gBAAgB,CAAC;AAExB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,QAA4B,GAAG,SAAS,MAAM,EAAE,CAEzF;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAEhD,CAAC;AA2BF;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAKhE;AAED,wEAAwE;AACxE,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEtE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAIjE;AAED,0EAA0E;AAC1E,wBAAgB,qBAAqB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAKrE;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAEpE;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,0BAA0B,GAClC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,0BAA0B,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpE,wBAAgB,0BAA0B,CACxC,uBAAuB,EAAE,MAAM,GAC9B,0BAA0B,GAAG,IAAI,CAUnC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,sBAAsB,CACpC,kBAAkB,EAAE,MAAM,EAC1B,uBAAuB,EAAE,MAAM,GAC9B,OAAO,CAOT"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { DEFAULT_KB_LAYOUT, FIXED_PLATFORM_FILE_NAMES, agentsFileOf, currentKbLayout, reservedRootDirNames, } from './kb-layout.js';
|
|
2
|
+
/**
|
|
3
|
+
* The files the platform reads as configuration, not content. `access.md`
|
|
4
|
+
* governs the folder it sits in and `.bevelignore` layers like `.gitignore`,
|
|
5
|
+
* so both count at any depth; `roles.yaml` and the agent guide are read from
|
|
6
|
+
* the repository root only, so a nested file of either name is ordinary
|
|
7
|
+
* content. Moving one changes what the platform enforces, so moves refuse them.
|
|
8
|
+
*
|
|
9
|
+
* A FUNCTION, not a constant, and that is the whole of the configurable-guide
|
|
10
|
+
* change on this side: the guide's name is a deployment setting, so the fourth
|
|
11
|
+
* platform file is `HEXIS.md` on one deployment and `AGENTS.md` on the next —
|
|
12
|
+
* and on the first, a root `AGENTS.md` is the CUSTOMER'S own conventions file,
|
|
13
|
+
* which has to move and delete like any page. Every gate asks this rather than
|
|
14
|
+
* reading a list captured at module load.
|
|
15
|
+
*/
|
|
16
|
+
export function platformFileNames(layout = currentKbLayout()) {
|
|
17
|
+
return [...FIXED_PLATFORM_FILE_NAMES, agentsFileOf(layout)];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The platform file names under the DEFAULT layout — what they were before the
|
|
21
|
+
* guide could be renamed. Kept for callers that want the default answer rather
|
|
22
|
+
* than this deployment's; anything judging a real path asks
|
|
23
|
+
* {@link platformFileNames}, which knows what this deployment called its guide.
|
|
24
|
+
*/
|
|
25
|
+
export const PLATFORM_FILE_NAMES = Object.freeze(platformFileNames(DEFAULT_KB_LAYOUT));
|
|
26
|
+
/** The platform files that are read wherever they sit, not only at the root. */
|
|
27
|
+
const PLATFORM_FILES_AT_ANY_DEPTH = new Set(['access.md', '.bevelignore']);
|
|
28
|
+
/** The names in effect, as a set — rebuilt per call, because the guide's is configurable. */
|
|
29
|
+
const platformFiles = () => new Set(platformFileNames());
|
|
30
|
+
const normalize = (path) => path.replace(/^\.?\/+/, '').replace(/\/+$/, '');
|
|
31
|
+
const baseName = (path) => {
|
|
32
|
+
const trimmed = normalize(path);
|
|
33
|
+
return trimmed.slice(trimmed.lastIndexOf('/') + 1);
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* A path that walks out of the repository (`..`) or stands still (`.`). The
|
|
37
|
+
* restore exception is the one write allowed past a destination that denies
|
|
38
|
+
* it, so it answers on the spelling it was handed and refuses anything whose
|
|
39
|
+
* meaning depends on resolving it: `KnowledgeBase/../access.md` names the
|
|
40
|
+
* root's file to a resolver and a nested one to a reader of segments. The
|
|
41
|
+
* move's own path-safety check refuses these too — this gate does not lean on
|
|
42
|
+
* that one.
|
|
43
|
+
*/
|
|
44
|
+
const hasTraversal = (path) => normalize(path).split('/').some((segment) => segment === '..' || segment === '.');
|
|
45
|
+
/**
|
|
46
|
+
* Whether the repository-relative `repoRelativePath` is a platform file.
|
|
47
|
+
* Exact spelling, as the platform reads it: `Access.md` is content. A caller
|
|
48
|
+
* on a case-insensitive disk passes the path's on-disk spelling.
|
|
49
|
+
*/
|
|
50
|
+
export function isPlatformFile(repoRelativePath) {
|
|
51
|
+
const norm = normalize(repoRelativePath);
|
|
52
|
+
const name = baseName(norm);
|
|
53
|
+
if (!platformFiles().has(name))
|
|
54
|
+
return false;
|
|
55
|
+
return PLATFORM_FILES_AT_ANY_DEPTH.has(name) || norm === name;
|
|
56
|
+
}
|
|
57
|
+
/** The one sentence every surface refuses a platform-file move with. */
|
|
58
|
+
export function platformFileRefusal(pathOrName) {
|
|
59
|
+
return `${baseName(pathOrName)} is a platform file and stays in its folder.`;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The sentence a move is refused with when the DESTINATION would be a platform
|
|
63
|
+
* file — a note renamed to `access.md`, or dragged onto the one that is there.
|
|
64
|
+
* The other refusal keeps a platform file where the platform reads it; this one
|
|
65
|
+
* keeps everything else from becoming one, which a rename on disk would
|
|
66
|
+
* otherwise do silently: the folder would come back governed by rules nobody
|
|
67
|
+
* wrote as rules.
|
|
68
|
+
*/
|
|
69
|
+
export function platformFileCreationRefusal(pathOrName) {
|
|
70
|
+
return `${baseName(pathOrName)} is a platform file name; a move cannot create a platform file.`;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Whether `repoRelativeDir` is a folder the platform owns rather than content:
|
|
74
|
+
* the repository root itself, or one of its reserved top-level folders
|
|
75
|
+
* (`KnowledgeBase/`, `Skills/`, `Plugins/`, `Data/`, …). Deleting or moving
|
|
76
|
+
* one takes a whole section of the knowledge base with it. Exact spelling; a
|
|
77
|
+
* caller on a case-insensitive disk passes the path's on-disk spelling.
|
|
78
|
+
*/
|
|
79
|
+
export function isPlatformFolder(repoRelativeDir) {
|
|
80
|
+
const norm = normalize(repoRelativeDir);
|
|
81
|
+
if (norm === '')
|
|
82
|
+
return true;
|
|
83
|
+
return !norm.includes('/') && reservedRootDirNames().has(norm);
|
|
84
|
+
}
|
|
85
|
+
/** The sentence a delete or move of a platform folder is refused with. */
|
|
86
|
+
export function platformFolderRefusal(repoRelativeDir) {
|
|
87
|
+
const norm = normalize(repoRelativeDir);
|
|
88
|
+
return norm === ''
|
|
89
|
+
? 'The repository root is a platform folder and cannot be moved or deleted.'
|
|
90
|
+
: `${norm}/ is a platform folder and cannot be moved or deleted.`;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Whether the platform file at `repoRelativePath` sits directly in the
|
|
94
|
+
* repository root — the copy every one of the four is read from there, and so
|
|
95
|
+
* never the misplaced one: it is the copy a restore puts back. A nested
|
|
96
|
+
* `access.md` or `.bevelignore` is a platform file too, but it layers on top
|
|
97
|
+
* of the root's rather than standing in for it, which is why moving the
|
|
98
|
+
* ROOT's copy into a folder is a move out and not a restore.
|
|
99
|
+
*/
|
|
100
|
+
export function isRootPlatformFile(repoRelativePath) {
|
|
101
|
+
return platformFiles().has(normalize(repoRelativePath));
|
|
102
|
+
}
|
|
103
|
+
export function platformRestoreDestination(repoRelativeDestination) {
|
|
104
|
+
const norm = normalize(repoRelativeDestination);
|
|
105
|
+
if (hasTraversal(norm))
|
|
106
|
+
return null;
|
|
107
|
+
const name = baseName(norm);
|
|
108
|
+
if (!platformFiles().has(name))
|
|
109
|
+
return null;
|
|
110
|
+
if (name === 'access.md') {
|
|
111
|
+
const slash = norm.lastIndexOf('/');
|
|
112
|
+
return { name, kind: 'folder-without-access-md', dir: slash === -1 ? '' : norm.slice(0, slash) };
|
|
113
|
+
}
|
|
114
|
+
return norm === name ? { name, kind: 'root' } : null;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Whether moving `repoRelativeSource` to `repoRelativeDestination` has the
|
|
118
|
+
* SHAPE of a platform-file restore — an admin putting a misplaced copy back.
|
|
119
|
+
* Who is asking is not part of the shape; `canRestorePlatformFile` answers
|
|
120
|
+
* that, and the state of the disk with it.
|
|
121
|
+
*
|
|
122
|
+
* Three things make the shape, and all three are about the move rather than
|
|
123
|
+
* about the source's current standing:
|
|
124
|
+
*
|
|
125
|
+
* - the source is NAMED like a platform file. A nested `roles.yaml`, or a
|
|
126
|
+
* nested copy of the agent guide, is ordinary content where it sits
|
|
127
|
+
* (`isPlatformFile` says so, and moving it needs no exception), but it is
|
|
128
|
+
* still the copy a restore carries back to the root — judging the shape on
|
|
129
|
+
* `isPlatformFile` would skip the exception for exactly the two files the
|
|
130
|
+
* root can lose;
|
|
131
|
+
* - the source is not the root's own copy, which is the copy a restore puts
|
|
132
|
+
* back, never the one it takes out;
|
|
133
|
+
* - the destination is a required location for that same name, so the file
|
|
134
|
+
* lands under the name the platform reads rather than beside it.
|
|
135
|
+
*/
|
|
136
|
+
export function isPlatformRestoreShape(repoRelativeSource, repoRelativeDestination) {
|
|
137
|
+
if (hasTraversal(repoRelativeSource))
|
|
138
|
+
return false;
|
|
139
|
+
const name = baseName(repoRelativeSource);
|
|
140
|
+
if (!platformFiles().has(name))
|
|
141
|
+
return false;
|
|
142
|
+
if (isRootPlatformFile(repoRelativeSource))
|
|
143
|
+
return false;
|
|
144
|
+
const target = platformRestoreDestination(repoRelativeDestination);
|
|
145
|
+
return target !== null && target.name === name;
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=platform-files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform-files.js","sourceRoot":"","sources":["../../src/workspace/platform-files.ts"],"names":[],"mappings":"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"}
|
|
@@ -25,6 +25,14 @@ export interface FileTreeEntry {
|
|
|
25
25
|
relativePath: string;
|
|
26
26
|
type: 'file' | 'directory';
|
|
27
27
|
children?: FileTreeEntry[];
|
|
28
|
+
/**
|
|
29
|
+
* Set on a DIRECTORY of a read-filtered listing, and only when non-zero:
|
|
30
|
+
* how many entries the caller's read rules kept out of that directory's
|
|
31
|
+
* subtree — on the root, out of the whole listing. A number, never names —
|
|
32
|
+
* it lets the explorer tell "nothing is shared with you" apart from "this
|
|
33
|
+
* folder is empty" without saying what exists.
|
|
34
|
+
*/
|
|
35
|
+
withheld?: number;
|
|
28
36
|
}
|
|
29
37
|
import type { AuthUser } from '../auth/types.js';
|
|
30
38
|
export interface IWorkspaceService {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/workspace/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B;;;;;OAKG;IACH,EAAE,EAAE,MAAM,CAAC;IACX,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,YAAY,EAAE,MAAM,CAAC;IACrB,4FAA4F;IAC5F,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/workspace/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B;;;;;OAKG;IACH,EAAE,EAAE,MAAM,CAAC;IACX,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,YAAY,EAAE,MAAM,CAAC;IACrB,4FAA4F;IAC5F,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAC7D;;;;;OAKG;IACH,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAC5E,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IAChE,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IACvD,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACrE,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrF,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5F,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChG,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACvD,oFAAoF;IACpF,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bevel-software/platform-shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "Shared types and pure domain utilities of the Bevel core platform (auth, workspace, git/workflow contracts, branch registry).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
|
-
"build": "tsc -p tsconfig.json",
|
|
39
|
+
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
|
|
40
40
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
41
41
|
}
|
|
42
42
|
}
|
package/src/auth/types.ts
CHANGED
|
@@ -11,6 +11,18 @@ export interface AuthUser {
|
|
|
11
11
|
* absent field must never resurrect the welcome flow.
|
|
12
12
|
*/
|
|
13
13
|
onboardingDone?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Is this the deployment admin — the account whose password is set in the
|
|
16
|
+
* deployment environment (`ADMIN_EMAIL` while `ADMIN_PASSWORD` is set)
|
|
17
|
+
* rather than stored as a hash? That credential is the platform's rescue
|
|
18
|
+
* path into a deployment, so this account's password cannot be changed from
|
|
19
|
+
* the Account page. Derived from configuration on every read rather than
|
|
20
|
+
* stored, and it carries no part of the credential itself. Optional for the
|
|
21
|
+
* same reason as `onboardingDone` above — pre-existing fixtures and cached
|
|
22
|
+
* user objects stay valid — and only an explicit `true` means "deployment
|
|
23
|
+
* admin".
|
|
24
|
+
*/
|
|
25
|
+
isEnvAdmin?: boolean;
|
|
14
26
|
}
|
|
15
27
|
|
|
16
28
|
export interface LoginRequest {
|
package/src/git/pr.types.ts
CHANGED
|
@@ -44,7 +44,39 @@ export interface PullRequestSummary {
|
|
|
44
44
|
/** Relative paths within `knowledge-base/`. Empty if not yet computed. */
|
|
45
45
|
touchedNodePaths: string[];
|
|
46
46
|
review: PullRequestReviewStatus;
|
|
47
|
+
/**
|
|
48
|
+
* Link to the change request: absolute (`<public frontend address>/change-requests/<number>`)
|
|
49
|
+
* when the deployment has a public address configured, else the in-app relative path.
|
|
50
|
+
*/
|
|
47
51
|
url: string;
|
|
52
|
+
/** Present when `url` is relative — says how to get absolute links. */
|
|
53
|
+
urlNote?: string;
|
|
54
|
+
/**
|
|
55
|
+
* The most recent apply attempt that did not land, while the request is
|
|
56
|
+
* still open — so its author and every other viewer see the refusal the
|
|
57
|
+
* person who clicked Apply saw. Replaced by a newer refusal; null or
|
|
58
|
+
* absent when there is nothing to report.
|
|
59
|
+
*/
|
|
60
|
+
lastApplyFailure?: ChangeRequestApplyFailure | null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* What refused an apply: the merge gate (approvals it still waits on), git
|
|
65
|
+
* (conflicts with the target), or anything else (a push, the roles.yaml guard,
|
|
66
|
+
* an internal error). Decides which later change makes the refusal obsolete.
|
|
67
|
+
*/
|
|
68
|
+
export type ChangeRequestApplyFailureKind = 'gate' | 'conflicts' | 'error';
|
|
69
|
+
|
|
70
|
+
/** Why the last apply of a change request failed, as persisted on the request. */
|
|
71
|
+
export interface ChangeRequestApplyFailure {
|
|
72
|
+
/** Human-readable reason, credentials already redacted. */
|
|
73
|
+
reason: string;
|
|
74
|
+
/** True when git refused the merge on conflicts with the target. */
|
|
75
|
+
conflicts: boolean;
|
|
76
|
+
/** ISO timestamp of the failed attempt. */
|
|
77
|
+
at: string;
|
|
78
|
+
/** Display name of whoever attempted the apply. */
|
|
79
|
+
byName: string;
|
|
48
80
|
}
|
|
49
81
|
|
|
50
82
|
export type PrFileStatus =
|
|
@@ -145,16 +177,31 @@ export interface FileApprovalState {
|
|
|
145
177
|
users: { name: string; email: string }[];
|
|
146
178
|
};
|
|
147
179
|
approvedBy: FileApprovalEntry[];
|
|
180
|
+
/**
|
|
181
|
+
* True only when `eligibleApprovers` is the access tree's actual answer for
|
|
182
|
+
* this file. False (or absent) when it could not be resolved — no workspace,
|
|
183
|
+
* no usable access config on the base, a failed lookup — in which case the
|
|
184
|
+
* empty approver set means "unknown", not "outside the gate", and anything
|
|
185
|
+
* granted on the strength of that emptiness must fail closed.
|
|
186
|
+
*/
|
|
187
|
+
eligibilityResolved?: boolean;
|
|
148
188
|
/**
|
|
149
189
|
* True iff at least one eligible approver has submitted a non-stale
|
|
150
190
|
* approval. Always `false` when `eligibleApprovers` is empty — with no
|
|
151
191
|
* eligible set, nobody can satisfy the check.
|
|
152
192
|
* Whether the gate *cares* about this file is a separate concern handled
|
|
153
|
-
* by the merge-gate logic:
|
|
154
|
-
*
|
|
193
|
+
* by the merge-gate logic: files with no eligible approvers, whatever
|
|
194
|
+
* their type, are silently excluded from the gate, so `isApproved: false`
|
|
155
195
|
* on one of them does not block merge.
|
|
156
196
|
*/
|
|
157
197
|
isApproved: boolean;
|
|
198
|
+
/**
|
|
199
|
+
* Whether the merge gate binds this file at all — the backend's one
|
|
200
|
+
* relevance rule, stamped per file so clients read the verdict instead of
|
|
201
|
+
* re-deriving it. False files neither warn nor block a merge, so no surface
|
|
202
|
+
* should count them as pending or name anyone to wait on.
|
|
203
|
+
*/
|
|
204
|
+
inMergeGate: boolean;
|
|
158
205
|
/**
|
|
159
206
|
* Pre-computed for the requesting viewer: would `approveFile` accept their
|
|
160
207
|
* click? True iff their email resolves to `write` on this path under the
|
|
@@ -184,21 +231,21 @@ export interface PullRequestDetail extends PullRequestSummary {
|
|
|
184
231
|
*/
|
|
185
232
|
approvals: FileApprovalState[];
|
|
186
233
|
/**
|
|
187
|
-
* True iff no
|
|
188
|
-
* merged/closed
|
|
189
|
-
*
|
|
190
|
-
*
|
|
234
|
+
* True iff no blocking reason remains — the PR is open, has files, isn't
|
|
235
|
+
* merged/closed, and every file with an eligible approver (of any file
|
|
236
|
+
* type) holds a current approval. False while any approval is missing; an
|
|
237
|
+
* admin may still merge past missing approvals with the bypass flag.
|
|
191
238
|
*/
|
|
192
239
|
mergeableInBevel: boolean;
|
|
193
240
|
/**
|
|
194
|
-
*
|
|
195
|
-
*
|
|
241
|
+
* Blocking reasons — hard blocks (PR state, no files) followed by each
|
|
242
|
+
* missing approval. Empty exactly when `mergeableInBevel` is true.
|
|
196
243
|
*/
|
|
197
244
|
mergeBlockedReasons: string[];
|
|
198
245
|
/**
|
|
199
|
-
*
|
|
200
|
-
* approval is stale).
|
|
201
|
-
*
|
|
246
|
+
* The missing approvals alone — files of any type with an eligible approver
|
|
247
|
+
* who hasn't approved (or whose approval is stale). An admin bypass merges
|
|
248
|
+
* past exactly these and records them. Files nobody can approve are silent.
|
|
202
249
|
*/
|
|
203
250
|
mergeWarnings: string[];
|
|
204
251
|
/**
|
|
@@ -221,6 +268,25 @@ export interface PullRequestDetail extends PullRequestSummary {
|
|
|
221
268
|
* tree can't be resolved.
|
|
222
269
|
*/
|
|
223
270
|
viewerCanCancel: boolean;
|
|
271
|
+
/**
|
|
272
|
+
* The commit this request forked from its target (merge base of `headSha`
|
|
273
|
+
* and `baseSha`). Every file diff in the request reads its "before" side
|
|
274
|
+
* here, never at the target tip — an edit made on the target after the
|
|
275
|
+
* proposal must not look like something the proposal deletes. `null` when
|
|
276
|
+
* the branches share no history (or no workspace could resolve them).
|
|
277
|
+
*/
|
|
278
|
+
mergeBaseSha: string | null;
|
|
279
|
+
/**
|
|
280
|
+
* True iff the target holds commits the proposal does not contain — the
|
|
281
|
+
* request needs updating. False for anything not open.
|
|
282
|
+
*/
|
|
283
|
+
behind: boolean;
|
|
284
|
+
/**
|
|
285
|
+
* True iff the viewer may Update the request (merge its target into it):
|
|
286
|
+
* the request is open AND the viewer is its author or may apply it. A UX
|
|
287
|
+
* hint — the update route re-checks the same predicate server-side.
|
|
288
|
+
*/
|
|
289
|
+
viewerCanUpdate: boolean;
|
|
224
290
|
}
|
|
225
291
|
|
|
226
292
|
/**
|
package/src/git/types.ts
CHANGED
|
@@ -249,6 +249,40 @@ export interface IGitService {
|
|
|
249
249
|
* Just the repo-relative paths a change request touches (three-dot diff,
|
|
250
250
|
* no statuses, no patches): the cheap form behind change-request list
|
|
251
251
|
* summaries and owner routing.
|
|
252
|
+
*
|
|
253
|
+
* `fetch: false` skips the per-request fetch of the two refs — for a caller
|
|
254
|
+
* that has just refreshed the whole clone's remote-tracking refs in one
|
|
255
|
+
* round trip, which is what a LIST does rather than paying one fetch per
|
|
256
|
+
* request. Pass it only when that is true; otherwise the diff can describe
|
|
257
|
+
* a stale head. It is a skip, not a promise: a branch the clone does not
|
|
258
|
+
* have yet is fetched anyway, since there is nothing to diff without it —
|
|
259
|
+
* so a list's first sight of a new request still costs one round trip.
|
|
260
|
+
*/
|
|
261
|
+
changedPathsForPr(
|
|
262
|
+
workspaceId: string,
|
|
263
|
+
baseBranch: string,
|
|
264
|
+
headBranch: string,
|
|
265
|
+
opts?: { fetch?: boolean },
|
|
266
|
+
): Promise<string[]>;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* A change request's fork point (merge base of the two resolved commits)
|
|
270
|
+
* and whether the target has commits the proposal does not contain. No
|
|
271
|
+
* fetch: `at` is what `resolvePrShas` just returned.
|
|
252
272
|
*/
|
|
253
|
-
|
|
273
|
+
forkPointForPr(
|
|
274
|
+
workspaceId: string,
|
|
275
|
+
at: { baseSha: string; headSha: string },
|
|
276
|
+
): Promise<{ mergeBaseSha: string | null; behind: boolean }>;
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* A file's content at a change request's fork point — a commit that must
|
|
280
|
+
* be on `baseBranch`'s history. `null` when the path did not exist there.
|
|
281
|
+
*/
|
|
282
|
+
readFileAtForkPoint(
|
|
283
|
+
workspaceId: string,
|
|
284
|
+
baseBranch: string,
|
|
285
|
+
sha: string,
|
|
286
|
+
relativePath: string,
|
|
287
|
+
): Promise<string | null>;
|
|
254
288
|
}
|
package/src/index.ts
CHANGED
|
@@ -6,10 +6,16 @@ export * from './chat/types.js';
|
|
|
6
6
|
|
|
7
7
|
// Workspace
|
|
8
8
|
export * from './workspace/types.js';
|
|
9
|
+
export * from './workspace/agent-preamble.js';
|
|
9
10
|
export * from './workspace/filename.js';
|
|
11
|
+
export * from './workspace/entry-exists.js';
|
|
10
12
|
export * from './workspace/kb-layout.js';
|
|
11
13
|
export * from './workspace/join-request.js';
|
|
12
14
|
export * from './workspace/frontmatter.js';
|
|
15
|
+
export * from './workspace/placeholder.js';
|
|
16
|
+
export * from './workspace/frontmatter-carriers.js';
|
|
17
|
+
export * from './workspace/platform-files.js';
|
|
18
|
+
export * from './workspace/access-verbs.js';
|
|
13
19
|
|
|
14
20
|
// Git
|
|
15
21
|
export * from './git/types.js';
|
package/src/workflow/events.ts
CHANGED
|
@@ -221,6 +221,28 @@ export interface ChangeRequestRejectedEvent {
|
|
|
221
221
|
number: number;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
/**
|
|
225
|
+
* An apply did NOT land, announced to EVERY session — the counterpart of the
|
|
226
|
+
* user-scoped `change-request-merge-failed` below, which only the clicker gets.
|
|
227
|
+
*
|
|
228
|
+
* The request stays open, so everyone who can see it (its author waiting on
|
|
229
|
+
* the verdict, another owner, another admin) needs to learn that the attempt
|
|
230
|
+
* failed and why. The reason is deliberately NOT carried here: it is persisted
|
|
231
|
+
* on the request (`PullRequestSummary.lastApplyFailure`) and read back through
|
|
232
|
+
* the list and detail endpoints the viewer already uses, so the broadcast puts
|
|
233
|
+
* no error text in front of every session, and a session that missed the event
|
|
234
|
+
* reads the same answer on its next fetch.
|
|
235
|
+
*
|
|
236
|
+
* Also sent when a recorded refusal is CLEARED because a change made it
|
|
237
|
+
* obsolete (an approval for a gate refusal, a moved source head for any):
|
|
238
|
+
* either way the request's `lastApplyFailure` changed, and every viewer
|
|
239
|
+
* re-reads it.
|
|
240
|
+
*/
|
|
241
|
+
export interface ChangeRequestApplyFailedEvent {
|
|
242
|
+
kind: 'change-request-apply-failed';
|
|
243
|
+
number: number;
|
|
244
|
+
}
|
|
245
|
+
|
|
224
246
|
/**
|
|
225
247
|
* A merge the caller triggered did NOT land — either the gate refused it, the
|
|
226
248
|
* branch needs conflict resolution, or `gh pr merge` failed. The merge route
|
|
@@ -228,9 +250,10 @@ export interface ChangeRequestRejectedEvent {
|
|
|
228
250
|
* gateway timeout), so this is how the failure reaches the UI that kicked it
|
|
229
251
|
* off. Success travels on `change-request-merged` instead.
|
|
230
252
|
*
|
|
231
|
-
* User-scoped (`forUserId`, no `workspaceId`) —
|
|
232
|
-
*
|
|
233
|
-
*
|
|
253
|
+
* User-scoped (`forUserId`, no `workspaceId`) — the clicker's immediate
|
|
254
|
+
* answer, which also routes a conflict into the resolution flow. Everyone else
|
|
255
|
+
* learns of the failure from `change-request-apply-failed`, which carries no
|
|
256
|
+
* error string; the reason itself is persisted on the request.
|
|
234
257
|
*/
|
|
235
258
|
export interface ChangeRequestMergeFailedEvent {
|
|
236
259
|
kind: 'change-request-merge-failed';
|
|
@@ -243,6 +266,12 @@ export interface ChangeRequestMergeFailedEvent {
|
|
|
243
266
|
* The UI routes to the agent resolution flow instead of showing an error.
|
|
244
267
|
*/
|
|
245
268
|
conflicts: boolean;
|
|
269
|
+
/**
|
|
270
|
+
* When the attempt failed (ISO) — the same instant persisted as
|
|
271
|
+
* `lastApplyFailure.at` when the refusal is recorded, so the clicker's tab
|
|
272
|
+
* can tell its own refusal from a later one somebody else's attempt made.
|
|
273
|
+
*/
|
|
274
|
+
at?: string;
|
|
246
275
|
}
|
|
247
276
|
|
|
248
277
|
/**
|
|
@@ -296,6 +325,7 @@ export type WorkflowEventPayload =
|
|
|
296
325
|
| ChangeRequestMergedEvent
|
|
297
326
|
| ChangeRequestRejectedEvent
|
|
298
327
|
| ChangeRequestMergeFailedEvent
|
|
328
|
+
| ChangeRequestApplyFailedEvent
|
|
299
329
|
| ApprovalChangedEvent
|
|
300
330
|
| HeartbeatEvent
|
|
301
331
|
| ResyncEvent;
|