@amsterdamdatalabs/adl-agent-control-linux-x64 0.1.35 → 0.1.46
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 +2 -2
- package/bin/reconcile +3 -11
- package/bin/setup +15 -15
- package/package.json +1 -1
- package/packages/agent-hooks/deploy-profile.json +1 -1
- package/packages/agent-hooks/package.json +1 -1
- package/packages/agent-hooks/src/main.ts +23 -2
- package/packages/agent-hooks/src/pre-tool-use/handler.ts +2 -2
- package/packages/agent-hooks/src/rules/impl/adl-build-run-role.ts +116 -0
- package/packages/agent-hooks/src/rules/impl/adl-git-user-override-block.ts +159 -0
- package/packages/agent-hooks/src/rules/impl/adl-mcp-cli.ts +22 -12
- package/packages/agent-hooks/src/rules/impl/adl-pr-complete-pending.ts +9 -9
- package/packages/agent-hooks/src/rules/impl/adl-pr-role.ts +6 -5
- package/packages/agent-hooks/src/rules/impl/adl-run-required.ts +11 -5
- package/packages/agent-hooks/src/rules/impl/adl-workitem-role.ts +18 -5
- package/packages/agent-hooks/src/rules/impl/agent-tamper-protection.ts +30 -25
- package/packages/agent-hooks/src/rules/impl/git-core.ts +4 -8
- package/packages/agent-hooks/src/rules/impl/git-target.ts +5 -0
- package/packages/agent-hooks/src/rules/impl/mcp-tool.ts +4 -2
- package/packages/agent-hooks/src/rules/impl/tool-frontend.ts +230 -11
- package/packages/agent-hooks/src/rules/registry.ts +11 -2
- package/packages/agent-hooks/src/rules/shell/git-identity.ts +1 -1
- package/packages/agent-hooks/src/rules/shell/invocation.ts +14 -2
- package/packages/agent-hooks/src/rules/shell/protected-carriers.ts +10 -7
- package/packages/agent-settings/assets/TARUN-GLOBAL-AGENTS-INSTRUCTIONS.md +2 -2
- package/packages/agent-settings/package.json +1 -1
- package/packages/agent-settings/src/deploy/agents-deploy.ts +19 -1
- package/packages/agent-settings/src/deploy/index.ts +1 -0
- package/packages/agent-skills/assets/agent-search-tool-audit/SKILL.md +4 -6
- package/packages/agent-skills/assets/code-review/SKILL.md +84 -66
- package/packages/agent-skills/assets/diagnosing-bugs/SKILL.md +23 -2
- package/packages/agent-skills/assets/diagnosing-bugs/references/FEEDBACK-LOOP-TACTICS.md +1 -1
- package/packages/agent-skills/assets/explore-codebase/SKILL.md +3 -2
- package/packages/agent-skills/assets/finalize-feature/SKILL.md +63 -0
- package/packages/agent-skills/assets/fix-ci/SKILL.md +58 -0
- package/packages/agent-skills/assets/handoff/SKILL.md +1 -1
- package/packages/agent-skills/assets/implement-feature/SKILL.md +150 -0
- package/packages/agent-skills/assets/implement-feature/references/briefs.md +94 -0
- package/packages/agent-skills/assets/implement-item/SKILL.md +71 -0
- package/packages/agent-skills/assets/mechanics/SKILL.md +38 -0
- package/packages/agent-skills/assets/plan-feature/SKILL.md +141 -0
- package/packages/agent-skills/assets/prepare-promotion/SKILL.md +90 -0
- package/packages/agent-skills/assets/ralplan/SKILL.md +149 -0
- package/packages/agent-skills/assets/refactor-safely/SKILL.md +4 -3
- package/packages/agent-skills/assets/report-bug/SKILL.md +72 -0
- package/packages/agent-skills/assets/review-change/SKILL.md +82 -0
- package/packages/agent-skills/assets/review-plan/SKILL.md +124 -0
- package/packages/agent-skills/assets/triage-workitems/SKILL.md +99 -0
- package/packages/agent-skills/assets/ultragoal/SKILL.md +77 -0
- package/packages/agent-skills/assets/validate-design/SKILL.md +64 -0
- package/packages/agent-skills/assets/verify-item/SKILL.md +71 -0
- package/packages/agent-skills/assets/wayfinder/SKILL.md +1 -1
- package/packages/agent-skills/assets/workitems-process/SKILL.md +145 -0
- package/packages/agent-skills/assets/workitems-process/references/ado-and-git.md +227 -0
- package/packages/agent-skills/assets/workitems-process/references/evidence.md +74 -0
- package/packages/agent-skills/assets/workitems-process/references/workitem-states.md +92 -0
- package/packages/agent-skills/assets/write-feature/SKILL.md +94 -0
- package/packages/agent-skills/dist/deploy.d.ts +52 -0
- package/packages/agent-skills/dist/deploy.d.ts.map +1 -1
- package/packages/agent-skills/dist/deploy.js +218 -7
- package/packages/agent-skills/dist/deploy.js.map +1 -1
- package/packages/agent-skills/dist/index.d.ts +1 -1
- package/packages/agent-skills/dist/index.d.ts.map +1 -1
- package/packages/agent-skills/dist/index.js +1 -1
- package/packages/agent-skills/dist/index.js.map +1 -1
- package/packages/agent-skills/package.json +1 -1
- package/packages/agent-skills/src/deploy.ts +288 -6
- package/packages/agent-skills/src/index.ts +10 -1
- package/packages/azdo/README.md +13 -4
- package/packages/azdo/bunfig.toml +6 -0
- package/packages/azdo/package.json +1 -1
- package/packages/azdo/scripts/probe-ado-rules.ts +1 -1
- package/packages/azdo/src/activity-log.ts +118 -0
- package/packages/azdo/src/azdo-attachments.ts +109 -0
- package/packages/azdo/src/azdo-pl.ts +100 -3
- package/packages/azdo/src/azdo-pr.ts +376 -17
- package/packages/azdo/src/azdo-wit.ts +594 -34
- package/packages/azdo/src/cli.ts +119 -12
- package/packages/azdo/src/lib/client.ts +95 -17
- package/packages/azdo/src/lib/validate-pr-metadata.ts +14 -5
- package/packages/azdo/src/mcp-server.ts +121 -8
- package/packages/azdo/src/tool-dispatch.ts +107 -23
- package/packages/azdo/src/workflow-ladder.csv +125 -107
- package/packages/azdo/src/workflow-rules.ts +254 -97
- package/packages/fleet/src/install-adl-package.ts +4 -3
- package/packages/repo-controls/README.md +12 -4
- package/packages/repo-controls/assets/promote.ts +1 -0
- package/packages/repo-controls/assets/scaffold/docs/AGENTS.md +5 -5
- package/packages/repo-controls/assets/scaffold/plans/AGENTS.md +42 -13
- package/packages/repo-controls/assets/scaffold/root/CONTRIBUTING.md +5 -5
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/test_check.py +0 -11
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/test_check.py +4 -6
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/test_check.py +15 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/docs-okf-profiles.yaml +1 -17
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-untyped/docs/research/no-type.md +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-untyped/docs/research/typed-experiment.md +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-untyped/docs/research/typed-research.md +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/extra-key.md +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/extra-keys.md +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-created.md +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-description.md +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-last-update.md +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-status.md +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-title.md +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/optional-keys.md +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/research-ok.md +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/test_check.py +3 -22
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/check.py +39 -182
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/fixtures/open-work-cap-clean/plans/features/az601-clean/intent.md +17 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/test_check.py +90 -321
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plan_metadata.py +31 -5
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/check.py +282 -54
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/features/az347-test-coverage/design.md +35 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/features/az347-test-coverage/file-map-reference.md +19 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/features/az347-test-coverage/intent.md +25 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/features/az347-test-coverage/items/az351-restore-entrypoint.md +25 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/features/az347-test-coverage/requirements.md +44 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-violation/plans/features/az347-test-coverage/requirements.md +44 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/test_check.py +646 -53
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/cli.py +3 -3
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/docs_shape.py +1 -12
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.alt.yaml +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.yaml +1 -1
- package/packages/repo-controls/package.json +1 -1
- package/packages/repo-controls/src/config.ts +21 -2
- package/packages/repo-controls/src/doctor.ts +9 -14
- package/packages/repo-controls/src/findings-ratchet.ts +12 -0
- package/packages/repo-controls/src/hook.ts +61 -4
|
@@ -389,9 +389,9 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
389
389
|
"`root-files=STATUS.md,DRIFT.md` or `root-dirs=apps,packaging`; and "
|
|
390
390
|
"`scripts-direct-files` (LAY-014), a positive integer replacing the "
|
|
391
391
|
"default limit of 15 direct files per scripts directory, as "
|
|
392
|
-
"`scripts-direct-files=20`; `open-
|
|
393
|
-
"
|
|
394
|
-
"
|
|
392
|
+
"`scripts-direct-files=20`; `open-plans` (LAY-016), "
|
|
393
|
+
"a positive integer replacing the default cap of 10 open plans, "
|
|
394
|
+
"as `open-plans=11`; and "
|
|
395
395
|
"`markdown-roots` (LAY-006), a "
|
|
396
396
|
"comma-separated list of repository-relative directories whose "
|
|
397
397
|
"Markdown is allowed in addition to the declared docs trees "
|
package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/docs_shape.py
CHANGED
|
@@ -6,8 +6,7 @@ this", and the answer is declared once, here, so a second docs gate does not
|
|
|
6
6
|
grow its own, independently drifting copy the way LAY-008/LAY-012 once did for
|
|
7
7
|
the repository root (see `root_shape.py`'s module docstring).
|
|
8
8
|
|
|
9
|
-
Every entry below is read off an EXISTING docs gate
|
|
10
|
-
is declared by an explicit decision (see its entry):
|
|
9
|
+
Every entry below is read off an EXISTING docs gate:
|
|
11
10
|
|
|
12
11
|
ALLOWED_DOCS_DIRS
|
|
13
12
|
architecture, research, compliance, operations
|
|
@@ -43,15 +42,6 @@ ALLOWED_DOCS_FILES
|
|
|
43
42
|
no repository in this estate ships a top-level `docs/index.md` today,
|
|
44
43
|
and declaring it costs nothing.
|
|
45
44
|
|
|
46
|
-
notes.md
|
|
47
|
-
the task ledger. Not read off a docs gate: Tarun's global agent rule
|
|
48
|
-
(decision of 2026-10-01) requires every agent to append one line to
|
|
49
|
-
`docs/notes.md` when it starts a task and one when it finishes
|
|
50
|
-
(append-only, at most 100 characters per line), so the file exists in
|
|
51
|
-
every repository an agent works in and this gate would otherwise flag
|
|
52
|
-
it as undeclared. `docs-okf-profiles.yaml` carries a matching
|
|
53
|
-
`docs/notes.md` profile so `docs.okf-profile` (DOC-006) grades it too.
|
|
54
|
-
|
|
55
45
|
These are PERMITTED, not required — the same distinction `root_shape.py`
|
|
56
46
|
draws for the repository root. A repository using only two of the five
|
|
57
47
|
directories is not missing three findings; it has simply not written those
|
|
@@ -72,5 +62,4 @@ ALLOWED_DOCS_FILES: frozenset[str] = frozenset({
|
|
|
72
62
|
"AGENTS.md",
|
|
73
63
|
"CLAUDE.md",
|
|
74
64
|
"index.md",
|
|
75
|
-
"notes.md",
|
|
76
65
|
})
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"scripts": {
|
|
15
15
|
"typecheck": "tsc --noEmit",
|
|
16
|
-
"test": "bun test tests --coverage --timeout 30000",
|
|
16
|
+
"test": "bun test --isolate tests --coverage --timeout 30000",
|
|
17
17
|
"check": "tsc --noEmit && bun test tests --coverage --timeout 30000"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
@@ -78,7 +78,21 @@ export interface FindingsConfig {
|
|
|
78
78
|
|
|
79
79
|
export interface RepoControlsConfig {
|
|
80
80
|
branches: { protected: string[] }
|
|
81
|
-
preCommit: {
|
|
81
|
+
preCommit: {
|
|
82
|
+
staticChecks: StaticCheck[]
|
|
83
|
+
stagedCoverage: boolean
|
|
84
|
+
docFreshness: boolean
|
|
85
|
+
compliance: boolean
|
|
86
|
+
/**
|
|
87
|
+
* Defense-in-depth branch gate at pre-commit (see
|
|
88
|
+
* `hook.ts:protectedBranchStep` and `tests/protected-branch-step.test.ts`).
|
|
89
|
+
* Defaults OFF because the agent-hooks `git.commit-protected-branch` rule
|
|
90
|
+
* already covers host-routed `git commit`; this opt-in is for repositories
|
|
91
|
+
* that want the same protection on the path that bypasses the host layer
|
|
92
|
+
* (a `Bun.spawn`-issued commit, a post-checkout hook that lands a commit).
|
|
93
|
+
*/
|
|
94
|
+
branchProtection: boolean
|
|
95
|
+
}
|
|
82
96
|
commitMsg: { staticChecks: StaticCheck[] }
|
|
83
97
|
release: { optedOut: boolean; source: string; target: string; mergeStrategy: MergeStrategy; preBump: string[] }
|
|
84
98
|
postCommit: { staticChecks: StaticCheck[] }
|
|
@@ -491,7 +505,7 @@ export function parseConfig(text: string): RepoControlsConfig {
|
|
|
491
505
|
// `branches` and `pre_push.remote` are REQUIRED: install always seeds them, and
|
|
492
506
|
// `pre-push` reads their env vars with `${VAR:?}`, which aborts on an empty value —
|
|
493
507
|
// so "no branch policy" cannot be spelled as an empty list.
|
|
494
|
-
const preCommit = problems.optionalTable(controls, 'pre_commit', ['static_checks', 'staged_coverage', 'doc_freshness', 'compliance'])
|
|
508
|
+
const preCommit = problems.optionalTable(controls, 'pre_commit', ['static_checks', 'staged_coverage', 'doc_freshness', 'compliance', 'branch_protection'])
|
|
495
509
|
const prePush = problems.table(controls, 'pre_push', ['coverage_ratchet', 'coverage_produce', 'findings_ratchet', 'static_checks', 'export_links', 'remote'])
|
|
496
510
|
// Optional, and every field defaults, so a repository that never writes this
|
|
497
511
|
// table behaves exactly as before. `pre_push.coverage_ratchet` stays where it
|
|
@@ -549,6 +563,11 @@ export function parseConfig(text: string): RepoControlsConfig {
|
|
|
549
563
|
stagedCoverage: problems.requiredBool(preCommit, 'staged_coverage', 'pre_commit.staged_coverage'),
|
|
550
564
|
docFreshness: problems.requiredBool(preCommit, 'doc_freshness', 'pre_commit.doc_freshness'),
|
|
551
565
|
compliance: problems.requiredBool(preCommit, 'compliance', 'pre_commit.compliance'),
|
|
566
|
+
// Opt-in defense-in-depth: defaults to OFF so a config that doesn't
|
|
567
|
+
// declare the key keeps the agent-hooks layer as the only gate, and
|
|
568
|
+
// turning it on requires an explicit `branch_protection = true` line
|
|
569
|
+
// (or the install seed).
|
|
570
|
+
branchProtection: problems.optionalBool(preCommit, 'branch_protection', 'pre_commit.branch_protection', false),
|
|
552
571
|
},
|
|
553
572
|
prePush: {
|
|
554
573
|
coverageRatchet: problems.requiredBool(prePush, 'coverage_ratchet', 'pre_push.coverage_ratchet'),
|
|
@@ -176,25 +176,20 @@ export function doctor(repoArg: string, env: Env, options: string | DoctorOption
|
|
|
176
176
|
: null,
|
|
177
177
|
'pre_push.coverage_produce is disabled or [controls.coverage].produce_push is declared',
|
|
178
178
|
)
|
|
179
|
-
|
|
179
|
+
// A missing floor file is a zero-grant declaration, not a configuration
|
|
180
|
+
// error. The gate is armed and every emitted finding from a counter
|
|
181
|
+
// is undeclared. The doctor is silent: the gate IS the loud part.
|
|
182
|
+
// Tarun, 2026-10-13: "the gate should not be dependent on a ratchet
|
|
183
|
+
// file. no ratchet file means zero grants."
|
|
180
184
|
add(
|
|
181
185
|
'findings-floor',
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
: null,
|
|
185
|
-
`${config.findings.floor} is present or the ratchet is disabled`,
|
|
186
|
+
null,
|
|
187
|
+
`${config.findings.floor} is committed or absent (absent = zero grants); the ratchet is disabled`,
|
|
186
188
|
)
|
|
187
|
-
if (config.prePush.findingsRatchet &&
|
|
188
|
-
add(
|
|
189
|
-
'findings-counters',
|
|
190
|
-
'pre_push.findings_ratchet is enabled and a floor is committed but [controls.findings.counters] declares none — a ratchet with nothing to measure grades nothing, so the push is rejected; declare at least one counter or set pre_push.findings_ratchet = false',
|
|
191
|
-
'',
|
|
192
|
-
'fail',
|
|
193
|
-
)
|
|
194
|
-
} else if (config.prePush.findingsRatchet && Object.keys(config.findings.counters).length === 0) {
|
|
189
|
+
if (config.prePush.findingsRatchet && Object.keys(config.findings.counters).length === 0) {
|
|
195
190
|
add(
|
|
196
191
|
'findings-counters',
|
|
197
|
-
'pre_push.findings_ratchet is enabled but [controls.findings.counters] declares none —
|
|
192
|
+
'pre_push.findings_ratchet is enabled but [controls.findings.counters] declares none — a ratchet with nothing to measure grades nothing, so the push is rejected; declare at least one counter or set pre_push.findings_ratchet = false',
|
|
198
193
|
'at least one findings counter is declared, or the ratchet is disabled',
|
|
199
194
|
'fail',
|
|
200
195
|
)
|
|
@@ -34,6 +34,18 @@
|
|
|
34
34
|
* NO TOOL NAME APPEARS IN THIS FILE. The repository declares its counters and
|
|
35
35
|
* what each one runs; the package knows a name and a command, the same division
|
|
36
36
|
* `pre_push.static_checks` already draws.
|
|
37
|
+
*
|
|
38
|
+
* ABSENT FLOOR = ZERO GRANTS
|
|
39
|
+
* The floor file is bookkeeping, not a switch. A missing floor file is
|
|
40
|
+
* read as an empty `Floor` (the strictest possible state): every
|
|
41
|
+
* finding-bearing file a counter emits is undeclared, the push is
|
|
42
|
+
* refused, each file is named. The gate is armed iff the pre-push flag
|
|
43
|
+
* says so, full stop. Tarun, 2026-10-13: "the gate should not be
|
|
44
|
+
* dependent on a ratchet file. no ratchet file means zero grants."
|
|
45
|
+
* The runtime hook (`runFindingsRatchet`) and the doctor are aligned
|
|
46
|
+
* on this; the previous behaviour — failing loud on a missing floor —
|
|
47
|
+
* conflated "no file" with "misconfiguration" and made the gate's
|
|
48
|
+
* ON state require a positive declaration. It does not.
|
|
37
49
|
*/
|
|
38
50
|
|
|
39
51
|
/** `counter -> section -> declared files`. */
|
|
@@ -621,6 +621,37 @@ export function integrityStep(
|
|
|
621
621
|
return 1
|
|
622
622
|
}
|
|
623
623
|
|
|
624
|
+
/**
|
|
625
|
+
* Returns 1 and reports a recovery action when `currentBranch` is in
|
|
626
|
+
* `protectedBranches`; 0 otherwise. The recovery line is what an agent
|
|
627
|
+
* (and the e2e test) needs to know which ref to move to.
|
|
628
|
+
*/
|
|
629
|
+
export function protectedBranchStep(
|
|
630
|
+
protectedBranches: readonly string[],
|
|
631
|
+
currentBranch: string,
|
|
632
|
+
err: (line: string) => void,
|
|
633
|
+
): number {
|
|
634
|
+
if (!protectedBranches.includes(currentBranch)) return 0
|
|
635
|
+
err(`refusing to commit on the protected branch '${currentBranch}'`)
|
|
636
|
+
err(`switch to a feature branch (for example: git switch -c feat/<short-summary>) and retry the commit there`)
|
|
637
|
+
return 1
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Gate wrapper that resolves the current branch from the working tree, then
|
|
642
|
+
* calls {@link protectedBranchStep}. Reads the protected list from the same
|
|
643
|
+
* `loadConfig` the rest of the hook chain uses, so the validator and the gate
|
|
644
|
+
* never disagree on what's protected.
|
|
645
|
+
*/
|
|
646
|
+
function protectedBranchGate(repo: string, env: Env, err: (line: string) => void): number {
|
|
647
|
+
const head = capture(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], repo, env)
|
|
648
|
+
// Detached HEAD or fresh repo: nothing to compare. `git rev-parse --abbrev-ref
|
|
649
|
+
// HEAD` returns "HEAD" itself when detached, which is never in
|
|
650
|
+
// `branches.protected` (validated as a branch name), so the step is a no-op.
|
|
651
|
+
if (head.code !== 0) return 0
|
|
652
|
+
return protectedBranchStep(loadConfig(repo).branches.protected, head.stdout.trim(), err)
|
|
653
|
+
}
|
|
654
|
+
|
|
624
655
|
/**
|
|
625
656
|
* The final-index gate plus the dirty-but-excluded check. The index reader is
|
|
626
657
|
* separate from status because `git diff --cached --no-renames -z` is the
|
|
@@ -703,10 +734,17 @@ function runFindingsRatchet(repo: string, context: RunContext): number {
|
|
|
703
734
|
return findingsRatchetReport(
|
|
704
735
|
config.findings,
|
|
705
736
|
() => {
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
737
|
+
// A missing floor file is the strictest possible floor: zero grants.
|
|
738
|
+
// Returning an empty `parseFloor`-shaped document (just the schema
|
|
739
|
+
// line) means `findingsProblems` reports every emitted finding as a
|
|
740
|
+
// regression. The doctor and the runtime gate are aligned: a missing
|
|
741
|
+
// file is not a configuration error, it is a zero-grant declaration
|
|
742
|
+
// — the gate is armed and every finding-bearing file from a counter
|
|
743
|
+
// is undeclared. Tarun, 2026-10-13: "the gate should not be
|
|
744
|
+
// dependent on a ratchet file. no ratchet file means zero grants."
|
|
745
|
+
const floorPath = join(repo, config.findings.floor)
|
|
746
|
+
if (!existsSync(floorPath)) return 'schema: adl.findings-ratchet/v2\n'
|
|
747
|
+
return readFileSync(floorPath, 'utf8')
|
|
710
748
|
},
|
|
711
749
|
(command) => capture(['bash', '-c', command], repo, context.env),
|
|
712
750
|
context.err,
|
|
@@ -785,6 +823,25 @@ export function runHook(name: HookName, args: string[], context: RunContext): nu
|
|
|
785
823
|
)
|
|
786
824
|
if (code !== 0) return code
|
|
787
825
|
}
|
|
826
|
+
// Defense-in-depth branch gate (see tests/protected-branch-step.test.ts).
|
|
827
|
+
// The agent-hooks `git.commit-protected-branch` rule is the authoritative
|
|
828
|
+
// gate for host-routed `git commit` calls, but that hook only sees commits
|
|
829
|
+
// forwarded by the host's tool layer. A `git commit` invoked from a
|
|
830
|
+
// Bun.spawn, a post-checkout hook that lands a commit, or any other path
|
|
831
|
+
// that bypasses the host would never reach that rule. The repo-controls
|
|
832
|
+
// pre-commit chain refuses the commit itself by reading the same
|
|
833
|
+
// `[controls].branches.protected` declaration the rest of the package keys
|
|
834
|
+
// off, so a non-host-routed commit still hits the same wall.
|
|
835
|
+
//
|
|
836
|
+
// Opt-in via `[controls].pre_commit.branch_protection = true` (defaults OFF
|
|
837
|
+
// in the install seed): the agent-hooks layer already covers host-routed
|
|
838
|
+
// commits; this gate is for repositories that want the same protection on
|
|
839
|
+
// the host-bypass path. Runs LAST in the pre-commit chain so file-level
|
|
840
|
+
// gates deny a commit on content grounds first.
|
|
841
|
+
if (name === 'pre-commit' && loadConfig(repo).preCommit.branchProtection) {
|
|
842
|
+
const code = protectedBranchGate(repo, context.env, context.err)
|
|
843
|
+
if (code !== 0) return code
|
|
844
|
+
}
|
|
788
845
|
// The Azure DevOps promotion-governance checks, moved here from pre-commit
|
|
789
846
|
// (2026-09-28, Tarun): network access and a minted token are already
|
|
790
847
|
// expected at push time, unlike at commit time. `azureDevOpsPromotionGovernanceChecks`
|