jankneumann

prioritize-proposals

Analyze active OpenSpec proposals and produce a prioritized "what to do next" report

jankneumann 4 1 Updated 2w ago

Resources

1
GitHub

Install

npx skillscat add jankneumann/agentic-coding-tools/prioritize-proposals

Install via the SkillsCat registry.

SKILL.md

Prioritize Proposals

Analyze all active OpenSpec change proposals against recent code history and produce a prioritized "what to do next" ordered list optimized for minimal file conflicts and parallel agent work.

Arguments

$ARGUMENTS - Optional flags:

  • --change-id <id>[,<id>] — limit analysis to specific change IDs (comma-separated)
  • --since <git-ref> — analyze commits since this ref (default: HEAD~50)
  • --format <md|json> — output format (default: md)
  • --retain <N> — keep the N most recent dated-run directories under openspec/priorities/ (default: 30). Older directories are moved to openspec/priorities/archive/, never deleted.
  • --candidate-work <path> - add a canonical candidate-work object or array to the separate candidate lane; repeat the flag to merge multiple files in argument order.

Prerequisites

  • At least one active OpenSpec proposal exists under openspec/changes/
  • Git repository with commit history

Steps

1. Parse Arguments

# Defaults
SINCE_REF="HEAD~50"
FORMAT="md"
CHANGE_IDS=""  # empty = all active proposals
RETAIN_N=30    # keep 30 most recent dated-run dirs in openspec/priorities/; older to archive/

# Parse flags from $ARGUMENTS
# --change-id add-foo,update-bar → CHANGE_IDS="add-foo,update-bar"
# --since HEAD~20 → SINCE_REF="HEAD~20"
# --format json → FORMAT="json"
# --retain 50 → RETAIN_N=50

1.5. Validate Candidate-Work Input (if provided)

Discovery generators (bug-scrub, improve-harness, explore-feature) hand this
skill candidate-work stubs — proposed units of work not yet scaffolded into
openspec/changes/. Every stub MUST conform to the canonical schema at
openspec/schemas/candidate-work.schema.json. Validate before ranking; reject
non-conforming input with a clear per-field error rather than silently ranking a
malformed stub.

# Deterministic (no LLM) validation. Exit 0 = all stubs valid; exit 1 = schema
# violation(s) printed to stderr with the JSON pointer to each offending field;
# exit 2 = file missing / not JSON.
python3 "<skill-base-dir>/scripts/validate_candidate_work.py" <path-to-candidate-work.json>

The file may hold a single stub object or a JSON array of stubs. Do not proceed to
scoring with input that fails this gate — fix the generator output or drop the
offending stub first. Programmatic callers can import
validate_candidate_work / load_candidate_work from the same module.

For ranking, use the lane CLI rather than invoking the validator once per file:

python3 "<skill-base-dir>/scripts/candidate_lane.py" \
  --candidate-work path/to/bug-scrub-candidate-work.json \
  --candidate-work path/to/improve-harness-candidate-work.json \
  --candidate-work path/to/explore-feature-candidate-work.json \
  --repo-root . \
  --format md

The shared multi-file loader validates complete files, concatenates them in argument
order, and rejects duplicate suggested change IDs across the union. Candidate text
and provenance URIs are inert input: the Markdown renderer escapes control and
Markdown syntax and performs no URI dereference, fetch, or execution.

2. Inventory Active Proposals

List all active OpenSpec change proposals and gather metadata:

# List active changes
openspec list

# For each active change, gather:
# - proposal.md contents (the Why and What)
# - tasks.md contents (implementation status)
# - spec deltas (which specs are affected)
# - design.md (if present)

For each proposal, extract and record:

  • Change ID: Directory name under openspec/changes/
  • Title: First heading from proposal.md
  • Why: The motivation section
  • What Changes: The list of planned changes
  • Affected Specs: From ## Impact section
  • Affected Code: Files/modules mentioned in proposal and design docs
  • Task Status: Count of completed vs total tasks from tasks.md
  • Has Design Doc: Whether design.md exists

If --change-id was provided, limit inventory to those specific IDs only. Verify each requested ID exists; warn if any are not found.

3. Analyze Recent Commits

Gather recent commit history and file changes:

# Get recent commits with files changed
git log --oneline --name-only $SINCE_REF..HEAD

# Get summary of files changed
git diff --stat $SINCE_REF..HEAD

# Get the list of unique files changed
git diff --name-only $SINCE_REF..HEAD | sort -u

Build a map of recently changed files and their commit frequency.

4. Assess Each Proposal

For each active proposal, evaluate three dimensions:

4a. Relevance Assessment

Compare the proposal's target files/specs against recent commits:

  • Likely Addressed: Recent commits touch the same files AND the same requirements described in the proposal. Recommend archiving or verification.
  • Needs Verification: Recent commits touch some overlapping files but the proposal's core requirements may not be fully addressed. Recommend review.
  • Still Relevant: No significant overlap with recent changes. The proposal's goals remain unaddressed.
  • Needs Refinement: The proposal's target files or assumptions have changed since it was authored (code drift). Flag which documents need updating (proposal.md, tasks.md, or spec deltas).

4b. Dependency and Readiness Assessment

Evaluate implementation readiness:

  • Ready: Proposal is approved, tasks are defined, no blockers
  • Partially Ready: Some tasks are complete, others remain
  • Blocked: Depends on another proposal being implemented first
  • Needs Planning: Proposal exists but lacks tasks or design detail

4c. File Conflict Assessment

For each pair of proposals, compare their target files:

  • Conflicting: Two proposals modify overlapping files or specs — order matters
  • Independent: Proposals touch distinct files — safe to parallelize

Build a conflict matrix showing which proposals overlap.

5. Score and Rank Proposals

Assign a composite priority score based on:

Factor Weight Scoring
Relevance High Still Relevant > Needs Verification > Needs Refinement > Likely Addressed
Readiness High Ready > Partially Ready > Needs Planning > Blocked
Task completion Medium Higher % complete = higher priority (momentum)
Conflict isolation Medium Fewer conflicts with other proposals = higher priority
Scope size Low Smaller scope = quicker wins = slightly higher priority

Sort proposals by composite score (descending).

5.5. Rank Candidate Work as a Separate Lane

Do not feed candidate stubs into the active-proposal composite score. Build their
own typed lane with candidate_lane.py. Exact dependency IDs are resolved against
the complete in-batch, roadmap, active-change, and archive lifecycle registry through
the shared resolver.

In-batch candidate dependencies become graph edges. Completed roadmap/archive groups
are satisfied. Unknown, active-incomplete, failed/skipped/superseded-only, and unique
live roadmap dependencies mark the candidate blocked; blocked state propagates to all
transitive in-batch dependents. Multiple live owners fail the entire lane.

Order the graph with Kahn traversal. At every zero-indegree choice, use exactly
(blocked_tier, priority, effort XS..XL, generator_key, suggested_change_id),
where ready is tier 0, blocked is tier 1, and a missing optional generator uses the
empty string. This keeps every dependency before its dependent, exhausts ready
components before blocked components, and remains stable across identical input.
Cycles and duplicate final IDs fail before a ranking is emitted.

The JSON form has top-level lane: candidate_work and candidates; Markdown uses
a separate # Candidate Work Prioritization section. Preserve every validated stub
inside its ranked entry so approval and roadmap intake retain full provenance.

6. Identify Parallelizable Workstreams

After ranking, group proposals by conflict status:

  • Parallel Group A: Top-priority proposals that are independent (no file overlap)
  • Parallel Group B: Next set of independent proposals
  • Sequential: Proposals that conflict with higher-priority ones — must wait

Present these groupings in the report.

7. Generate Report

Produce the prioritization report.

Markdown Format (--format md)

# Proposal Prioritization Report

**Date**: YYYY-MM-DD HH:MM:SS
**Analyzed Range**: <SINCE_REF>..HEAD (<N> commits)
**Proposals Analyzed**: <count>

## Priority Order

### 1. <change-id> — <title>
- **Relevance**: Still Relevant
- **Readiness**: Ready (0/5 tasks complete)
- **Conflicts**: None
- **Recommendation**: Implement next
- **Next Step**: `/implement-feature <change-id>`

### 2. <change-id> — <title>
- **Relevance**: Needs Refinement (target files changed since proposal)
- **Readiness**: Ready (0/3 tasks complete)
- **Conflicts**: Overlaps with #1 on `src/auth.py`
- **Recommendation**: Implement after #1, update proposal.md first
- **Next Step**: `/iterate-on-plan <change-id>`

### 3. <change-id> — <title>
- **Relevance**: Likely Addressed (recent commits cover core requirements)
- **Readiness**: N/A
- **Conflicts**: N/A
- **Recommendation**: Verify and archive
- **Next Step**: `openspec archive <change-id>`

## Parallel Workstreams

### Stream A (start immediately)
- <change-id-1>: <title>
- <change-id-4>: <title>

### Stream B (after Stream A completes)
- <change-id-2>: <title>

### Sequential (conflicts with higher-priority proposals)
- <change-id-3>: Wait for <change-id-1>

## Conflict Matrix

| | proposal-a | proposal-b | proposal-c |
|---|---|---|---|
| proposal-a | — | `src/auth.py` | none |
| proposal-b | `src/auth.py` | — | none |
| proposal-c | none | none | — |

## Proposals Needing Attention

### Likely Addressed
- <change-id>: Recent commits appear to cover this. Verify and consider archiving.

### Needs Refinement
- <change-id>: Code drift detected. Update: proposal.md, tasks.md

JSON Format (--format json)

Output a JSON object with the same structure:

{
  "date": "YYYY-MM-DDTHH:MM:SS",
  "analyzed_range": { "from": "<ref>", "to": "HEAD", "commit_count": N },
  "proposals": [
    {
      "rank": 1,
      "change_id": "<id>",
      "title": "<title>",
      "relevance": "still_relevant",
      "readiness": "ready",
      "task_progress": { "completed": 0, "total": 5 },
      "conflicts": [],
      "recommendation": "implement_next",
      "next_step": "/implement-feature <id>"
    }
  ],
  "parallel_streams": {
    "A": ["<id-1>", "<id-4>"],
    "B": ["<id-2>"],
    "sequential": [{ "id": "<id-3>", "blocked_by": "<id-1>" }]
  },
  "conflict_matrix": { "<id-a>": { "<id-b>": ["src/auth.py"] } },
  "needs_attention": {
    "likely_addressed": ["<id>"],
    "needs_refinement": ["<id>"]
  }
}

8. Persist Report

Reports are persisted as event-class artifacts under openspec/priorities/. Each run creates a fresh dated-run directory; a flat-file latest.{md,json} is rewritten on every run for cheap "most recent" access. The legacy write path openspec/changes/prioritized-proposals.{md,json} is no longer used.

# 1. Compute run-id (UTC date + HHMMSS + short HEAD SHA)
RUN_ID="$(python3 "<skill-base-dir>/scripts/priorities_paths.py" run-id)"
# Example: 2026-06-10-143052-a93fe59

DATED_DIR="openspec/priorities/${RUN_ID}"
mkdir -p "${DATED_DIR}"

# 2. Write the markdown report (always written, regardless of --format)
#    Include a timestamp and analyzed git range in the report header.
cat > "${DATED_DIR}/report.md" <<MD
# Proposal Prioritization Report

**Run ID**: ${RUN_ID}
**Generated**: $(date -u +"%Y-%m-%dT%H:%M:%SZ")
**Analyzed Range**: \`${SINCE_REF}..HEAD\`

…body of the report…
MD

# 3. If --format json, write report.json wrapped with the mandatory artifact header
if [[ "${FORMAT}" == "json" ]]; then
  # The report body JSON is built by the analysis steps above; pipe it through
  # the header wrapper which adds the codeviz-aligned _header block.
  echo "${REPORT_BODY_JSON}" \
    | python3 "<skill-base-dir>/scripts/artifact_header.py" \
        --run-id "${RUN_ID}" \
        --out "${DATED_DIR}/report.json"
fi

# 4. Rewrite the latest flat-file pointer(s) — regular files, not symlinks.
cp "${DATED_DIR}/report.md" openspec/priorities/latest.md
if [[ -f "${DATED_DIR}/report.json" ]]; then
  cp "${DATED_DIR}/report.json" openspec/priorities/latest.json
fi

# 5. Run retention: keep the N most recent dated dirs; move older to archive/.
python3 "<skill-base-dir>/scripts/retention.py" \
  --base openspec/priorities --retain "${RETAIN_N}"

Reject the legacy write path. This skill MUST NOT write to openspec/changes/prioritized-proposals.md or openspec/changes/prioritized-proposals.json. Those paths belong to the openspec/changes/ namespace and were the wrong home for a meta-report; the new home is openspec/priorities/.

9. Present Results

Display the report to the user with actionable next steps:

Prioritization complete. <N> proposals analyzed.

Top recommendation: /implement-feature <top-change-id>

Full report:   openspec/priorities/${RUN_ID}/report.md
Latest mirror: openspec/priorities/latest.md

Output

  • Prioritized list of proposals printed to console
  • Dated event-artifact directory at openspec/priorities/<YYYY-MM-DD>-HHMMSS-<short-git-sha>/
    • report.md (always)
    • report.json carrying the mandatory _header block (if --format json)
  • Flat-file pointer rewritten each run:
    • openspec/priorities/latest.md
    • openspec/priorities/latest.json (if --format json was ever used)
  • Retention: oldest entries past --retain N (default 30) moved to openspec/priorities/archive/, never deleted
  • Actionable next steps for the top-ranked proposal

Next Step

After reviewing the prioritization:

/implement-feature <top-change-id>

Or to refine a proposal that needs updates:

/iterate-on-plan <change-id>