Skip to content
Merged
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Each entry links the GitHub issue (the canonical spec) and the merge PR (the shi

### Removed

- **backlog-triage judgment heuristics** — replaced ~265 lines of semantic judgment (`scanPhraseEdges`/`scanBlocks`/`scanDependsOn`, title-Jaccard duplicates in relate/stale, and rule-based `buildPriorityActions`/`buildMilestoneActions`) with prompt-level rubric guidance. Scripts now emit deterministic signals only (mentions, merged-PR links, dates, labels); the model's judged blocks/depends-on/duplicate edges and priority/milestone proposals enter the report through the new `triage-report.js --model-actions PATH`, keeping anchors, dedupe, and the Apply Checklist deterministic. Net −491 script/test lines. Closes [#358](https://github.com/sungjunlee/dev-backlog/issues/358).

- **Monthly Progress sync and sprint issue publication** — removed 3,758 lines of scripts and dedicated tests after their intended adoption windows finished. Only one monthly issue was ever created (`Progress: April 2026`, #46; none in May–July), and the four sprint issues (#230, #234, #237, #239) were all created on 2026-07-03/04 with none since. No core lifecycle script or dev-relay integration invokes either feature, and `task-progress-reporting` accumulated no Learnings. Closes [#340](https://github.com/sungjunlee/dev-backlog/issues/340).
- **Required task mirrors** — the GitHub-native core now resolves task specification, AC, and lifecycle directly from live Issues. Fresh setup and complete sprint execution require no `backlog/tasks/` or `backlog/completed/`; `sync-pull --legacy-export` remains an explicit one-way diagnostic/rollback boundary. Closes [#347](https://github.com/sungjunlee/dev-backlog/issues/347).
- **Zero-adopter local tracker and generic compatibility machinery** — measured adoption found 0 of 17 consumers selecting a non-default tracker and GitHub remotes in all 18 known consumers. The local JSON store, local lifecycle tests, and generic/local design surface are removed; GitHub failure is fail-loud with no fallback, while Backlog.md remains manual import/explicit export compatibility only. Closes [#348](https://github.com/sungjunlee/dev-backlog/issues/348).
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,8 @@ SNAP=backlog/triage/.cache/<ts>.json
node $SKILL/triage-collect.js
node $SKILL/triage-relate.js --snapshot $SNAP --json > /tmp/relate.json
node $SKILL/triage-stale.js --snapshot $SNAP --json > /tmp/stale.json
node $SKILL/triage-report.js --snapshot $SNAP --relate /tmp/relate.json --stale /tmp/stale.json
# model-authored: blocks/depends-on/duplicate edges + priority/milestone actions
node $SKILL/triage-report.js --snapshot $SNAP --relate /tmp/relate.json --stale /tmp/stale.json --model-actions /tmp/model-actions.json
Comment on lines +313 to +314

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Collect closed issues before judging duplicates

In the documented workflow, triage-collect.js is still invoked without --with-closed-issues, so the snapshot contains no closed_issues evidence even though this new model step advertises duplicate edges and replaces duplicate-of-closed detection. Because the skill defines the snapshot as the canonical input and forbids downstream re-fetches, following this command sequence cannot produce duplicate-of-closed proposals; add the collection flag to this workflow (and the matching workflow-patterns example) or narrow the advertised scope to open/open duplicates.

Useful? React with 👍 / 👎.


# Apply phase (opt-in): review the report, check accepted proposals, then
node $SKILL/triage-apply.js backlog/triage/<date>-report.md --apply
Expand Down
1 change: 0 additions & 1 deletion backlog/triage-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,3 @@ activity_days:
warm: 14
cold: 60
stale_days: 60
duplicate_threshold: 0.75
58 changes: 48 additions & 10 deletions skills/backlog-triage/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ Phase 1 — Report (default, read-only) Phase 2 — Apply (explicit mutation
| Phase | Step | Completion boundary |
| --- | --- | --- |
| Report | Collect | One `gh` fetch writes a snapshot JSON; downstream steps use `--snapshot PATH` and do not re-fetch. |
| Report | Analyze | Classification, relationships, stale/obsolete signals, Alignment, and Decision Review are computed from the same snapshot/spec evidence. |
| Report | Render | One markdown report is written with anchored proposals and a consolidated Apply Checklist. |
| Report | Analyze | Classification and deterministic signals come from scripts; the model judges blocks/depends-on/duplicates, priority, and milestone actions from the snapshot and writes them to a `--model-actions` JSON file. Alignment and Decision Review are prompt-driven from the same evidence. |
| Report | Render | `triage-report.js` validates the model actions, merges them with deterministic signals, and writes one markdown report with anchored proposals and a consolidated Apply Checklist. |
Comment on lines +28 to +29

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

obsolete 모델 액션을 분석 및 판단 분포에 명시하세요.

제공된 triage-report.js, Lines 567-606은 section === "obsolete" 모델 액션을 결정론적 stale 액션과 병합합니다. 그러나 Lines 28-29, 71-80, 148-152는 모델 판단을 관계, 우선순위, 마일스톤으로만 설명합니다. 이 불일치는 모델이 유효한 obsolete 액션을 생성하지 않게 할 수 있습니다. 세 설명에 obsolete 모델 액션과 결정론적 stale 신호의 관계를 명시하세요.

Also applies to: 71-80, 148-152

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skills/backlog-triage/SKILL.md` around lines 28 - 29, Update the
model-judgment descriptions in the Report/Analyze, model-action guidance, and
Report/Render sections to explicitly include obsolete actions. State that
model-generated obsolete actions are combined with deterministic stale signals
by triage-report.js, while preserving the existing descriptions of relationship,
priority, and milestone decisions.

| Apply | Review | A human accepts proposals by flipping paired checkboxes from `[ ]` to `[x]`; unchecked anchors remain inert. |
| Apply | Dry-run | `triage-apply.js <report.md>` prints intended `gh` mutations without writing. |
| Apply | Mutate | `triage-apply.js <report.md> --apply` executes only accepted actions; `--yes` is required for non-interactive apply. |
Expand Down Expand Up @@ -68,14 +68,16 @@ The report is a derived artifact under `backlog/triage/`. GitHub Issues remain t
Required sections:

- `## Classification` — issue buckets by theme, label, age, activity, and milestone state.
- `## Relationships` — mentions, blocks, depends-on, duplicate candidates, and merged closing PR links.
- `## Relationships` — deterministic mentions/comment-mentions/merged-PR links from scripts, plus model-judged blocks, depends-on, and duplicate-candidate edges passed via `--model-actions`.
- `## Obsolete Candidates` — anchored close/revisit proposals with evidence.
- `## Priority Proposals` — anchored priority proposals with rationale.
- `## Milestone Suggestions` — anchored milestone proposals grouped into candidate sprint clusters.
- `## Priority Proposals` — model-judged anchored priority proposals with rationale (delivered via `--model-actions`).
- `## Milestone Suggestions` — model-judged anchored milestone proposals grouped into candidate sprint clusters (delivered via `--model-actions`).
- `## Alignment` — objective coverage, orphan work, neglected objectives, contradictions, and proposed charter changes; when no charter exists, record that alignment was skipped.
- `## Decision Review` — `Do Now`, `Shape First`, `Defer`, and `Drop / Close`.
- `## Apply Checklist` — consolidated review surface for every anchored action.

Judgment distribution: scripts own deterministic signals (issue refs, merged-PR links, dates, labels); the model owns semantic judgment (blocks, depends-on, duplicates, priority and milestone proposals). The model's judgment is rendered by `triage-report.js` from a `--model-actions` JSON file, so anchor formatting, dedupe, and the Apply Checklist stay deterministic.

Full section examples and rubric details live in `references/classification.md`, `references/relationships.md`, `references/stale.md`, `references/decision-review.md`, and `references/apply.md`.

## Relationship To dev-backlog
Expand Down Expand Up @@ -105,19 +107,55 @@ node "$skill_dir/scripts/triage-collect.js" --dry-run --json
node "$skill_dir/scripts/triage-apply.js" backlog/triage/YYYY-MM-DD-report.md
```

Model-judged actions (`--model-actions`) are a JSON array of action objects. Sections `priority` / `milestone` / `obsolete` carry a positive `issueNumber`, `verb`, `summary`, and `args` with the mutation payload; section `relationship` carries `args.from` / `args.to` / `args.kind` for a model-judged edge. `triage-report.js` validates every entry before rendering (see `references/apply.md` for the anchor grammar each verb maps to):

```json
[
{
"section": "priority",
"verb": "set-priority",
"issueNumber": 42,
"args": { "value": "high", "reason": "customer-reported outage blocks the auth theme" },
"summary": "Set priority:high on #42 — customer-reported outage blocks the auth theme"
},
{
"section": "milestone",
"verb": "assign-milestone",
"issueNumber": 43,
"args": { "name": "Sprint W34" },
"cluster": "auth",
"sprintName": "Sprint W34",
"summary": "Assign Sprint W34 to #43 — auth cluster"
},
{
"section": "obsolete",
"verb": "close-duplicate",
"issueNumber": 44,
"args": { "target": "#12", "reason": "open issue duplicates closed #12" },
"summary": "Close duplicate #44 into #12 — open issue duplicates closed #12"
},
{
"section": "relationship",
"verb": "edge",
"args": { "from": 45, "to": 46, "kind": "blocks", "evidence": { "phrase": "Blocks #46" } },
"summary": "Blocks edge 45 -> 46"
}
]
```

Useful scripts:

- `scripts/triage-collect.js [--repo OWNER/REPO] [--limit N] [--json] [--dry-run] [--with-comments] [--with-closed-issues]` — fetch open issues and write `backlog/triage/.cache/<ISO-timestamp>.json`; `--with-comments` enables comment-mention edges and `--with-closed-issues` enables duplicate-of-closed signals, see `references/classification.md`.
- `scripts/triage-relate.js --snapshot PATH [--json]` — detect mentions, blocks, depends-on, duplicates, and merged PR links.
- `scripts/triage-stale.js --snapshot PATH [--since N] [--json]` — flag stale/obsolete candidates with evidence.
- `scripts/triage-report.js --snapshot PATH [--relate PATH] [--stale PATH] [--active-sprint PATH] [--out PATH] [--json]` — render report; creates `.bak` on overwrite.
- `scripts/triage-collect.js [--repo OWNER/REPO] [--limit N] [--json] [--dry-run] [--with-comments] [--with-closed-issues]` — fetch open issues and write `backlog/triage/.cache/<ISO-timestamp>.json`; `--with-comments` hydrates comment bodies and `--with-closed-issues` enriches the snapshot with recent closed issues for the model's duplicate judgment, see `references/classification.md`.
- `scripts/triage-relate.js --snapshot PATH [--json]` — deterministic edges: mentions, comment-mentions, and merged closing PR links.
- `scripts/triage-stale.js --snapshot PATH [--since N] [--json]` — deterministic stale/obsolete candidates from dates and labels.
- `scripts/triage-report.js --snapshot PATH [--relate PATH] [--stale PATH] [--active-sprint PATH] [--model-actions PATH] [--out PATH] [--json]` — render report; model-judged actions (blocks/depends-on/duplicate edges, priority and milestone proposals) come from `--model-actions` JSON; creates `.bak` on overwrite.
- `scripts/triage-apply.js <report.md> [--apply] [--yes] [--json]` — parse accepted anchors and execute/dry-run GitHub mutations.
- `scripts/triage-apply.integration.test.js` — opt-in live integration test against the disposable sandbox repo; requires `TRIAGE_APPLY_INTEGRATION=1` and `GH_TOKEN`.

## References

- `references/classification.md` — bucketing rules and YAML config schema.
- `references/relationships.md` — relationship heuristics and evidence format.
- `references/relationships.md` — deterministic edge rules and the model-judged blocks/depends-on/duplicate rubric.
- `references/stale.md` — obsolescence signals, thresholds, and suggested-action grammar.
- `references/apply.md` — anchor grammar, parse rules, idempotency contract, and apply-log schema.
- `references/decision-review.md` — prompt-driven Do Now / Shape First / Defer / Drop rubric.
Expand Down
3 changes: 1 addition & 2 deletions skills/backlog-triage/references/classification.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ comment_fetch_concurrency: 5
closed_issue_days: 180
closed_issue_limit: 200
stale_days: 60
duplicate_threshold: 0.75
```

- `theme_keywords` maps a theme name to title-keyword substrings. The first matching theme wins.
Expand All @@ -24,7 +23,7 @@ duplicate_threshold: 0.75
- `comment_fetch_concurrency` bounds `--with-comments` fan-out when comment hydration is enabled.
- `closed_issue_days` bounds the lookback window for `--with-closed-issues`.
- `closed_issue_limit` caps how many recent closed issues are collected for snapshot v2 enrichment.
- `stale_days` and `duplicate_threshold` are collected as config-as-data for downstream scripts (`triage-stale`, `triage-relate`); `triage-collect` does not apply them yet.
- `stale_days` is collected as config-as-data for `triage-stale`; `triage-collect` does not apply it yet.

## Snapshot shape

Expand Down
25 changes: 25 additions & 0 deletions skills/backlog-triage/references/decision-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,28 @@ Evidence absent: `spec/system-map.md`.
```

Do not include anchors for non-mutating recommendations. Only include close/relabel/milestone anchors when the normal apply contract supports the action and a human can accept it with a checkbox.

## Priority and Milestone Proposals

Priority Proposals and Milestone Suggestions are also prompt-driven and delivered to `triage-report.js --model-actions`. The old script heuristics become judgment guidance:

### Priority Proposals (`section: "priority"`, `verb: "set-priority"`)

Suggest `priority:high` only for issues that are all of:

- not already `priority:high` or `priority:critical`
- not cold (recent activity or warm bucket)
- not already proposed for close
- high-leverage by at least one of: sits in a theme with several recent/warm issues, participates in relationship edges, or is called out by an active sprint or Decision Review

Rationale must name the trigger (theme heat, relationship edges, sprint focus), not just restate the issue title.

### Milestone Suggestions (`section: "milestone"`, `verb: "assign-milestone"`)

Suggest a milestone only for issues that:

- have no milestone yet
- are not already proposed for close
- are active or related (relationship edges, active theme, or a priority proposal above)

Set `args.name` to a concrete candidate sprint (`Sprint W<next>`), `args.cluster` to the dominant theme, and put the same values in top-level `sprintName` / `cluster` for grouping. Only suggest a milestone when the issue is genuinely ready for sprint planning; otherwise leave it unassigned.
101 changes: 50 additions & 51 deletions skills/backlog-triage/references/relationships.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
# Relationships

**Purpose.** `triage-relate.js` reads a previously collected issue snapshot and emits read-only relationship edges for snapshot-resident signals:
**Purpose.** The `## Relationships` section of the triage report maps how open issues connect to each other and to merged closing PRs. Deterministic signals come from `triage-relate.js`; semantic judgment (blocking, dependency, duplication) is the model's job and is rendered from the model's own edge JSON.

## Script-generated Edges (deterministic)

`triage-relate.js` reads a previously collected issue snapshot and emits read-only edges for snapshot-resident signals that need no interpretation:

- `mentions` from plain `#123` references in issue bodies
- `comment-mentions` from plain `#123` references in optional issue comments
- `blocks` from explicit blocking / closing phrases in issue bodies
- `depends-on` from explicit dependency phrases in issue bodies
- `merged-pr-link` from per-issue merged closing PR metadata
- `duplicate-candidate` from title-token Jaccard overlap

Every emitted edge carries evidence taken directly from the snapshot so downstream report rendering can show why the relationship was inferred without re-fetching from GitHub.

## Implemented Heuristics

### `mentions`

- Source: `issue.body`
Expand Down Expand Up @@ -42,29 +41,6 @@ Every emitted edge carries evidence taken directly from the snapshot so downstre
- `match`: matched issue reference
- `snippet`: normalized sentence/line fragment containing the match

### `blocks`

- Source: `issue.body`
- Keywords used by `scanBlocks`:
- `blocks #123`
- `closes #123`
- Confidence: `1`
- Evidence:
- `phrase`: normalized matched phrase, for example `Blocks #123`
- `snippet`: normalized sentence/line fragment containing the phrase

### `depends-on`

- Source: `issue.body`
- Keywords used by `scanDependsOn`:
- `blocked by #123`
- `depends on #123`
- `depends-on #123`
- Confidence: `1`
- Evidence:
- `phrase`: normalized matched phrase, for example `depends on #123`
- `snippet`: normalized sentence/line fragment containing the phrase

### `merged-pr-link`

- Source: `issue.closing_prs`
Expand All @@ -79,31 +55,55 @@ Every emitted edge carries evidence taken directly from the snapshot so downstre
- `pr.mergedAt`: merge timestamp
- `pr.url`: closing PR URL when present

### `duplicate-candidate`
## Model-judged Edges (semantic)

Blocking, dependency, and duplication require reading issue intent, so they are judged by the model reading the snapshot, not by phrase matching or title token overlap. The model emits them as `section: "relationship"` entries in a `--model-actions` JSON file; `triage-report.js` validates each entry and merges it into the Relationships path. Wire shape:

- Source: `issue.title`
- Threshold: `backlog/triage-config.yml -> duplicate_threshold`
- Confidence: Jaccard similarity score
- Canonicalization:
- compare each issue pair once
- emit a single edge with the smaller issue number as `from`
```json
{
"section": "relationship",
"verb": "edge",
"args": {
"from": 100,
"to": 101,
"kind": "blocks",
"confidence": 1,
"evidence": { "phrase": "Blocks #101" }
},
"summary": "Blocks edge 100 -> 101"
}
```

`args.kind` must be one of the script's deterministic kinds plus the semantic kinds below; `triage-report.js` rejects unknown kinds.

### `blocks`

- Read `issue.body` and comment bodies for explicit statements that issue X blocks issue Y, or closes it once completed.
- Only emit for issues that exist in the snapshot; never for `#999`-style dangling references.
- Confidence: `1` when the phrasing is explicit, lower when inferred.
- Evidence:
- `score`: rounded Jaccard score (`4` decimal places)
- `overlap`: sorted shared title tokens
- `titles.from`: lower-numbered issue title
- `titles.to`: higher-numbered issue title
- `phrase`: normalized matched phrase, for example `Blocks #123`
- `snippet`: normalized sentence/line fragment containing the phrase

## Jaccard Tokenization Rules
### `depends-on`

- Read `issue.body` and comment bodies for explicit dependency statements: `blocked by #123`, `depends on #123`, `depends-on #123`, or equivalent intent.
- Only emit for issues that exist in the snapshot.
- Confidence: `1` when explicit, lower when inferred.
- Evidence:
- `phrase`: normalized matched phrase, for example `depends on #123`
- `snippet`: normalized sentence/line fragment containing the phrase

Title similarity uses the following normalization before scoring:
### `duplicate-candidate`

- lowercase the title
- extract tokens with regex `[a-z0-9]+`
- drop one-character tokens
- deduplicate tokens per title by converting to a set
- compute `overlap / union`
- Compare open issues against each other and against closed issues, judging semantic duplication from titles, bodies, labels, and comments — not title-token overlap alone.
- Emit one canonical edge with the smaller issue number as `from`.
- `args.evidence`:
- `reason`: short human-readable why this is a duplicate candidate
- `titles.from`: lower-numbered issue title
- `titles.to`: higher-numbered issue title

If the union is empty, the score is `0` and no edge is emitted.
A duplicate of a closed issue should be proposed as an Obsolete Candidate (`section: "obsolete"`, `verb: "close-duplicate"`), not only as a relationship edge — see `references/stale.md`.

## Evidence Schema

Expand Down Expand Up @@ -169,13 +169,12 @@ Evidence payloads vary by kind:

```json
{
"score": 0.8,
"overlap": ["flow", "oauth", "refresh", "token"],
"reason": "same OAuth refresh flow as #200 with no substantive delta",
"titles": {
"from": "OAuth token refresh flow",
"to": "OAuth token refresh flow redesign"
}
}
```

`triage-relate.js` is intentionally still read-only. Close or duplicate proposals belong to `triage-stale.js` and still require report review plus an accepted apply checkbox before any GitHub mutation.
`triage-relate.js` is intentionally read-only. Close or duplicate proposals still require report review plus an accepted apply checkbox before any GitHub mutation.
Loading
Loading