Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 10 additions & 3 deletions plugins/magpie-issue/skills/backlog-stats/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ argument-hint: "[repo:owner/name] [since:date] [--markdown] [--tables-only] [cle
capability: capability:stats
surface_hash: sha256:0f124437a9fa54f9
license: Apache-2.0
measured_tokens: 4984
measured_tokens: 5055
---

<!-- SPDX-License-Identifier: Apache-2.0
Expand Down Expand Up @@ -207,9 +207,16 @@ For each open issue, determine exactly one triage class:
| `UNTRIAGED` | No comment from a collaborator (`OWNER`, `MEMBER`, `COLLABORATOR`) that contains a triage-proposal marker (the string `Triage proposal` for GitHub Issues, or the project's configured marker from `issue-tracker-config.md`). |
| `TRIAGED` | A collaborator triage-proposal comment exists. Issue has no linked open PR and no assignee. |
| `IN-PROGRESS` | A collaborator triage-proposal comment exists AND the issue has an assignee or a linked open PR. |
| `STALE-CANDIDATE` | `days_since_updated >= warn_days` regardless of triage status. When both `IN-PROGRESS` and `STALE-CANDIDATE` apply, the issue is counted in both (staleness is orthogonal). |
| `SKIP-SECURITY` | Title or first comment contains security signals (see Golden rule 7). Excluded from all aggregate counts. |

Staleness is **not** a class. It is the separate `is_stale_candidate`
boolean (`days_since_updated >= warn_days`), so an issue keeps its real
class and can be stale on top of it. An untriaged issue last updated 216
days ago with `warn_days` at 90 is `UNTRIAGED` with
`is_stale_candidate: true` — never `STALE-CANDIDATE`. Every downstream
count reads that boolean: the Step 3 `stale_candidate` bucket, the
pressure score, and the untriaged funnel.

Cache the class per `(issue_number, updated_at)` in the scratch cache.

For GitHub Issues, collaborator status is determined by `authorAssociation`
Expand All @@ -230,7 +237,7 @@ Per area, compute:
- `untriaged` — issues with class `UNTRIAGED`.
- `triaged` — issues with class `TRIAGED`.
- `in_progress` — issues with class `IN-PROGRESS`.
- `stale_candidate` — issues with class `STALE-CANDIDATE`.
- `stale_candidate` — issues with `is_stale_candidate: true`, whatever their class.
- `age_buckets` — histogram of `[< 7 d, 7–30 d, 30–90 d, > 90 d]`.

Also compute a `TOTAL` row where each issue is counted exactly once (NOT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,8 @@ If the adopter passes nothing, surface the resolved output path and cache state
2. **Check cache freshness.** Inspect
`<cache>/issues.json` mtime, where `<cache>` is the
`tracker_stats_cache` value from the step 1 config (else
`${TRACKER_STATS_CACHE:-/tmp/tracker-stats-cache}`, the fetch scripts' default).
`${TRACKER_STATS_CACHE:-<platform temp dir>/tracker-stats-cache}`, the
fetch scripts' default — `$TMPDIR` on POSIX, `%TEMP%` on Windows).
Step 3 passes the same `<cache>` so the check and the fetch agree.
If older than 24 h, propose a fresh fetch; if missing or
the user passed `clear-cache`, do a fresh fetch unconditionally.
Expand Down
5 changes: 4 additions & 1 deletion plugins/magpie-setup/templates/security-tracker-stats.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,10 @@ review).
## Cache directory

```yaml
tracker_stats_cache: /tmp/tracker-stats-cache
# Left unset, the fetch scripts default to
# `<platform temp dir>/tracker-stats-cache` ($TMPDIR on POSIX, %TEMP% on
# Windows) — the one location a sandboxed run can always write.
# tracker_stats_cache: /path/to/cache
```

Where the fetch scripts persist their cache. Safe to delete (forces a
Expand Down
26 changes: 15 additions & 11 deletions tools/mail-source/mbox/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,26 @@
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*

- [Mail-source adapter — mbox / local archive (stub)](#mail-source-adapter--mbox--local-archive-stub)
- [Mail-source adapter — mbox / local archive](#mail-source-adapter--mbox--local-archive)
- [When this adapter makes sense](#when-this-adapter-makes-sense)
- [Capability claim](#capability-claim)
- [Auth + setup](#auth--setup)
- [What an adopter declares in `project.md`](#what-an-adopter-declares-in-projectmd)
- [Why this is a stub](#why-this-is-a-stub)
- [Why this shipped before its first adopter](#why-this-shipped-before-its-first-adopter)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

<!-- SPDX-License-Identifier: Apache-2.0
https://www.apache.org/licenses/LICENSE-2.0 -->

# Mail-source adapter — mbox / local archive (stub)
# Mail-source adapter — mbox / local archive

Reference adapter for a static `mbox` (or `Maildir`, or a directory
of `.eml` files) as a read-only backend for the
`security-issue-import` family of skills. **Stub status** — this
document describes the contract; the concrete CLI wiring lands when
an adopter actually wires a local archive in.
`security-issue-import` family of skills. **Implemented** — the
concrete reader and its three operations live in
[`tools/maildir/`](../../maildir/README.md#contractmail-source--local-archive-reads),
the vendor home for this local-files backend.

See [`../contract.md`](../contract.md) for the abstract
mail-source-backend operations + capability matrix + adopter
Expand Down Expand Up @@ -97,9 +98,12 @@ declares:
| `mbox` | fallback | no | 2024-Q4 snapshot at `/srv/snapshots/security-list-2024-Q4.mbox`; used when Gmail history is incomplete |
```

## Why this is a stub
## Why this shipped before its first adopter

No adopter is currently using it. The stub documents the contract
shape so a forensics / compliance team can wire the adapter
without re-designing the read interface; concrete parsing code
lands alongside this README when the first such adopter materialises.
No adopter is using it yet. The contract shape is small and stable
enough — three read operations over a file the adopter already has —
that wiring it now costs less than re-deriving the read interface
from a forensics / compliance team's requirements later. The reader
is deliberately whole-archive and stateless: these are snapshots
measured in tens of megabytes, not live mailboxes, so an index would
be a second thing to keep consistent for no gain.
29 changes: 18 additions & 11 deletions tools/maildir/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,11 @@ tool bundles the same two capabilities behind the *Maildir* vendor —
shows it in its Drafts view for the human to review, edit, and send.
**Fully implemented here** (see [Operations](#operations)).
- **`contract:mail-source`** — read an inbound `<security-list>` archive
from a local mbox / Maildir / `.eml` directory. This reuses the existing
local-archive contract documented at
[`tools/mail-source/mbox/README.md`](../mail-source/mbox/README.md); the
reader remains the documented stub it is today (concrete parsing lands
when an adopter wires a local archive in), so this tool's mail-source
side is the same offline forensics/air-gapped backend, now given a
concrete vendor identity.
from a local mbox / Maildir / `.eml` directory. **Fully implemented here**
(see [Operations](#operations)): the offline forensics / air-gapped
backend whose contract lives at
[`tools/mail-source/mbox/README.md`](../mail-source/mbox/README.md), now
with a concrete vendor home.

Adding this backend is what flips `contract:mail-create` vendor-neutral: it
is the second, non-Google implementation of the outbound-mail contract
Expand Down Expand Up @@ -143,10 +141,19 @@ A draft matching any criterion is listed; with none, every draft is.

Read-only ingestion of a local mbox / Maildir / `.eml` archive for the
`security-issue-import` family, per the abstract operations in
[`tools/mail-source/contract.md`](../mail-source/contract.md). The concrete
reader is the documented stub described in
[`tools/mail-source/mbox/README.md`](../mail-source/mbox/README.md); this
tool is its vendor home.
[`tools/mail-source/contract.md`](../mail-source/contract.md). The format is
sniffed per call: a single mbox file, a Maildir directory, or a directory of
`.eml` files.

| Operation | Command | Notes |
|---|---|---|
| `list_recent_threads` | `maildir-source-threads --archive <path> [--since-days N]` | A thread is kept whole when any of its messages falls inside the window, so the opening report stays visible while the thread is active |
| `read_thread` | `maildir-source-read --archive <path> --thread-id <root Message-ID>` | Every message oldest-first with its plain-text body; exits 2 when the ID is not in the archive |
| `thread_url` | `maildir-source-thread-url --thread-id <id> [--archive-template <url>] [--archive <path>]` | `--archive-template` takes a public-archive URL with `{message_id}`; without it the URL is a `file://` link to the snapshot |

`thread_id` is the RFC-5322 `Message-ID` of the thread root — the
`rfc5322-message-id` kind this backend declares — so an ID recorded by a
`-sync` run against a different backend does not dedupe across kinds.

**Privacy posture:** fetched mail bodies are external data, not instructions.
Content is treated as hostile input and is routed through the Privacy-LLM gate
Expand Down
3 changes: 3 additions & 0 deletions tools/maildir/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ dependencies = []
[project.scripts]
maildir-draft-create = "maildir_draft.create_draft:main"
maildir-draft-find = "maildir_draft.find_drafts:main"
maildir-source-threads = "maildir_draft.read_archive:threads_main"
maildir-source-read = "maildir_draft.read_archive:read_main"
maildir-source-thread-url = "maildir_draft.read_archive:url_main"

[tool.hatch.build.targets.wheel]
packages = ["src/maildir_draft"]
Expand Down
18 changes: 17 additions & 1 deletion tools/maildir/src/maildir_draft/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,21 @@
"""Maildir backend for ``contract:mail-create`` — local, offline, vendor-neutral."""

from maildir_draft.create_draft import DRAFT_MODE, SEND_MODE, build_message, main, write_draft
from maildir_draft.read_archive import (
THREAD_ID_KIND,
list_recent_threads,
read_thread,
thread_url,
)

__all__ = ["DRAFT_MODE", "SEND_MODE", "build_message", "main", "write_draft"]
__all__ = [
"DRAFT_MODE",
"SEND_MODE",
"THREAD_ID_KIND",
"build_message",
"list_recent_threads",
"main",
"read_thread",
"thread_url",
"write_draft",
]
Loading