@bevel-software/platform-core-backend 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/dist/core/core-ports.d.ts +7 -0
  2. package/dist/core/core-ports.d.ts.map +1 -1
  3. package/dist/core/core-ports.js.map +1 -1
  4. package/dist/core/create-core-server.d.ts.map +1 -1
  5. package/dist/core/create-core-server.js +24 -11
  6. package/dist/core/create-core-server.js.map +1 -1
  7. package/dist/core/create-core-services.d.ts +7 -2
  8. package/dist/core/create-core-services.d.ts.map +1 -1
  9. package/dist/core/create-core-services.js +39 -18
  10. package/dist/core/create-core-services.js.map +1 -1
  11. package/dist/modules/access/access.routes.d.ts +3 -1
  12. package/dist/modules/access/access.routes.d.ts.map +1 -1
  13. package/dist/modules/access/access.routes.js +4 -2
  14. package/dist/modules/access/access.routes.js.map +1 -1
  15. package/dist/modules/access/render-roles-yaml.d.ts +22 -0
  16. package/dist/modules/access/render-roles-yaml.d.ts.map +1 -0
  17. package/dist/modules/access/render-roles-yaml.js +56 -0
  18. package/dist/modules/access/render-roles-yaml.js.map +1 -0
  19. package/dist/modules/access/roles-admin.service.d.ts +15 -1
  20. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  21. package/dist/modules/access/roles-admin.service.js +30 -15
  22. package/dist/modules/access/roles-admin.service.js.map +1 -1
  23. package/dist/modules/settings/setup.routes.d.ts +10 -1
  24. package/dist/modules/settings/setup.routes.d.ts.map +1 -1
  25. package/dist/modules/settings/setup.routes.js +111 -6
  26. package/dist/modules/settings/setup.routes.js.map +1 -1
  27. package/dist/modules/workspace/startup/kb-git.d.ts +23 -0
  28. package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -0
  29. package/dist/modules/workspace/startup/kb-git.js +86 -0
  30. package/dist/modules/workspace/startup/kb-git.js.map +1 -0
  31. package/dist/modules/workspace/startup/kb-startup-runner.d.ts +74 -0
  32. package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -0
  33. package/dist/modules/workspace/startup/kb-startup-runner.js +528 -0
  34. package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -0
  35. package/dist/modules/workspace/startup/on-server-start.d.ts +105 -0
  36. package/dist/modules/workspace/startup/on-server-start.d.ts.map +1 -0
  37. package/dist/modules/workspace/startup/on-server-start.js +21 -0
  38. package/dist/modules/workspace/startup/on-server-start.js.map +1 -0
  39. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts +46 -0
  40. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts.map +1 -0
  41. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js +492 -0
  42. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js.map +1 -0
  43. package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts +23 -0
  44. package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts.map +1 -0
  45. package/dist/modules/workspace/startup/steps/roles-yaml.step.js +69 -0
  46. package/dist/modules/workspace/startup/steps/roles-yaml.step.js.map +1 -0
  47. package/dist/modules/workspace/startup/steps/seed-tree.d.ts +17 -0
  48. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -0
  49. package/dist/modules/workspace/startup/steps/seed-tree.js +109 -0
  50. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -0
  51. package/dist/modules/workspace/startup/steps/template-files.step.d.ts +103 -0
  52. package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -0
  53. package/dist/modules/workspace/startup/steps/template-files.step.js +337 -0
  54. package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -0
  55. package/dist/modules/workspace/workspace.service.d.ts +0 -35
  56. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  57. package/dist/modules/workspace/workspace.service.js +2 -101
  58. package/dist/modules/workspace/workspace.service.js.map +1 -1
  59. package/kb-template/gitignore.template +16 -0
  60. package/package.json +3 -3
  61. package/src/core/core-ports.ts +7 -0
  62. package/src/core/create-core-server.ts +30 -11
  63. package/src/core/create-core-services.ts +43 -22
  64. package/src/modules/access/__tests__/roles-admin.service.test.ts +24 -2
  65. package/src/modules/access/access.routes.ts +3 -0
  66. package/src/modules/access/render-roles-yaml.ts +65 -0
  67. package/src/modules/access/roles-admin.service.ts +32 -14
  68. package/src/modules/settings/__tests__/setup.routes.test.ts +124 -3
  69. package/src/modules/settings/setup.routes.ts +112 -5
  70. package/src/modules/workspace/__tests__/workspace.service.test.ts +5 -94
  71. package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +495 -0
  72. package/src/modules/workspace/startup/kb-git.ts +94 -0
  73. package/src/modules/workspace/startup/kb-startup-runner.ts +597 -0
  74. package/src/modules/workspace/startup/on-server-start.ts +97 -0
  75. package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +636 -0
  76. package/src/modules/workspace/{plugins-migration.ts → startup/steps/groups-to-plugins.step.ts} +342 -260
  77. package/src/modules/workspace/startup/steps/roles-yaml.step.ts +71 -0
  78. package/src/modules/workspace/startup/steps/seed-tree.ts +115 -0
  79. package/src/modules/workspace/startup/steps/template-files.step.ts +360 -0
  80. package/src/modules/workspace/workspace.service.ts +2 -106
  81. package/src/modules/workspace/__tests__/kb-seed.service.test.ts +0 -512
  82. package/src/modules/workspace/__tests__/plugins-migration.test.ts +0 -427
  83. package/src/modules/workspace/kb-seed.interface.ts +0 -36
  84. package/src/modules/workspace/kb-seed.service.ts +0 -584
@@ -0,0 +1,109 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { renderRolesYaml } from '../../../access/render-roles-yaml.js';
4
+ import { TEMPLATE_SOURCE_FALLBACKS, reservedRootDirs, templateSource } from './template-files.step.js';
5
+ /**
6
+ * The empty-remote seed builder the runner takes as `buildSeedTree`: the full
7
+ * template tree, every reserved root, and a generated roles.yaml. DIRECT fs
8
+ * writes are correct here — the target is a temp directory the runner inits,
9
+ * commits and pushes itself, not a branch handle with buffered ops.
10
+ *
11
+ * Resolves to the repo-relative paths it GENERATED (roles.yaml plus each
12
+ * reserved root's .gitkeep): a template `.gitignore` rule could match any of
13
+ * them, and the runner force-adds them after `git add -A` so a required seed
14
+ * file can never be silently dropped from the seed commit.
15
+ *
16
+ * `extraRootDirs` is validated eagerly, at composition time: a bad value
17
+ * should fail at boot beside the rest of the wiring, not mid-seed of
18
+ * somebody's knowledge base.
19
+ */
20
+ export function buildSeedTree(templateDir, extraRootDirs, seedAdminEmails) {
21
+ const requiredDirs = reservedRootDirs(extraRootDirs);
22
+ return async (dir) => {
23
+ const generated = [];
24
+ await copyTemplateTree(templateDir, dir);
25
+ // Reserved roots the template does not carry. Without this the seed commit
26
+ // would hold only what the template has, and a distribution's own roots
27
+ // would appear a step later, when the first startup phase tops them up —
28
+ // the same folders, arriving in a second commit for no reason. Keyed on
29
+ // the DIRECTORY's existence: a template already carrying content under a
30
+ // root never gets a pointless placeholder beside it.
31
+ for (const rootDir of requiredDirs) {
32
+ const abs = path.join(dir, rootDir);
33
+ const found = await lstatOrNull(abs);
34
+ if (found) {
35
+ if (found.isDirectory())
36
+ continue;
37
+ // Only a template shipping a FILE under a reserved name reaches this —
38
+ // a broken build, not a broken knowledge base.
39
+ throw new Error(`KB root "${rootDir}" exists in the template but is not a directory.`);
40
+ }
41
+ await fs.mkdir(abs, { recursive: true });
42
+ await fs.writeFile(path.join(abs, '.gitkeep'), '', 'utf8');
43
+ generated.push(`${rootDir}/.gitkeep`);
44
+ }
45
+ // Generated, never templated — see roles-yaml.step.ts. The runner refuses
46
+ // to seed an empty remote with no admins, so the list is non-empty here.
47
+ await fs.writeFile(path.join(dir, 'roles.yaml'), renderRolesYaml(seedAdminEmails), 'utf8');
48
+ generated.push('roles.yaml');
49
+ return generated;
50
+ };
51
+ }
52
+ /** Copy the entire template tree into `dest` (roles.yaml isn't in it — it's generated). */
53
+ async function copyTemplateTree(templateDir, dest) {
54
+ const packableToReal = new Map(Object.entries(TEMPLATE_SOURCE_FALLBACKS).map(([real, packable]) => [packable, real]));
55
+ const walk = async (relDir) => {
56
+ const abs = path.join(templateDir, relDir);
57
+ const entries = await fs.readdir(abs, { withFileTypes: true });
58
+ for (const entry of entries) {
59
+ // Never copy a git dir: a KB_TEMPLATE_DIR that is itself a working tree
60
+ // (this repo in a Docker build) must not seed its history into the KB.
61
+ if (entry.name === '.git')
62
+ continue;
63
+ const rel = relDir ? path.join(relDir, entry.name) : entry.name;
64
+ if (entry.isDirectory()) {
65
+ await walk(rel);
66
+ continue;
67
+ }
68
+ // A packable spelling at the template root seeds under its REAL name
69
+ // — unless the template also carries the literal file (a
70
+ // distribution's own template), which wins and is copied by its own
71
+ // walk entry; copying the packable twin too would clobber it.
72
+ const realName = relDir === '' ? packableToReal.get(entry.name) : undefined;
73
+ if (realName !== undefined) {
74
+ if (!(await exists(path.join(templateDir, realName)))) {
75
+ await copyTemplateFile(templateDir, realName, dest);
76
+ }
77
+ continue;
78
+ }
79
+ await copyTemplateFile(templateDir, rel, dest);
80
+ }
81
+ };
82
+ await walk('');
83
+ }
84
+ /** Copy one template file (by repo-relative path) into `dest`, creating parents. */
85
+ async function copyTemplateFile(templateDir, relPath, dest) {
86
+ const from = await templateSource(templateDir, relPath);
87
+ const to = path.join(dest, relPath);
88
+ await fs.mkdir(path.dirname(to), { recursive: true });
89
+ await fs.copyFile(from, to);
90
+ }
91
+ async function exists(p) {
92
+ try {
93
+ await fs.access(p);
94
+ return true;
95
+ }
96
+ catch {
97
+ return false;
98
+ }
99
+ }
100
+ /** `lstat` without the throw — null when nothing is at `p`. */
101
+ async function lstatOrNull(p) {
102
+ try {
103
+ return await fs.lstat(p);
104
+ }
105
+ catch {
106
+ return null;
107
+ }
108
+ }
109
+ //# sourceMappingURL=seed-tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed-tree.js","sourceRoot":"","sources":["../../../../../src/modules/workspace/startup/steps/seed-tree.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ import type { OnServerStart, ServerStartContext, StepResult } from '../on-server-start.js';
2
+ /**
3
+ * The **required scaffolding** — the minimum an operational KB needs. Any of
4
+ * these missing from a protected branch are added at the startup phase; the
5
+ * sample ontology is NOT (it only seeds a fully-empty repo, see seed-tree.ts).
6
+ *
7
+ * Two kinds:
8
+ * - {@link REQUIRED_FILES}: repo-root files added when the file is missing.
9
+ * - Reserved root dirs (core's two plus a distribution's `extraRootDirs`):
10
+ * when a dir is entirely absent it's created by adding its `<dir>/.gitkeep`.
11
+ * Keyed on the *directory's* existence, not the `.gitkeep` file — so a
12
+ * branch that already has content under `KnowledgeBase/` never gets a
13
+ * pointless placeholder.
14
+ *
15
+ * `roles.yaml` is in neither, and is not part of the template at all: it is
16
+ * generated from `ADMIN_EMAIL` (see roles-yaml.step.ts), so a repo can't be
17
+ * seeded with a stale hard-coded Admin list.
18
+ */
19
+ export declare const REQUIRED_FILES: readonly string[];
20
+ /**
21
+ * Repo-root files the startup phase GENERATES rather than copies — today just
22
+ * `roles.yaml`, rendered from `ADMIN_EMAIL` (see roles-yaml.step.ts and
23
+ * seed-tree.ts). Reserved-root validation must treat these exactly like
24
+ * {@link REQUIRED_FILES}: a root claiming a generated name is the same silent
25
+ * typo with the same silent outcome.
26
+ */
27
+ export declare const GENERATED_FILES: readonly string[];
28
+ /**
29
+ * Destination name → the packable spelling the template may carry instead.
30
+ * npm strips every file named `.gitignore` from a published tarball, so the
31
+ * packaged template cannot ship one under its real name (see
32
+ * {@link templateSource}).
33
+ */
34
+ export declare const TEMPLATE_SOURCE_FALLBACKS: Readonly<Record<string, string>>;
35
+ /**
36
+ * Core's guaranteed roots plus a distribution's extras, validated once at
37
+ * composition time: every entry is joined onto the repo root and onto
38
+ * `<dir>/.gitkeep`, so a separator or a `..` would write outside the repo
39
+ * being maintained, and a bad value should fail at boot beside the rest of
40
+ * the wiring rather than part-way through maintaining somebody's knowledge
41
+ * base. Shared with the empty-remote seed builder (seed-tree.ts) so the two
42
+ * paths can never disagree about what a deployment guarantees.
43
+ */
44
+ export declare function reservedRootDirs(extraRootDirs: readonly string[]): readonly string[];
45
+ /**
46
+ * Where `relPath`'s template content actually lives. npm refuses to pack
47
+ * files named `.gitignore` — every such file is silently stripped from the
48
+ * published tarball — so the packaged template ships the KB's gitignore
49
+ * under a packable name and the seeder writes it to its real one. A
50
+ * template carrying the literal file (a distribution's own
51
+ * KB_TEMPLATE_DIR, or this repo's tree in a Docker build) wins outright:
52
+ * the mapping is a fallback, never a rename.
53
+ */
54
+ export declare function templateSource(templateDir: string, relPath: string): Promise<string>;
55
+ /**
56
+ * The template top-up as an {@link OnServerStart} step: add any missing base
57
+ * scaffolding to every PROTECTED branch, and keep the managed AGENTS.md
58
+ * current. Drafts are deliberately out of scope — whatever the protected
59
+ * branches gain, drafts fork from; a scaffolding addition on a draft would
60
+ * surface as noise in its change request's diff. (Unlike the Groups→Plugins
61
+ * rename, a missing file diffs as one file, not the whole tree — so the
62
+ * uniform-application argument does not bite here.)
63
+ *
64
+ * Everything is DECLARED on the branch handle; reads go against the pre-step
65
+ * tree via `repoDir()`. Fail-open behavior from the lazy top-up (best-effort,
66
+ * never throws) is deliberately gone: an unexpected state — a file squatting
67
+ * a reserved root name — now throws and stops the boot, which is the phase's
68
+ * contract for states a human must look at.
69
+ */
70
+ export declare class TemplateFilesStep implements OnServerStart {
71
+ readonly name = "template-files";
72
+ private readonly requiredDirs;
73
+ /**
74
+ * @param extraRootDirs Additional root folders this distribution reserves,
75
+ * on top of core's two. Their `.gitkeep` is written
76
+ * directly rather than copied, so a distribution can
77
+ * claim a root without also shipping a template entry
78
+ * for it.
79
+ */
80
+ constructor(extraRootDirs?: readonly string[]);
81
+ run(ctx: ServerStartContext): Promise<StepResult>;
82
+ private topUp;
83
+ /**
84
+ * Which reserved roots are absent — and which are SQUATTED. `lstat`, not
85
+ * `exists`: `fs.access` answers "is there something here?", which is true of
86
+ * a FILE named `Plugins` — and a skip-if-present check would then do nothing
87
+ * and report success, leaving a knowledge base permanently missing a root it
88
+ * claims to guarantee. `lstat` rather than `stat` so a SYMLINK is rejected
89
+ * too: a link named `Plugins` is not a KB layout, and one pointing outside
90
+ * the repo would make every later write into it land somewhere nobody asked
91
+ * for. A squatter THROWS — under this phase's fail-closed contract that
92
+ * stops the boot, which such a state deserves.
93
+ */
94
+ private missingDirs;
95
+ /**
96
+ * Declare each missing reserved root as an empty `<dir>/.gitkeep`.
97
+ * WRITTEN, not copied from the template: a `.gitkeep` is empty by
98
+ * definition, and requiring a template entry per root would mean a
99
+ * distribution could not reserve one without forking the packaged template.
100
+ */
101
+ private ensureRequiredDirs;
102
+ }
103
+ //# sourceMappingURL=template-files.step.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-files.step.d.ts","sourceRoot":"","sources":["../../../../../src/modules/workspace/startup/steps/template-files.step.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAY,aAAa,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAErG;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,cAAc,EAAE,SAAS,MAAM,EAA6D,CAAC;AAE1G;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,MAAM,EAAmB,CAAC;AAEjE;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAEtE,CAAC;AAwCF;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM,EAAE,CAcpF;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAS1F;AAoBD;;;;;;;;;;;;;;GAcG;AACH,qBAAa,iBAAkB,YAAW,aAAa;IACrD,QAAQ,CAAC,IAAI,oBAAoB;IAEjC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoB;IAEjD;;;;;;OAMG;gBACS,aAAa,GAAE,SAAS,MAAM,EAAO;IAI3C,GAAG,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,UAAU,CAAC;YAOzC,KAAK;IAoEnB;;;;;;;;;;OAUG;YACW,WAAW;IAiBzB;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;CAQ3B"}
@@ -0,0 +1,337 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { KNOWLEDGE_BASE_DIR, PLUGINS_DIR } from '@bevel-software/platform-shared';
4
+ import { IGNORE_FILENAME } from '../../bevel-ignore.js';
5
+ /**
6
+ * The **required scaffolding** — the minimum an operational KB needs. Any of
7
+ * these missing from a protected branch are added at the startup phase; the
8
+ * sample ontology is NOT (it only seeds a fully-empty repo, see seed-tree.ts).
9
+ *
10
+ * Two kinds:
11
+ * - {@link REQUIRED_FILES}: repo-root files added when the file is missing.
12
+ * - Reserved root dirs (core's two plus a distribution's `extraRootDirs`):
13
+ * when a dir is entirely absent it's created by adding its `<dir>/.gitkeep`.
14
+ * Keyed on the *directory's* existence, not the `.gitkeep` file — so a
15
+ * branch that already has content under `KnowledgeBase/` never gets a
16
+ * pointless placeholder.
17
+ *
18
+ * `roles.yaml` is in neither, and is not part of the template at all: it is
19
+ * generated from `ADMIN_EMAIL` (see roles-yaml.step.ts), so a repo can't be
20
+ * seeded with a stale hard-coded Admin list.
21
+ */
22
+ export const REQUIRED_FILES = ['access.md', 'AGENTS.md', '.bevelignore', '.gitignore'];
23
+ /**
24
+ * Repo-root files the startup phase GENERATES rather than copies — today just
25
+ * `roles.yaml`, rendered from `ADMIN_EMAIL` (see roles-yaml.step.ts and
26
+ * seed-tree.ts). Reserved-root validation must treat these exactly like
27
+ * {@link REQUIRED_FILES}: a root claiming a generated name is the same silent
28
+ * typo with the same silent outcome.
29
+ */
30
+ export const GENERATED_FILES = ['roles.yaml'];
31
+ /**
32
+ * Destination name → the packable spelling the template may carry instead.
33
+ * npm strips every file named `.gitignore` from a published tarball, so the
34
+ * packaged template cannot ship one under its real name (see
35
+ * {@link templateSource}).
36
+ */
37
+ export const TEMPLATE_SOURCE_FALLBACKS = {
38
+ '.gitignore': 'gitignore.template',
39
+ };
40
+ /**
41
+ * The two roots CORE gives a knowledge base: the ontologies, and the plugins
42
+ * that hold skills and tools.
43
+ *
44
+ * `Data/`, `Agents/` and `Pipelines/` are deliberately absent. They scaffold
45
+ * the agentic execution layer, which is not part of this platform — a core
46
+ * deployment that created them would be handing every operator three empty
47
+ * folders it has no feature to fill. A distribution that DOES own that layer
48
+ * passes them as `extraRootDirs` (and ships a template carrying their
49
+ * READMEs); the names stay reserved in `kb-layout.ts` either way, so a KB
50
+ * that has them still renders them as roots rather than folding them into
51
+ * Knowledge.
52
+ */
53
+ const CORE_REQUIRED_DIRS = [KNOWLEDGE_BASE_DIR, PLUGINS_DIR];
54
+ /**
55
+ * A reserved root must be ONE path segment — `Data`, not `Data/x`, `../x` or
56
+ * `/x`. The name is joined onto the repo root, so anything else writes outside
57
+ * the repo being maintained.
58
+ *
59
+ * Deliberately NOT a check against the reserved-root set in `kb-layout.ts`:
60
+ * `Data`, `Agents` and `Pipelines` are all in that set, and they are precisely
61
+ * what a distribution passes here. Being reserved is what makes a name worth
62
+ * claiming — the file tree renders it as its own root instead of folding it
63
+ * into Knowledge — so rejecting reserved names would reject the only real use.
64
+ */
65
+ function assertRootSegment(dir) {
66
+ if (!dir || dir === '.' || dir === '..' || dir.includes('/') || dir.includes('\\') || path.isAbsolute(dir)) {
67
+ throw new Error(`Reserved KB root must be a single path segment (no separators, no ".."); got "${dir}"`);
68
+ }
69
+ // `.git` can never be a KB root: writing `<dir>/.gitkeep` under it would
70
+ // corrupt the clone's own metadata. Any case — Windows filesystems treat
71
+ // `.GIT` as the same directory.
72
+ if (dir.toLowerCase() === '.git') {
73
+ throw new Error(`Reserved KB root must not be ".git" (any case); got "${dir}"`);
74
+ }
75
+ }
76
+ /**
77
+ * Core's guaranteed roots plus a distribution's extras, validated once at
78
+ * composition time: every entry is joined onto the repo root and onto
79
+ * `<dir>/.gitkeep`, so a separator or a `..` would write outside the repo
80
+ * being maintained, and a bad value should fail at boot beside the rest of
81
+ * the wiring rather than part-way through maintaining somebody's knowledge
82
+ * base. Shared with the empty-remote seed builder (seed-tree.ts) so the two
83
+ * paths can never disagree about what a deployment guarantees.
84
+ */
85
+ export function reservedRootDirs(extraRootDirs) {
86
+ for (const dir of extraRootDirs) {
87
+ assertRootSegment(dir);
88
+ // A root named after a required OR generated FILE is a typo with a silent
89
+ // outcome: the file is laid down first, so the dir check finds the path
90
+ // taken and skips it, and the directory the caller asked for never appears
91
+ // with nothing said about why.
92
+ if (REQUIRED_FILES.includes(dir) || GENERATED_FILES.includes(dir)) {
93
+ throw new Error(`Reserved KB root "${dir}" collides with a required or generated file of the same name`);
94
+ }
95
+ }
96
+ return [...CORE_REQUIRED_DIRS, ...extraRootDirs];
97
+ }
98
+ /**
99
+ * Where `relPath`'s template content actually lives. npm refuses to pack
100
+ * files named `.gitignore` — every such file is silently stripped from the
101
+ * published tarball — so the packaged template ships the KB's gitignore
102
+ * under a packable name and the seeder writes it to its real one. A
103
+ * template carrying the literal file (a distribution's own
104
+ * KB_TEMPLATE_DIR, or this repo's tree in a Docker build) wins outright:
105
+ * the mapping is a fallback, never a rename.
106
+ */
107
+ export async function templateSource(templateDir, relPath) {
108
+ const direct = path.join(templateDir, relPath);
109
+ if (await exists(direct))
110
+ return direct;
111
+ const packable = TEMPLATE_SOURCE_FALLBACKS[relPath];
112
+ if (packable !== undefined) {
113
+ const fallback = path.join(templateDir, packable);
114
+ if (await exists(fallback))
115
+ return fallback;
116
+ }
117
+ return direct; // let the ENOENT surface under the name the caller asked for
118
+ }
119
+ async function exists(p) {
120
+ try {
121
+ await fs.access(p);
122
+ return true;
123
+ }
124
+ catch {
125
+ return false;
126
+ }
127
+ }
128
+ /** `lstat` without the throw — null when nothing is at `p`. */
129
+ async function lstatOrNull(p) {
130
+ try {
131
+ return await fs.lstat(p);
132
+ }
133
+ catch {
134
+ return null;
135
+ }
136
+ }
137
+ /**
138
+ * The template top-up as an {@link OnServerStart} step: add any missing base
139
+ * scaffolding to every PROTECTED branch, and keep the managed AGENTS.md
140
+ * current. Drafts are deliberately out of scope — whatever the protected
141
+ * branches gain, drafts fork from; a scaffolding addition on a draft would
142
+ * surface as noise in its change request's diff. (Unlike the Groups→Plugins
143
+ * rename, a missing file diffs as one file, not the whole tree — so the
144
+ * uniform-application argument does not bite here.)
145
+ *
146
+ * Everything is DECLARED on the branch handle; reads go against the pre-step
147
+ * tree via `repoDir()`. Fail-open behavior from the lazy top-up (best-effort,
148
+ * never throws) is deliberately gone: an unexpected state — a file squatting
149
+ * a reserved root name — now throws and stops the boot, which is the phase's
150
+ * contract for states a human must look at.
151
+ */
152
+ export class TemplateFilesStep {
153
+ name = 'template-files';
154
+ requiredDirs;
155
+ /**
156
+ * @param extraRootDirs Additional root folders this distribution reserves,
157
+ * on top of core's two. Their `.gitkeep` is written
158
+ * directly rather than copied, so a distribution can
159
+ * claim a root without also shipping a template entry
160
+ * for it.
161
+ */
162
+ constructor(extraRootDirs = []) {
163
+ this.requiredDirs = reservedRootDirs(extraRootDirs);
164
+ }
165
+ async run(ctx) {
166
+ for (const branch of await ctx.protectedBranches()) {
167
+ await this.topUp(ctx.templateDir, branch);
168
+ }
169
+ return { outcome: 'ok' };
170
+ }
171
+ async topUp(templateDir, branch) {
172
+ const repoDir = await branch.repoDir();
173
+ const added = [];
174
+ for (const rel of REQUIRED_FILES) {
175
+ // `lstat`, not `exists`: a DIRECTORY or SYMLINK squatting a required
176
+ // file's name would read as "present", and a skip-if-present check
177
+ // would then report success over a knowledge base whose root access
178
+ // policy (say) cannot be read. Fail-closed, same as the reserved-root
179
+ // squatting check below: this is a state a human must fix.
180
+ const found = await lstatOrNull(path.join(repoDir, rel));
181
+ if (found) {
182
+ if (found.isFile())
183
+ continue;
184
+ throw new Error(`Required KB file "${rel}" on branch "${branch.name}" exists but is not a regular file ` +
185
+ `(${found.isSymbolicLink() ? 'symlink' : found.isDirectory() ? 'directory' : 'special file'}). ` +
186
+ 'Remove or rename it — the platform requires this name to be a readable file.');
187
+ }
188
+ let content = await readTemplate(templateDir, rel);
189
+ // The on-disk merge below only runs against an EXISTING ignore file; a
190
+ // freshly-declared one was merely assumed to carry the AGENTS.md rule —
191
+ // true of the packaged template, not necessarily of a distribution's
192
+ // custom one. Make it true here, so the managed conventions doc is
193
+ // hidden from the file tree from the first boot either way.
194
+ if (rel === IGNORE_FILENAME) {
195
+ content = withIgnorePattern(new TextDecoder().decode(content), 'AGENTS.md');
196
+ }
197
+ branch.write(rel, content);
198
+ added.push(rel);
199
+ }
200
+ // AGENTS.md left VISIBLE by a stale `.bevelignore` is closed here — and
201
+ // UNCONDITIONALLY, not only when the file was just added: a KB whose
202
+ // AGENTS.md predates the CLAUDE.md→AGENTS.md rename has an ignore file
203
+ // that lists the old name and knows nothing of the new one, so the
204
+ // conventions doc shows up in the file tree and the agent view.
205
+ // Idempotent: an ignore file already carrying the rule — or absent, in
206
+ // which case the template's copy declared above arrives with the rule in
207
+ // it — changes nothing and produces no note. Deliberately checked by
208
+ // LINE PRESENCE, not effective outcome: a later `!AGENTS.md` negation is
209
+ // the operator explicitly choosing to SHOW the file, and hiding it is a
210
+ // default this step provides, not a mandate it re-imposes every boot.
211
+ added.push(...(await mergeIgnorePattern(repoDir, branch, 'AGENTS.md')));
212
+ // AGENTS.md is MANAGED, not merely seeded: the platform owns its content,
213
+ // and a stale copy is replaced with the packaged template's every startup
214
+ // phase. The file's own header says so, which is what makes overwriting
215
+ // edits a stated contract instead of a surprise.
216
+ let agentsRefreshed = false;
217
+ if (!added.includes('AGENTS.md') && (await templateDiffers(templateDir, repoDir, 'AGENTS.md'))) {
218
+ branch.write('AGENTS.md', await readTemplate(templateDir, 'AGENTS.md'));
219
+ added.push('AGENTS.md');
220
+ agentsRefreshed = true;
221
+ }
222
+ added.push(...this.ensureRequiredDirs(repoDir, branch, await this.missingDirs(repoDir)));
223
+ if (added.length === 0)
224
+ return;
225
+ // One honest line; it becomes the commit subject when this step is the
226
+ // first to dirty the branch.
227
+ branch.note(agentsRefreshed && added.length === 1
228
+ ? 'Update AGENTS.md to the current platform template'
229
+ : `Add missing KB scaffolding: ${added.join(', ')}`);
230
+ }
231
+ /**
232
+ * Which reserved roots are absent — and which are SQUATTED. `lstat`, not
233
+ * `exists`: `fs.access` answers "is there something here?", which is true of
234
+ * a FILE named `Plugins` — and a skip-if-present check would then do nothing
235
+ * and report success, leaving a knowledge base permanently missing a root it
236
+ * claims to guarantee. `lstat` rather than `stat` so a SYMLINK is rejected
237
+ * too: a link named `Plugins` is not a KB layout, and one pointing outside
238
+ * the repo would make every later write into it land somewhere nobody asked
239
+ * for. A squatter THROWS — under this phase's fail-closed contract that
240
+ * stops the boot, which such a state deserves.
241
+ */
242
+ async missingDirs(repoDir) {
243
+ const missing = [];
244
+ for (const rootDir of this.requiredDirs) {
245
+ const found = await lstatOrNull(path.join(repoDir, rootDir));
246
+ if (found) {
247
+ if (found.isDirectory())
248
+ continue;
249
+ throw new Error(`KB root "${rootDir}" exists but is not a directory ` +
250
+ `(${found.isSymbolicLink() ? 'symlink' : 'file'}). Remove or rename it — ` +
251
+ 'the platform requires this name to be a folder.');
252
+ }
253
+ missing.push(rootDir);
254
+ }
255
+ return missing;
256
+ }
257
+ /**
258
+ * Declare each missing reserved root as an empty `<dir>/.gitkeep`.
259
+ * WRITTEN, not copied from the template: a `.gitkeep` is empty by
260
+ * definition, and requiring a template entry per root would mean a
261
+ * distribution could not reserve one without forking the packaged template.
262
+ */
263
+ ensureRequiredDirs(repoDir, branch, missing) {
264
+ const added = [];
265
+ for (const rootDir of missing) {
266
+ branch.write(`${rootDir}/.gitkeep`, '');
267
+ added.push(`${rootDir}/.gitkeep`);
268
+ }
269
+ return added;
270
+ }
271
+ }
272
+ /** The template's content for `relPath`, bytes as shipped. */
273
+ async function readTemplate(templateDir, relPath) {
274
+ return fs.readFile(await templateSource(templateDir, relPath));
275
+ }
276
+ /**
277
+ * Whether the repo's copy of `relPath` differs from the template's, modulo
278
+ * line endings — a CRLF checkout of identical content must read as "same",
279
+ * or the managed-file refresh would commit churn on every boot forever.
280
+ */
281
+ async function templateDiffers(templateDir, repoDir, relPath) {
282
+ const norm = (text) => text.replace(/\r\n?/g, '\n');
283
+ const [current, template] = await Promise.all([
284
+ fs.readFile(path.join(repoDir, relPath), 'utf8'),
285
+ templateSource(templateDir, relPath).then((from) => fs.readFile(from, 'utf8')),
286
+ ]);
287
+ return norm(current) !== norm(template);
288
+ }
289
+ /**
290
+ * Ensure `.bevelignore` carries `pattern`, declaring the appended content when
291
+ * absent. Returns the paths changed, for the note.
292
+ *
293
+ * APPENDS — never rewrites. The file is the operator's, and every rule
294
+ * already in it is theirs to keep; this adds one line under a comment saying
295
+ * where it came from. Absent file, or a file that already lists the pattern,
296
+ * is a no-op — an absent file means the template's copy (declared in the same
297
+ * step) arrives with the pattern in it.
298
+ *
299
+ * Matched line-wise rather than by substring: a rule for `Plugins/AGENTS.md`
300
+ * is not a rule for the root `AGENTS.md`, and treating it as one would leave
301
+ * the mismatch this exists to close.
302
+ */
303
+ async function mergeIgnorePattern(repoDir, branch, pattern) {
304
+ let current;
305
+ try {
306
+ current = await fs.readFile(path.join(repoDir, IGNORE_FILENAME), 'utf8');
307
+ }
308
+ catch {
309
+ // No ignore file — the copy declared from the template arrives with the
310
+ // pattern in it (guaranteed at declaration time, see the required-files
311
+ // loop above).
312
+ return [];
313
+ }
314
+ const merged = withIgnorePattern(current, pattern);
315
+ if (merged === current)
316
+ return [];
317
+ branch.write(IGNORE_FILENAME, merged);
318
+ return [IGNORE_FILENAME];
319
+ }
320
+ /**
321
+ * `text` with `pattern` guaranteed present as a LINE — appended under a
322
+ * comment naming its origin when absent, returned unchanged when present.
323
+ * Line-wise match, same rationale as {@link mergeIgnorePattern}.
324
+ *
325
+ * An explicit `!pattern` line also returns the text unchanged: that is the
326
+ * operator choosing to SHOW the file, and ordered matching means a positive
327
+ * line appended after it would win and silently defeat the choice. Hiding
328
+ * the conventions doc is a default this provides, never a mandate.
329
+ */
330
+ function withIgnorePattern(text, pattern) {
331
+ const lines = text.split('\n').map((l) => l.trim());
332
+ if (lines.includes(pattern) || lines.includes(`!${pattern}`))
333
+ return text;
334
+ const separator = text.endsWith('\n') ? '' : '\n';
335
+ return `${text}${separator}\n# Added by the platform: the conventions doc is not node content.\n${pattern}\n`;
336
+ }
337
+ //# sourceMappingURL=template-files.step.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-files.step.js","sourceRoot":"","sources":["../../../../../src/modules/workspace/startup/steps/template-files.step.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,5 @@
1
1
  import type { AuthUser, IWorkspaceService, WorkspaceInfo, FileTreeEntry } from '@bevel-software/platform-shared';
2
2
  import type { IDiffService } from '../diff/diff.interface.js';
3
- import type { IKbSeedService } from './kb-seed.interface.js';
4
3
  /**
5
4
  * Workspaces are per-branch, not per-user (PLAN §3). One on-disk clone per
6
5
  * branch name, shared by every user editing that branch — coordination of
@@ -67,21 +66,6 @@ export declare class WorkspaceService implements IWorkspaceService {
67
66
  * each other's working tree.
68
67
  */
69
68
  private readonly inFlightBootstraps;
70
- /**
71
- * Branches whose clone already got this process's scaffolding top-up.
72
- * Per-process on purpose: each boot of a new build re-offers the top-up
73
- * once, which is exactly when an upgrade's migrations must run. Cleared
74
- * wherever `branchDirs` forgets a branch (delete, failed bootstrap), so a
75
- * re-created clone is offered the top-up again rather than judged by the
76
- * fate of one that no longer exists.
77
- *
78
- * `inFlightTopUps` is the concurrency half: membership in the Set is
79
- * claimed BEFORE the (slow, file-moving) top-up finishes, so a concurrent
80
- * caller that finds the branch claimed must still await the in-flight run
81
- * — returning early would hand out a workspace mid-migration.
82
- */
83
- private readonly toppedUpBranches;
84
- private readonly inFlightTopUps;
85
69
  /** Per-workspace-dir `git fetch origin` timestamp + in-flight tracker. */
86
70
  private readonly lastFetchAt;
87
71
  private readonly inFlightFetches;
@@ -90,13 +74,6 @@ export declare class WorkspaceService implements IWorkspaceService {
90
74
  * WorkspaceService for path resolution.
91
75
  */
92
76
  private diffService;
93
- /**
94
- * Late-bound by the composition root. Awaited once before the first per-branch
95
- * clone so the KB remote is guaranteed to carry the protected branches + base
96
- * scaffolding — a fresh (or partially-populated) remote is seeded rather than
97
- * assumed. Null in tests / setups that pre-provision the remote themselves.
98
- */
99
- private seedService;
100
77
  /**
101
78
  * Late-bound by the composition root: notified with the workspace id
102
79
  * right after a branch's clone is created. The git layer uses this to
@@ -118,7 +95,6 @@ export declare class WorkspaceService implements IWorkspaceService {
118
95
  private readonly kbRepoUrl;
119
96
  private readonly gitUsername;
120
97
  setDiffService(diffService: IDiffService): void;
121
- setSeedService(seedService: IKbSeedService): void;
122
98
  setWorkspaceClonedListener(listener: (workspaceId: string) => void): void;
123
99
  /** Build git clone args that inject credentials via -c headers so the token never appears in the repo URL. */
124
100
  private gitCloneArgs;
@@ -182,17 +158,6 @@ export declare class WorkspaceService implements IWorkspaceService {
182
158
  * same keys before it pulls.
183
159
  */
184
160
  private normalizeCloneTracking;
185
- /**
186
- * The ONE spelling of the best-effort scaffolding top-up (three call sites:
187
- * the restart-survivor path in `getOrCreateForBranch`, and the
188
- * already-cloned / fresh-clone paths in `cloneProcessMapForBranch`). Once
189
- * per branch per process, and SINGLE-FLIGHT: the branch is claimed in
190
- * `toppedUpBranches` before the run starts, so a concurrent caller that
191
- * finds it claimed awaits the in-flight run instead of returning a
192
- * workspace the migration is still moving files under. Never throws — a
193
- * failed top-up costs the upgrade, not the clone.
194
- */
195
- private topUpScaffolding;
196
161
  private cloneProcessMapForBranch;
197
162
  getMetadata(workspaceId: string): Promise<WorkspaceInfo | null>;
198
163
  /**