specrails-core 3.1.0 → 3.3.0

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.
@@ -0,0 +1,218 @@
1
+ # Local Ticket Management
2
+
3
+ specrails-core ships with a built-in, file-based ticket management system. It is the **default backlog provider** — no GitHub account, CLI tool, or external service required.
4
+
5
+ ---
6
+
7
+ ## Overview
8
+
9
+ Tickets live in `.claude/local-tickets.json` at your project root. Because it's a plain JSON file, tickets are:
10
+
11
+ - **Version-controlled** — tracked by git, diffable in PRs
12
+ - **Offline-first** — no network calls, no rate limits
13
+ - **Tool-agnostic** — readable by any script or editor
14
+
15
+ The file is shared between specrails-core (which reads and writes tickets during command execution) and specrails-hub (which provides a visual ticket panel with real-time sync).
16
+
17
+ ---
18
+
19
+ ## Storage format
20
+
21
+ `.claude/local-tickets.json`:
22
+
23
+ ```json
24
+ {
25
+ "schema_version": "1.0",
26
+ "revision": 7,
27
+ "last_updated": "2026-03-23T10:00:00.000Z",
28
+ "next_id": 8,
29
+ "tickets": {
30
+ "1": {
31
+ "id": 1,
32
+ "title": "Add dark mode",
33
+ "description": "Support system-level dark mode preference via CSS variables.",
34
+ "status": "todo",
35
+ "priority": "medium",
36
+ "labels": ["area:frontend", "effort:medium"],
37
+ "assignee": null,
38
+ "prerequisites": [],
39
+ "metadata": {
40
+ "vpc_scores": { "persona-a": 4, "persona-b": 3 },
41
+ "effort_level": "Medium",
42
+ "user_story": "As a user working at night, I want dark mode...",
43
+ "area": "frontend"
44
+ },
45
+ "comments": [],
46
+ "created_at": "2026-03-20T09:00:00.000Z",
47
+ "updated_at": "2026-03-20T09:00:00.000Z",
48
+ "created_by": "sr-product-manager",
49
+ "source": "product-backlog"
50
+ }
51
+ }
52
+ }
53
+ ```
54
+
55
+ ### Field reference
56
+
57
+ **Root fields**
58
+
59
+ | Field | Type | Description |
60
+ |-------|------|-------------|
61
+ | `schema_version` | string | Always `"1.0"` for the current format |
62
+ | `revision` | number | Incremented on every write — used for optimistic concurrency control |
63
+ | `last_updated` | ISO-8601 | Timestamp of the most recent mutation |
64
+ | `next_id` | number | Auto-increment counter for new ticket IDs |
65
+ | `tickets` | object | Map of ticket ID (as string) → ticket object |
66
+
67
+ **Ticket fields**
68
+
69
+ | Field | Type | Values | Description |
70
+ |-------|------|--------|-------------|
71
+ | `id` | number | Auto-assigned | Numeric ID, referenced as `#<id>` |
72
+ | `title` | string | — | Short title |
73
+ | `description` | string | Markdown | Full description |
74
+ | `status` | string | `todo`, `in_progress`, `done`, `cancelled` | Current state |
75
+ | `priority` | string | `critical`, `high`, `medium`, `low` | Priority level |
76
+ | `labels` | string[] | Freeform | Tag strings; convention: `area:*`, `effort:*` |
77
+ | `assignee` | string\|null | — | Agent name or user, if assigned |
78
+ | `prerequisites` | number[] | — | IDs of tickets that must be done first |
79
+ | `metadata` | object | — | VPC scores, effort level, user story, area (set by agents) |
80
+ | `comments` | array | — | Progress notes appended during implementation |
81
+ | `created_at` | ISO-8601 | — | Creation timestamp |
82
+ | `updated_at` | ISO-8601 | — | Last mutation timestamp |
83
+ | `created_by` | string | — | Agent name or `"user"` |
84
+ | `source` | string | `manual`, `product-backlog`, `propose-spec` | How the ticket was created |
85
+
86
+ ---
87
+
88
+ ## Setup
89
+
90
+ Local tickets become the default during `/setup`. The wizard prompts:
91
+
92
+ ```
93
+ ## Backlog Provider
94
+
95
+ Use local ticket management or connect an external provider?
96
+
97
+ 1. Local tickets (default, recommended) — lightweight JSON-based ticket management.
98
+ No external tools or accounts required.
99
+ 2. External provider — connect GitHub Issues, JIRA, or disable backlog commands
100
+ ```
101
+
102
+ Pressing **Enter** or selecting **1** initializes `.claude/local-tickets.json` with an empty ticket store and writes `.claude/backlog-config.json`:
103
+
104
+ ```json
105
+ {
106
+ "provider": "local",
107
+ "write_access": true,
108
+ "git_auto": true
109
+ }
110
+ ```
111
+
112
+ To switch providers later, re-run the setup wizard:
113
+
114
+ ```bash
115
+ npx specrails-core@latest init --root-dir .
116
+ > /setup
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Concurrency model
122
+
123
+ Both CLI agents and specrails-hub can modify `local-tickets.json` simultaneously. The system uses two complementary mechanisms:
124
+
125
+ ### Advisory file lock
126
+
127
+ Before every write, the agent creates `.claude/local-tickets.json.lock`:
128
+
129
+ ```json
130
+ {
131
+ "agent": "sr-product-manager",
132
+ "timestamp": "2026-03-23T10:00:00.000Z"
133
+ }
134
+ ```
135
+
136
+ If the lock file already exists:
137
+ - **Fresh lock** (< 30 seconds old): wait 500 ms and retry, up to 5 attempts
138
+ - **Stale lock** (≥ 30 seconds old): treat as orphaned, delete it, proceed
139
+
140
+ The lock is deleted immediately after the write completes. The window is minimal: read → modify in memory → write → release.
141
+
142
+ ### Revision counter
143
+
144
+ Every write increments `revision`. Readers that want to detect external changes compare the `revision` they last saw against the current value. This is how specrails-hub avoids echoing its own writes back through the file watcher.
145
+
146
+ ---
147
+
148
+ ## Command integration
149
+
150
+ ### `/sr:implement`
151
+
152
+ Pass local ticket IDs the same way you would GitHub issue numbers:
153
+
154
+ ```bash
155
+ /sr:implement #1, #4, #7
156
+ ```
157
+
158
+ The command reads each ticket from `local-tickets.json`, extracts metadata (area, effort, description), and tracks the ticket through the pipeline — updating status to `in_progress` on start and `done` on successful completion.
159
+
160
+ ### `/sr:product-backlog`
161
+
162
+ ```bash
163
+ /sr:product-backlog # all areas
164
+ /sr:product-backlog UI, Backend # filter by area
165
+ ```
166
+
167
+ Reads all `todo` and `in_progress` tickets, scores them by VPC match, respects the `prerequisites` dependency graph, and recommends the top 3 for your next sprint.
168
+
169
+ ### `/sr:update-product-driven-backlog`
170
+
171
+ ```bash
172
+ /sr:update-product-driven-backlog # explore all areas
173
+ /sr:update-product-driven-backlog Analytics # focus on one area
174
+ ```
175
+
176
+ Runs product discovery using your VPC personas. Creates new local tickets for discovered feature ideas, tagged with `source: "product-backlog"` and `labels: ["product-driven-backlog", "area:<area>"]`. Existing tickets are checked for duplicates before creating new ones.
177
+
178
+ ### `/sr:propose-spec`
179
+
180
+ When a proposal is finalized, a local ticket is created automatically:
181
+
182
+ ```
183
+ Created local ticket #12: Add analytics export
184
+ ```
185
+
186
+ The ticket captures the full proposal as its description and is tagged `source: "propose-spec"` with the label `spec-proposal`.
187
+
188
+ ---
189
+
190
+ ## integration-contract.json
191
+
192
+ The `ticketProvider` section tells specrails-hub where to find and how to use the ticket store:
193
+
194
+ ```json
195
+ {
196
+ "ticketProvider": {
197
+ "type": "local",
198
+ "storageFile": "local-tickets.json",
199
+ "lockFile": "local-tickets.json.lock",
200
+ "capabilities": ["crud", "labels", "status", "priorities", "dependencies", "comments"]
201
+ }
202
+ }
203
+ ```
204
+
205
+ | Field | Description |
206
+ |-------|-------------|
207
+ | `type` | `"local"` — file-based storage (the only supported type currently) |
208
+ | `storageFile` | Filename relative to the project's specrails dir (`.claude/` or `.codex/`) |
209
+ | `lockFile` | Advisory lock filename, same directory |
210
+ | `capabilities` | Features the hub enables based on what the provider supports |
211
+
212
+ specrails-hub reads this file when it loads a project. If the file is missing or the `ticketProvider` key is absent, hub falls back to safe defaults (`type: "local"`, `storageFile: "local-tickets.json"`).
213
+
214
+ ---
215
+
216
+ ## Migrating from GitHub Issues or JIRA
217
+
218
+ See the [Migration Guide](./migration-guide.md).
@@ -0,0 +1,141 @@
1
+ # Migration Guide: Switching to Local Tickets
2
+
3
+ This guide is for teams currently using GitHub Issues or JIRA as their specrails backlog provider who want to switch to the built-in local ticket system.
4
+
5
+ Switching is optional. GitHub Issues and JIRA remain fully supported. Local tickets are the recommended default for new projects.
6
+
7
+ ---
8
+
9
+ ## Should you switch?
10
+
11
+ **Switch to local tickets if:**
12
+ - Your team prefers a simple, zero-dependency setup
13
+ - You want tickets version-controlled alongside your code
14
+ - You use specrails-hub and want the visual ticket panel
15
+ - You don't need GitHub/JIRA for other workflows (project boards, external stakeholders)
16
+
17
+ **Stay on GitHub Issues / JIRA if:**
18
+ - Other teams or stakeholders manage tickets in those tools
19
+ - You rely on GitHub Projects, Milestones, or JIRA sprints
20
+ - You want PR auto-close on issue merge (requires GitHub Issues)
21
+
22
+ ---
23
+
24
+ ## Step 1: Switch the provider
25
+
26
+ Edit `.claude/backlog-config.json` in your project root:
27
+
28
+ ```json
29
+ {
30
+ "provider": "local",
31
+ "write_access": true,
32
+ "git_auto": true
33
+ }
34
+ ```
35
+
36
+ Then initialize the ticket store if it doesn't exist yet:
37
+
38
+ ```bash
39
+ # Inside Claude Code or Codex
40
+ /sr:implement --setup-local-tickets
41
+ ```
42
+
43
+ Or create the file manually:
44
+
45
+ ```bash
46
+ cat > .claude/local-tickets.json << 'EOF'
47
+ {
48
+ "schema_version": "1.0",
49
+ "revision": 0,
50
+ "last_updated": null,
51
+ "next_id": 1,
52
+ "tickets": {}
53
+ }
54
+ EOF
55
+ ```
56
+
57
+ ---
58
+
59
+ ## Step 2: Import existing issues (one-time migration)
60
+
61
+ ### From GitHub Issues
62
+
63
+ Use the `sr:migrate-from-github` command (requires `gh` CLI):
64
+
65
+ ```bash
66
+ # Inside Claude Code
67
+ /sr:migrate-from-github
68
+ ```
69
+
70
+ This command:
71
+ 1. Fetches all open issues labeled `product-driven-backlog` (the label specrails uses)
72
+ 2. Maps GitHub issue fields to local ticket schema:
73
+ - `number` → `id` (uses next available local ID)
74
+ - `title` → `title`
75
+ - `body` → `description`
76
+ - `labels` → `labels`
77
+ - `state: open` → `status: todo`
78
+ 3. Writes each ticket to `local-tickets.json`
79
+ 4. Prints a summary: `Imported 14 tickets from GitHub Issues`
80
+
81
+ To import all open issues regardless of label:
82
+
83
+ ```bash
84
+ /sr:migrate-from-github --all
85
+ ```
86
+
87
+ To do a dry run (preview without writing):
88
+
89
+ ```bash
90
+ /sr:migrate-from-github --dry-run
91
+ ```
92
+
93
+ **After import:** Your GitHub Issues are unchanged. The migration is additive — it only creates local tickets. You can continue using GitHub Issues in parallel until you're ready to stop.
94
+
95
+ ### From JIRA
96
+
97
+ Use the `sr:migrate-from-jira` command (requires `jira` CLI or REST API credentials in `.claude/backlog-config.json`):
98
+
99
+ ```bash
100
+ # Inside Claude Code
101
+ /sr:migrate-from-jira
102
+ ```
103
+
104
+ This command:
105
+ 1. Fetches all open issues from the configured JIRA project
106
+ 2. Maps JIRA fields to local ticket schema:
107
+ - Issue key (`PROJECT-123`) → stored in `metadata.jira_key`
108
+ - `summary` → `title`
109
+ - `description` → `description`
110
+ - `priority` → `priority` (Critical/High/Medium/Low mapped directly)
111
+ - `status: To Do / Backlog` → `status: todo`
112
+ - `status: In Progress` → `status: in_progress`
113
+ - `labels` → `labels`
114
+ 3. Writes each ticket to `local-tickets.json`
115
+
116
+ The original JIRA key is preserved in `metadata.jira_key` so you can cross-reference during the transition.
117
+
118
+ ---
119
+
120
+ ## Step 3: Regenerate commands (optional but recommended)
121
+
122
+ Command templates are generated at `/setup` time with provider-specific instructions baked in. After switching providers, regenerate them so commands use the local file operations instead of GitHub/JIRA CLI calls:
123
+
124
+ ```bash
125
+ npx specrails-core@latest init --root-dir .
126
+ > /setup --update
127
+ ```
128
+
129
+ The `--update` flag regenerates only the backlog commands (`product-backlog`, `update-product-driven-backlog`, `implement`) without re-running the full stack analysis.
130
+
131
+ ---
132
+
133
+ ## Rollback
134
+
135
+ To revert to GitHub Issues:
136
+
137
+ 1. Edit `.claude/backlog-config.json` and set `"provider": "github"`
138
+ 2. Re-run `/setup --update` to regenerate commands
139
+ 3. Your `local-tickets.json` is preserved — switch back any time
140
+
141
+ Local tickets and external provider data are independent. Switching providers does not delete tickets from either system.
@@ -14,17 +14,20 @@ It creates a `.claude/` directory with agent templates, commands, and configurat
14
14
 
15
15
  Yes. Node 18+ is required to run `npx specrails-core@latest`. Once installed, SpecRails works with any language or framework — the agents adapt to whatever stack your project uses.
16
16
 
17
- **Can I run SpecRails on a project that doesn't have GitHub Issues?**
17
+ **Do I need GitHub Issues?**
18
18
 
19
- Yes. During `/setup`, you can choose "none" as your backlog provider. Commands like `/sr:implement` will accept plain text descriptions instead of issue numbers:
19
+ No. SpecRails ships with a built-in local ticket system no GitHub account required. Local tickets are the default. Commands like `/sr:implement` accept ticket IDs or plain text:
20
20
 
21
21
  ```
22
+ /sr:implement #1, #4
22
23
  /sr:implement "add rate limiting to the API"
23
24
  ```
24
25
 
26
+ You can switch to GitHub Issues or JIRA during `/setup` (Phase 3) if you prefer.
27
+
25
28
  **How long does /setup take?**
26
29
 
27
- About 5 minutes. Most of the time is spent on Phase 2 (persona generation), which does web research on your domain.
30
+ The full wizard takes about 5 minutes most of the time is Phase 2 (persona research via web search). For a faster start, use `/setup --lite`: three questions, under a minute, no web research.
28
31
 
29
32
  ---
30
33
 
@@ -95,7 +95,7 @@ codex # Codex
95
95
  /setup
96
96
  ```
97
97
 
98
- The wizard runs 5 phases:
98
+ By default, `/setup` runs the full 5-phase wizard:
99
99
 
100
100
  | Phase | What happens |
101
101
  |-------|-------------|
@@ -105,6 +105,8 @@ The wizard runs 5 phases:
105
105
  | **4. Generate** | Fills all templates with your project-specific context |
106
106
  | **5. Cleanup** | Removes setup files, leaving only your tailored workflow |
107
107
 
108
+ **In a hurry?** Run `/setup --lite` instead — three questions, sensible defaults, done in under a minute.
109
+
108
110
  After setup, `.claude/` contains fully configured agents and commands ready to use. The `/setup` command removes itself — it only runs once.
109
111
 
110
112
  ## Verify
@@ -46,7 +46,7 @@ Then run:
46
46
  /setup
47
47
  ```
48
48
 
49
- The wizard runs automatically and takes about 5 minutes. It analyzes your codebase and configures SpecRails for your specific project:
49
+ The wizard runs the full 5-phase setup (about 5 minutes). It analyzes your codebase and configures SpecRails for your specific project:
50
50
 
51
51
  ```
52
52
  Phase 1/5 Analyzing codebase...
@@ -59,14 +59,14 @@ Phase 2/5 Generating user personas...
59
59
  → Created 3 VPC profiles
60
60
 
61
61
  Phase 3/5 Configuration...
62
- → Backlog provider: GitHub Issues
62
+ → Backlog provider: local
63
63
  → Git workflow: trunk-based
64
64
 
65
65
  Phase 4/5 Generating files...
66
66
  → sr-architect.md (adapted to your stack)
67
67
  → sr-developer.md (knows your CI commands)
68
68
  → sr-reviewer.md (runs your specific checks)
69
- 9 more agents
69
+ 11 more agents
70
70
 
71
71
  Phase 5/5 Cleanup complete. /setup removed.
72
72
 
@@ -75,6 +75,8 @@ Phase 5/5 Cleanup complete. /setup removed.
75
75
 
76
76
  After setup, the `/setup` command is gone — it's a one-time wizard.
77
77
 
78
+ **In a hurry?** Use `/setup --lite` for a 3-question quick setup (under a minute). You can always run the full wizard later.
79
+
78
80
  ## Step 3: Implement your first feature
79
81
 
80
82
  Pick something small. Either reference a GitHub Issue or describe it in plain text:
@@ -129,7 +131,7 @@ One command. The PR is ready for human review.
129
131
  /sr:product-backlog
130
132
  ```
131
133
 
132
- See your GitHub Issues ranked by persona fit and effort. The top 3 are safe to implement next.
134
+ See your tickets ranked by persona fit and effort. The top 3 are safe to implement next. Uses local tickets by default.
133
135
 
134
136
  **Generate new feature ideas:**
135
137
 
@@ -137,7 +139,7 @@ See your GitHub Issues ranked by persona fit and effort. The top 3 are safe to i
137
139
  /sr:update-product-driven-backlog
138
140
  ```
139
141
 
140
- The Product Manager researches your competitive landscape and creates well-formed GitHub Issues for new features.
142
+ The Product Manager researches your competitive landscape and creates new tickets (local by default, or GitHub Issues if configured).
141
143
 
142
144
  **Run multiple features in parallel:**
143
145
 
package/docs/workflows.md CHANGED
@@ -126,7 +126,7 @@ View your prioritized product backlog, ranked by VPC fit and effort.
126
126
 
127
127
  ### What it shows
128
128
 
129
- The Product Analyst reads your GitHub Issues (labeled `product-driven-backlog`) and produces:
129
+ The Product Analyst reads your backlog (local tickets in `.claude/local-tickets.json` by default, or GitHub Issues labeled `product-driven-backlog` if configured) and produces:
130
130
 
131
131
  - **Backlog table** per area — sorted by Total Persona Score
132
132
  - **Top 3 recommendations** — ranked by VPC score / effort ratio, filtered to Wave 1 of the safe implementation order
@@ -166,7 +166,7 @@ Generate new feature ideas through product discovery. The Product Manager (Opus)
166
166
  2. Researches competitors via web search
167
167
  3. Generates 2–4 feature ideas per area
168
168
  4. Scores each against every persona (0–5)
169
- 5. Creates GitHub Issues (if write access) or displays for manual creation
169
+ 5. Creates tickets in your active backlog provider (local tickets by default; GitHub Issues or JIRA if configured) or displays for manual creation
170
170
 
171
171
  ---
172
172
 
@@ -35,5 +35,11 @@
35
35
  "refactor-recommender",
36
36
  "health-check",
37
37
  "compat-check"
38
- ]
38
+ ],
39
+ "ticketProvider": {
40
+ "type": "local",
41
+ "storageFile": "local-tickets.json",
42
+ "lockFile": "local-tickets.json.lock",
43
+ "capabilities": ["crud", "labels", "status", "priorities", "dependencies", "comments"]
44
+ }
39
45
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specrails-core",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "description": "AI agent workflow system for Claude Code — installs 12 specialized agents, orchestration commands, and persona-driven product discovery into any repository",
5
5
  "bin": {
6
6
  "specrails-core": "bin/specrails-core.js"
@@ -24,12 +24,22 @@ Check the environment variable `CLAUDE_CODE_ENTRYPOINT`. If it contains `remote_
24
24
 
25
25
  ### Checks to run (sequential, fail-fast)
26
26
 
27
- #### 1. GitHub CLI authentication
27
+ #### 1. Backlog provider availability
28
28
 
29
+ Read `.claude/backlog-config.json` and extract `BACKLOG_PROVIDER`.
30
+
31
+ **If `BACKLOG_PROVIDER=local`:**
29
32
  ```bash
30
- gh auth status 2>&1
33
+ [[ -f "$SPECRAILS_DIR/local-tickets.json" ]] && echo "Local tickets storage: OK" || echo "WARNING: local-tickets.json not found"
31
34
  ```
35
+ - Set `LOCAL_TICKETS_AVAILABLE=true/false` based on file existence.
36
+ - Set `GH_AVAILABLE=false` (GitHub CLI not needed for local provider).
37
+ - Set `BACKLOG_AVAILABLE=true` if local-tickets.json exists.
32
38
 
39
+ **Otherwise:**
40
+ ```bash
41
+ gh auth status 2>&1
42
+ ```
33
43
  - Set `GH_AVAILABLE=true/false` for later phases.
34
44
 
35
45
  #### 2. OpenSpec CLI
@@ -107,7 +117,7 @@ Initialize conflict-tracking variables:
107
117
  - Set `SINGLE_MODE = true`. No worktrees, no parallelism.
108
118
  - **Skip Phase 1 and Phase 2** — go directly to Phase 3a.
109
119
 
110
- **If the user passed issue/ticket references** (e.g. `#85, #71` for GitHub or `PROJ-85, PROJ-71` for JIRA):
120
+ **If the user passed issue/ticket references** (e.g. `#85, #71` for GitHub, `#1, #2` for local tickets, or `PROJ-85, PROJ-71` for JIRA):
111
121
  - Fetch each issue/ticket:
112
122
  ```bash
113
123
  {{BACKLOG_VIEW_CMD}}
@@ -120,7 +130,44 @@ Initialize conflict-tracking variables:
120
130
 
121
131
  After fetching issue refs, capture a baseline snapshot for conflict detection.
122
132
 
123
- **If `GH_AVAILABLE=true` and the input mode was issue numbers:**
133
+ ##### If `BACKLOG_PROVIDER=local` and input mode was issue numbers:
134
+
135
+ For each resolved ticket ID, read `$SPECRAILS_DIR/local-tickets.json` and extract the ticket object at `tickets["{id}"]`.
136
+
137
+ Build a snapshot object for each ticket:
138
+ - `number`: ticket `id` (integer)
139
+ - `title`: ticket `title` string
140
+ - `state`: map ticket `status` — `"done"` or `"cancelled"` → `"closed"`, otherwise → `"open"`
141
+ - `assignees`: `[ticket.assignee]` if non-null, else `[]`
142
+ - `labels`: ticket `labels` array, sorted alphabetically
143
+ - `body_sha`: SHA-256 of the ticket `description` string — compute with:
144
+ ```bash
145
+ echo -n "{description}" | sha256sum | cut -d' ' -f1
146
+ ```
147
+ If `sha256sum` is not available, fall back to `openssl dgst -sha256 -r` or `shasum -a 256`.
148
+ - `updated_at`: ticket `updated_at` value
149
+ - `captured_at`: current local time in ISO 8601 format
150
+
151
+ Write the following JSON to `.claude/backlog-cache.json` (overwrite fully — this establishes a fresh baseline for this run):
152
+
153
+ ```json
154
+ {
155
+ "schema_version": "1",
156
+ "provider": "local",
157
+ "last_updated": "<ISO 8601 timestamp>",
158
+ "written_by": "implement",
159
+ "issues": {
160
+ "<id>": { <snapshot object> },
161
+ ...
162
+ }
163
+ }
164
+ ```
165
+
166
+ If the write succeeds: set `SNAPSHOTS_CAPTURED=true`.
167
+
168
+ If the write fails: print `[backlog-cache] Warning: could not write cache. Conflict detection disabled for this run.` and set `SNAPSHOTS_CAPTURED=false`. Do NOT abort the pipeline.
169
+
170
+ ##### If `GH_AVAILABLE=true` and input mode was issue numbers (GitHub/JIRA):
124
171
 
125
172
  For each resolved issue number, run:
126
173
 
@@ -161,9 +208,9 @@ If the write succeeds: set `SNAPSHOTS_CAPTURED=true`.
161
208
 
162
209
  If the write fails (e.g., `.claude/` directory does not exist): print `[backlog-cache] Warning: could not write cache. Conflict detection disabled for this run.` and set `SNAPSHOTS_CAPTURED=false`. Do NOT abort the pipeline.
163
210
 
164
- **If `GH_AVAILABLE=false` or input was not issue numbers:**
211
+ ##### Otherwise (no backlog available or non-issue input):
165
212
 
166
- Set `SNAPSHOTS_CAPTURED=false`. Print: `[conflict-check] Snapshot skipped — GH unavailable or non-issue input.`
213
+ Set `SNAPSHOTS_CAPTURED=false`. Print: `[conflict-check] Snapshot skipped — backlog unavailable or non-issue input.`
167
214
 
168
215
  #### Gitignore advisory
169
216
 
@@ -267,7 +314,9 @@ Pick the single idea with the best impact/effort ratio from each exploration. Pr
267
314
 
268
315
  Otherwise, re-fetch each issue in scope and diff against the Phase 0 snapshot:
269
316
 
270
- For each issue number in `ISSUE_REFS`:
317
+ **If `BACKLOG_PROVIDER=local`:** For each ticket ID in `ISSUE_REFS`, read `$SPECRAILS_DIR/local-tickets.json` and extract the ticket at `tickets["{id}"]`. If the ticket does not exist (deleted): treat as a CRITICAL conflict — field `"state"`, was `<cached state>`, now `"deleted"`. Otherwise, reconstruct a current snapshot using the same mapping as the Phase 0 local snapshot.
318
+
319
+ **If `BACKLOG_PROVIDER=github`:** For each issue number in `ISSUE_REFS`:
271
320
 
272
321
  ```bash
273
322
  gh issue view {number} --json number,title,state,assignees,labels,body,updatedAt
@@ -275,7 +324,7 @@ gh issue view {number} --json number,title,state,assignees,labels,body,updatedAt
275
324
 
276
325
  If the `gh` command returns non-zero (issue deleted or inaccessible): treat as a CRITICAL conflict — field `"state"`, was `<cached state>`, now `"deleted"`.
277
326
 
278
- Otherwise, reconstruct a current snapshot (same shape as Phase 0: sort `assignees` and `labels`, compute `body_sha`).
327
+ In both cases, reconstruct a current snapshot (same shape as Phase 0: sort `assignees` and `labels`, compute `body_sha`).
279
328
 
280
329
  **Short-circuit:** If `current.updatedAt == cached.updated_at`, mark the issue as clean and skip field comparison.
281
330
 
@@ -848,6 +897,9 @@ This check is independent of Phase 3a.0. Even if the user chose to continue thro
848
897
 
849
898
  Re-fetch each issue in `ISSUE_REFS` and diff against `.claude/backlog-cache.json` using the same algorithm as Phase 3a.0:
850
899
 
900
+ **If `BACKLOG_PROVIDER=local`:** Read `$SPECRAILS_DIR/local-tickets.json` and extract each ticket by ID.
901
+
902
+ **If `BACKLOG_PROVIDER=github`:**
851
903
  ```bash
852
904
  gh issue view {number} --json number,title,state,assignees,labels,body,updatedAt
853
905
  ```
@@ -881,8 +933,12 @@ Record skipped operations to `.cache-manifest.json` under `skipped_operations`:
881
933
  - `"git: commit"`
882
934
  - `"git: push"`
883
935
  - `"github: pr creation"` (if `GH_AVAILABLE=true`)
884
- - `"github: issue comment #N"` for each issue in scope (if `BACKLOG_WRITE=true`)
885
- - `"github: issue close #N (via PR merge)"` for each fully resolved issue (if `BACKLOG_WRITE=true`)
936
+ - If `BACKLOG_PROVIDER=local` and `BACKLOG_WRITE=true`:
937
+ - `"local: ticket comment #{id}"` for each ticket in scope
938
+ - `"local: ticket status update #{id}"` for each fully resolved ticket
939
+ - If `BACKLOG_PROVIDER=github` and `BACKLOG_WRITE=true`:
940
+ - `"github: issue comment #N"` for each issue in scope
941
+ - `"github: issue close #N (via PR merge)"` for each fully resolved issue
886
942
 
887
943
  Then skip the rest of Phase 4c and proceed directly to Phase 4e.
888
944
 
@@ -934,17 +990,19 @@ All implementation is complete and CI checks pass.
934
990
  #### Backlog updates (both modes)
935
991
 
936
992
  **If `BACKLOG_WRITE=true`:**
937
- - For fully resolved issues/tickets: add a comment noting completion and reference the PR. Do NOT close the issue explicitly — use `Closes #N` in the PR body so GitHub/JIRA closes it automatically when the PR is merged:
993
+ - For fully resolved issues/tickets: add a comment noting completion and reference the PR:
938
994
  ```bash
939
995
  {{BACKLOG_COMMENT_CMD}}
940
996
  ```
941
- - GitHub: `gh issue comment {number} --body "Implemented in PR #XX. All acceptance criteria met."`
942
- - JIRA: `jira issue comment {key} --message "Implemented in PR #XX. All acceptance criteria met."`
943
- - Ensure the PR body includes `Closes #N` for each fully resolved issue (GitHub auto-closes on merge)
997
+ - **Local:** Update the ticket status to `"done"` using `{{BACKLOG_UPDATE_CMD}}` and add a comment: `"Implemented in PR #XX. All acceptance criteria met."` via `{{BACKLOG_COMMENT_CMD}}`. Local tickets are closed directly — there is no auto-close-on-merge mechanism.
998
+ - **GitHub:** `gh issue comment {number} --body "Implemented in PR #XX. All acceptance criteria met."` — do NOT close the issue explicitly. Use `Closes #N` in the PR body so GitHub auto-closes on merge.
999
+ - **JIRA:** `jira issue comment {key} --message "Implemented in PR #XX. All acceptance criteria met."`
1000
+ - For GitHub/JIRA: ensure the PR body includes `Closes #N` for each fully resolved issue (auto-closes on merge)
944
1001
  - For partially resolved issues/tickets: add a comment noting progress:
945
1002
  ```bash
946
1003
  {{BACKLOG_PARTIAL_COMMENT_CMD}}
947
1004
  ```
1005
+ - **Local:** Additionally update the ticket status to `"in_progress"` via `{{BACKLOG_UPDATE_CMD}}` if it is still `"todo"`.
948
1006
 
949
1007
  **If `BACKLOG_WRITE=false`:**
950
1008
  - Do NOT create, modify, or comment on any issues/tickets.