@amsterdamdatalabs/adl-agent-control-linux-x64 0.1.34 → 0.1.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/bin/adl +2 -2
  2. package/bin/reconcile +4 -3
  3. package/package.json +1 -1
  4. package/packages/agent-hooks/README.md +14 -0
  5. package/packages/agent-hooks/assets/opencode-plugin.template.ts +140 -104
  6. package/packages/agent-hooks/deploy-profile.json +1 -1
  7. package/packages/agent-hooks/src/deploy/opencode-template.ts +5 -18
  8. package/packages/agent-hooks/src/deploy/render.ts +12 -3
  9. package/packages/agent-hooks/src/pre-tool-use/handler.ts +2 -2
  10. package/packages/agent-hooks/src/rules/impl/adl-build-run-role.ts +116 -0
  11. package/packages/agent-hooks/src/rules/impl/adl-git-user-override-block.ts +159 -0
  12. package/packages/agent-hooks/src/rules/impl/adl-mcp-cli.ts +12 -6
  13. package/packages/agent-hooks/src/rules/impl/adl-pr-complete-pending.ts +9 -9
  14. package/packages/agent-hooks/src/rules/impl/adl-pr-role.ts +6 -5
  15. package/packages/agent-hooks/src/rules/impl/agent-tamper-protection.ts +15 -14
  16. package/packages/agent-hooks/src/rules/impl/git-core.ts +4 -8
  17. package/packages/agent-hooks/src/rules/impl/mcp-tool.ts +1 -1
  18. package/packages/agent-hooks/src/rules/impl/tool-native-search.ts +8 -7
  19. package/packages/agent-hooks/src/rules/registry.ts +11 -2
  20. package/packages/agent-hooks/src/rules/shell/protected-carriers.ts +9 -5
  21. package/packages/agent-settings/README.md +10 -4
  22. package/packages/agent-settings/assets/TARUN-GLOBAL-AGENTS-INSTRUCTIONS.md +40 -0
  23. package/packages/agent-settings/src/global-agents.ts +43 -8
  24. package/packages/agent-skills/assets/adl-mechanics/SKILL.md +38 -0
  25. package/packages/agent-skills/assets/agent-search-tool-audit/scripts/audit.sh +1 -1
  26. package/packages/agent-skills/assets/code-review/SKILL.md +84 -66
  27. package/packages/agent-skills/assets/diagnosing-bugs/SKILL.md +23 -2
  28. package/packages/agent-skills/assets/diagnosing-bugs/references/FEEDBACK-LOOP-TACTICS.md +1 -1
  29. package/packages/agent-skills/assets/explore-codebase/SKILL.md +3 -2
  30. package/packages/agent-skills/assets/finalize-feature/SKILL.md +63 -0
  31. package/packages/agent-skills/assets/fix-ci/SKILL.md +58 -0
  32. package/packages/agent-skills/assets/handoff/SKILL.md +1 -1
  33. package/packages/agent-skills/assets/implement-feature/SKILL.md +153 -0
  34. package/packages/agent-skills/assets/implement-feature/references/briefs.md +94 -0
  35. package/packages/agent-skills/assets/implement-item/SKILL.md +71 -0
  36. package/packages/agent-skills/assets/mechanics/SKILL.md +38 -0
  37. package/packages/agent-skills/assets/plan-feature/SKILL.md +141 -0
  38. package/packages/agent-skills/assets/prepare-promotion/SKILL.md +91 -0
  39. package/packages/agent-skills/assets/ralplan/SKILL.md +149 -0
  40. package/packages/agent-skills/assets/refactor-safely/SKILL.md +4 -3
  41. package/packages/agent-skills/assets/report-bug/SKILL.md +72 -0
  42. package/packages/agent-skills/assets/review-change/SKILL.md +82 -0
  43. package/packages/agent-skills/assets/review-plan/SKILL.md +124 -0
  44. package/packages/agent-skills/assets/to-feature/SKILL.md +1 -1
  45. package/packages/agent-skills/assets/to-workitems/SKILL.md +1 -1
  46. package/packages/agent-skills/assets/to-workitems/references/AZURE-DEVOPS-HIERARCHY.md +3 -0
  47. package/packages/agent-skills/assets/triage-workitems/SKILL.md +99 -0
  48. package/packages/agent-skills/assets/ultragoal/SKILL.md +77 -0
  49. package/packages/agent-skills/assets/validate-design/SKILL.md +70 -0
  50. package/packages/agent-skills/assets/verify-item/SKILL.md +71 -0
  51. package/packages/agent-skills/assets/workitems-process/SKILL.md +147 -0
  52. package/packages/agent-skills/assets/workitems-process/references/ado-and-git.md +194 -0
  53. package/packages/agent-skills/assets/workitems-process/references/evidence.md +74 -0
  54. package/packages/agent-skills/assets/workitems-process/references/workitem-states.md +92 -0
  55. package/packages/agent-skills/assets/write-feature/SKILL.md +95 -0
  56. package/packages/agent-skills/dist/deploy.d.ts +7 -0
  57. package/packages/agent-skills/dist/deploy.d.ts.map +1 -1
  58. package/packages/agent-skills/dist/deploy.js +52 -5
  59. package/packages/agent-skills/dist/deploy.js.map +1 -1
  60. package/packages/agent-skills/src/deploy.ts +48 -4
  61. package/packages/azdo/scripts/probe-ado-rules.ts +1 -1
  62. package/packages/azdo/src/activity-log.ts +118 -0
  63. package/packages/azdo/src/azdo-attachments.ts +108 -0
  64. package/packages/azdo/src/azdo-pl.ts +100 -3
  65. package/packages/azdo/src/azdo-pr.ts +194 -11
  66. package/packages/azdo/src/azdo-wit.ts +329 -18
  67. package/packages/azdo/src/cli.ts +42 -2
  68. package/packages/azdo/src/lib/agent-identity.ts +1 -1
  69. package/packages/azdo/src/lib/client.ts +7 -1
  70. package/packages/azdo/src/lib/validate-pr-metadata.ts +14 -5
  71. package/packages/azdo/src/mcp-server.ts +53 -5
  72. package/packages/azdo/src/tool-dispatch.ts +53 -4
  73. package/packages/azdo/src/workflow-ladder.csv +125 -107
  74. package/packages/azdo/src/workflow-rules.ts +254 -97
  75. package/packages/repo-controls/README.md +24 -11
  76. package/packages/repo-controls/assets/scaffold/docs/AGENTS.md +5 -5
  77. package/packages/repo-controls/assets/scaffold/plans/AGENTS.md +42 -13
  78. package/packages/repo-controls/assets/scaffold/root/AGENTS.md +60 -11
  79. package/packages/repo-controls/assets/scaffold/root/CONTRIBUTING.md +28 -0
  80. package/packages/repo-controls/assets/scaffold/root/INVARIANTS.md +17 -0
  81. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/test_check.py +0 -11
  82. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/test_check.py +4 -6
  83. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/test_check.py +15 -0
  84. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/docs-okf-profiles.yaml +1 -17
  85. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/test_check.py +3 -22
  86. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/check.py +39 -182
  87. 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
  88. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/test_check.py +90 -321
  89. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plan_metadata.py +31 -5
  90. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/check.py +282 -54
  91. 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
  92. 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
  93. 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
  94. 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
  95. 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
  96. 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
  97. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/test_check.py +646 -53
  98. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/cli.py +3 -3
  99. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/docs_shape.py +1 -12
  100. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.alt.yaml +1 -1
  101. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.yaml +1 -1
  102. package/packages/repo-controls/src/cli.ts +23 -5
  103. package/packages/repo-controls/src/constants.ts +2 -15
  104. package/packages/repo-controls/src/install.ts +15 -6
@@ -1,22 +1,71 @@
1
1
  # Agent entry point
2
2
 
3
- This is the first file a human or agent should read on arriving at this
4
- repository. Edit it to describe what this repository is; the sections below
5
- describe the layout the adl repo controls enforce.
3
+ First file a human or agent reads here. Keep it short.
6
4
 
7
- ## Layout
5
+ ## What this repository is
6
+ - <!-- Owner: one or two lines on what this repo is and who uses it. -->
7
+
8
+ ## Layout (paths as declared in `[controls.compliance.trees]`)
8
9
 
9
10
  | Path | What lives there |
10
11
  | --- | --- |
11
12
  | `docs/` | reference documentation, see `docs/AGENTS.md` |
12
13
  | `plans/` | Features and the PBIs/Bugs under them, see `plans/AGENTS.md` |
13
14
  | `workflows/` | CI pipelines and build/release scripts, see `workflows/AGENTS.md` |
14
- | `.workflows/` | managed hook stubs and lock, rebuilt by `adl repo controls install`; never hand-edit |
15
-
16
- ## Rules
15
+ | `.workflows/` | managed hook stubs and lock; rebuilt by `adl repo controls install`, never hand-edit |
17
16
 
18
- - This repository is graded by `adl-compliance` on every commit; the enabled
19
- gates are `[controls.compliance].enabled` in `adl-config.toml`. A failing
20
- gate is fixed, not skipped.
21
- - `INVARIANTS.md` lists what this repository must never regress.
17
+ ## Where to look next
18
+ - `CONTRIBUTING.md`: plans and work items, commits and PRs, release flow, tasks ledger.
19
+ - `INVARIANTS.md`: what must never regress, how each is verified, protected paths.
20
+ - Graded on every commit by `adl repo controls compliance run`; enabled gates are `[controls.compliance].enabled` in `adl-config.toml`. A failing gate is fixed, not skipped.
22
21
  - Work-item status lives on the board, never in a file.
22
+
23
+ ## Keep this guidance alive
24
+ - **When:** you were corrected by the owner, a gate or test failed for a reason not written here, or you learned a rule the hard way.
25
+ - **Where (one place only):**
26
+ - convention or workflow → `CONTRIBUTING.md`
27
+ - must-never-regress rule → `INVARIANTS.md`, only with its verifying gate/test/command; no check, no invariant
28
+ - orientation (what lives where) → `AGENTS.md`
29
+ - folder-specific → that folder's `AGENTS.md`
30
+ - applies to every repository → tell the owner; never edit global files
31
+ - **How:** put the edit in the same commit/PR that taught you it; edit the existing bullet instead of adding a near-duplicate; one bullet, at most 2 lines.
32
+ - **Never record:** secrets, ids/URLs of one-off items, session or progress state, unverified claims, rules a gate already enforces and explains in its own message.
33
+
34
+ ## Working defaults for code
35
+ Tarun's global instructions cover communication, truthfulness, verification, scope and delegation. These are the coding defaults. Edit them for this repository, except "Truthfulness and verification in code", which a repository may add to but never relax.
36
+
37
+ ### Truthfulness and verification in code
38
+ - No `|| true`, no swallowed errors, no skipped gates.
39
+ - Negative claims need source-level proof: grep the implementation, not a tool listing. Search the whole package first, then narrow.
40
+ - Before reporting what the repository contains, ask the remote (`git show origin/<branch>:<path>`); before reporting a shipped tool's behaviour, ask the installed binary.
41
+ - Name the ref. Where a change is in flight, two branches answer differently and both are right about themselves; say which one you asked.
42
+ - Deployed is not running: a long-lived server or daemon keeps the code it started with. When a fix is installed and behaviour has not changed, restart the process first.
43
+
44
+ ### Checkouts
45
+ - Commit rules: `CONTRIBUTING.md`.
46
+ - At most 3 checkouts per repository, including the main folder; git refuses the 4th and names the command to use. Reconcile old worktrees first (merge, then `git worktree remove <path>`).
47
+ - Prefer the one checkout; parallel subagents share it, each with its own write scope. Only the orchestrator commits.
48
+
49
+ ### Tests
50
+ - For features, bugfixes and non-trivial refactors: tests first, confirmed failing for the right reason; then minimal code to pass; then a separate review pass.
51
+ - Every code change ships a new test. Exempt: pure config, docs, trivial one-liners.
52
+ - A test changes only when the intended behaviour changes. Don't loosen, skip or delete an assertion, or set an expectation to whatever the code returns, to hide a failure. Red is honest.
53
+ - When a change deliberately alters behaviour, rework the tests that assert the old behaviour in the same change; name each one and the decision that changed it.
54
+
55
+ ### Fixing things
56
+ - Fix at the source — the primitive, token or shared layer — so it propagates everywhere, not a narrow downstream patch.
57
+ - Consult official docs before coding against an unfamiliar SDK, framework or API.
58
+
59
+ ### Context tooling
60
+ - Match the tool to the question (when installed; otherwise `rg`):
61
+ - Orient in unfamiliar code → `crg architecture`, `crg communities`.
62
+ - Symbols and semantic code queries → `crg search "<text>"`.
63
+ - Callers, callees, blast radius → `crg query callers_of|callees_of|tests_for|importers_of <target>`, `crg impact`.
64
+ - Unreferenced code → `crg dead-code`.
65
+ - A code shape rather than a string → `ast-grep`.
66
+ - Prose in documentation folders (`docs/`, `plans/`) → `search <text>` (hybrid semantic + keyword).
67
+ - Literal text or regex → `rg`. Never `grep`; never `find | grep`, which filters file names and misses the file whose contents match.
68
+ - Files by name or tree → the host's native file-listing tools.
69
+
70
+ ### Temporary files
71
+ - Scratch and throwaway work goes in `.scratchpad/` at the repository root, never `/tmp` or `/private/tmp`. Create it if missing; it is gitignored. Real deliverables go to their proper home.
@@ -0,0 +1,28 @@
1
+ # Contributing
2
+
3
+ How to contribute to THIS repository. Orientation is in `AGENTS.md`; what must never regress, and the protected paths, are in `INVARIANTS.md`. Tree paths are those declared in `[controls.compliance.trees]`; `plans/` is the default.
4
+
5
+ ## Plans and work items
6
+ - A plan is a Feature; a backlog item is a PBI or a Bug. One file each; the board is the only place their state lives.
7
+ - Layout: `plans/features/az<feature id>-<slug>/` with `intent.md`, `requirements.md` (plan files `design.md`/`file-map-reference.md` optional), items under `items/az<id>-<slug>.md`.
8
+ - The id is the identity: allocate the item on the board first, then name the file. Until then it is a row in the plan's `## Items` table.
9
+ - Nothing moves between directories; the plan text is the work item's fields.
10
+ - An item names its `feature:` as the Feature's full board URL; a gap found along the way is filed through `report-bug`, never a register file.
11
+
12
+ ## Commits and PRs
13
+ - Each commit is independently green and on one topic. Never `git stash`. Green wins over one topic, and say so.
14
+ - Subject: `<type>(<scope>): <lowercase subject>`; types `feat|fix|docs|refactor|perf|chore`; no `!`.
15
+ - Branch: `<type>/<kebab>`, same types, not configurable.
16
+ - `release:` is reserved for the promotion PR the pipeline raises; never write one by hand.
17
+ - A PR body needs non-empty `## Summary`, `## Why` and `## Changes`; the title follows the subject grammar.
18
+
19
+ ## Release flow
20
+ - `<type>/<kebab>` branch → PR → `integration` (auto-completes when green) → pipeline opens `integration → main` → owner approves. No agent opens that PR.
21
+ - A repository that differs states it under "Release flow of this repository".
22
+
23
+ ## Tasks ledger
24
+ - At the start and the end of a task, log with `adl activity add "<note>"` and read the log with `adl activity list`. The MCP tools `activity_add`/`activity_list` are the same storage; the CLI is the documented fallback.
25
+ - The log is `.adl/agent-activity.sqlite` — gitignored, local only. One ledger per repository; the tool fills time, surface, agent and repo, and only the note is caller-supplied.
26
+
27
+ ## Release flow of this repository
28
+ - <!-- Owner: state any difference from the flow above; delete this section if none. -->
@@ -0,0 +1,17 @@
1
+ # Invariants
2
+
3
+ Must-never-regress rules. One bullet each: `rule` — verified by `gate/test/command`. No check, no invariant. Add yours under "This repository".
4
+
5
+ ## Estate-wide
6
+ - The compliance gates enabled in `adl-config.toml` pass before every commit — verified by `adl repo controls` pre-commit and CI. A gate is switched off only by the owner, by removing it from `[controls.compliance] enabled`.
7
+ - A compliance exemption list only shrinks: remove a file once it passes; never add one — verified by the gate that reads the register.
8
+ - No multi-line comments in code; the explanation lives in `docs/` — verified by `repository.no-multiline-comments`.
9
+ - The standard trees `docs`, `plans`, `workflows` and `compliance` are those declared in `[controls.compliance.trees]`, matching the layout in `AGENTS.md` — verified by the layout and docs gates.
10
+
11
+ ## Protected paths
12
+ - Landed only by the owner; an agent leaves them uncommitted and asks — verified by the pre-commit integrity gate.
13
+ - `adl-config.toml`, `compliance/` (or the compliance tree declared in `[controls.compliance.trees]`) and `workflows/`, plus any pattern this repository adds under `[controls.integrity].watched`.
14
+ - No dot folder is protected as a whole: `.workflows/` and the agent surface folders hold the repository's own hooks and skills.
15
+
16
+ ## This repository
17
+ - <!-- Owner: `rule` — verified by `gate/test/command`. -->
@@ -92,17 +92,6 @@ def test_declared_dirs_and_files_stay_silent() -> None:
92
92
  assert f"docs/{entry}: undeclared" not in text
93
93
 
94
94
 
95
- def test_the_task_ledger_is_declared() -> None:
96
- """`docs/notes.md` is the repository's task ledger (global agent rule: one
97
- line at the start and one at the finish of every task), so it is a
98
- declared top-level file. The stray file next to it is still reported, which
99
- proves the ledger is allowed by name and not because the gate went quiet."""
100
- assert "docs/notes.md" in corpus_files(CORPUS)
101
- messages = tuple(f.render() for f in findings_for(corpus_files(CORPUS)))
102
- assert not any("docs/notes.md" in m for m in messages), messages
103
- assert any("docs/stray-root-file.md" in m for m in messages), messages
104
-
105
-
106
95
  def test_an_empty_set_grades_nothing() -> None:
107
96
  """Every undeclared entry is still on disk. A check that grades only what
108
97
  it is handed must report none of them against an empty given set."""
@@ -45,7 +45,7 @@ VIOLATING = (
45
45
  )
46
46
 
47
47
  # In the corpus, never findings: exempt guide names (root and nested),
48
- # the standing catalog/ledger names, non-Markdown assets (including one
48
+ # the standing catalog name, non-Markdown assets (including one
49
49
  # with a deliberately violating stem), and a path that merely lookalikes
50
50
  # the docs/ prefix.
51
51
  NOT_FINDINGS = (
@@ -54,7 +54,6 @@ NOT_FINDINGS = (
54
54
  "docs/CLAUDE.md",
55
55
  "docs/architecture/AGENTS.md",
56
56
  "docs/index.md",
57
- "docs/notes.md",
58
57
  "docs/reference.html",
59
58
  "docs/assets/diagram.png",
60
59
  "docs/assets/UI-CONTRACT.png",
@@ -147,12 +146,11 @@ def test_guide_names_are_exempt_at_any_depth() -> None:
147
146
  assert "docs/architecture/AGENTS.md" not in text
148
147
 
149
148
 
150
- def test_the_standing_page_names_are_silent() -> None:
151
- """`index.md` (the standing catalog name, also scope-excluded) and
152
- `notes.md` (the task ledger) carry conforming stems; both stay silent."""
149
+ def test_the_standing_catalog_name_is_silent() -> None:
150
+ """`index.md` (the standing catalog name, also scope-excluded) carries a
151
+ conforming stem and stays silent."""
153
152
  text = "\n".join(messages_for(corpus_files(CORPUS)))
154
153
  assert "docs/index.md" not in text
155
- assert "docs/notes.md" not in text
156
154
 
157
155
 
158
156
  def test_non_markdown_files_are_not_graded() -> None:
@@ -282,6 +282,21 @@ def test_a_ref_with_an_anchor_onto_a_real_path_passes(tmp_path: pathlib.Path) ->
282
282
  assert verdict.outcome is Outcome.CLEAN, verdict.render()
283
283
 
284
284
 
285
+ # ---------------------------------------------------------------------- R9 (§4a)
286
+
287
+ def test_a_dispatch_order_table_beside_the_event_table_is_not_graded(
288
+ tmp_path: pathlib.Path,
289
+ ) -> None:
290
+ """R9: the LEDGER holds the event table only. Order comes from Feature
291
+ predecessor links on the board, so a leftover `## Next` table -- any table
292
+ whose columns are not the event columns -- is not ledger content and must
293
+ not be graded, reported or required."""
294
+ body = table("| 2026-01-01 | an-entry | docs | something happened | — |")
295
+ body += "\n## Next\n\n| Order | Feature |\n| --- | --- |\n| 1 | az999-next |\n"
296
+ verdict = run_body(tmp_path, body)
297
+ assert verdict.outcome is Outcome.CLEAN, verdict.render()
298
+
299
+
285
300
  # -------------------------------------------------------------- the inverted half
286
301
 
287
302
 
@@ -42,7 +42,7 @@ common_optional:
42
42
  - last_update
43
43
  - related
44
44
 
45
- # Five profiles, deliberately (Compliance is disabled, see below).
45
+ # Four profiles, deliberately (Compliance is disabled, see below).
46
46
  #
47
47
  # There were eight. Benchmark, Evaluation, Field Note and Specification declared
48
48
  # trees that do not exist -- docs/research/benchmarks, docs/research/evaluations,
@@ -64,7 +64,6 @@ common_optional:
64
64
  # Architecture what the system is docs/architecture/**
65
65
  # ADR why it is that way docs/architecture/decisions/**, docs/decisions/**
66
66
  # Operations how it is operated docs/operations/**
67
- # Task ledger what each agent task did docs/notes.md
68
67
  # Research what is being found out docs/research/**
69
68
  # (Compliance what we must show docs/compliance/** -- disabled, see below)
70
69
  #
@@ -142,21 +141,6 @@ profiles:
142
141
  - ["Credentials", "Access", "Sign"]
143
142
  - ["Verification", "Evidence", "Check"]
144
143
 
145
- # The task ledger: one append-only list of dated lines, one per task start
146
- # and finish, that the global agent rule requires in every repository
147
- # (docs/notes.md, declared in policy/docs_shape.py). It is an operations
148
- # document, but not a procedure: it has no Purpose/Procedure/Credentials
149
- # sections and no `related` field, so it gets its own profile rather than
150
- # the Operations one above. It is graded for what it really has: OKF
151
- # frontmatter with `sources`, and its `## Lines` section.
152
- - type: Operations
153
- paths:
154
- - docs/notes.md
155
- required:
156
- - sources
157
- headings:
158
- - ["Lines"]
159
-
160
144
  # Research: inputs, findings and experiment plans under docs/research/. No
161
145
  # `type` -- the pages there differ (Research, Experiment) -- and no headings;
162
146
  # see the header. Title, description and status come from common_required.
@@ -5,9 +5,9 @@ Architecture page, and one with an invalid `status` value. Each corpus carries
5
5
  its own cut-down profile register, passed as the `profiles` input override —
6
6
  the packaged production register is not what these two scenarios are about.
7
7
 
8
- The tests below them grade the PACKAGED register: the task-ledger profile, and
9
- the Research profile for `docs/research/**` together with the engine rule it
10
- needs (a profile may declare no `type`, leaving the value unconstrained).
8
+ The tests below them grade the PACKAGED register: the Research profile for
9
+ `docs/research/**` together with the engine rule it needs (a profile may
10
+ declare no `type`, leaving the value unconstrained).
11
11
 
12
12
  The last section grades the closed key set (2026-10-02): a document may carry
13
13
  only the keys its profile admits — `common_required`, `common_optional`, and
@@ -72,25 +72,6 @@ def run_packaged(case: str, only: tuple[str, ...] | None = None) -> object:
72
72
  return runner.run(ROW, directory, given)
73
73
 
74
74
 
75
- def test_the_task_ledger_matches_a_packaged_profile() -> None:
76
- """`docs/notes.md` is the repository's task ledger (the global agent rule:
77
- one line at the start and one at the finish of every task). It carries
78
- OKF frontmatter and a `## Lines` section and nothing else a profile could
79
- demand, so the packaged register must select a profile for it."""
80
- verdict = run_packaged("okf-ledger-clean")
81
- assert verdict.outcome is Outcome.CLEAN, verdict.render()
82
-
83
-
84
- def test_the_task_ledger_profile_requires_the_lines_section() -> None:
85
- """Without this, the clean case above also passes against a profile that
86
- asserts nothing about the ledger."""
87
- verdict = run_packaged("okf-ledger-no-lines")
88
- assert verdict.outcome is Outcome.FINDINGS
89
- assert any("missing one of headings: Lines" in f.message for f in verdict.findings), (
90
- verdict.render()
91
- )
92
-
93
-
94
75
  def messages(verdict: object, relative: str) -> list[str]:
95
76
  """Every message reported against one file, sorted, so a test can assert the
96
77
  EXACT set rather than that some finding somewhere mentioned a field."""
@@ -1,17 +1,20 @@
1
- """LAY-016 — at most ten open plans and at most ten open gaps, by default.
2
-
3
- Plans and linked gap items use the exact board-state vocabulary shared with
4
- LAY-009. An unraised gap and a gap linked to active work both remain open. A
5
- gap linked to terminal work is stale register content and must be deleted.
6
-
7
- The two caps are independent and each is `OPEN_LIMIT` (10) unless the
8
- repository declares its own, as `--declare open-gaps=N` / `--declare
9
- open-plans=N` (`[controls.compliance.limits].open_gaps` / `.open_plans` in its
10
- adl-config.toml). `open_gaps_limit` / `open_plans_limit` resolve them: absent
11
- is the default, and a value that is not a base-10 positive integer is
12
- could-not-run, never a quiet fall back to the default. The engine default is
13
- not raised; a repository states the count it carries today and ratchets it
14
- down.
1
+ """LAY-016 — at most ten open plans, by default.
2
+
3
+ Open plans use the Feature board-state vocabulary shared with LAY-009 (R1):
4
+ a Feature folder's `intent.md` is the one plan file that exists from the
5
+ Feature's first day (R2), so it is the state this cap counts. New, In Progress
6
+ and Awaiting Release are open; Done and Removed are closed.
7
+
8
+ The cap is `OPEN_LIMIT` (10) unless the repository declares its own, as
9
+ `--declare open-plans=N` (`[controls.compliance.limits].open_plans` in its
10
+ adl-config.toml). `open_plans_limit` resolves it: absent is the default, and a
11
+ value that is not a base-10 positive integer is could-not-run, never a quiet
12
+ fall back to the default. The engine default is not raised; a repository
13
+ states the count it carries today and ratchets it down.
14
+
15
+ R7 (ADL process handover, 2026-10-12) retired the register half of this gate
16
+ with `plans/GAPS-AND-FINDINGS.md` itself: the open-gaps cap, the register
17
+ columns and the `open-gaps` declaration are gone. The open-plans cap stays.
15
18
  """
16
19
 
17
20
  from __future__ import annotations
@@ -33,63 +36,30 @@ class OpenWorkCap(Check):
33
36
  inputs: ClassVar[dict[str, str]] = {"plans_root": "<plans>"}
34
37
  whole_scope: ClassVar[bool] = True
35
38
 
36
- # The engine default for each cap. A repository states its own with the
37
- # `open-gaps` / `open-plans` declarations; see `open_gaps_limit`.
39
+ # The engine default. A repository states its own with the
40
+ # `open-plans` declaration; see `open_plans_limit`.
38
41
  OPEN_LIMIT: ClassVar[int] = 10
39
- OPEN_GAPS_DECLARATION: ClassVar[str] = "open-gaps"
40
42
  OPEN_PLANS_DECLARATION: ClassVar[str] = "open-plans"
41
43
  FEATURES: ClassVar[str] = "features"
42
- SPEC: ClassVar[str] = "spec.md"
43
- REGISTER: ClassVar[str] = "GAPS-AND-FINDINGS.md"
44
+ INTENT: ClassVar[str] = "intent.md"
44
45
  IGNORED: ClassVar[str] = ".DS_Store"
45
- COLUMNS: ClassVar[tuple[str, ...]] = (
46
- "Found",
47
- "Finding",
48
- "From plan",
49
- "Item file",
50
- "Work item",
51
- )
52
- DIVIDER_CELL: ClassVar[re.Pattern[str]] = re.compile(r"^:?-{3,}:?$")
53
- WORK_ITEM_LINK: ClassVar[re.Pattern[str]] = re.compile(
54
- r"^\[([0-9]+)\]"
55
- r"\((https://dev\.azure\.com/\S+/_workitems/edit/([0-9]+)/?)\)$"
56
- )
57
- UNRAISED: ClassVar[frozenset[str]] = frozenset(("", "-", "—", "TBD"))
58
- EXCERPT: ClassVar[int] = 60
59
-
60
- def open_gaps_limit(self, ctx: Context) -> int:
61
- """The open-gap cap in force: the declared one, else `OPEN_LIMIT`."""
62
- return self._declared_limit(ctx, self.OPEN_GAPS_DECLARATION, "open_gaps")
63
46
 
64
47
  def open_plans_limit(self, ctx: Context) -> int:
65
48
  """The open-plan cap in force: the declared one, else `OPEN_LIMIT`."""
66
- return self._declared_limit(ctx, self.OPEN_PLANS_DECLARATION, "open_plans")
67
-
68
- def _declared_limit(self, ctx: Context, declaration: str, key: str) -> int:
69
- """One cap's declared value, else `OPEN_LIMIT`.
70
-
71
- ASCII digits only, so `+20`, `1_0`, ` 20` and full-width digits are
72
- refused, which `int()` alone would accept. A declaration that cannot be
73
- read as a positive integer is could-not-run: falling back to the default
74
- would grade the repository against a cap it explicitly replaced.
75
- """
76
- declared = ctx.declarations.get(declaration)
49
+ declared = ctx.declarations.get(self.OPEN_PLANS_DECLARATION)
77
50
  if declared is None:
78
51
  return self.OPEN_LIMIT
79
52
  if re.fullmatch(r"[0-9]+", declared) is None or int(declared) < 1:
80
53
  raise CouldNotRun(
81
- f"declaration {declaration}={declared!r} is not a base-10 positive "
82
- f"integer; set [controls.compliance.limits].{key} to a whole number "
54
+ f"declaration {self.OPEN_PLANS_DECLARATION}={declared!r} is not a "
55
+ "base-10 positive integer; set "
56
+ "[controls.compliance.limits].open_plans to a whole number "
83
57
  "of at least 1"
84
58
  )
85
59
  return int(declared)
86
60
 
87
61
  def graded(self, ctx: Context, files: FileSet) -> Iterable[Finding]:
88
- # Both caps are resolved before either is graded, so an unreadable
89
- # declaration is could-not-run even when the cap it names is not the
90
- # one being exceeded.
91
62
  plans_limit = self.open_plans_limit(ctx)
92
- gaps_limit = self.open_gaps_limit(ctx)
93
63
 
94
64
  yield from self._cap(
95
65
  "plans",
@@ -99,17 +69,6 @@ class OpenWorkCap(Check):
99
69
  f"finish or remove open Features until no more than {plans_limit} remain",
100
70
  )
101
71
 
102
- open_gaps, stale_gaps = self._gaps(ctx, set(files))
103
- yield from stale_gaps
104
- yield from self._cap(
105
- "gaps",
106
- f"{self._plans(ctx)}/{self.REGISTER}",
107
- open_gaps,
108
- gaps_limit,
109
- "finish linked work and delete its gap row, or resolve unraised gaps, "
110
- f"until no more than {gaps_limit} remain",
111
- )
112
-
113
72
  def _cap(
114
73
  self, subject: str, path: str, open_items: list[str], limit: int, fix: str
115
74
  ) -> Iterator[Finding]:
@@ -137,135 +96,33 @@ class OpenWorkCap(Check):
137
96
  for name in sorted(names):
138
97
  if name == self.IGNORED or not ctx.path(f"{features}/{name}").is_dir():
139
98
  continue
140
- spec = f"{features}/{name}/{self.SPEC}"
141
- if spec not in given:
99
+ intent = f"{features}/{name}/{self.INTENT}"
100
+ if not ctx.path(intent).is_file():
101
+ # A Feature whose intent.md does not exist yet is mid-migration;
102
+ # LAY-009 reports the missing file. Counting it either way would
103
+ # grade a state nobody established.
104
+ continue
105
+ if intent not in given:
142
106
  raise CouldNotRun(
143
- f"{spec} is not in the widened Git-visible file set; an on-disk "
144
- "spec outside that set cannot establish plan state"
107
+ f"{intent} is not in the widened Git-visible file set; an on-disk "
108
+ "intent outside that set cannot establish plan state"
145
109
  )
146
- body = self.text(ctx, spec)
110
+ body = self.text(ctx, intent)
147
111
  if body is None:
148
112
  raise CouldNotRun(
149
- f"{spec} is missing; an unreadable plan state cannot be counted"
113
+ f"{intent} is missing; an unreadable plan state cannot be counted"
150
114
  )
151
- status = self._status(body, spec)
152
- if status in PlanMetadata.ACTIVE_STATUSES:
115
+ status = self._status(body, intent)
116
+ if status in PlanMetadata.FEATURE_ACTIVE_STATUSES:
153
117
  open_plans.append(name)
154
118
  return open_plans
155
119
 
156
120
  def _status(self, body: str, path: str) -> str:
157
121
  status = PlanMetadata.field(body, PlanMetadata.STATUS_LINE)
158
- if status not in PlanMetadata.WORK_ITEM_STATUSES:
122
+ if status not in PlanMetadata.FEATURE_STATUSES:
159
123
  shown = "missing" if status is None else repr(status)
160
124
  raise CouldNotRun(
161
125
  f"{path} has {shown} status; expected an exact work item state: "
162
- f"{', '.join(PlanMetadata.WORK_ITEM_STATUSES)}"
126
+ f"{', '.join(PlanMetadata.FEATURE_STATUSES)}"
163
127
  )
164
128
  return status
165
-
166
- def _gaps(self, ctx: Context, given: set[str]) -> tuple[list[str], list[Finding]]:
167
- register = f"{self._plans(ctx)}/{self.REGISTER}"
168
- if register not in given:
169
- return [], []
170
- body = self.text(ctx, register)
171
- if body is None:
172
- raise CouldNotRun(
173
- f"{register} is in the widened Git-visible file set but cannot be read"
174
- )
175
-
176
- rows = [
177
- (number, self._cells(line))
178
- for number, line in enumerate(body.splitlines(), start=1)
179
- if line.lstrip().startswith("|")
180
- ]
181
- if not rows:
182
- raise CouldNotRun(f"{register} exists but holds no Markdown table")
183
- if tuple(rows[0][1]) != self.COLUMNS:
184
- raise CouldNotRun(
185
- f"{register} has columns {rows[0][1]}, expected {list(self.COLUMNS)}"
186
- )
187
-
188
- open_gaps: list[str] = []
189
- stale: list[Finding] = []
190
- for number, cells in rows[1:]:
191
- if len(cells) != len(self.COLUMNS):
192
- raise CouldNotRun(
193
- f"{register}:{number} has {len(cells)} cells; expected "
194
- f"{len(self.COLUMNS)}, so its work-item column cannot be identified"
195
- )
196
- if all(self.DIVIDER_CELL.match(cell) for cell in cells):
197
- continue
198
-
199
- label = f"line {number}: {cells[0]} {self._excerpt(cells[1])}"
200
- work_item_cell = self._plain(cells[4])
201
- if work_item_cell in self.UNRAISED:
202
- open_gaps.append(label)
203
- continue
204
-
205
- linked = self.WORK_ITEM_LINK.match(work_item_cell)
206
- if linked is None or linked.group(1) != linked.group(3):
207
- raise CouldNotRun(
208
- f"{register}:{number} has malformed work item link "
209
- f"{work_item_cell!r}; use [id](https://dev.azure.com/.../_workitems/edit/id)"
210
- )
211
- linked_id = int(linked.group(1))
212
- item_path = self._plain(cells[3])
213
- item_match = re.fullmatch(
214
- rf"{re.escape(self._plans(ctx))}/{self.FEATURES}/"
215
- r"az[0-9]+-[a-z0-9]+(?:-[a-z0-9]+)*/items/"
216
- r"az([0-9]+)-[a-z0-9]+(?:-[a-z0-9]+)*\.md",
217
- item_path,
218
- )
219
- if item_match is None or int(item_match.group(1)) != linked_id:
220
- raise CouldNotRun(
221
- f"{register}:{number} item file {item_path!r} must be a normalized "
222
- f"repository-relative {self._plans(ctx)}/features/az<feature>-<slug>/"
223
- f"items/az{linked_id}-<slug>.md path"
224
- )
225
- if item_path not in given:
226
- raise CouldNotRun(
227
- f"{register}:{number} item file {item_path!r} is not in the widened "
228
- "Git-visible file set; an on-disk path outside that set cannot "
229
- "establish linked work state"
230
- )
231
- item_body = self.text(ctx, item_path)
232
- if item_body is None:
233
- raise CouldNotRun(
234
- f"{register}:{number} names item file {item_path!r}, but it is missing"
235
- )
236
- found_id, why = PlanMetadata.work_item_id(item_body)
237
- if why or found_id != linked_id:
238
- raise CouldNotRun(
239
- f"{item_path} must carry a work_item link for {linked_id}; "
240
- f"found {why or found_id}"
241
- )
242
- status = self._status(item_body, item_path)
243
- if status in PlanMetadata.ACTIVE_STATUSES:
244
- open_gaps.append(label)
245
- elif status in PlanMetadata.TERMINAL_STATUSES:
246
- stale.append(
247
- self.report(
248
- register,
249
- f"closed gap remains in the register at {label}; linked item "
250
- f"{linked_id} is {status}",
251
- f"delete line {number} from {register}; closed gaps no longer remain",
252
- line=number,
253
- )
254
- )
255
- return open_gaps, stale
256
-
257
- @staticmethod
258
- def _plain(value: str) -> str:
259
- stripped = value.strip()
260
- if len(stripped) >= 2 and stripped.startswith("`") and stripped.endswith("`"):
261
- return stripped[1:-1].strip()
262
- return stripped
263
-
264
- @staticmethod
265
- def _cells(line: str) -> list[str]:
266
- inner = line.strip().removeprefix("|").removesuffix("|")
267
- return [cell.strip().replace(r"\|", "|") for cell in re.split(r"(?<!\\)\|", inner)]
268
-
269
- def _excerpt(self, value: str) -> str:
270
- flat = " ".join(value.split())
271
- return flat if len(flat) <= self.EXCERPT else f"{flat[: self.EXCERPT]}…"
@@ -0,0 +1,17 @@
1
+ ---
2
+ type: intent
3
+ title: "A clean feature"
4
+ status: New
5
+ work_item: https://dev.azure.com/org/project/_workitems/edit/601
6
+ last_update: 2026-09-17T00:00:00Z
7
+ ---
8
+
9
+ # A clean feature
10
+
11
+ ## Why
12
+
13
+ ## Outcome
14
+
15
+ ## Constraints
16
+
17
+ ## Non-goals