@bevel-software/platform-core-backend 0.25.2 → 0.26.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/agent-guide/access-control.md +234 -0
- package/agent-guide/conventions.md +27 -0
- package/agent-guide/directory-structure.md +145 -0
- package/agent-guide/finding-things.md +7 -0
- package/agent-guide/introduction.md +27 -0
- package/agent-guide/skills.md +47 -0
- package/agent-guide/tool-manuals.md +217 -0
- package/agent-guide/where-a-new-file-goes.md +36 -0
- package/dist/assets.d.ts +7 -0
- package/dist/assets.d.ts.map +1 -1
- package/dist/assets.js +9 -0
- package/dist/assets.js.map +1 -1
- package/dist/core/core-ports.d.ts +11 -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 +13 -2
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +9 -0
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +14 -4
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +9 -0
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +1 -0
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +16 -0
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/agent-guide/agent-guide.d.ts +139 -0
- package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -0
- package/dist/modules/agent-guide/agent-guide.js +191 -0
- package/dist/modules/agent-guide/agent-guide.js.map +1 -0
- package/dist/modules/agent-guide/agent-guide.tools.d.ts +24 -0
- package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -0
- package/dist/modules/agent-guide/agent-guide.tools.js +100 -0
- package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -0
- package/dist/modules/agent-guide/index.d.ts +4 -0
- package/dist/modules/agent-guide/index.d.ts.map +1 -0
- package/dist/modules/agent-guide/index.js +4 -0
- package/dist/modules/agent-guide/index.js.map +1 -0
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +3 -2
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.js +3 -2
- package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -1
- package/dist/modules/agent-instructions/compose.d.ts +9 -6
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
- package/dist/modules/agent-instructions/compose.js +9 -6
- package/dist/modules/agent-instructions/compose.js.map +1 -1
- package/dist/modules/agent-instructions/index.d.ts +1 -1
- package/dist/modules/agent-instructions/index.d.ts.map +1 -1
- package/dist/modules/agent-instructions/index.js +1 -1
- package/dist/modules/agent-instructions/index.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts +10 -50
- package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.js +32 -85
- package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +29 -2
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +113 -16
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/tool-schema-guard.d.ts +105 -0
- package/dist/modules/mcp/tool-schema-guard.d.ts.map +1 -0
- package/dist/modules/mcp/tool-schema-guard.js +171 -0
- package/dist/modules/mcp/tool-schema-guard.js.map +1 -0
- package/dist/modules/plugins/plugins.tools.d.ts +36 -2
- package/dist/modules/plugins/plugins.tools.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.tools.js +71 -14
- package/dist/modules/plugins/plugins.tools.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.d.ts +0 -7
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +14 -53
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/settings/setup.routes.d.ts.map +1 -1
- package/dist/modules/settings/setup.routes.js +3 -6
- package/dist/modules/settings/setup.routes.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +58 -16
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +23 -4
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +4 -0
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +14 -0
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +7 -0
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +66 -36
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/tool-registry/description-length.d.ts +14 -14
- package/dist/modules/tool-registry/description-length.d.ts.map +1 -1
- package/dist/modules/tool-registry/description-length.js +24 -26
- package/dist/modules/tool-registry/description-length.js.map +1 -1
- package/dist/modules/tool-registry/guide-first.d.ts +23 -0
- package/dist/modules/tool-registry/guide-first.d.ts.map +1 -0
- package/dist/modules/tool-registry/guide-first.js +32 -0
- package/dist/modules/tool-registry/guide-first.js.map +1 -0
- package/dist/modules/tool-registry/tool-registry.d.ts +6 -0
- package/dist/modules/tool-registry/tool-registry.d.ts.map +1 -1
- package/dist/modules/tool-registry/tool-registry.js +9 -2
- package/dist/modules/tool-registry/tool-registry.js.map +1 -1
- package/dist/modules/workflow/agent-tools/change-request-read-shape.d.ts +449 -0
- package/dist/modules/workflow/agent-tools/change-request-read-shape.d.ts.map +1 -0
- package/dist/modules/workflow/agent-tools/change-request-read-shape.js +481 -0
- package/dist/modules/workflow/agent-tools/change-request-read-shape.js.map +1 -0
- package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts +73 -0
- package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +1 -0
- package/dist/modules/workflow/agent-tools/change-request-read.tools.js +582 -0
- package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +1 -0
- package/dist/modules/workflow/agent-tools/change-request-summary.d.ts +12 -1
- package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/change-request-summary.js +5 -1
- package/dist/modules/workflow/agent-tools/change-request-summary.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 +9 -0
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +210 -13
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +456 -91
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/merge-commit.d.ts +73 -0
- package/dist/modules/workflow/git/merge-commit.d.ts.map +1 -0
- package/dist/modules/workflow/git/merge-commit.js +89 -0
- package/dist/modules/workflow/git/merge-commit.js.map +1 -0
- package/dist/modules/workflow/git/pull-request.service.d.ts +94 -1
- package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.js +332 -37
- package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts +35 -0
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js +178 -12
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
- package/dist/modules/workflow/workflow.routes.d.ts +6 -2
- package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.routes.js +7 -2
- package/dist/modules/workflow/workflow.routes.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +4 -0
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +3 -0
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js +22 -27
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts +58 -52
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.js +209 -223
- package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.d.ts +5 -3
- package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.js +5 -3
- package/dist/modules/workspace/startup/steps/template-source.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +10 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +211 -18
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/shared/domain-errors.d.ts +11 -0
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +14 -0
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/hidden-tools.d.ts +44 -0
- package/dist/shared/hidden-tools.d.ts.map +1 -0
- package/dist/shared/hidden-tools.js +13 -0
- package/dist/shared/hidden-tools.js.map +1 -0
- package/kb-template/.bevelignore +0 -5
- package/package.json +4 -3
- package/src/__tests__/kb-layout-config.test.ts +10 -100
- package/src/__tests__/packaged-assets-ship.test.ts +54 -0
- package/src/assets.ts +10 -0
- package/src/core/core-ports.ts +11 -0
- package/src/core/create-core-server.ts +13 -2
- package/src/core/create-core-services.ts +28 -4
- package/src/index.ts +2 -2
- package/src/modules/access/__tests__/access-control.atref-batch.test.ts +58 -0
- package/src/modules/access/__tests__/access-control.platform-restore.test.ts +8 -7
- package/src/modules/access/__tests__/access-personal-plugin.test.ts +1 -18
- package/src/modules/access/access-control.interface.ts +15 -0
- package/src/modules/access/access-control.service.ts +21 -0
- package/src/modules/agent-guide/__tests__/agent-guide.test.ts +328 -0
- package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +189 -0
- package/src/modules/agent-guide/agent-guide.tools.ts +122 -0
- package/src/modules/agent-guide/agent-guide.ts +291 -0
- package/src/modules/agent-guide/index.ts +21 -0
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +28 -121
- package/src/modules/agent-instructions/agent-instructions.routes.ts +3 -2
- package/src/modules/agent-instructions/compose.ts +9 -6
- package/src/modules/agent-instructions/index.ts +0 -3
- package/src/modules/agent-instructions/shared-file-rules.ts +31 -93
- package/src/modules/mcp/__tests__/fake-downstream-mcp-server.ts +14 -3
- package/src/modules/mcp/__tests__/mcp.e2e.test.ts +250 -0
- package/src/modules/mcp/__tests__/mcp.service.test.ts +31 -23
- package/src/modules/mcp/__tests__/tool-schema-guard.test.ts +266 -0
- package/src/modules/mcp/mcp.service.ts +137 -19
- package/src/modules/mcp/tool-schema-guard.ts +196 -0
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +154 -4
- package/src/modules/plugins/plugins.tools.ts +75 -15
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +26 -55
- package/src/modules/settings/deployment-settings.service.ts +13 -54
- package/src/modules/settings/setup.routes.ts +3 -6
- package/src/modules/skills/__tests__/skills.tools.description.test.ts +91 -0
- package/src/modules/skills/skills.tools.ts +62 -16
- package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +57 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +73 -4
- package/src/modules/tool-manuals/tool-manuals.contract.ts +24 -4
- package/src/modules/tool-manuals/tool-manuals.service.ts +17 -0
- package/src/modules/tool-manuals/tool-manuals.tools.ts +74 -36
- package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +160 -0
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +61 -59
- package/src/modules/tool-registry/description-length.ts +24 -26
- package/src/modules/tool-registry/guide-first.ts +34 -0
- package/src/modules/tool-registry/tool-registry.ts +9 -2
- package/src/modules/workflow/__tests__/apply-failure.test.ts +6 -1
- package/src/modules/workflow/agent-tools/__tests__/change-request-read-shape.test.ts +705 -0
- package/src/modules/workflow/agent-tools/__tests__/change-request-read.tools.test.ts +1518 -0
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +23 -2
- package/src/modules/workflow/agent-tools/change-request-read-shape.ts +712 -0
- package/src/modules/workflow/agent-tools/change-request-read.tools.ts +724 -0
- package/src/modules/workflow/agent-tools/change-request-summary.ts +5 -1
- package/src/modules/workflow/agent-tools/workflow.tools.ts +8 -0
- package/src/modules/workflow/git/__tests__/git.service.appliedChange.test.ts +285 -0
- package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +124 -0
- package/src/modules/workflow/git/__tests__/git.service.mergeChangeRequest.test.ts +334 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.list-fetch.test.ts +72 -2
- package/src/modules/workflow/git/__tests__/pull-request.service.placeholder.test.ts +24 -2
- package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +620 -1
- package/src/modules/workflow/git/git.service.ts +537 -94
- package/src/modules/workflow/git/merge-commit.ts +88 -0
- package/src/modules/workflow/git/pull-request.service.ts +380 -54
- package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +7 -1
- package/src/modules/workflow/review-workflow/__tests__/merge-records-own-commit.test.ts +407 -0
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +189 -11
- package/src/modules/workflow/workflow.routes.ts +7 -2
- package/src/modules/workflow/workflow.service.ts +7 -0
- package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +4 -3
- package/src/modules/workspace/__tests__/workspace.routes.move-platform-files.test.ts +21 -10
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +33 -55
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +255 -22
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +191 -489
- package/src/modules/workspace/startup/steps/seed-tree.ts +21 -27
- package/src/modules/workspace/startup/steps/template-files.step.ts +217 -249
- package/src/modules/workspace/startup/steps/template-source.ts +5 -3
- package/src/modules/workspace/workspace.tools.ts +226 -16
- package/src/shared/domain-errors.ts +15 -0
- package/src/shared/hidden-tools.ts +45 -0
- package/kb-template/AGENTS.md +0 -730
|
@@ -2,23 +2,29 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import {
|
|
4
4
|
LEGACY_AGENTS_FILE,
|
|
5
|
-
agentsFilePointerSentence,
|
|
6
|
-
gitignoreLiteral,
|
|
7
|
-
mentionsAgentsFile,
|
|
8
|
-
retargetAgentsFilePointer,
|
|
9
5
|
validateKbRootName,
|
|
10
6
|
type KbLayout,
|
|
11
7
|
} from '@bevel-software/platform-shared';
|
|
12
8
|
import { IGNORE_FILENAME, isAbsence, type IFsProbe } from '../../../../shared/fs.contract.js';
|
|
13
9
|
import type { KbContext } from '../../../../shared/kb-context.js';
|
|
14
10
|
import { PREAMBLE_FILE } from '../../../agent-instructions/compose.js';
|
|
11
|
+
import { isManagedGuide } from '../../../agent-guide/agent-guide.js';
|
|
15
12
|
import { TemplateSource } from './template-source.js';
|
|
16
13
|
import type { KbBranch, OnServerStart, ServerStartContext, StepResult } from '../on-server-start.js';
|
|
17
14
|
import { hasGitInternalsSegment } from '../../../../shared/git-internals.js';
|
|
18
15
|
|
|
16
|
+
export { isManagedGuide };
|
|
17
|
+
|
|
19
18
|
/** Root-anchored so a knowledge folder may still contain an ordinary namesake. */
|
|
20
19
|
const PREAMBLE_IGNORE_PATTERN = `/${PREAMBLE_FILE}`;
|
|
21
20
|
|
|
21
|
+
/**
|
|
22
|
+
* The guide's name before it was `AGENTS.md`, back when it was a file. A copy
|
|
23
|
+
* the platform wrote under it is taken out of the repository like one under
|
|
24
|
+
* the current name (see {@link TemplateFilesStep.retireGuideCopies}).
|
|
25
|
+
*/
|
|
26
|
+
const PRE_RENAME_AGENTS_FILE = 'CLAUDE.md';
|
|
27
|
+
|
|
22
28
|
/**
|
|
23
29
|
* The **required scaffolding** — the minimum an operational KB needs. Any of
|
|
24
30
|
* these missing from a protected branch are added at the startup phase; the
|
|
@@ -34,16 +40,14 @@ const PREAMBLE_IGNORE_PATTERN = `/${PREAMBLE_FILE}`;
|
|
|
34
40
|
*
|
|
35
41
|
* `roles.yaml` is in neither, and is not part of the template at all: it is
|
|
36
42
|
* generated from `ADMIN_EMAIL` (see roles-yaml.step.ts), so a repo can't be
|
|
37
|
-
* seeded with a stale hard-coded Admin list.
|
|
43
|
+
* seeded with a stale hard-coded Admin list. The agent guide is in neither
|
|
44
|
+
* either, any more: it is served from code (see modules/agent-guide), and the
|
|
45
|
+
* copies earlier releases wrote are REMOVED here, not refreshed.
|
|
38
46
|
*/
|
|
39
|
-
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- the list is a function of the layout to its callers, though no name in it is configurable today
|
|
48
|
+
export function requiredFiles(_layout: Required<KbLayout>): readonly string[] {
|
|
40
49
|
return [
|
|
41
50
|
'access.md',
|
|
42
|
-
// The managed agent guide, under whatever this deployment calls it. The
|
|
43
|
-
// PACKAGED template still carries it as `AGENTS.md` — one file, one
|
|
44
|
-
// spelling in the tarball — so the write target and the template source
|
|
45
|
-
// part company here and nowhere else (see {@link templateNameOf}).
|
|
46
|
-
layout.agentsFile,
|
|
47
51
|
'.bevelignore',
|
|
48
52
|
'.gitignore',
|
|
49
53
|
// The deployment preamble every connected agent is told at session start
|
|
@@ -54,33 +58,6 @@ export function requiredFiles(layout: Required<KbLayout>): readonly string[] {
|
|
|
54
58
|
];
|
|
55
59
|
}
|
|
56
60
|
|
|
57
|
-
/**
|
|
58
|
-
* The template's own name for a required file. The guide is the one file whose
|
|
59
|
-
* name on disk is a deployment's choice while its name in the template is
|
|
60
|
-
* fixed; everything else is spelled the same on both sides.
|
|
61
|
-
*/
|
|
62
|
-
function templateNameOf(repoRel: string, agentsFile: string): string {
|
|
63
|
-
return repoRel === agentsFile ? LEGACY_AGENTS_FILE : repoRel;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* The sentence the managed guide carries about itself, and the ONLY thing that
|
|
68
|
-
* makes a root `AGENTS.md` provably the platform's rather than the customer's.
|
|
69
|
-
*
|
|
70
|
-
* Matched on this one phrase rather than on the whole header: the lines around
|
|
71
|
-
* it have changed between releases (they name the configured file now), and a
|
|
72
|
-
* knowledge base seeded by any of those releases is still ours to remove. A
|
|
73
|
-
* customer file would have to quote the platform's own claim about itself
|
|
74
|
-
* verbatim to be mistaken for one, and the consequence of the mistake is a
|
|
75
|
-
* deletion — which is why nothing looser will do.
|
|
76
|
-
*/
|
|
77
|
-
const MANAGED_GUIDE_MARKER = '**This file is managed by the platform.**';
|
|
78
|
-
|
|
79
|
-
/** Whether `text` is a copy of the platform's managed guide, of any vintage. */
|
|
80
|
-
export function isManagedGuide(text: string): boolean {
|
|
81
|
-
return text.includes(MANAGED_GUIDE_MARKER);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
61
|
/**
|
|
85
62
|
* Repo-root files the startup phase GENERATES rather than copies — today just
|
|
86
63
|
* `roles.yaml`, rendered from `ADMIN_EMAIL` (see roles-yaml.step.ts and
|
|
@@ -161,13 +138,16 @@ export function reservedRootDirs(extraRootDirs: readonly string[], layout: Requi
|
|
|
161
138
|
|
|
162
139
|
/**
|
|
163
140
|
* The template top-up as an {@link OnServerStart} step: add any missing base
|
|
164
|
-
* scaffolding to every PROTECTED branch, and
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
141
|
+
* scaffolding to every PROTECTED branch, and take the agent guide copies
|
|
142
|
+
* earlier releases wrote OUT of them — the guide is served from code now, and
|
|
143
|
+
* a copy left on disk would be read as the organisation's own conventions
|
|
144
|
+
* file, stale and under a header that says the platform owns it. Drafts are
|
|
145
|
+
* deliberately out of scope — whatever the protected branches gain, drafts
|
|
146
|
+
* fork from; a scaffolding addition on a draft would surface as noise in its
|
|
147
|
+
* change request's diff. (Unlike the Groups→Plugins rename, a missing file
|
|
148
|
+
* diffs as one file, not the whole tree — so the uniform-application argument
|
|
149
|
+
* does not bite here. A stale guide copy on a draft is recognised by its
|
|
150
|
+
* header and never served, see `modules/agent-guide`.)
|
|
171
151
|
*
|
|
172
152
|
* Everything is DECLARED on the branch handle; reads go against the pre-step
|
|
173
153
|
* tree via `repoDir()`. Fail-open behavior from the lazy top-up (best-effort,
|
|
@@ -185,12 +165,6 @@ export class TemplateFilesStep implements OnServerStart {
|
|
|
185
165
|
* claim a root without also shipping a template entry
|
|
186
166
|
* for it.
|
|
187
167
|
*/
|
|
188
|
-
/**
|
|
189
|
-
* @param agentsFileLink Whether to keep the platform's pointer sentence in a
|
|
190
|
-
* customer-owned root `AGENTS.md` — a GETTER, because
|
|
191
|
-
* the setting behind it may be saved by the very
|
|
192
|
-
* first-run save that then runs this phase.
|
|
193
|
-
*/
|
|
194
168
|
constructor(
|
|
195
169
|
private readonly disk: IFsProbe,
|
|
196
170
|
/**
|
|
@@ -200,7 +174,6 @@ export class TemplateFilesStep implements OnServerStart {
|
|
|
200
174
|
*/
|
|
201
175
|
private readonly kb: Pick<KbContext, 'layout'>,
|
|
202
176
|
private readonly extraRootDirs: readonly string[] = [],
|
|
203
|
-
private readonly agentsFileLink: () => boolean = () => true,
|
|
204
177
|
) {
|
|
205
178
|
// Validated NOW, so a bad extra fails at boot beside the rest of the
|
|
206
179
|
// wiring — but the list itself is NOT kept, for the reason `kb` says.
|
|
@@ -224,8 +197,6 @@ export class TemplateFilesStep implements OnServerStart {
|
|
|
224
197
|
// Read ONCE per branch: a value re-read between the write and the ignore
|
|
225
198
|
// rule could disagree with itself.
|
|
226
199
|
const layout = this.kb.layout;
|
|
227
|
-
const agentsFile = layout.agentsFile;
|
|
228
|
-
const renamed = agentsFile !== LEGACY_AGENTS_FILE;
|
|
229
200
|
|
|
230
201
|
for (const rel of requiredFiles(layout)) {
|
|
231
202
|
// `lstat`, not `exists`: a DIRECTORY or SYMLINK squatting a required
|
|
@@ -242,58 +213,45 @@ export class TemplateFilesStep implements OnServerStart {
|
|
|
242
213
|
'Remove or rename it — the platform requires this name to be a readable file.',
|
|
243
214
|
);
|
|
244
215
|
}
|
|
245
|
-
let content = await templates.read(
|
|
216
|
+
let content = await templates.read(rel);
|
|
246
217
|
// The on-disk merge below only runs against an EXISTING ignore file; a
|
|
247
|
-
// freshly-declared one
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
//
|
|
254
|
-
//
|
|
255
|
-
// it: the on-disk reconciliation below never sees a file that was
|
|
256
|
-
// absent, so the declared content is reconciled here instead.
|
|
218
|
+
// freshly-declared one is reconciled here instead, so a distribution's
|
|
219
|
+
// custom template that predates a rule — or still ships one an earlier
|
|
220
|
+
// release had — declares the same file the merge would have produced.
|
|
221
|
+
// The deployment preamble is edited through External agent access, not
|
|
222
|
+
// as an ordinary knowledge-base document, so its rule is guaranteed;
|
|
223
|
+
// the rules that hid the skills root, the plugins root and the guide
|
|
224
|
+
// copies the platform used to write are taken out (see the merge below
|
|
225
|
+
// for why each).
|
|
257
226
|
if (rel === IGNORE_FILENAME) {
|
|
258
227
|
// A template still shipping the unanchored preamble rule an earlier
|
|
259
228
|
// release had is respelled first, so the guarantee below adds nothing
|
|
260
229
|
// beside it.
|
|
261
230
|
content = withPlatformIgnorePatternRespelled(content, PREAMBLE_FILE, PREAMBLE_IGNORE_PATTERN);
|
|
262
|
-
content =
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
withIgnorePattern(content,
|
|
266
|
-
|
|
267
|
-
agentsFile,
|
|
231
|
+
content = withoutPlatformGuideRules(
|
|
232
|
+
withoutIgnoreLine(
|
|
233
|
+
withoutPlatformIgnorePattern(
|
|
234
|
+
withIgnorePattern(content, PREAMBLE_IGNORE_PATTERN),
|
|
235
|
+
`${layout.skillsDir}/`,
|
|
268
236
|
),
|
|
269
|
-
`${layout.
|
|
237
|
+
`${layout.pluginsDir}/`,
|
|
270
238
|
),
|
|
271
|
-
|
|
239
|
+
[LEGACY_AGENTS_FILE, PRE_RENAME_AGENTS_FILE],
|
|
272
240
|
);
|
|
273
|
-
// …and a template (a distribution's, a stale packaged one) still
|
|
274
|
-
// hiding `AGENTS.md` while this deployment's guide is called something
|
|
275
|
-
// else would hide the CUSTOMER'S file from the first boot — the exact
|
|
276
|
-
// thing the rename exists to prevent.
|
|
277
|
-
if (renamed) content = withoutPlatformAgentsRule(content);
|
|
278
241
|
}
|
|
279
242
|
branch.write(rel, content);
|
|
280
243
|
added.push(rel);
|
|
281
244
|
}
|
|
282
245
|
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
// UNCONDITIONALLY, not only when the file was just added: a KB whose
|
|
286
|
-
// guide predates the CLAUDE.md→AGENTS.md rename has an ignore file
|
|
287
|
-
// that lists the old name and knows nothing of the new one, so the
|
|
288
|
-
// conventions doc shows up in the file tree and the agent view. A KB
|
|
289
|
-
// whose deployment has just RENAMED the guide is the same story one
|
|
290
|
-
// rename later: the rule names a file that is now the customer's.
|
|
246
|
+
// mcp-description.md left VISIBLE by a stale `.bevelignore` is closed
|
|
247
|
+
// here — and UNCONDITIONALLY, not only when the file was just added.
|
|
291
248
|
// Idempotent: an ignore file already carrying the rule — or absent, in
|
|
292
249
|
// which case the template's copy declared above arrives with the rule in
|
|
293
250
|
// it — changes nothing and produces no note. Deliberately checked by
|
|
294
|
-
// LINE PRESENCE, not effective outcome: a later
|
|
295
|
-
// the operator explicitly choosing to SHOW the file, and
|
|
296
|
-
// default this step provides, not a mandate it re-imposes
|
|
251
|
+
// LINE PRESENCE, not effective outcome: a later `!/mcp-description.md`
|
|
252
|
+
// negation is the operator explicitly choosing to SHOW the file, and
|
|
253
|
+
// hiding it is a default this step provides, not a mandate it re-imposes
|
|
254
|
+
// every boot.
|
|
297
255
|
//
|
|
298
256
|
// The shared-skills root goes the OTHER way. An earlier release hid it
|
|
299
257
|
// like `Plugins/`; the Skills & Tools sidebar now renders it as a file
|
|
@@ -310,159 +268,104 @@ export class TemplateFilesStep implements OnServerStart {
|
|
|
310
268
|
// it as a file tree too, so the rule that hid it since the first seed
|
|
311
269
|
// comes out. That one has no comment to know it by — it was in the
|
|
312
270
|
// template body from the start — so every line spelling it goes,
|
|
313
|
-
// whoever wrote it (see `withoutIgnoreLine`).
|
|
314
|
-
//
|
|
315
|
-
//
|
|
271
|
+
// whoever wrote it (see `withoutIgnoreLine`).
|
|
272
|
+
//
|
|
273
|
+
// And the guide's rules go the same way as the skills root's. Every
|
|
274
|
+
// release that wrote the guide to disk hid it with a rule of its own —
|
|
275
|
+
// under `AGENTS.md`, under the name a deployment gave the guide (any
|
|
276
|
+
// name it ever gave it: the copies found at the root say which), and
|
|
277
|
+
// under `CLAUDE.md` for the copy that predates the rename. The guide is
|
|
278
|
+
// not on disk any more, so a root file under any of those names is the
|
|
279
|
+
// organisation's own conventions page, which they must be able to see
|
|
280
|
+
// and edit in the app. The platform's own lines come out, recognised by
|
|
281
|
+
// the comment or the template slot each release wrote them in (see
|
|
282
|
+
// {@link withoutPlatformGuideRules}); a rule an operator wrote by hand
|
|
283
|
+
// is theirs and stays. ONE read-modify-write for all the rules: separate
|
|
284
|
+
// passes would each read the on-disk file and a later declared write
|
|
285
|
+
// would lose an earlier one's.
|
|
286
|
+
//
|
|
287
|
+
// The copies come out first only so the one commit this step makes can
|
|
288
|
+
// name them in its subject; the rule pass below does not read the names
|
|
289
|
+
// — a guide rule is known by the platform's comment above it, whatever
|
|
290
|
+
// name it spells.
|
|
291
|
+
const retired = await this.retireGuideCopies(repoDir, branch);
|
|
292
|
+
added.push(...retired);
|
|
316
293
|
added.push(
|
|
317
294
|
...(await this.reconcileIgnoreRules(repoDir, branch, {
|
|
318
295
|
// The preamble rule is respelled before it is added: a knowledge base
|
|
319
296
|
// that booted the release shipping the unanchored spelling carries the
|
|
320
297
|
// platform's own line, and that line hides a nested namesake too.
|
|
321
298
|
respell: [[PREAMBLE_FILE, PREAMBLE_IGNORE_PATTERN]],
|
|
322
|
-
|
|
323
|
-
// escaped, or the rule would hide nothing.
|
|
324
|
-
add: [gitignoreLiteral(agentsFile), PREAMBLE_IGNORE_PATTERN],
|
|
299
|
+
add: [PREAMBLE_IGNORE_PATTERN],
|
|
325
300
|
drop: [`${this.kb.layout.skillsDir}/`],
|
|
326
301
|
dropEvery: [`${this.kb.layout.pluginsDir}/`],
|
|
327
|
-
// The
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
|
|
331
|
-
// (see {@link withoutPlatformAgentsRule}); a rule an operator wrote by
|
|
332
|
-
// hand is theirs and stays.
|
|
333
|
-
dropAgentsRule: renamed,
|
|
302
|
+
// The two names the template itself shipped a rule for. A rule under
|
|
303
|
+
// any other name — a retired copy's, or a name no copy is left to
|
|
304
|
+
// tell — is known by the platform's comment above it instead.
|
|
305
|
+
guideNames: [LEGACY_AGENTS_FILE, PRE_RENAME_AGENTS_FILE],
|
|
334
306
|
})),
|
|
335
307
|
);
|
|
336
308
|
|
|
337
|
-
// The guide is MANAGED, not merely seeded: the platform owns its content,
|
|
338
|
-
// and a stale copy is replaced with the packaged template's every startup
|
|
339
|
-
// phase. The file's own header says so, which is what makes overwriting
|
|
340
|
-
// edits a stated contract instead of a surprise.
|
|
341
|
-
let agentsRefreshed = false;
|
|
342
|
-
if (
|
|
343
|
-
!added.includes(agentsFile) &&
|
|
344
|
-
(await templates.differsFrom(repoDir, agentsFile, LEGACY_AGENTS_FILE))
|
|
345
|
-
) {
|
|
346
|
-
branch.write(agentsFile, await templates.read(LEGACY_AGENTS_FILE));
|
|
347
|
-
added.push(agentsFile);
|
|
348
|
-
agentsRefreshed = true;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// What becomes of the `AGENTS.md` the platform used to own, now that the
|
|
352
|
-
// guide lives somewhere else. Nothing at all while the name is the default.
|
|
353
|
-
if (renamed) {
|
|
354
|
-
added.push(
|
|
355
|
-
...(await this.reconcileLegacyGuide(repoDir, branch, {
|
|
356
|
-
agentsFile,
|
|
357
|
-
// Only on the boot the rename lands — the boot that first writes the
|
|
358
|
-
// guide under its new name. Said every boot after, the "kept" note
|
|
359
|
-
// would caption commits about other things forever.
|
|
360
|
-
announceKept: added.includes(agentsFile),
|
|
361
|
-
})),
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
309
|
added.push(...this.ensureRequiredDirs(repoDir, branch, await this.missingDirs(repoDir)));
|
|
366
310
|
|
|
367
311
|
if (added.length === 0) return;
|
|
368
312
|
// One honest line; it becomes the commit subject when this step is the
|
|
369
313
|
// first to dirty the branch.
|
|
314
|
+
const others = added.filter((rel) => !retired.includes(rel));
|
|
370
315
|
branch.note(
|
|
371
|
-
|
|
372
|
-
? `
|
|
316
|
+
retired.length > 0
|
|
317
|
+
? `Remove the platform-written ${retired.join(' and ')} — the agent guide is served by the platform now` +
|
|
318
|
+
(others.length > 0 ? `; update ${others.join(', ')}` : '')
|
|
373
319
|
: `Add missing KB scaffolding: ${added.join(', ')}`,
|
|
374
320
|
);
|
|
375
321
|
}
|
|
376
322
|
|
|
377
323
|
/**
|
|
378
|
-
* The
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
* Removal is gated on the managed header and nothing else. An admin who
|
|
384
|
-
* renames the guide on a knowledge base the platform seeded would otherwise
|
|
385
|
-
* be left with stale platform content sitting under the very name they
|
|
386
|
-
* wanted for their own file; an admin who renames it on a repository whose
|
|
387
|
-
* `AGENTS.md` is their own must find that file byte-for-byte untouched. The
|
|
388
|
-
* header is the one fact that tells the two apart, so it is the one thing
|
|
389
|
-
* asked.
|
|
324
|
+
* The copies of the guide the platform wrote to the repository root while
|
|
325
|
+
* the guide was a file — under `AGENTS.md`, under `CLAUDE.md` from before
|
|
326
|
+
* the rename, and under every name a deployment ever gave the guide —
|
|
327
|
+
* removed when the platform can PROVE it wrote them, and otherwise left
|
|
328
|
+
* exactly alone.
|
|
390
329
|
*
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
*
|
|
330
|
+
* Found by SCANNING the root's markdown files rather than by the names the
|
|
331
|
+
* deployment knows today: a deployment that renamed the guide more than
|
|
332
|
+
* once left a copy under each earlier name, and the current setting
|
|
333
|
+
* remembers only the last. The header is what makes a scan safe, and the
|
|
334
|
+
* one fact that tells a copy of ours from a file of theirs: the
|
|
335
|
+
* organisation's own `AGENTS.md`, a `CLAUDE.md` its people edited, a note
|
|
336
|
+
* of theirs that happens to sit at the root, must all be found byte for
|
|
337
|
+
* byte untouched. A SYMLINK or a directory is left as it is: reading a link
|
|
338
|
+
* follows it, so a link pointing at a copy of the guide — or at any other
|
|
339
|
+
* file carrying the header — would read as ours and the removal would take
|
|
340
|
+
* the organisation's entry. Links are never followed anywhere else in the
|
|
341
|
+
* platform, and they are not followed here.
|
|
397
342
|
*
|
|
398
|
-
* Returns the
|
|
343
|
+
* Returns the names removed, in name order, for the note and for the
|
|
344
|
+
* ignore rules that hid them.
|
|
399
345
|
*/
|
|
400
|
-
private async
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
if (found === null || !found.isFile()) return [];
|
|
422
|
-
|
|
423
|
-
let current: string;
|
|
424
|
-
try {
|
|
425
|
-
current = await this.disk.readTextFile(legacyPath);
|
|
426
|
-
} catch (err) {
|
|
427
|
-
// Gone between the probe and the read — a concurrent delete reads as the
|
|
428
|
-
// absence it is, on the same terms as the probe above.
|
|
429
|
-
if (isAbsence(err)) return [];
|
|
430
|
-
throw err;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
if (isManagedGuide(current)) {
|
|
434
|
-
branch.remove(LEGACY_AGENTS_FILE);
|
|
435
|
-
branch.note(`Remove the platform-written AGENTS.md — the agent guide is now ${opts.agentsFile}`);
|
|
436
|
-
return [LEGACY_AGENTS_FILE];
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
if (opts.announceKept) {
|
|
440
|
-
branch.note('Keep AGENTS.md — it is not a platform template, so it is the knowledge base\'s own');
|
|
441
|
-
}
|
|
442
|
-
// The admin's consent gates every write below, this file being theirs.
|
|
443
|
-
if (!this.agentsFileLink()) return [];
|
|
444
|
-
|
|
445
|
-
// A sentence of OURS already in the file is updated rather than joined by
|
|
446
|
-
// a second one. The guide's name can be changed again, and after a second
|
|
447
|
-
// rename the sentence this step wrote last time points at a file that is
|
|
448
|
-
// no longer there — which asking "is the new name mentioned?" cannot see,
|
|
449
|
-
// because the old sentence does not mention it.
|
|
450
|
-
const retargeted = retargetAgentsFilePointer(current, opts.agentsFile);
|
|
451
|
-
if (retargeted !== null) {
|
|
452
|
-
if (retargeted === current) return [];
|
|
453
|
-
branch.write(LEGACY_AGENTS_FILE, retargeted);
|
|
454
|
-
branch.note(`Point the sentence in AGENTS.md at ${opts.agentsFile}`);
|
|
455
|
-
return [LEGACY_AGENTS_FILE];
|
|
346
|
+
private async retireGuideCopies(repoDir: string, branch: KbBranch): Promise<string[]> {
|
|
347
|
+
const removed: string[] = [];
|
|
348
|
+
const entries = await fs.readdir(repoDir, { withFileTypes: true });
|
|
349
|
+
// `isFile` is false for a link, which is the point (see above).
|
|
350
|
+
const candidates = entries
|
|
351
|
+
.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.md'))
|
|
352
|
+
.map((entry) => entry.name)
|
|
353
|
+
.sort();
|
|
354
|
+
for (const name of candidates) {
|
|
355
|
+
let current: string;
|
|
356
|
+
try {
|
|
357
|
+
current = await this.disk.readTextFile(path.join(repoDir, name));
|
|
358
|
+
} catch (err) {
|
|
359
|
+
// Gone between the listing and the read — a concurrent delete reads
|
|
360
|
+
// as the absence it is.
|
|
361
|
+
if (isAbsence(err)) continue;
|
|
362
|
+
throw err;
|
|
363
|
+
}
|
|
364
|
+
if (!isManagedGuide(current)) continue;
|
|
365
|
+
branch.remove(name);
|
|
366
|
+
removed.push(name);
|
|
456
367
|
}
|
|
457
|
-
|
|
458
|
-
// Nothing of ours in there, so the question is the customer's own text.
|
|
459
|
-
// Asked through the shared reading, not a raw `includes`: the sentence
|
|
460
|
-
// spells the name escaped and percent-encoded, so on a punctuated name the
|
|
461
|
-
// copy written last boot need not carry the raw name at all.
|
|
462
|
-
if (mentionsAgentsFile(current, opts.agentsFile)) return [];
|
|
463
|
-
branch.write(LEGACY_AGENTS_FILE, withPointerSentence(current, opts.agentsFile));
|
|
464
|
-
branch.note(`Add a pointer to ${opts.agentsFile} at the end of AGENTS.md`);
|
|
465
|
-
return [LEGACY_AGENTS_FILE];
|
|
368
|
+
return removed;
|
|
466
369
|
}
|
|
467
370
|
|
|
468
371
|
/**
|
|
@@ -533,8 +436,8 @@ export class TemplateFilesStep implements OnServerStart {
|
|
|
533
436
|
dropEvery?: string[];
|
|
534
437
|
/** `[from, to]` pairs: a rule an earlier release wrote, and its spelling now. */
|
|
535
438
|
respell?: ReadonlyArray<readonly [string, string]>;
|
|
536
|
-
/**
|
|
537
|
-
|
|
439
|
+
/** The names the template itself shipped a guide rule for; a rule under any other name is known by its comment (see {@link withoutPlatformGuideRules}). */
|
|
440
|
+
guideNames: readonly string[];
|
|
538
441
|
},
|
|
539
442
|
): Promise<string[]> {
|
|
540
443
|
let current: string;
|
|
@@ -553,15 +456,12 @@ export class TemplateFilesStep implements OnServerStart {
|
|
|
553
456
|
(text, [from, to]) => withPlatformIgnorePatternRespelled(text, from, to),
|
|
554
457
|
current,
|
|
555
458
|
);
|
|
556
|
-
const added = rules.add.reduce(
|
|
557
|
-
(text, pattern) => withIgnorePattern(text, pattern, this.kb.layout.agentsFile),
|
|
558
|
-
respelled,
|
|
559
|
-
);
|
|
459
|
+
const added = rules.add.reduce((text, pattern) => withIgnorePattern(text, pattern), respelled);
|
|
560
460
|
const dropped = (rules.dropEvery ?? []).reduce(
|
|
561
461
|
(text, pattern) => withoutIgnoreLine(text, pattern),
|
|
562
462
|
rules.drop.reduce((text, pattern) => withoutPlatformIgnorePattern(text, pattern), added),
|
|
563
463
|
);
|
|
564
|
-
const merged = rules.
|
|
464
|
+
const merged = withoutPlatformGuideRules(dropped, rules.guideNames);
|
|
565
465
|
if (merged === current) return [];
|
|
566
466
|
branch.write(IGNORE_FILENAME, merged);
|
|
567
467
|
return [IGNORE_FILENAME];
|
|
@@ -638,15 +538,102 @@ function followsTemplateHygieneBlock(kept: readonly string[]): boolean {
|
|
|
638
538
|
return kept.slice(-block.length).every((line, i) => line.trim() === block[i]);
|
|
639
539
|
}
|
|
640
540
|
|
|
541
|
+
/**
|
|
542
|
+
* `text` without the rules THE PLATFORM WROTE to hide the guide while it was a
|
|
543
|
+
* file. Under WHATEVER name: every rule sitting directly under one of the two
|
|
544
|
+
* comments the platform wrote above the guide's rule
|
|
545
|
+
* ({@link withoutPlatformConventionsRules}) — the name a deployment saved for
|
|
546
|
+
* the guide is not read any more, so the rule for it is known by its comment
|
|
547
|
+
* and by nothing else. Then the two names the template itself shipped a rule
|
|
548
|
+
* for, under each of `names`: the `AGENTS.md` line in the slot at the end of
|
|
549
|
+
* the template's own repo-hygiene block
|
|
550
|
+
* ({@link TEMPLATE_HYGIENE_BLOCK_ABOVE_AGENTS_RULE}) and the `CLAUDE.md` line
|
|
551
|
+
* under the two-line comment the template carried it with. Each goes with
|
|
552
|
+
* its comment.
|
|
553
|
+
*
|
|
554
|
+
* The guide is not on disk any more, which is what makes every one of these
|
|
555
|
+
* lines wrong: it hides a file the platform never writes, which is therefore
|
|
556
|
+
* the organisation's own. A `!AGENTS.md` negation is not the rule and stays,
|
|
557
|
+
* as everywhere else here, and so does a bare rule an operator wrote by hand.
|
|
558
|
+
*/
|
|
559
|
+
export function withoutPlatformGuideRules(text: string, names: readonly string[]): string {
|
|
560
|
+
let out = withoutPlatformConventionsRules(text);
|
|
561
|
+
for (const name of new Set(names)) {
|
|
562
|
+
if (name === LEGACY_AGENTS_FILE) out = withoutPlatformAgentsRule(out);
|
|
563
|
+
else if (name === PRE_RENAME_AGENTS_FILE) out = withoutPlatformClaudeRule(out);
|
|
564
|
+
}
|
|
565
|
+
return out;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* `text` without every rule line that sits DIRECTLY under one of the two
|
|
570
|
+
* comments the platform wrote above the guide's rule while the guide was a
|
|
571
|
+
* file ({@link PLATFORM_RULE_COMMENT}, {@link AGENTS_RULE_COMMENT}), whatever
|
|
572
|
+
* name the rule spells — `AGENTS.md`, `CLAUDE.md`, or the escaped form of a
|
|
573
|
+
* name a deployment chose — and without that comment, plus the blank line
|
|
574
|
+
* that opened the appended block. Those two comments were written above
|
|
575
|
+
* nothing else, so the comment is the whole provenance: a rule for a name
|
|
576
|
+
* nobody remembers is retired exactly like one for a name still known. A
|
|
577
|
+
* `!negation`, a blank or a further comment under the comment is not a rule
|
|
578
|
+
* and stays, comment included.
|
|
579
|
+
*/
|
|
580
|
+
function withoutPlatformConventionsRules(text: string): string {
|
|
581
|
+
const lines = text.split('\n');
|
|
582
|
+
const kept: string[] = [];
|
|
583
|
+
for (const line of lines) {
|
|
584
|
+
const above = kept[kept.length - 1]?.trim();
|
|
585
|
+
const rule = line.trim();
|
|
586
|
+
const ours =
|
|
587
|
+
(above === PLATFORM_RULE_COMMENT || above === AGENTS_RULE_COMMENT) &&
|
|
588
|
+
rule !== '' &&
|
|
589
|
+
!rule.startsWith('#') &&
|
|
590
|
+
!rule.startsWith('!');
|
|
591
|
+
if (!ours) {
|
|
592
|
+
kept.push(line);
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
kept.pop();
|
|
596
|
+
if (kept.length > 1 && kept[kept.length - 1]?.trim() === '') kept.pop();
|
|
597
|
+
}
|
|
598
|
+
return kept.join('\n');
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/** The two comment lines the packaged template carried above its `CLAUDE.md` rule, in order. */
|
|
602
|
+
const TEMPLATE_CLAUDE_RULE_COMMENT: readonly string[] = [
|
|
603
|
+
'# The pre-rename name. Listed so a knowledge base carrying both files hides',
|
|
604
|
+
'# both — top-up adds AGENTS.md but never deletes the CLAUDE.md beside it.',
|
|
605
|
+
];
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* `text` without the `CLAUDE.md` line the packaged template wrote, and without
|
|
609
|
+
* the two comment lines it wrote above it. Provenance is that comment, as
|
|
610
|
+
* everywhere else here: a bare `CLAUDE.md` an operator wrote is theirs.
|
|
611
|
+
*/
|
|
612
|
+
function withoutPlatformClaudeRule(text: string): string {
|
|
613
|
+
const lines = text.split('\n');
|
|
614
|
+
const kept: string[] = [];
|
|
615
|
+
for (const line of lines) {
|
|
616
|
+
const [first, second] = TEMPLATE_CLAUDE_RULE_COMMENT;
|
|
617
|
+
const ours =
|
|
618
|
+
line.trim() === PRE_RENAME_AGENTS_FILE &&
|
|
619
|
+
kept.length >= 2 &&
|
|
620
|
+
kept[kept.length - 1]!.trim() === second &&
|
|
621
|
+
kept[kept.length - 2]!.trim() === first;
|
|
622
|
+
if (!ours) {
|
|
623
|
+
kept.push(line);
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
kept.splice(-2, 2);
|
|
627
|
+
}
|
|
628
|
+
return kept.join('\n');
|
|
629
|
+
}
|
|
630
|
+
|
|
641
631
|
/**
|
|
642
632
|
* `text` without the `AGENTS.md` line THE PLATFORM WROTE — under its own
|
|
643
633
|
* comment (either spelling), or in the slot at the end of the template's own
|
|
644
634
|
* repo-hygiene block ({@link TEMPLATE_HYGIENE_BLOCK_ABOVE_AGENTS_RULE}) — and
|
|
645
|
-
* without that comment.
|
|
646
|
-
*
|
|
647
|
-
* Called only when the guide has been renamed, which is what makes the line
|
|
648
|
-
* wrong: it hides a file the platform no longer owns. A `!AGENTS.md` negation
|
|
649
|
-
* is not the rule and stays, as everywhere else here.
|
|
635
|
+
* without that comment. A `!AGENTS.md` negation is not the rule and stays, as
|
|
636
|
+
* everywhere else here.
|
|
650
637
|
*/
|
|
651
638
|
export function withoutPlatformAgentsRule(text: string): string {
|
|
652
639
|
const lines = text.split('\n');
|
|
@@ -674,18 +661,6 @@ export function withoutPlatformAgentsRule(text: string): string {
|
|
|
674
661
|
return kept.join('\n');
|
|
675
662
|
}
|
|
676
663
|
|
|
677
|
-
/**
|
|
678
|
-
* `text` with the platform's pointer sentence appended as its own paragraph.
|
|
679
|
-
*
|
|
680
|
-
* One blank line between the customer's last line and ours, whether or not
|
|
681
|
-
* their file ended in a newline: a sentence glued onto the end of their last
|
|
682
|
-
* paragraph would read as a continuation of something they wrote.
|
|
683
|
-
*/
|
|
684
|
-
export function withPointerSentence(text: string, agentsFile: string): string {
|
|
685
|
-
const body = text.replace(/\n+$/, '');
|
|
686
|
-
return `${body}\n\n${agentsFilePointerSentence(agentsFile)}\n`;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
664
|
/** The comment written above the preamble rule on an existing knowledge base. */
|
|
690
665
|
const PREAMBLE_RULE_COMMENT =
|
|
691
666
|
'# Added by the platform: agent instructions are edited from External agent access.';
|
|
@@ -805,20 +780,13 @@ function withPlatformIgnorePatternRespelled(text: string, from: string, to: stri
|
|
|
805
780
|
* still standing here is the operator's: it already hides the file, and
|
|
806
781
|
* appending the anchored rule beside it would say nothing they have not.
|
|
807
782
|
*/
|
|
808
|
-
function withIgnorePattern(text: string, pattern: string
|
|
783
|
+
function withIgnorePattern(text: string, pattern: string): string {
|
|
809
784
|
const lines = text.split('\n').map((l) => l.trim());
|
|
810
785
|
const operatorPreambleRule =
|
|
811
786
|
pattern === PREAMBLE_IGNORE_PATTERN &&
|
|
812
787
|
(lines.includes(PREAMBLE_FILE) || lines.includes(`!${PREAMBLE_FILE}`));
|
|
813
788
|
if (lines.includes(pattern) || lines.includes(`!${pattern}`) || operatorPreambleRule) return text;
|
|
814
789
|
const separator = text.endsWith('\n') ? '' : '\n';
|
|
815
|
-
|
|
816
|
-
// needs it), so it is recognised in that spelling.
|
|
817
|
-
const comment =
|
|
818
|
-
pattern === PREAMBLE_IGNORE_PATTERN
|
|
819
|
-
? PREAMBLE_RULE_COMMENT
|
|
820
|
-
: pattern === gitignoreLiteral(agentsFile)
|
|
821
|
-
? AGENTS_RULE_COMMENT
|
|
822
|
-
: PLATFORM_RULE_COMMENT;
|
|
790
|
+
const comment = pattern === PREAMBLE_IGNORE_PATTERN ? PREAMBLE_RULE_COMMENT : PLATFORM_RULE_COMMENT;
|
|
823
791
|
return `${text}${separator}\n${comment}\n${pattern}\n`;
|
|
824
792
|
}
|