@bevel-software/platform-core-backend 0.4.0 → 0.5.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/core/core-ports.d.ts +15 -0
- package/dist/core/core-ports.d.ts.map +1 -1
- package/dist/core/core-ports.js.map +1 -1
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +40 -6
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +17 -1
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +153 -29
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core-config.d.ts +70 -21
- package/dist/core-config.d.ts.map +1 -1
- package/dist/core-config.js +134 -58
- package/dist/core-config.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +6 -0
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +74 -5
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +116 -15
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access-declarations.d.ts +93 -0
- package/dist/modules/access/access-declarations.d.ts.map +1 -0
- package/dist/modules/access/access-declarations.js +168 -0
- package/dist/modules/access/access-declarations.js.map +1 -0
- package/dist/modules/access/access-mutation.service.js +4 -4
- package/dist/modules/access/access-mutation.service.js.map +1 -1
- package/dist/modules/access/access-splice.d.ts +16 -1
- package/dist/modules/access/access-splice.d.ts.map +1 -1
- package/dist/modules/access/access-splice.js +40 -21
- package/dist/modules/access/access-splice.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +51 -0
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/creator-access.d.ts +8 -0
- package/dist/modules/access/creator-access.d.ts.map +1 -1
- package/dist/modules/access/creator-access.js +21 -2
- package/dist/modules/access/creator-access.js.map +1 -1
- package/dist/modules/admin/admin-access.service.d.ts +1 -1
- package/dist/modules/admin/admin-access.service.d.ts.map +1 -1
- package/dist/modules/admin/admin-access.service.js +3 -2
- package/dist/modules/admin/admin-access.service.js.map +1 -1
- package/dist/modules/auth/auth.routes.d.ts.map +1 -1
- package/dist/modules/auth/auth.routes.js +50 -1
- package/dist/modules/auth/auth.routes.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +39 -10
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +38 -16
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +124 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +45 -0
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/groups/group-provision.service.d.ts +130 -0
- package/dist/modules/groups/group-provision.service.d.ts.map +1 -0
- package/dist/modules/groups/group-provision.service.js +288 -0
- package/dist/modules/groups/group-provision.service.js.map +1 -0
- package/dist/modules/groups/groups.contract.d.ts +106 -0
- package/dist/modules/groups/groups.contract.d.ts.map +1 -0
- package/dist/modules/groups/groups.contract.js +36 -0
- package/dist/modules/groups/groups.contract.js.map +1 -0
- package/dist/modules/groups/groups.routes.d.ts +42 -0
- package/dist/modules/groups/groups.routes.d.ts.map +1 -0
- package/dist/modules/groups/groups.routes.js +379 -0
- package/dist/modules/groups/groups.routes.js.map +1 -0
- package/dist/modules/groups/groups.service.d.ts +60 -0
- package/dist/modules/groups/groups.service.d.ts.map +1 -0
- package/dist/modules/groups/groups.service.js +172 -0
- package/dist/modules/groups/groups.service.js.map +1 -0
- package/dist/modules/groups/index.d.ts +7 -0
- package/dist/modules/groups/index.d.ts.map +1 -0
- package/dist/modules/groups/index.js +6 -0
- package/dist/modules/groups/index.js.map +1 -0
- package/dist/modules/groups/join-proposals.d.ts +53 -0
- package/dist/modules/groups/join-proposals.d.ts.map +1 -0
- package/dist/modules/groups/join-proposals.js +67 -0
- package/dist/modules/groups/join-proposals.js.map +1 -0
- package/dist/modules/groups/join-requests.service.d.ts +81 -0
- package/dist/modules/groups/join-requests.service.d.ts.map +1 -0
- package/dist/modules/groups/join-requests.service.js +135 -0
- package/dist/modules/groups/join-requests.service.js.map +1 -0
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +20 -0
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.js +59 -11
- package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.d.ts +129 -0
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -0
- package/dist/modules/settings/deployment-settings.service.js +368 -0
- package/dist/modules/settings/deployment-settings.service.js.map +1 -0
- package/dist/modules/settings/setup.routes.d.ts +49 -0
- package/dist/modules/settings/setup.routes.d.ts.map +1 -0
- package/dist/modules/settings/setup.routes.js +292 -0
- package/dist/modules/settings/setup.routes.js.map +1 -0
- package/dist/modules/skills/index.d.ts +2 -1
- package/dist/modules/skills/index.d.ts.map +1 -1
- package/dist/modules/skills/index.js +1 -0
- package/dist/modules/skills/index.js.map +1 -1
- package/dist/modules/skills/pending-skills.service.d.ts +40 -0
- package/dist/modules/skills/pending-skills.service.d.ts.map +1 -0
- package/dist/modules/skills/pending-skills.service.js +147 -0
- package/dist/modules/skills/pending-skills.service.js.map +1 -0
- package/dist/modules/skills/skills.contract.d.ts +32 -3
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.contract.js +1 -1
- package/dist/modules/skills/skills.routes.d.ts +3 -2
- package/dist/modules/skills/skills.routes.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.js +18 -1
- package/dist/modules/skills/skills.routes.js.map +1 -1
- package/dist/modules/skills/skills.service.d.ts +22 -0
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +24 -12
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/tool-manuals/index.d.ts +1 -1
- package/dist/modules/tool-manuals/index.d.ts.map +1 -1
- package/dist/modules/tool-manuals/index.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +47 -2
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.js +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +4 -0
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.js +23 -0
- package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +3 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +84 -17
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +6 -2
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +4 -2
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/event-bus.d.ts +11 -0
- package/dist/modules/workflow/event-bus.d.ts.map +1 -1
- package/dist/modules/workflow/event-bus.js +22 -0
- package/dist/modules/workflow/event-bus.js.map +1 -1
- package/dist/modules/workflow/file-lock.service.d.ts +9 -0
- package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
- package/dist/modules/workflow/file-lock.service.js +16 -0
- package/dist/modules/workflow/file-lock.service.js.map +1 -1
- package/dist/modules/workflow/git/branch-name.d.ts +1 -1
- package/dist/modules/workflow/git/branch-name.d.ts.map +1 -1
- package/dist/modules/workflow/git/branch-name.js +1 -1
- package/dist/modules/workflow/git/branch-name.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +31 -2
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +157 -54
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.d.ts +12 -6
- package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.js +19 -9
- package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js +12 -3
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
- package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.routes.js +95 -14
- package/dist/modules/workflow/workflow.routes.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +97 -5
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +393 -114
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/bevel-ignore.d.ts +5 -0
- package/dist/modules/workspace/bevel-ignore.d.ts.map +1 -1
- package/dist/modules/workspace/bevel-ignore.js +5 -1
- package/dist/modules/workspace/bevel-ignore.js.map +1 -1
- package/dist/modules/workspace/kb-seed.interface.d.ts +1 -1
- package/dist/modules/workspace/kb-seed.service.d.ts +60 -5
- package/dist/modules/workspace/kb-seed.service.d.ts.map +1 -1
- package/dist/modules/workspace/kb-seed.service.js +228 -51
- package/dist/modules/workspace/kb-seed.service.js.map +1 -1
- package/dist/modules/workspace/workspace.routes.d.ts +2 -1
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +51 -18
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +16 -4
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +36 -9
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +8 -3
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/shared/kb-layout.test.js +2 -2
- package/dist/test-setup.d.ts +2 -0
- package/dist/test-setup.d.ts.map +1 -0
- package/dist/test-setup.js +18 -0
- package/dist/test-setup.js.map +1 -0
- package/kb-template/.bevelignore +6 -2
- package/kb-template/AGENTS.md +272 -0
- package/kb-template/KnowledgeBase/How to get started.md +65 -0
- package/kb-template/access.md +36 -36
- package/migrations/0002_user_onboarding_done.sql +1 -0
- package/migrations/0003_deployment_settings.sql +9 -0
- package/migrations/meta/0001_snapshot.json +1486 -0
- package/migrations/meta/0002_snapshot.json +1493 -0
- package/migrations/meta/0003_snapshot.json +1552 -0
- package/migrations/meta/_journal.json +15 -1
- package/package.json +3 -3
- package/src/__tests__/branch-model.test.ts +79 -0
- package/src/__tests__/core-config.admin.test.ts +114 -0
- package/src/__tests__/database-url.test.ts +70 -0
- package/src/core/core-ports.ts +15 -0
- package/src/core/create-core-server.ts +55 -6
- package/src/core/create-core-services.ts +201 -36
- package/src/core-config.ts +139 -59
- package/src/modules/access/__tests__/access-declarations.test.ts +195 -0
- package/src/modules/access/__tests__/access-md-format.test.ts +191 -0
- package/src/modules/access/__tests__/access-splice.test.ts +21 -0
- package/src/modules/access/__tests__/access.routes.overrides.test.ts +214 -0
- package/src/modules/access/access-control.interface.ts +11 -0
- package/src/modules/access/access-control.service.ts +113 -15
- package/src/modules/access/access-declarations.ts +251 -0
- package/src/modules/access/access-mutation.service.ts +4 -4
- package/src/modules/access/access-splice.ts +64 -21
- package/src/modules/access/access.routes.ts +61 -0
- package/src/modules/access/creator-access.ts +22 -2
- package/src/modules/admin/admin-access.service.ts +4 -3
- package/src/modules/auth/__tests__/auth.service.test.ts +57 -0
- package/src/modules/auth/__tests__/onboarding-done.route.test.ts +147 -0
- package/src/modules/auth/auth.routes.ts +50 -1
- package/src/modules/auth/auth.service.ts +55 -17
- package/src/modules/database/core-schema.ts +46 -0
- package/src/modules/groups/__tests__/group-index.service.test.ts +225 -0
- package/src/modules/groups/__tests__/group-provision.service.test.ts +247 -0
- package/src/modules/groups/__tests__/groups.routes.test.ts +493 -0
- package/src/modules/groups/__tests__/join-proposals.test.ts +101 -0
- package/src/modules/groups/__tests__/join-requests.service.test.ts +175 -0
- package/src/modules/groups/group-provision.service.ts +328 -0
- package/src/modules/groups/groups.contract.ts +107 -0
- package/src/modules/groups/groups.routes.ts +433 -0
- package/src/modules/groups/groups.service.ts +193 -0
- package/src/modules/groups/index.ts +17 -0
- package/src/modules/groups/join-proposals.ts +114 -0
- package/src/modules/groups/join-requests.service.ts +185 -0
- package/src/modules/secrets-vault/__tests__/oauth-return-to.route.test.ts +283 -0
- package/src/modules/secrets-vault/secrets-vault.routes.ts +68 -12
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +256 -0
- package/src/modules/settings/__tests__/setup.routes.test.ts +201 -0
- package/src/modules/settings/deployment-settings.service.ts +419 -0
- package/src/modules/settings/setup.routes.ts +317 -0
- package/src/modules/skills/__tests__/pending-skills.service.test.ts +240 -0
- package/src/modules/skills/__tests__/skills.service.test.ts +13 -13
- package/src/modules/skills/index.ts +3 -0
- package/src/modules/skills/pending-skills.service.ts +168 -0
- package/src/modules/skills/skills.contract.ts +34 -3
- package/src/modules/skills/skills.routes.ts +23 -2
- package/src/modules/skills/skills.service.ts +24 -16
- package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +262 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +2 -2
- package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +61 -23
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +156 -156
- package/src/modules/tool-manuals/index.ts +2 -0
- package/src/modules/tool-manuals/tool-manuals.contract.ts +50 -2
- package/src/modules/tool-manuals/tool-manuals.routes.ts +21 -0
- package/src/modules/tool-manuals/tool-manuals.service.ts +86 -17
- package/src/modules/tool-manuals/tool-manuals.tools.ts +206 -202
- package/src/modules/workflow/__tests__/event-bus.test.ts +35 -0
- package/src/modules/workflow/__tests__/pending-commits.enqueue-if-absent.test.ts +82 -72
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +295 -5
- package/src/modules/workflow/agent-tools/workflow.tools.ts +4 -2
- package/src/modules/workflow/event-bus.ts +25 -0
- package/src/modules/workflow/file-lock.service.ts +17 -0
- package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +9 -49
- package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +101 -0
- package/src/modules/workflow/git/__tests__/git.service.commitFile.test.ts +219 -211
- package/src/modules/workflow/git/__tests__/git.service.fileContentsAtCommit.test.ts +132 -0
- package/src/modules/workflow/git/__tests__/git.service.logForFile.test.ts +4 -2
- package/src/modules/workflow/git/__tests__/git.service.postMergePull.test.ts +275 -272
- package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +311 -306
- package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-cancel.test.ts +44 -0
- package/src/modules/workflow/git/branch-name.ts +5 -1
- package/src/modules/workflow/git/git.service.ts +172 -64
- package/src/modules/workflow/git/pull-request.service.ts +22 -11
- package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +4 -1
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +12 -3
- package/src/modules/workflow/workflow.routes.ts +91 -13
- package/src/modules/workflow/workflow.service.ts +365 -19
- package/src/modules/workspace/__tests__/kb-seed.service.test.ts +255 -24
- package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +236 -233
- package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +179 -176
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +320 -303
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +337 -276
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +14 -6
- package/src/modules/workspace/bevel-ignore.ts +66 -62
- package/src/modules/workspace/kb-seed.interface.ts +1 -1
- package/src/modules/workspace/kb-seed.service.ts +247 -53
- package/src/modules/workspace/workspace.routes.ts +54 -18
- package/src/modules/workspace/workspace.service.ts +45 -7
- package/src/modules/workspace/workspace.tools.ts +922 -917
- package/src/shared/__tests__/frontmatter-id.test.ts +53 -0
- package/src/shared/__tests__/join-request.test.ts +96 -0
- package/src/shared/__tests__/kb-layout.group.test.ts +45 -0
- package/src/shared/kb-layout.test.ts +2 -2
- package/src/test-setup.ts +18 -0
- package/kb-template/Agents/README.md +0 -38
- package/kb-template/CLAUDE.md +0 -419
- package/kb-template/Data/.gitkeep +0 -0
- package/kb-template/Data/README.md +0 -39
- package/kb-template/KnowledgeBase/README.md +0 -37
- package/kb-template/Pipelines/.gitkeep +0 -0
- package/kb-template/Pipelines/README.md +0 -38
- package/kb-template/Skills/.gitkeep +0 -0
- package/kb-template/Skills/README.md +0 -32
- package/kb-template/Tools/.gitkeep +0 -0
- package/kb-template/Tools/README.md +0 -42
- /package/kb-template/{Agents → Groups}/.gitkeep +0 -0
|
@@ -232,55 +232,6 @@ describe('GitService — commit gate uses HEAD (not working tree)', () => {
|
|
|
232
232
|
});
|
|
233
233
|
});
|
|
234
234
|
|
|
235
|
-
describe('GitService — revert gate uses HEAD', () => {
|
|
236
|
-
let root: string;
|
|
237
|
-
const workspaceId = 'ws-revert-gate';
|
|
238
|
-
|
|
239
|
-
beforeEach(async () => {
|
|
240
|
-
root = await mkTmpRoot();
|
|
241
|
-
});
|
|
242
|
-
afterEach(async () => {
|
|
243
|
-
await fs.rm(root, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it('passes ref=HEAD when gating a revert', async () => {
|
|
247
|
-
const { ac, calls } = recordingAccessControl();
|
|
248
|
-
const { svc, repo } = await makeSvc(root, workspaceId, ac);
|
|
249
|
-
await commitFile(repo, 'Knowledge/Foo.md', 'a\n', 'seed');
|
|
250
|
-
await commitFile(repo, 'Knowledge/Foo.md', 'b\n', 'second');
|
|
251
|
-
const { stdout: sha } = await execFileAsync('git', ['-C', repo, 'rev-parse', 'HEAD'], {
|
|
252
|
-
encoding: 'utf-8',
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
// We only care that the gate is invoked with `HEAD`; downstream
|
|
256
|
-
// behaviour of `git revert` (which may or may not succeed for unrelated
|
|
257
|
-
// environmental reasons — author parsing, signing config, etc.) is out
|
|
258
|
-
// of scope for this test.
|
|
259
|
-
await svc.revertCommit(workspaceId, USER, sha.trim()).catch(() => undefined);
|
|
260
|
-
|
|
261
|
-
const gateCalls = calls.filter((c) => c.method === 'canWriteBatchAtRef');
|
|
262
|
-
expect(gateCalls).toHaveLength(1);
|
|
263
|
-
expect(gateCalls[0].ref).toBe('HEAD');
|
|
264
|
-
expect(gateCalls[0].paths).toContain('Knowledge/Foo.md');
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
it('refuses with AccessDeniedError when the gate denies', async () => {
|
|
268
|
-
const { ac } = recordingAccessControl({
|
|
269
|
-
canWriteBatchAtRef: (_ref, _email, paths) => new Map(paths.map((p) => [p, false])),
|
|
270
|
-
});
|
|
271
|
-
const { svc, repo } = await makeSvc(root, workspaceId, ac);
|
|
272
|
-
await commitFile(repo, 'Knowledge/Foo.md', 'a\n', 'seed');
|
|
273
|
-
await commitFile(repo, 'Knowledge/Foo.md', 'b\n', 'second');
|
|
274
|
-
const { stdout: sha } = await execFileAsync('git', ['-C', repo, 'rev-parse', 'HEAD'], {
|
|
275
|
-
encoding: 'utf-8',
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
await expect(svc.revertCommit(workspaceId, USER, sha.trim())).rejects.toBeInstanceOf(
|
|
279
|
-
AccessDeniedError,
|
|
280
|
-
);
|
|
281
|
-
});
|
|
282
|
-
});
|
|
283
|
-
|
|
284
235
|
describe('GitService — push gate uses origin/<branch> (not HEAD or working tree)', () => {
|
|
285
236
|
let root: string;
|
|
286
237
|
const workspaceId = 'ws-push-gate';
|
|
@@ -327,6 +278,15 @@ describe('GitService — push gate uses origin/<branch> (not HEAD or working tre
|
|
|
327
278
|
expect(gateCalls[0].paths).toContain('Knowledge/Foo.md');
|
|
328
279
|
});
|
|
329
280
|
|
|
281
|
+
it('systemAuthorized skips the gate entirely, and the push still lands', async () => {
|
|
282
|
+
// The group-provisioning path: its endpoint IS the authorization (any
|
|
283
|
+
// signed-in user may claim an unused name under Groups/), and the gate
|
|
284
|
+
// could only read the new folder's chain at origin as `write: Admin`.
|
|
285
|
+
const { svc, calls } = await setupWithOrigin();
|
|
286
|
+
await svc.push(workspaceId, USER, { systemAuthorized: true });
|
|
287
|
+
expect(calls.filter((c) => c.method === 'canWriteBatchAtRef')).toHaveLength(0);
|
|
288
|
+
});
|
|
289
|
+
|
|
330
290
|
it('refuses the push when origin/<branch> rules deny the caller', async () => {
|
|
331
291
|
const { workspaceDir, repo } = await seedWorkspace(root, workspaceId);
|
|
332
292
|
const originDir = path.join(root, 'origin.git');
|
|
@@ -38,6 +38,14 @@ async function seedWorkspace(root: string, workspaceId: string) {
|
|
|
38
38
|
const repo = path.join(workspaceDir, 'knowledge-base');
|
|
39
39
|
await fs.mkdir(workspaceDir, { recursive: true });
|
|
40
40
|
await runGit(root, ['clone', upstream, repo]);
|
|
41
|
+
// Repo-local identity, because `GitService` spawns its own git children
|
|
42
|
+
// with the AMBIENT env — `runGit`'s `GIT_COMMITTER_*` vars never reach
|
|
43
|
+
// them. A dev machine hides this behind a global git config; a CI runner
|
|
44
|
+
// has none, so anything committing through the service (here:
|
|
45
|
+
// `commitFile`) fails with "Committer identity unknown". Same reason
|
|
46
|
+
// `git.service.commitFile.test.ts` configures its fixture this way.
|
|
47
|
+
await runGit(repo, ['config', 'user.email', 'test@bevel.local']);
|
|
48
|
+
await runGit(repo, ['config', 'user.name', 'Test Runner']);
|
|
41
49
|
return { upstream, repo };
|
|
42
50
|
}
|
|
43
51
|
|
|
@@ -121,4 +129,97 @@ describe('GitService.changedFilesForPr / resolvePrShas', () => {
|
|
|
121
129
|
expect(headSha).toMatch(/^[0-9a-f]{40}$/);
|
|
122
130
|
expect(baseSha).not.toBe(headSha);
|
|
123
131
|
});
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* roles.yaml can never change through a merge — `preserveBaseRolesYaml`
|
|
135
|
+
* restores the base copy onto the source before every merge — so the review
|
|
136
|
+
* surface must not list it as changed: the claim would be false, the empty
|
|
137
|
+
* diff reads as a bug, and its per-file approval would gate the merge on a
|
|
138
|
+
* change that cannot land. Counts stay aligned with the surviving files.
|
|
139
|
+
*/
|
|
140
|
+
it('excludes roles.yaml from the changed-file list and the touched paths', async () => {
|
|
141
|
+
const { repo } = await seedWorkspace(root, workspaceId);
|
|
142
|
+
await runGit(repo, ['checkout', '-b', 'mallory/self-promote']);
|
|
143
|
+
await fs.writeFile(path.join(repo, 'roles.yaml'), 'roles:\n Admin:\n - mallory@x.com\n');
|
|
144
|
+
await fs.writeFile(path.join(repo, 'honest.md'), 'real change\nsecond line\n');
|
|
145
|
+
await runGit(repo, ['add', '-A']);
|
|
146
|
+
await runGit(repo, ['commit', '-m', 'work + attempted escalation']);
|
|
147
|
+
await runGit(repo, ['push', '-u', 'origin', 'mallory/self-promote']);
|
|
148
|
+
|
|
149
|
+
const git = new GitService(
|
|
150
|
+
stubWorkspaceService(workspaceId, repo),
|
|
151
|
+
new WorkflowHooks(),
|
|
152
|
+
'knowledge-base',
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const files = await git.changedFilesForPr(
|
|
156
|
+
workspaceId,
|
|
157
|
+
'current-company-state',
|
|
158
|
+
'mallory/self-promote',
|
|
159
|
+
);
|
|
160
|
+
expect(files.map((f) => f.path)).toEqual(['honest.md']);
|
|
161
|
+
// The counts filter moved in step with the statuses filter.
|
|
162
|
+
expect(files[0].additions).toBe(2);
|
|
163
|
+
|
|
164
|
+
const paths = await git.changedPathsForPr(
|
|
165
|
+
workspaceId,
|
|
166
|
+
'current-company-state',
|
|
167
|
+
'mallory/self-promote',
|
|
168
|
+
);
|
|
169
|
+
expect(paths).toEqual(['honest.md']);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* The per-file revert's two primitives: the merge-base a revert restores
|
|
174
|
+
* from, and the restore itself — byte-exact via git, with "absent at the
|
|
175
|
+
* merge-base" meaning deletion (the revert of an added file).
|
|
176
|
+
*/
|
|
177
|
+
it('mergeBaseForPr + restorePathFromRef restore a modified file and delete an added one', async () => {
|
|
178
|
+
const { repo } = await seedWorkspace(root, workspaceId);
|
|
179
|
+
await runGit(repo, ['checkout', '-b', 'alice/feature']);
|
|
180
|
+
await fs.writeFile(path.join(repo, 'base.md'), 'rewritten\n');
|
|
181
|
+
await fs.writeFile(path.join(repo, 'added.md'), 'brand new\n');
|
|
182
|
+
await runGit(repo, ['add', '-A']);
|
|
183
|
+
await runGit(repo, ['commit', '-m', 'feature work']);
|
|
184
|
+
await runGit(repo, ['push', '-u', 'origin', 'alice/feature']);
|
|
185
|
+
|
|
186
|
+
const git = new GitService(
|
|
187
|
+
stubWorkspaceService(workspaceId, repo),
|
|
188
|
+
new WorkflowHooks(),
|
|
189
|
+
'knowledge-base',
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const mergeBase = await git.mergeBaseForPr(workspaceId, 'current-company-state', 'alice/feature');
|
|
193
|
+
expect(mergeBase).toMatch(/^[0-9a-f]{40}$/);
|
|
194
|
+
|
|
195
|
+
await git.restorePathFromRef(workspaceId, mergeBase!, 'base.md');
|
|
196
|
+
// Normalized: a Windows dev box with core.autocrlf smudges the checkout
|
|
197
|
+
// to CRLF; the blob git commits back is what matters, not the smudge.
|
|
198
|
+
const restored = await fs.readFile(path.join(repo, 'base.md'), 'utf8');
|
|
199
|
+
expect(restored.replace(/\r\n/g, '\n')).toBe('base\n');
|
|
200
|
+
|
|
201
|
+
// Simulate the wreckage a previously-failed attempt leaves behind: a
|
|
202
|
+
// STAGED deletion (index entry gone). The restore must self-heal it —
|
|
203
|
+
// without the index realignment, the commit below fatals on `git add`.
|
|
204
|
+
await runGit(repo, ['rm', '--force', '--quiet', '--', 'added.md']);
|
|
205
|
+
|
|
206
|
+
await git.restorePathFromRef(workspaceId, mergeBase!, 'added.md');
|
|
207
|
+
await expect(fs.access(path.join(repo, 'added.md'))).rejects.toThrow();
|
|
208
|
+
|
|
209
|
+
// The chain that actually failed in production: committing the DELETION
|
|
210
|
+
// through commitFile, whose `git add -- <path>` can only stage it while
|
|
211
|
+
// the index still tracks the path — a `git rm`-based restore broke here
|
|
212
|
+
// with "pathspec did not match any files".
|
|
213
|
+
const committed = await git.commitFile(
|
|
214
|
+
workspaceId,
|
|
215
|
+
{ id: 'u1', email: 'reviewer@x.com', name: 'Reviewer' },
|
|
216
|
+
'added.md',
|
|
217
|
+
'Revert added.md (declined in change request #1)',
|
|
218
|
+
true,
|
|
219
|
+
);
|
|
220
|
+
expect(committed).not.toBeNull();
|
|
221
|
+
await expect(
|
|
222
|
+
execFileAsync('git', ['-C', repo, 'cat-file', '-e', 'HEAD:added.md']),
|
|
223
|
+
).rejects.toThrow();
|
|
224
|
+
});
|
|
124
225
|
});
|
|
@@ -1,211 +1,219 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import { execFile } from 'node:child_process';
|
|
3
|
-
import { promisify } from 'node:util';
|
|
4
|
-
import fs from 'node:fs/promises';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
import os from 'node:os';
|
|
7
|
-
import type { WorkspaceService } from '../../../workspace/workspace.service.js';
|
|
8
|
-
import { WorkflowHooks } from '../../workflow-hooks.js';
|
|
9
|
-
import { GitService } from '../git.service.js';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* `GitService.commitFile` — the `skipValidator` flag. The reject flow passes
|
|
13
|
-
* `skipValidator: true` because it re-commits a pre-agent baseline that was
|
|
14
|
-
* already validated when first written; running the (advisory) KB validator
|
|
15
|
-
* there is pure latency.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
const execFileAsync = promisify(execFile);
|
|
19
|
-
const PROCESS_MAP_DIR = 'knowledge-base';
|
|
20
|
-
const USER = { id: 'u-alice', name: 'Alice', email: 'alice@bevel.software' };
|
|
21
|
-
|
|
22
|
-
async function runGit(cwd: string, args: string[]): Promise<void> {
|
|
23
|
-
await execFileAsync('git', args, {
|
|
24
|
-
cwd,
|
|
25
|
-
env: {
|
|
26
|
-
...process.env,
|
|
27
|
-
GIT_AUTHOR_NAME: 'Test',
|
|
28
|
-
GIT_AUTHOR_EMAIL: 't@x.com',
|
|
29
|
-
GIT_COMMITTER_NAME: 'Test',
|
|
30
|
-
GIT_COMMITTER_EMAIL: 't@x.com',
|
|
31
|
-
},
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function seedWorkspace(
|
|
36
|
-
root: string,
|
|
37
|
-
workspaceId: string,
|
|
38
|
-
): Promise<{ workspaceDir: string; repo: string }> {
|
|
39
|
-
const workspaceDir = path.join(root, workspaceId);
|
|
40
|
-
const repo = path.join(workspaceDir, PROCESS_MAP_DIR);
|
|
41
|
-
await fs.mkdir(repo, { recursive: true });
|
|
42
|
-
// A feature branch — not a protected branch — so `commitFile` skips the
|
|
43
|
-
// protected-branch access gate; this test is only about the validator.
|
|
44
|
-
await runGit(repo, ['init', '-b', 'feature-test']);
|
|
45
|
-
await runGit(repo, ['config', 'user.email', 'test@bevel.local']);
|
|
46
|
-
await runGit(repo, ['config', 'user.name', 'Test Runner']);
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
await
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
await
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
'
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
expect(
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import fs from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import type { WorkspaceService } from '../../../workspace/workspace.service.js';
|
|
8
|
+
import { WorkflowHooks } from '../../workflow-hooks.js';
|
|
9
|
+
import { GitService } from '../git.service.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* `GitService.commitFile` — the `skipValidator` flag. The reject flow passes
|
|
13
|
+
* `skipValidator: true` because it re-commits a pre-agent baseline that was
|
|
14
|
+
* already validated when first written; running the (advisory) KB validator
|
|
15
|
+
* there is pure latency.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const execFileAsync = promisify(execFile);
|
|
19
|
+
const PROCESS_MAP_DIR = 'knowledge-base';
|
|
20
|
+
const USER = { id: 'u-alice', name: 'Alice', email: 'alice@bevel.software' };
|
|
21
|
+
|
|
22
|
+
async function runGit(cwd: string, args: string[]): Promise<void> {
|
|
23
|
+
await execFileAsync('git', args, {
|
|
24
|
+
cwd,
|
|
25
|
+
env: {
|
|
26
|
+
...process.env,
|
|
27
|
+
GIT_AUTHOR_NAME: 'Test',
|
|
28
|
+
GIT_AUTHOR_EMAIL: 't@x.com',
|
|
29
|
+
GIT_COMMITTER_NAME: 'Test',
|
|
30
|
+
GIT_COMMITTER_EMAIL: 't@x.com',
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function seedWorkspace(
|
|
36
|
+
root: string,
|
|
37
|
+
workspaceId: string,
|
|
38
|
+
): Promise<{ workspaceDir: string; repo: string }> {
|
|
39
|
+
const workspaceDir = path.join(root, workspaceId);
|
|
40
|
+
const repo = path.join(workspaceDir, PROCESS_MAP_DIR);
|
|
41
|
+
await fs.mkdir(repo, { recursive: true });
|
|
42
|
+
// A feature branch — not a protected branch — so `commitFile` skips the
|
|
43
|
+
// protected-branch access gate; this test is only about the validator.
|
|
44
|
+
await runGit(repo, ['init', '-b', 'feature-test']);
|
|
45
|
+
await runGit(repo, ['config', 'user.email', 'test@bevel.local']);
|
|
46
|
+
await runGit(repo, ['config', 'user.name', 'Test Runner']);
|
|
47
|
+
// Repo-local so GitService's own git children (which run with the ambient
|
|
48
|
+
// env) see them too: autocrlf off keeps `git checkout` (discardPath) from
|
|
49
|
+
// rewriting the LF fixtures as CRLF under Git-for-Windows' system-level
|
|
50
|
+
// `core.autocrlf=true`; longpaths lifts Windows' 260-char MAX_PATH, which
|
|
51
|
+
// the deep-KB-path subject-truncation test exceeds. Both are no-ops on
|
|
52
|
+
// Linux/CI — they pin the repo to the behaviour the assertions assume.
|
|
53
|
+
await runGit(repo, ['config', 'core.autocrlf', 'false']);
|
|
54
|
+
await runGit(repo, ['config', 'core.longpaths', 'true']);
|
|
55
|
+
// An initial commit so HEAD exists.
|
|
56
|
+
await fs.writeFile(path.join(repo, 'seed.md'), 'seed\n');
|
|
57
|
+
await runGit(repo, ['add', 'seed.md']);
|
|
58
|
+
await runGit(repo, ['commit', '-m', 'seed']);
|
|
59
|
+
return { workspaceDir, repo };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function stubWorkspaceService(workspaceId: string, workspaceDir: string): WorkspaceService {
|
|
63
|
+
return {
|
|
64
|
+
getWorkspacePath: async (id: string) => {
|
|
65
|
+
if (id !== workspaceId) throw new Error(`unexpected workspace ${id}`);
|
|
66
|
+
return workspaceDir;
|
|
67
|
+
},
|
|
68
|
+
} as unknown as WorkspaceService;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function makeValidator(): { hooks: WorkflowHooks; runValidation: ReturnType<typeof vi.fn> } {
|
|
72
|
+
const report = { ok: true, mustFix: [], warnings: [], rawOutput: '' };
|
|
73
|
+
const runValidation = vi.fn(async () => report);
|
|
74
|
+
// Registered as a commit-validation hook — the seam GitService consults
|
|
75
|
+
// where it used to call the injected validator.
|
|
76
|
+
const hooks = new WorkflowHooks();
|
|
77
|
+
hooks.onCommitValidation(() => runValidation());
|
|
78
|
+
return { hooks, runValidation };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
describe('GitService.commitFile — skipValidator', () => {
|
|
82
|
+
let root: string;
|
|
83
|
+
const workspaceId = 'ws-commitfile-1';
|
|
84
|
+
|
|
85
|
+
beforeEach(async () => {
|
|
86
|
+
root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-git-commitfile-'));
|
|
87
|
+
});
|
|
88
|
+
afterEach(async () => {
|
|
89
|
+
await fs.rm(root, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('skipValidator=true commits the file WITHOUT running the KB validator', async () => {
|
|
93
|
+
const { workspaceDir, repo } = await seedWorkspace(root, workspaceId);
|
|
94
|
+
const { hooks, runValidation } = makeValidator();
|
|
95
|
+
const svc = new GitService(
|
|
96
|
+
stubWorkspaceService(workspaceId, workspaceDir),
|
|
97
|
+
hooks,
|
|
98
|
+
'knowledge-base',
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
await fs.writeFile(path.join(repo, 'Foo.md'), 'reverted content\n');
|
|
102
|
+
const change = await svc.commitFile(workspaceId, USER, 'Foo.md', 'Revert agent change', true);
|
|
103
|
+
|
|
104
|
+
expect(runValidation).not.toHaveBeenCalled();
|
|
105
|
+
expect(change?.sha).toBeTruthy();
|
|
106
|
+
expect(change?.subject).toBe('Revert agent change');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('runs the KB validator when skipValidator is omitted', async () => {
|
|
110
|
+
const { workspaceDir, repo } = await seedWorkspace(root, workspaceId);
|
|
111
|
+
const { hooks, runValidation } = makeValidator();
|
|
112
|
+
const svc = new GitService(
|
|
113
|
+
stubWorkspaceService(workspaceId, workspaceDir),
|
|
114
|
+
hooks,
|
|
115
|
+
'knowledge-base',
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
await fs.writeFile(path.join(repo, 'Bar.md'), 'new content\n');
|
|
119
|
+
const change = await svc.commitFile(workspaceId, USER, 'Bar.md', 'normal commit');
|
|
120
|
+
|
|
121
|
+
expect(runValidation).toHaveBeenCalledTimes(1);
|
|
122
|
+
expect(change?.sha).toBeTruthy();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Regression: filenames with bracketed prefixes (`[Approved] …`, `[New] …`,
|
|
126
|
+
// `[Updated 03.09.2025] …`) used to be rejected at `assertValidRelativePath`
|
|
127
|
+
// because their `[` / `]` characters would be interpreted by `git add -- <path>`
|
|
128
|
+
// as pathspec globs. The fix is `GIT_LITERAL_PATHSPECS=1` on the subprocess
|
|
129
|
+
// env (set in `GitService.git()`), which forces literal matching. This test
|
|
130
|
+
// commits one of the user-reported offenders end-to-end through real git so
|
|
131
|
+
// a regression in the env-var plumbing would surface as a subprocess failure
|
|
132
|
+
// here rather than as a 400 on the upload route.
|
|
133
|
+
it('commits a filename containing pathspec glob characters (regression: [Approved] / [New] prefixes)', async () => {
|
|
134
|
+
const { workspaceDir, repo } = await seedWorkspace(root, workspaceId);
|
|
135
|
+
const { hooks } = makeValidator();
|
|
136
|
+
const svc = new GitService(
|
|
137
|
+
stubWorkspaceService(workspaceId, workspaceDir),
|
|
138
|
+
hooks,
|
|
139
|
+
'knowledge-base',
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const relPath = 'Purchasing/[Updated 03.09.2025] Purchasing_How to search_Product detail page.md';
|
|
143
|
+
await fs.mkdir(path.join(repo, 'Purchasing'), { recursive: true });
|
|
144
|
+
await fs.writeFile(path.join(repo, relPath), 'content\n');
|
|
145
|
+
|
|
146
|
+
const change = await svc.commitFile(workspaceId, USER, relPath);
|
|
147
|
+
|
|
148
|
+
expect(change?.sha).toBeTruthy();
|
|
149
|
+
// Working tree must end up clean — `git add -- <path>` literally matched
|
|
150
|
+
// the bracketed name, so there's no leftover untracked file ghost on disk.
|
|
151
|
+
const { stdout: status } = await execFileAsync('git', ['-C', repo, 'status', '--porcelain'], {
|
|
152
|
+
env: { ...process.env, GIT_LITERAL_PATHSPECS: '1' },
|
|
153
|
+
});
|
|
154
|
+
expect(status.trim()).toBe('');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Regression: deep KB paths (≥194 chars) made the default `Update <path>`
|
|
158
|
+
// subject exceed the 200-char limit, so the lock-release auto-commit
|
|
159
|
+
// propagated `commit summary must be ≤ 200 characters` back through the
|
|
160
|
+
// agent's write tool. The default now falls back to the basename when the
|
|
161
|
+
// full path doesn't fit.
|
|
162
|
+
it('truncates the default subject when the path makes "Update <path>" exceed 200 chars', async () => {
|
|
163
|
+
const { workspaceDir, repo } = await seedWorkspace(root, workspaceId);
|
|
164
|
+
const { hooks } = makeValidator();
|
|
165
|
+
const svc = new GitService(
|
|
166
|
+
stubWorkspaceService(workspaceId, workspaceDir),
|
|
167
|
+
hooks,
|
|
168
|
+
'knowledge-base',
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const deepDir = path.join(
|
|
172
|
+
'Product',
|
|
173
|
+
'Knowledge',
|
|
174
|
+
'1. Service Commitment Management',
|
|
175
|
+
'1.1 In - Architecture Layering (AL)',
|
|
176
|
+
'0-Governance',
|
|
177
|
+
'0.5-Layer-Group-Specific-Governance-Principles',
|
|
178
|
+
);
|
|
179
|
+
const fileName = '0.5.5-Buyer-Side-Commercial-Agreement-Governance.md';
|
|
180
|
+
const relPath = path.posix.join(deepDir.split(path.sep).join('/'), fileName);
|
|
181
|
+
expect(`Update ${relPath}`.length).toBeGreaterThan(200);
|
|
182
|
+
|
|
183
|
+
await fs.mkdir(path.join(repo, deepDir), { recursive: true });
|
|
184
|
+
await fs.writeFile(path.join(repo, deepDir, fileName), 'content\n');
|
|
185
|
+
|
|
186
|
+
const change = await svc.commitFile(workspaceId, USER, relPath);
|
|
187
|
+
|
|
188
|
+
expect(change?.subject).toBe(`Update ${fileName}`);
|
|
189
|
+
expect(change?.subject.length).toBeLessThanOrEqual(200);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Regression: pathspec literal handling. `git checkout HEAD -- '[Approved] x.md'`
|
|
193
|
+
// and `git ls-files --error-unmatch -- '[Approved] x.md'` would interpret the
|
|
194
|
+
// brackets as a character-class glob, so discardPath would either silently
|
|
195
|
+
// no-op or fail to revert the on-disk content. Belongs alongside the
|
|
196
|
+
// commitFile regression because no dedicated discardPath test file exists
|
|
197
|
+
// yet — its full surface is covered transitively elsewhere.
|
|
198
|
+
it('discardPath reverts an unstaged modification to a bracketed-name file', async () => {
|
|
199
|
+
const { workspaceDir, repo } = await seedWorkspace(root, workspaceId);
|
|
200
|
+
const { hooks } = makeValidator();
|
|
201
|
+
const svc = new GitService(
|
|
202
|
+
stubWorkspaceService(workspaceId, workspaceDir),
|
|
203
|
+
hooks,
|
|
204
|
+
'knowledge-base',
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const relPath = '[Approved] config.md';
|
|
208
|
+
const committed = 'committed content\n';
|
|
209
|
+
await fs.writeFile(path.join(repo, relPath), committed);
|
|
210
|
+
await svc.commitFile(workspaceId, USER, relPath, 'add bracketed config');
|
|
211
|
+
|
|
212
|
+
// Dirty the working tree.
|
|
213
|
+
await fs.writeFile(path.join(repo, relPath), 'dirty content\n');
|
|
214
|
+
await svc.discardPath(workspaceId, relPath);
|
|
215
|
+
|
|
216
|
+
const restored = await fs.readFile(path.join(repo, relPath), 'utf-8');
|
|
217
|
+
expect(restored).toBe(committed);
|
|
218
|
+
});
|
|
219
|
+
});
|