@amsterdamdatalabs/adl-agent-control-linux-x64 0.1.32 → 0.1.33
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/bin/adl +1 -1
- package/package.json +1 -1
- package/packages/agent-hooks/src/rules/impl/adl-git-push-policy.ts +5 -4
- package/packages/agent-hooks/src/rules/shell/protected-carriers.ts +19 -5
- package/packages/agent-settings/assets/GLOBAL-AGENTS.md +1 -1
- package/packages/azdo/src/lib/commit-types.ts +8 -0
- package/packages/azdo/src/lib/validate-pr-metadata.ts +4 -1
- package/packages/repo-controls/AGENTS.md +1 -1
- package/packages/repo-controls/README.md +7 -7
- package/packages/repo-controls/scripts/hooks/commit-msg +1 -1
- package/packages/repo-controls/src/config.ts +7 -6
- package/packages/repo-controls/src/integrity.ts +5 -18
package/bin/adl
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amsterdamdatalabs/adl-agent-control-linux-x64",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.33",
|
|
4
4
|
"description": "adl CLI binary for linux/x64 (agent identity + Azure DevOps front door — see docs/architecture/cli-design.md)",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"os": [
|
|
@@ -39,14 +39,14 @@
|
|
|
39
39
|
* `classifyGitPushDestination`) — denied for EVERY identity, matching the
|
|
40
40
|
* matrix's ❌/❌ for this row. An unrecognized branch name (neither a
|
|
41
41
|
* protected name nor a recognized feature prefix) fails closed into this
|
|
42
|
-
* same bucket: the matrix only grants an exception for the
|
|
42
|
+
* same bucket: the matrix only grants an exception for the listed
|
|
43
43
|
* prefixes, not for "anything else". `develop` is retired
|
|
44
44
|
* (docs/specs/two-branch-standard-2026-09-27.md §1b.2 — the standard is
|
|
45
45
|
* two branches, not three) and is deliberately NOT a named protected
|
|
46
46
|
* branch any more; a push to it still lands in this same bucket, but as
|
|
47
47
|
* an ordinary unrecognized name, not a special case.
|
|
48
48
|
* 3. Row 2 — the destination is a recognized feature branch
|
|
49
|
-
* (`
|
|
49
|
+
* (`<commit type>/`, every type but `release` — packages/azdo/src/lib/commit-types.ts): allowed for `agent-tarun`/`agent-vivek`
|
|
50
50
|
* (Azure DevOps Contributors, and this is the ordinary way they ship
|
|
51
51
|
* work); denied for `agent-reviewer` (Azure DevOps Reader — cannot push
|
|
52
52
|
* at all, see docs/enact/handoff-enact-vps-global-tools-2026-09-25.md's
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
import type { Invocation } from '../shell/invocation.ts'
|
|
57
57
|
import type { Token } from '../shell/token.ts'
|
|
58
58
|
import { tokenValue } from '../shell/token.ts'
|
|
59
|
+
import { BRANCH_PREFIXES } from '../../../../azdo/src/lib/commit-types.ts'
|
|
59
60
|
import type { Finding, MessageContext, RuleContext, RuleSpec } from '../spec.ts'
|
|
60
61
|
import { noParams } from '../spec.ts'
|
|
61
62
|
import { AGENT_REVIEWER, resolveFromEnvOrFile } from './adl-identity.ts'
|
|
@@ -134,7 +135,7 @@ function pushBypassesPolicy(rest: readonly Token[]): boolean {
|
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
const PROTECTED_BRANCHES: ReadonlySet<string> = new Set(['integration', 'main'])
|
|
137
|
-
const FEATURE_PREFIXES: readonly string[] =
|
|
138
|
+
const FEATURE_PREFIXES: readonly string[] = BRANCH_PREFIXES.map((prefix) => `${prefix}/`)
|
|
138
139
|
|
|
139
140
|
function normalizeBranchRef(ref: string): string {
|
|
140
141
|
return ref.startsWith('refs/heads/') ? ref.slice('refs/heads/'.length) : ref
|
|
@@ -229,7 +230,7 @@ export const adlGitPushBranchPolicy: RuleSpec = {
|
|
|
229
230
|
const user = 'protected branch: push a feature branch and open a PR'
|
|
230
231
|
return {
|
|
231
232
|
user,
|
|
232
|
-
agent: `DENIED ${user}. \`integration\`/\`main\` (and any branch this hook cannot resolve from argv alone, including a retired name like \`develop\`) never take a direct push — push a \`
|
|
233
|
+
agent: `DENIED ${user}. \`integration\`/\`main\` (and any branch this hook cannot resolve from argv alone, including a retired name like \`develop\`) never take a direct push — push a \`${FEATURE_PREFIXES.join(' ')}\` branch and open a PR. Blocked: ${ctx.snippet}`,
|
|
233
234
|
}
|
|
234
235
|
}
|
|
235
236
|
|
|
@@ -47,6 +47,12 @@ export const PROTECTED_CARRIER_PATHS: ReadonlySet<string> = new Set([
|
|
|
47
47
|
// separate home-relative resolution needed — the suffix is the same
|
|
48
48
|
// regardless of what resolves the leading `~`/`$HOME`/absolute prefix.
|
|
49
49
|
'.kimi-code/config.toml',
|
|
50
|
+
// OpenCode's permission policy (surface-policy.ts); OpenCode reads both spellings (global-surface-sweep.ts).
|
|
51
|
+
'.config/opencode/opencode.jsonc',
|
|
52
|
+
'.config/opencode/opencode.json',
|
|
53
|
+
// Claude managed settings, macOS and Linux; suffix entries so /private/etc and ../ spellings match too.
|
|
54
|
+
'Library/Application Support/ClaudeCode/managed-settings.json',
|
|
55
|
+
'etc/claude-code/managed-settings.json',
|
|
50
56
|
])
|
|
51
57
|
|
|
52
58
|
/**
|
|
@@ -105,10 +111,8 @@ export function protectedCarrierMatch(path: string): string | null {
|
|
|
105
111
|
/**
|
|
106
112
|
* The per-host carrier DIRECTORIES — deleting, moving away, or overwriting
|
|
107
113
|
* the WHOLE directory removes (or replaces) the carrier file inside it too,
|
|
108
|
-
* exactly as much as touching that file directly.
|
|
109
|
-
*
|
|
110
|
-
* genuinely different, much bigger blast radius (moving the user's whole
|
|
111
|
-
* Kimi config directory, not a repo-scoped host-config directory).
|
|
114
|
+
* exactly as much as touching that file directly. All five surfaces, global and
|
|
115
|
+
* in the repo (Tarun, 2026-10-09): `.claude`/`.codex`/`.cursor`/`.opencode`, `.kimi-code`, `.config/opencode`.
|
|
112
116
|
*
|
|
113
117
|
* Covers `mv`/`cp -r` (destination)/`rsync` (destination) via
|
|
114
118
|
* `protected-carrier-writes.ts`'s existing operand extraction — a
|
|
@@ -119,7 +123,17 @@ export function protectedCarrierMatch(path: string): string | null {
|
|
|
119
123
|
* existing operand handlers, and covering them needs new, tool-specific
|
|
120
124
|
* parsing; a known gap, recorded here rather than silently dropped.
|
|
121
125
|
*/
|
|
122
|
-
|
|
126
|
+
// The managed-settings folders are protected too: moving one away takes its managed-settings.json with it.
|
|
127
|
+
const PROTECTED_CARRIER_DIRS: ReadonlySet<string> = new Set([
|
|
128
|
+
'.claude',
|
|
129
|
+
'.codex',
|
|
130
|
+
'.cursor',
|
|
131
|
+
'.opencode',
|
|
132
|
+
'.kimi-code',
|
|
133
|
+
'.config/opencode',
|
|
134
|
+
'Library/Application Support/ClaudeCode',
|
|
135
|
+
'etc/claude-code',
|
|
136
|
+
])
|
|
123
137
|
|
|
124
138
|
/**
|
|
125
139
|
* The protected carrier directory `path` names — exact match or a trailing
|
|
@@ -64,7 +64,7 @@ repo's own `AGENTS.md`.
|
|
|
64
64
|
- **Repository controls run via `adl repo controls`.** Git hooks in controlled repositories run pre-commit (local checks) and pre-push (Azure DevOps governance). Each repository is reconciled on its own with its `.agents/`, `.claude/`, `.codex/`, `.cursor/`, `.kimi-code/`, `.opencode/` projections.
|
|
65
65
|
- **The release flow (`integration` auto-complete; the `branch → PR → integration → main` promotion; the integration PR merging itself once green) applies to repositories whose root contains an `adl-config.toml`.** Other repositories follow the release flow declared in their own `AGENTS.md`. The human approval gate, where it exists, is the promotion from `integration` to `main`; everything before that is auto-complete once CI is green.
|
|
66
66
|
- Keep each commit **independently green** and on one cohesive topic. Never use `git stash`. When "one topic" and "independently green" conflict, green wins — and say so.
|
|
67
|
-
- Subjects are `<type>(<scope>): <lowercase subject>`. Types: `feat|fix|docs|refactor|perf|chore`. No `!` marker. `release:` is reserved for the promotion PR the pipeline raises — never write one by hand. **In repositories whose root contains an `adl-config.toml`, the pipeline opens `integration → main` once CI is green on `integration` — no agent opens it; the subject follows the commit grammar above.**
|
|
67
|
+
- Subjects are `<type>(<scope>): <lowercase subject>`. Types: `feat|fix|docs|refactor|perf|chore`. Branch prefixes are exactly these types (`<type>/<kebab>`), not configurable per repository. No `!` marker. `release:` is reserved for the promotion PR the pipeline raises — never write one by hand. **In repositories whose root contains an `adl-config.toml`, the pipeline opens `integration → main` once CI is green on `integration` — no agent opens it; the subject follows the commit grammar above.**
|
|
68
68
|
- A PR body needs non-empty `## Summary`, `## Why` and `## Changes`. The title follows the commit grammar above.
|
|
69
69
|
|
|
70
70
|
## Tests
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The one list of commit types (Bug 651, Tarun 2026-10-09); every other list derives from it.
|
|
2
|
+
export const COMMIT_TYPES = ['feat', 'fix', 'docs', 'refactor', 'perf', 'chore', 'release'] as const
|
|
3
|
+
|
|
4
|
+
// `release` is reserved for the promotion PR the pipeline raises; agents and humans never author it.
|
|
5
|
+
const RESERVED_COMMIT_TYPE = 'release'
|
|
6
|
+
|
|
7
|
+
// Branch prefixes are exactly the commit types minus `release`; no repository overrides them.
|
|
8
|
+
export const BRANCH_PREFIXES: readonly string[] = COMMIT_TYPES.filter((type) => type !== RESERVED_COMMIT_TYPE)
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import { BRANCH_PREFIXES } from './commit-types.ts'
|
|
2
|
+
|
|
3
|
+
// A PR title never carries the pipeline-reserved `release`, so its types are the branch prefixes.
|
|
4
|
+
const TITLE_PATTERN = new RegExp(`^(${BRANCH_PREFIXES.join('|')})(\\([a-z0-9][a-z0-9-]*\\))?: .+$`)
|
|
2
5
|
const REQUIRED_SECTIONS = ['Summary', 'Why', 'Changes']
|
|
3
6
|
|
|
4
7
|
export function validatePullRequestMetadata(title: string, description: string): string[] {
|
|
@@ -51,7 +51,7 @@ written) and the `core.hooksPath` setting are what `install --repo` wires in
|
|
|
51
51
|
**target** repos.
|
|
52
52
|
|
|
53
53
|
**Rule values are defaults, not universals.** The bundled defaults are enact-m5's:
|
|
54
|
-
branch prefixes `
|
|
54
|
+
branch prefixes fixed to the commit types minus `release` (`packages/azdo/src/lib/commit-types.ts`), protected `main` and `integration`,
|
|
55
55
|
lowercase kebab-case descriptions, lowercase commit subject with an 80-character
|
|
56
56
|
limit and a fixed type list, plus the coverage ratchet and the behind-origin push
|
|
57
57
|
check. Per-repo values are config-driven, in `adl-config.toml`'s `[controls]`
|
|
@@ -384,7 +384,7 @@ gate is switched off by an explicit `false`. See "No silent skips" below.
|
|
|
384
384
|
| Key | Seeded by install | When absent | enact-m5 | Effect |
|
|
385
385
|
|---|---|---|---|---|
|
|
386
386
|
| `branches.protected` | yes | *required* | `["main", "integration"]` | pre-push refuses while on these branches |
|
|
387
|
-
| `branches.prefixes` |
|
|
387
|
+
| `branches.prefixes` | removed | a config error naming the key | n/a | not configurable: branch prefixes are the commit types minus `release` (`packages/azdo/src/lib/commit-types.ts`); pre-push / azdo-push enforce `<prefix>/<kebab>`; delete the key |
|
|
388
388
|
| `pre_push.remote` | yes | *required* | `"origin"` | remote azdo-push pushes to and reads status from |
|
|
389
389
|
| `pre_commit.staged_coverage` | yes (`true`) | config error | `true` | enact-m5-only staged coverage measurement (fails when `[controls.coverage].produce` is not declared) |
|
|
390
390
|
| `pre_commit.doc_freshness` | yes (`true`) | config error | opt in | a staged `docs/` or `plans/` `.md` carries a `last_update` stamped within 5h (nothing to grade when no docs are staged) |
|
|
@@ -633,7 +633,7 @@ the repository, and any of their files with consecutive comment lines fail REP-0
|
|
|
633
633
|
result.
|
|
634
634
|
|
|
635
635
|
Fixed conventions (enact-m5, not configurable): commit subject
|
|
636
|
-
`^(
|
|
636
|
+
`^(<commit type>)(\([a-z0-9][a-z0-9-]*\))?: .+` (types `feat|fix|docs|refactor|perf|chore|release`, defined once in `packages/azdo/src/lib/commit-types.ts` and handed to the hook as `ADL_REPO_CONTROLS_COMMIT_TYPES`; `release` is the pipeline's only), at most 80
|
|
637
637
|
characters, lowercase description without trailing period; `Merge `, `Revert "`
|
|
638
638
|
and `X.Y.Z` subjects exempt; no staged `.scratchpad` paths or new references;
|
|
639
639
|
`git diff --cached --check`; git environment scrubbed before push checks.
|
|
@@ -650,16 +650,16 @@ The pre-commit gate has two deliberately different outcomes:
|
|
|
650
650
|
- A changed or untracked **top-level** dot path is **commit-required**.
|
|
651
651
|
When it is omitted from the index, the hook names it and tells the author to
|
|
652
652
|
stage it. Once staged, it can be committed normally. This covers top-level dot
|
|
653
|
-
paths that are not protected, such as `.mcp.json
|
|
653
|
+
paths that are not protected, such as `.mcp.json`, `.workflows/` and the agent surface roots
|
|
654
|
+
(`.agents/`, `.claude/`, `.codex/`, `.cursor/`, `.kimi-code/`, `.opencode/`): those hold a
|
|
655
|
+
repository's own hooks and skills, so they are not protected as a whole (Bug 644). Nested paths such as
|
|
654
656
|
`tools/.claude/**` are ordinary repository content. The designated ephemeral
|
|
655
657
|
scratch storage is never committable.
|
|
656
658
|
- A **protected control** requires human approval even when staged: every path
|
|
657
|
-
under `compliance
|
|
658
|
-
(`.agents/`, `.claude/`, `.codex/`, `.cursor/`, `.kimi-code/`, `.opencode/`),
|
|
659
|
-
`adl-config.toml`, the folder declared as `[controls.compliance.trees].compliance`
|
|
659
|
+
under `compliance/` or `workflows/`, `adl-config.toml`, the folder declared as `[controls.compliance.trees].compliance`
|
|
660
660
|
(in addition to the fixed `compliance/` root, so a relocated register folder stays
|
|
661
661
|
protected: `integrityWatched` in `src/hook.ts`), and the repository's explicit
|
|
662
|
-
`[controls.integrity].watched` patterns (`
|
|
662
|
+
`[controls.integrity].watched` patterns (`PROTECTED_ROOTS` and `WIRING` in `src/integrity.ts`). A repository protects any dot path it chooses through `watched`. The full definition and the
|
|
663
663
|
owner override: [`#protected-paths-and-owner-override`](../../docs/architecture/compliance-controls.md#protected-paths-and-owner-override). The same controls cannot be
|
|
664
664
|
left dirty while committing other work.
|
|
665
665
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# Enforce the conventional commit subject contract.
|
|
3
3
|
set -euo pipefail
|
|
4
4
|
|
|
5
|
-
ALLOWED_TYPES=
|
|
5
|
+
ALLOWED_TYPES="${ADL_REPO_CONTROLS_COMMIT_TYPES:?}"
|
|
6
6
|
ALLOWED_TYPES_DISPLAY="${ALLOWED_TYPES//|/, }"
|
|
7
7
|
FORMAT_RE="^(${ALLOWED_TYPES})"'(\([a-z0-9][a-z0-9-]*\))?: .+'
|
|
8
8
|
TYPE_RE='^([A-Za-z0-9]+)(\(|:)'
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
|
+
import { BRANCH_PREFIXES, COMMIT_TYPES } from '../../azdo/src/lib/commit-types.ts'
|
|
3
4
|
import { CONFIG_REL, INSTALL_HINT } from './constants.ts'
|
|
4
5
|
import { MERGE_STRATEGIES, type MergeStrategy } from './promote.ts'
|
|
5
6
|
|
|
@@ -76,7 +77,7 @@ export interface FindingsConfig {
|
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
export interface RepoControlsConfig {
|
|
79
|
-
branches: { protected: string[]
|
|
80
|
+
branches: { protected: string[] }
|
|
80
81
|
preCommit: { staticChecks: StaticCheck[]; stagedCoverage: boolean; docFreshness: boolean; compliance: boolean }
|
|
81
82
|
commitMsg: { staticChecks: StaticCheck[] }
|
|
82
83
|
release: { optedOut: boolean; source: string; target: string; mergeStrategy: MergeStrategy; preBump: string[] }
|
|
@@ -111,8 +112,8 @@ export const SEEDED_ATTRIBUTES: SeededAttribute[] = [
|
|
|
111
112
|
{
|
|
112
113
|
key: 'branches',
|
|
113
114
|
text: `
|
|
114
|
-
# pre-push refuses to push while one of these branches is checked out; prefixes
|
|
115
|
-
branches = { protected = ["main", "integration"]
|
|
115
|
+
# pre-push refuses to push while one of these branches is checked out; branch prefixes are fixed to the commit types.
|
|
116
|
+
branches = { protected = ["main", "integration"] }
|
|
116
117
|
`,
|
|
117
118
|
},
|
|
118
119
|
{
|
|
@@ -340,7 +341,6 @@ export function extractControlsSpan(text: string): string | null {
|
|
|
340
341
|
}
|
|
341
342
|
|
|
342
343
|
const BRANCH_RE = /^[a-z0-9][a-z0-9/-]*$/
|
|
343
|
-
const PREFIX_RE = /^[a-z0-9][a-z0-9-]*$/
|
|
344
344
|
const LABEL_RE = /^[a-z0-9][a-z0-9-]*$/
|
|
345
345
|
const REMOTE_RE = /^\S+$/
|
|
346
346
|
|
|
@@ -487,6 +487,7 @@ export function parseConfig(text: string): RepoControlsConfig {
|
|
|
487
487
|
problems.unknownKeys(controls, ['branches', 'pre_commit', 'pre_push', 'commit_msg', 'post_commit', 'compliance', 'coverage', 'findings', 'integrity', 'release'], '')
|
|
488
488
|
|
|
489
489
|
const branches = problems.table(controls, 'branches', ['protected', 'prefixes'])
|
|
490
|
+
if (branches.prefixes !== undefined) problems.add('branches.prefixes is no longer configurable — delete it; branch prefixes are fixed to the commit types')
|
|
490
491
|
// `branches` and `pre_push.remote` are REQUIRED: install always seeds them, and
|
|
491
492
|
// `pre-push` reads their env vars with `${VAR:?}`, which aborts on an empty value —
|
|
492
493
|
// so "no branch policy" cannot be spelled as an empty list.
|
|
@@ -539,7 +540,6 @@ export function parseConfig(text: string): RepoControlsConfig {
|
|
|
539
540
|
const config: RepoControlsConfig = {
|
|
540
541
|
branches: {
|
|
541
542
|
protected: problems.names(branches, 'protected', 'branches.protected', BRANCH_RE),
|
|
542
|
-
prefixes: problems.names(branches, 'prefixes', 'branches.prefixes', PREFIX_RE),
|
|
543
543
|
},
|
|
544
544
|
preCommit: {
|
|
545
545
|
// The repository declares WHAT runs at commit and WHICH hook it runs in.
|
|
@@ -747,7 +747,8 @@ export function configEnv(repo: string, config: RepoControlsConfig): Record<stri
|
|
|
747
747
|
const env: Record<string, string> = {
|
|
748
748
|
ADL_REPO_CONTROLS_ROOT: repo,
|
|
749
749
|
ADL_REPO_CONTROLS_PROTECTED_BRANCHES: config.branches.protected.join('|'),
|
|
750
|
-
ADL_REPO_CONTROLS_BRANCH_PREFIXES:
|
|
750
|
+
ADL_REPO_CONTROLS_BRANCH_PREFIXES: BRANCH_PREFIXES.join('|'),
|
|
751
|
+
ADL_REPO_CONTROLS_COMMIT_TYPES: COMMIT_TYPES.join('|'),
|
|
751
752
|
ADL_REPO_CONTROLS_COVERAGE_RATCHET: config.prePush.coverageRatchet ? '1' : '0',
|
|
752
753
|
ADL_REPO_CONTROLS_FINDINGS_RATCHET: config.prePush.findingsRatchet ? '1' : '0',
|
|
753
754
|
ADL_REPO_CONTROLS_EXPORT_LINKS: config.prePush.exportLinks.join('\n'),
|
|
@@ -9,21 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
const WIRING: readonly string[] = ['adl-config.toml']
|
|
11
11
|
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
// only Tarun lands it. They were "centrally managed dotfiles" an agent could
|
|
15
|
-
// commit alongside a related change; that let an agent land gate-defining files.
|
|
16
|
-
const FIXED_ROOTS: readonly string[] = [
|
|
17
|
-
'compliance',
|
|
18
|
-
'workflows',
|
|
19
|
-
'.workflows',
|
|
20
|
-
'.agents',
|
|
21
|
-
'.claude',
|
|
22
|
-
'.codex',
|
|
23
|
-
'.cursor',
|
|
24
|
-
'.kimi-code',
|
|
25
|
-
'.opencode',
|
|
26
|
-
]
|
|
12
|
+
// Only these folders are protected as a whole; dot folders hold a repository's own hooks and skills (Bug 644).
|
|
13
|
+
const PROTECTED_ROOTS: readonly string[] = ['compliance', 'workflows']
|
|
27
14
|
|
|
28
15
|
/** A declared pattern, as a regex. `*` stays in one directory; `**` crosses them. */
|
|
29
16
|
function patternToRegExp(pattern: string): RegExp {
|
|
@@ -59,18 +46,18 @@ function protectedMatchers(watched: readonly string[]): ((path: string) => boole
|
|
|
59
46
|
return [...wiring, ...declared]
|
|
60
47
|
}
|
|
61
48
|
|
|
62
|
-
const
|
|
49
|
+
const isUnderProtectedRoot = (path: string): boolean => PROTECTED_ROOTS.some((root) => path === root || path.startsWith(`${root}/`))
|
|
63
50
|
|
|
64
51
|
/** Centrally managed repository files that must travel with the next commit. */
|
|
65
52
|
const isTopLevelDotPath = (path: string): boolean => path.startsWith('.')
|
|
66
53
|
|
|
67
54
|
/**
|
|
68
55
|
* The single source of truth used for final-index and dirty-worktree checks.
|
|
69
|
-
*
|
|
56
|
+
* Package policy protects root compliance/workflows and the enrollment
|
|
70
57
|
* file; each repository adds its own gate files through `watched`.
|
|
71
58
|
*/
|
|
72
59
|
export function isProtectedPath(path: string, watched: readonly string[]): boolean {
|
|
73
|
-
return
|
|
60
|
+
return isUnderProtectedRoot(path) || protectedMatchers(watched).some((match) => match(path))
|
|
74
61
|
}
|
|
75
62
|
|
|
76
63
|
/**
|