Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
2f1b224
fix(digest): classify adapter lifecycle states
May 15, 2026
8e4d194
feat(digest): add digest handlers and tests for CRM/productivity adap…
May 15, 2026
f73fc59
fix(adapters): preserve lifecycle actions through webhook normalization
May 15, 2026
3d1a887
fix(digest): wire lifecycle coverage across adapters
May 15, 2026
c1ce9a7
fix(digest): enforce category layout contracts
May 15, 2026
dd4f9f8
Address PR review lifecycle digest gaps
May 15, 2026
2ea0c3e
fix(digest): add issue tracking category aliases
May 15, 2026
62722aa
test(digest): cover issue metadata alias reconciliation
May 15, 2026
c130ccb
Tighten digest contract review feedback
May 15, 2026
99cb342
Tighten digest root filters and Linear wording
May 15, 2026
e908f4d
Fix Azure digest root contract
May 15, 2026
0930b9c
Tighten adapter digest review gaps
May 15, 2026
0c03c37
Anchor GitLab tombstone cleanup on prior aliases
May 15, 2026
f3c4eb3
Tighten adapter lifecycle digest consistency
May 15, 2026
8befe37
Close final adapter review gaps
May 15, 2026
968d1e9
Tighten GitLab alias cleanup
May 15, 2026
83121b3
Harden adapter digest contracts
May 15, 2026
6230cba
Harden GitHub tombstone recovery
May 15, 2026
4de562f
Tighten Salesforce lifecycle digest coverage
May 15, 2026
23cf184
Materialize Asana and ClickUp task aliases
May 15, 2026
6627ea0
Harden digest and alias lifecycle coverage
May 15, 2026
77b4d14
Close digest alias review gaps
May 15, 2026
a83d6ad
Tighten digest alias review gaps
May 15, 2026
d4ff5a7
Fix digest alias edge cases
May 15, 2026
50e0693
Harden digest alias path parsing
May 15, 2026
d7d9409
Close digest review edge cases
May 15, 2026
bb5b0d0
Handle GitLab tag deletes and wrapper digests
May 15, 2026
cd34b52
Avoid digest wrapper path collisions
May 15, 2026
8eb63a2
Require wrapper ids for digest suffix stripping
May 16, 2026
00a69ac
Use provider names for storage digest wrappers
May 16, 2026
954415d
Close digest wrapper and tag delete gaps
May 16, 2026
9b84884
Classify GitLab pipeline jobs in digests
May 16, 2026
ce278e3
Handle complex GitLab tag refs in digests
May 16, 2026
5613b38
Round trip complex GitLab tag refs
May 16, 2026
db500f3
Harden GitLab resource path parsing
May 16, 2026
c158891
Handle raw GitLab tag ref path boundaries
May 16, 2026
75a5c4b
Fix ambiguous GitLab tag ref paths
May 16, 2026
c7019ba
Normalize GitLab tag webhook paths
May 16, 2026
e2a0a70
Handle composed GitLab tag object IDs
May 16, 2026
75a2a31
Normalize GitLab tag auxiliary lifecycle paths
May 16, 2026
bc5e5bc
Normalize id-only GitLab tag refs
May 16, 2026
bdef03d
Suppress legacy GitLab tag digest paths
May 16, 2026
6be07dc
Harden GitLab tag cleanup digest paths
May 16, 2026
ed07033
Suppress flat GitLab tag cleanup digests
May 16, 2026
81a73d4
Finalize GitLab adapter publish lockfile
May 16, 2026
0c2313e
Harden GitLab adapter publish and digest contracts
May 16, 2026
d845d67
Close GitLab adapter digest parity gaps
May 17, 2026
d810670
Revert GitLab feature version bump
May 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions .claude/rules/alias-subtrees.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,17 @@ Add an alias subtree whenever the entity has a natural human-readable lookup key
- `by-title` / `by-name` — for entities whose human-readable label changes independently of the ID.
- `by-key` — for entities with a provider-issued short key (e.g. Jira `ENG-42`, Linear identifier `ENG-42`).
- `by-state` / `by-status` — when the consumer cares about the lifecycle bucket (`open`, `closed`, `in-progress`, ...).
- `by-assignee` — when work items have a current owner/assignee.
- `by-creator` — when work items track the user who opened or created them.
- `by-priority` — when work items expose a native priority or a conventional priority label.
- `by-parent` — when the entity has a single natural parent (issues -> project, pages -> space).

The enforced category matrix lives in `docs/digest-layout-contract.md` and is
checked by `npm run test:digest-contracts`. If a resource belongs to a matrix
category such as issue-tracking, add the required aliases (`by-state`,
`by-assignee`, `by-creator`, and `by-priority` for issue-tracking, `by-status`
for CI/deploy) in the same change as the layout, emitter, and tests.

Do NOT add an alias for a key that already matches the canonical filename one-to-one — it would be redundant.

## Path shape
Expand All @@ -21,9 +30,13 @@ Do NOT add an alias for a key that already matches the canonical filename one-to
```

For `by-state`, group records under a state subdirectory:
For `by-assignee`, `by-creator`, and `by-priority`, use the same grouped shape.

```text
/<provider>/<resource>/by-state/<state>/<id>.json
/<provider>/<resource>/by-assignee/<assignee>/<id>.json
/<provider>/<resource>/by-creator/<creator>/<id>.json
/<provider>/<resource>/by-priority/<priority>/<id>.json
```

## Collision handling
Expand All @@ -32,13 +45,21 @@ Always use `aliasCollisionSuffix` from `packages/core/src/alias-slug.ts` (an 8-c

## Alias file content

Each alias file is a minimal pointer, not a full copy of the record:
Alias files may use either of these shapes, but the choice must be consistent
within a resource and covered by tests:

- Minimal pointer:

```json
{ "id": "<id>", "canonicalPath": "/<provider>/<resource>/<slug>__<id>.json", "title": "<optional>" }
```

Readers follow `canonicalPath` to get the full record. This keeps the cost of adding alias views low and means the canonical record is the single source of truth.
- Materialized canonical mirror: the same provider envelope as the canonical
record, written to the alias path for direct reads.

For minimal pointers, readers follow `canonicalPath` to get the full record. For
materialized mirrors, readers can use the alias body directly. Do not mix pointer
and mirror files under the same resource subtree.

## Read pattern

Expand Down
2 changes: 1 addition & 1 deletion .claude/rules/layout-md.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Indexes are sorted by \`updated\` descending.
- By id: \`/foo/<resource>/by-id/<id>.json\`.
- By <natural key>: \`/foo/<resource>/by-<key>/<slug>__<id>.json\`.

Alias files are minimal pointers (\`{ id, canonicalPath, title? }\`); follow \`canonicalPath\` for the full record. Collisions get a deterministic 8-char hash suffix.
Alias files are either minimal pointers (\`{ id, canonicalPath, title? }\`) or materialized canonical mirrors; document the choice and keep it consistent within a resource. Collisions get a deterministic 8-char hash suffix.

## JSONL And Querying

Expand Down
41 changes: 41 additions & 0 deletions .claude/rules/relayfile-integration-digests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
description: Provider adapters must keep Relayfile digests and lifecycle state wired
paths:
- packages/*/src/**
- packages/*/test/**
- packages/*/src/**/__tests__/**
---

# Relayfile integration digests

Relayfile digests are part of the adapter contract. If an adapter writes
provider records that agents can read, the adapter must also make those records
summarizable in the activity digest.

## Required for adapters

- Export a real `digest` handler from `src/digest.ts` and the package `src/index.ts`.
- The digest handler must use `ctx.changeEvents({ providers: [ctx.provider] })`
and return deterministic bullets sorted by event time and id.
- Lifecycle verbs must be provider-aware. Explicitly classify terminal-state
actions such as `closed`, `merged`, `archived`, `completed`, `canceled`, and
`resolved`.
- Webhook ingestion must preserve terminal lifecycle state as record data.
Do not convert `closed`/`merged`/`archived`/`completed` into deletion unless
the upstream object was actually deleted.
- Add tests for create/update, terminal state, delete, deterministic ordering,
and empty windows.
- Keep the provider layout aligned with the category matrix in
`docs/digest-layout-contract.md`. For example, issue-tracking resources must
expose `by-state`, `by-assignee`, `by-creator`, and `by-priority`; CI/deploy
resources must expose `by-status` unless the matrix documents an explicit
exception.
- Run `npm run test:digest-contracts` whenever adding or materially changing an
adapter, digest handler, layout manifest, or category matrix entry.

## No-op handlers

A digest handler that returns `null` is a temporary placeholder, not a pattern.
Do not add new no-op digest handlers for providers that expose records in
Relayfile. If a provider intentionally has no digest output, document that
choice and test the exclusion.
2 changes: 1 addition & 1 deletion .claude/rules/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ It must be green before the PR opens. Do not bypass git hooks; if the pre-commit
## What each helper needs covered

- **Path-mapper helpers** — round-trip tests: compose with the helper, parse the result back to its inputs, assert equality. Cover at least one ASCII-clean case, one case with characters that the slugifier collapses, and one case with an empty / missing slug input.
- **Alias subtrees** — collision test: emit two entities whose slugs collide, assert that both alias paths are distinct and deterministic (re-emitting produces the same paths), and that each alias file points at a different `canonicalPath`.
- **Alias subtrees** — collision test: emit two entities whose slugs collide, assert that both alias paths are distinct and deterministic (re-emitting produces the same paths), and assert the resource's alias strategy. Minimal pointer aliases must point at different `canonicalPath` values; materialized canonical mirrors must match the canonical provider envelope/body for each entity.
- **`LAYOUT.md` emitter** — non-empty content test asserting:
- `file.path === '/<provider>/LAYOUT.md'`.
- `file.contentType === 'text/markdown; charset=utf-8'`.
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ jobs:
run: npx turbo typecheck

- name: Test
run: npx turbo test
run: npm test
1 change: 1 addition & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ jobs:
id: resolve
env:
INPUT_PACKAGE: ${{ github.event.inputs.package }}
INPUT_VERSION: ${{ github.event.inputs.version }}
run: |
node scripts/resolve-publish-targets.mjs "$INPUT_PACKAGE" >> "$GITHUB_OUTPUT"

Expand Down
79 changes: 78 additions & 1 deletion .trajectories/index.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"version": 1,
"lastUpdated": "2026-05-12T01:36:53.866Z",
"lastUpdated": "2026-05-15T13:10:33.804Z",
"trajectories": {
"traj_q7vhb9378uqk": {
"title": "Wire Jira privacy-safe adapter storage",
Expand Down Expand Up @@ -334,6 +334,83 @@
"startedAt": "2026-05-12T01:29:12.191Z",
"completedAt": "2026-05-12T01:36:53.740Z",
"path": "/Users/khaliqgant/Projects/AgentWorkforce/relayfile-adapters/.trajectories/completed/2026-05/traj_16jy5n5ejwmn.json"
},
"traj_26dxjcy6eeao": {
"title": "Implement Tier-2 buildSummary adapters for Salesforce, HubSpot, Stripe, Zendesk, and Asana",
"status": "completed",
"startedAt": "2026-05-12T07:43:30.336Z",
"completedAt": "2026-05-12T07:51:21.689Z",
"path": "/Users/khaliqgant/Projects/AgentWorkforce/relayfile-adapters/.trajectories/completed/2026-05/traj_26dxjcy6eeao.json"
},
"traj_653cvqi7c650": {
"title": "Address PR 67 feedback",
"status": "abandoned",
"startedAt": "2026-05-12T10:04:48.218Z",
"completedAt": "2026-05-12T10:19:47.488Z",
"path": "/Users/khaliqgant/Projects/AgentWorkforce/relayfile-adapters/.trajectories/completed/2026-05/traj_653cvqi7c650.json"
},
"traj_aad65ivnpfxj": {
"title": "Address PR 68 feedback",
"status": "completed",
"startedAt": "2026-05-12T10:24:14.001Z",
"completedAt": "2026-05-15T10:22:52.332Z",
"path": "/Users/khaliqgant/Projects/AgentWorkforce/relayfile-adapters/.trajectories/completed/2026-05/traj_aad65ivnpfxj.json"
},
"traj_d8xc6lop46pt": {
"title": "Track G: Tier-3 buildSummary (9 providers)",
"status": "completed",
"startedAt": "2026-05-12T08:09:19.612Z",
"completedAt": "2026-05-12T08:28:28.057Z",
"path": "/Users/khaliqgant/Projects/AgentWorkforce/relayfile-adapters/.trajectories/completed/2026-05/traj_d8xc6lop46pt.json"
},
"traj_et4ghsuda3uz": {
"title": "Finish relayfile adapter lifecycle digest sweep",
"status": "completed",
"startedAt": "2026-05-15T10:36:32.907Z",
"completedAt": "2026-05-15T10:37:12.092Z",
"path": "/Users/khaliqgant/Projects/AgentWorkforce/relayfile-adapters/.trajectories/completed/2026-05/traj_et4ghsuda3uz.json"
},
"traj_vy46it87lscw": {
"title": "Implement Tier-2 buildSummary adapters for Salesforce, HubSpot, Stripe, Zendesk, and Asana",
"status": "completed",
"startedAt": "2026-05-12T06:28:57.987Z",
"completedAt": "2026-05-12T06:37:06.442Z",
"path": "/Users/khaliqgant/Projects/AgentWorkforce/relayfile-adapters/.trajectories/completed/2026-05/traj_vy46it87lscw.json"
},
"traj_gp710tis5d5b": {
"title": "Address relayfile-adapters PR 97 review comments",
"status": "completed",
"startedAt": "2026-05-15T11:47:53.245Z",
"completedAt": "2026-05-15T11:58:11.229Z",
"path": "/Users/khaliqgant/Projects/AgentWorkforce/relayfile-adapters/.trajectories/completed/2026-05/traj_gp710tis5d5b.json"
},
"traj_9tel0oqwwd1y": {
"title": "Add issue-tracking digest category aliases",
"status": "completed",
"startedAt": "2026-05-15T12:01:17.105Z",
"completedAt": "2026-05-15T12:16:08.421Z",
"path": "/Users/khaliqgant/Projects/AgentWorkforce/relayfile-adapters/.trajectories/completed/2026-05/traj_9tel0oqwwd1y.json"
},
"traj_2jcdbcpmirqj": {
"title": "Review issue-tracking digest alias coverage",
"status": "completed",
"startedAt": "2026-05-15T12:23:07.386Z",
"completedAt": "2026-05-15T12:26:39.246Z",
"path": "/Users/khaliqgant/Projects/AgentWorkforce/relayfile-adapters/.trajectories/completed/2026-05/traj_2jcdbcpmirqj.json"
},
"traj_r1g8avti0ax8": {
"title": "Review and tighten relayfile-adapters PR 97",
"status": "completed",
"startedAt": "2026-05-15T12:49:18.431Z",
"completedAt": "2026-05-15T12:57:44.735Z",
"path": "/Users/khaliqgant/Projects/AgentWorkforce/relayfile-adapters/.trajectories/completed/2026-05/traj_r1g8avti0ax8.json"
},
"traj_a1zqsgk4yiah": {
"title": "Final comprehensive self-review of relayfile-adapters PR 97",
"status": "completed",
"startedAt": "2026-05-15T13:05:27.270Z",
"completedAt": "2026-05-15T13:10:33.685Z",
"path": "/Users/khaliqgant/Projects/AgentWorkforce/relayfile-adapters/.trajectories/completed/2026-05/traj_a1zqsgk4yiah.json"
}
}
}
30 changes: 28 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Every adapter under `packages/<name>` MUST:
- Export a `path-mapper.ts` with typed helpers for every canonical record path it emits. Importing the helper is the only supported way to compute a path; consumers must not construct paths by string concatenation.
- Emit a provider-specific `LAYOUT.md` at the root of its provider tree (e.g. `/<provider>/LAYOUT.md`) of at least ~1000 bytes describing the tree, naming convention, indexes, aliases, and copy-pasteable `jq`/`ls` examples. The 250-300 byte generic fallback is not acceptable for any shipping adapter.
- Emit `_index.json` files at each resource root listing all materialized records. The row schema MUST include `{ id, title, updated }` at a minimum; additional fields are encouraged when they enable filterless reads (e.g. `state`, `key`, `is_bot`, `parent_id`).
- Provide `by-*` alias subtree views when the underlying entity has a natural human-readable lookup key distinct from its stable ID (titles, names, keys, statuses, parents). Each alias path resolves to the canonical record; alias content is the minimal pointer `{ id, canonicalPath, ...minimal pointer fields }`.
- Provide `by-*` alias subtree views when the underlying entity has a natural human-readable lookup key distinct from its stable ID (titles, names, keys, statuses, parents). Each alias path must resolve to the same record as the canonical path. Alias content may be either a minimal pointer `{ id, canonicalPath, ...minimal pointer fields }` or a materialized canonical mirror, but the choice must be consistent within a resource and covered by tests.
- Use `packages/core/src/alias-slug.ts` (`slugifyAlias`, `aliasCollisionSuffix`) for slug normalization and collision suffixes. Provider-local alias modules should re-export those helpers for backward compatibility. NEVER write a new slugifier.

### Generated adapter path templates are not authoritative
Expand Down Expand Up @@ -52,7 +52,7 @@ ID format: whatever the provider's stable ID is (UUID, numeric, string key). Do

- Path shape: `/<provider>/<resource>/by-<key>/<slug>__<id>.json` (e.g. `/notion/pages/by-title/my-page__a1b2c3d4.json`).
- Collisions: append a deterministic short hash of the ID via `aliasCollisionSuffix`. NEVER pick "first writer wins" — collision handling must be deterministic across sync runs.
- Each alias file contains a minimal pointer: `{ id, canonicalPath, title? }`. Do not duplicate the full record — readers follow `canonicalPath`.
- Alias files are either minimal pointers (`{ id, canonicalPath, title? }`) or materialized canonical mirrors. Keep the choice consistent within each resource, and document/test it so readers know whether to follow `canonicalPath` or read the alias body directly.

### Versioning

Expand Down Expand Up @@ -315,3 +315,29 @@ Your trajectory helps others understand:

Future agents can query past trajectories to learn from your decisions.
<!-- prpm:snippet:end @agent-workforce/trail-snippet@1.1.2 -->

# Relayfile Integration Digest Contract

Every adapter that exposes provider records to Relayfile must also expose a
usable digest contract. When adding or materially changing an adapter:

- Add or update `src/digest.ts` and export it from the package barrel.
- Classify lifecycle actions explicitly. Terminal states such as `closed`,
`merged`, `archived`, `completed`, `canceled`, and `resolved` must not fall
through to a generic "updated" line unless the provider has no terminal
concept.
- Do not model terminal lifecycle states as deletion in adapter webhook
handling. Only actual upstream deletes should produce delete semantics.
- Add digest tests beside the adapter tests. Cover at least create/update,
terminal state, delete, deterministic sorting, and empty-window behavior.
- If an adapter intentionally does not participate in digests, document why in
the package README or PR and keep the no-op handler covered by a test.
- Keep digest behavior and layout aliases aligned with the category matrix in
`docs/digest-layout-contract.md`. Issue-tracking resources must expose
`by-state`, `by-assignee`, `by-creator`, and `by-priority` unless the matrix
documents an explicit exception; status-driven build/deploy resources must
expose `by-status`.
- Run `npm run test:digest-contracts` after adding or changing an adapter,
digest handler, layout manifest, or category matrix entry.

Full rule: `.claude/rules/relayfile-integration-digests.md`.
62 changes: 62 additions & 0 deletions docs/digest-layout-contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Relayfile Digest And Layout Contract

Relayfile adapters expose provider records as an agent-facing filesystem. A
record that can be written, synced, or received through a webhook must also be
discoverable through the activity digest and through the provider's natural
lookup aliases.

Run the contract check with:

```bash
npm run test:digest-contracts
```

## Baseline Digest Contract

Every provider package under `packages/<provider>` must:

- Export `src/digest.ts` from `src/index.ts`.
- Scope digest reads with `ctx.changeEvents({ providers: [ctx.provider] })`.
- Return deterministic bullets sorted by event time and id.
- Test create/update behavior, provider lifecycle behavior, delete or terminal
behavior, deterministic ordering, and empty windows.
- Preserve terminal lifecycle state in webhook ingestion unless the upstream
object was actually deleted.

No-op digest handlers are not a shipping pattern. A provider that intentionally
does not appear in digests must document the exception and keep that exclusion
covered by a test.

Append-only providers are a narrow exception to the delete-or-terminal portion
of lifecycle coverage. Segment is currently the only enforced exception:
records are immutable once written, so its digest contract must explicitly
classify append/upsert activity and document the append-only behavior in
`src/digest.ts`.

## Category Matrix

These category rules are enforced by `scripts/digest-layout-contracts.mjs`.

| Category | Providers/resources | Required lookup | Rationale |
| --- | --- | --- | --- |
| issue-tracking | GitHub issues and pull requests, GitLab issues and merge requests, Jira issues, Linear issues | `by-state/<state>/<id>.json`, `by-assignee/<assignee>/<id>.json`, `by-creator/<creator>/<id>.json`, `by-priority/<priority>/<id>.json` | Agents often ask for open, closed, merged, completed, canceled, assigned, created-by, or priority-scoped work without already knowing an id. |
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot May 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Alias path format in the Category Matrix contradicts the established convention in AGENTS.md. The canonical pattern is by-<key>/<value>__<id>.json (flat, double-underscore joiner), but this table shows by-<key>/<value>/<id>.json (nested subdirectory per value). Implementers following this doc will produce a different tree shape than what the rest of the system expects.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/digest-layout-contract.md, line 36:

<comment>Alias path format in the Category Matrix contradicts the established convention in AGENTS.md. The canonical pattern is `by-<key>/<value>__<id>.json` (flat, double-underscore joiner), but this table shows `by-<key>/<value>/<id>.json` (nested subdirectory per value). Implementers following this doc will produce a different tree shape than what the rest of the system expects.</comment>

<file context>
@@ -0,0 +1,55 @@
+
+| Category | Providers/resources | Required lookup | Rationale |
+| --- | --- | --- | --- |
+| issue-tracking | GitHub issues and pull requests, GitLab issues and merge requests, Jira issues, Linear issues | `by-state/<state>/<id>.json`, `by-assignee/<assignee>/<id>.json`, `by-creator/<creator>/<id>.json`, `by-priority/<priority>/<id>.json` | Agents often ask for open, closed, merged, completed, canceled, assigned, created-by, or priority-scoped work without already knowing an id. |
+| ci-deploy | GitLab pipelines and deployments | `by-status/<status>/<id>.json` | Status is the primary lifecycle bucket for build and deploy resources. |
+| knowledge | Confluence pages | `by-state/<state>/<id>.json` | Pages can be current, archived, trashed, restored, or deleted and must remain discoverable by lifecycle bucket. |
</file context>
Suggested change
| issue-tracking | GitHub issues and pull requests, GitLab issues and merge requests, Jira issues, Linear issues | `by-state/<state>/<id>.json`, `by-assignee/<assignee>/<id>.json`, `by-creator/<creator>/<id>.json`, `by-priority/<priority>/<id>.json` | Agents often ask for open, closed, merged, completed, canceled, assigned, created-by, or priority-scoped work without already knowing an id. |
| issue-tracking | GitHub issues and pull requests, GitLab issues and merge requests, Jira issues, Linear issues | `by-state/<state>__<id>.json`, `by-assignee/<assignee>__<id>.json`, `by-creator/<creator>__<id>.json`, `by-priority/<priority>__<id>.json` | Agents often ask for open, closed, merged, completed, canceled, assigned, created-by, or priority-scoped work without already knowing an id. |
Fix with Cubic

| task-management | Asana tasks, ClickUp tasks | `by-state/<state>/<id>.json`, `by-assignee/<assignee>/<id>.json`, `by-creator/<creator>/<id>.json`, `by-priority/<priority>/<id>.json` | Task systems expose the same operational questions as issue trackers: what is open or completed, who owns it, who created it, and what priority bucket it sits in. |
| ci-deploy | GitLab pipelines and deployments | `by-status/<status>/<id>.json` | Status is the primary lifecycle bucket for build and deploy resources. |
| knowledge | Confluence pages | `by-state/<state>/<id>.json` | Pages can be current, archived, trashed, restored, or deleted and must remain discoverable by lifecycle bucket. |

Resources outside this matrix still need digest coverage. They only need a
`by-state`, `by-status`, `by-assignee`, `by-creator`, or `by-priority` alias
when the resource exposes a durable bucket that agents naturally browse by. Add
the resource to the matrix when that is true; do not rely on prose alone.

## Review Checklist

When adding or materially changing an adapter:

1. Update the adapter digest handler and tests.
2. Update the layout manifest when the provider exposes a category lookup such
as state, status, parent, key, name, or title.
3. Add the provider/resource to the category matrix above and to
`scripts/digest-layout-contracts.mjs` when the category behavior should be
enforced across future work.
4. Run `npm run test:digest-contracts` before opening or updating the PR.
Loading
Loading