jekyll-theme-zer0 1.20.2 → 1.22.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +27 -0
- data/_data/backlog.yml +131 -2
- data/_data/roadmap_plan.yml +37 -0
- data/_data/routing.yml +73 -0
- data/_includes/components/component-showcase.html +18 -10
- data/_includes/components/js-cdn.html +10 -2
- data/_includes/components/post-card.html +18 -4
- data/_includes/content/backlinks.html +47 -18
- data/_includes/content/giscus.html +30 -44
- data/_includes/core/footer-fabs.html +5 -2
- data/_includes/navigation/local-graph-fab.html +1 -1
- data/_includes/navigation/local-graph.html +4 -3
- data/_includes/navigation/section-sidebar.html +10 -3
- data/_includes/obsidian/full-graph.html +7 -5
- data/_layouts/article.html +23 -6
- data/_plugins/obsidian_links.rb +84 -15
- data/_sass/core/_obsidian.scss +59 -8
- data/assets/data/wiki-index.json +50 -3
- data/assets/js/obsidian-graph.js +66 -21
- data/assets/js/obsidian-local-graph.js +125 -32
- data/assets/js/obsidian-wiki-links.js +118 -21
- data/assets/js/search-modal.js +45 -9
- data/scripts/bin/giscus-discussions +509 -0
- data/scripts/sync-backlog.rb +149 -26
- data/scripts/sync-plan.rb +164 -0
- data/scripts/sync-plan.sh +21 -0
- metadata +7 -2
data/scripts/sync-backlog.rb
CHANGED
|
@@ -89,6 +89,7 @@ def validate(data)
|
|
|
89
89
|
return ['Missing or empty `tasks:` list.'] unless tasks.is_a?(Array) && !tasks.empty?
|
|
90
90
|
|
|
91
91
|
seen_ids = {}
|
|
92
|
+
seen_links = {}
|
|
92
93
|
tasks.each_with_index do |task, i|
|
|
93
94
|
where = "tasks[#{i}]"
|
|
94
95
|
unless task.is_a?(Hash)
|
|
@@ -114,6 +115,30 @@ def validate(data)
|
|
|
114
115
|
unless task['acceptance'].is_a?(Array) && !task['acceptance'].empty?
|
|
115
116
|
errors << "#{where}: `acceptance` must be a non-empty list."
|
|
116
117
|
end
|
|
118
|
+
|
|
119
|
+
# Adoption guard: an existing GitHub issue may be claimed by at most one task,
|
|
120
|
+
# so two tasks can never fight over (or duplicate) the same issue.
|
|
121
|
+
lnk = task.dig('links', 'issue')
|
|
122
|
+
if lnk
|
|
123
|
+
unless lnk.is_a?(Integer) || lnk.to_s =~ /\A\d+\z/
|
|
124
|
+
errors << "#{where}: `links.issue` must be an issue number (got #{lnk.inspect})."
|
|
125
|
+
end
|
|
126
|
+
key = lnk.to_s
|
|
127
|
+
if seen_links[key]
|
|
128
|
+
errors << "#{where}: `links.issue` ##{key} already claimed by #{seen_links[key]}."
|
|
129
|
+
else
|
|
130
|
+
seen_links[key] = (id || where)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
# Optional routing/dependency metadata (consumed by /issue-implement + /issue-plan).
|
|
134
|
+
if task.key?('route') && !task['route'].is_a?(String)
|
|
135
|
+
errors << "#{where}: `route` must be a string."
|
|
136
|
+
end
|
|
137
|
+
if task.key?('depends_on') &&
|
|
138
|
+
!(task['depends_on'].is_a?(Array) &&
|
|
139
|
+
task['depends_on'].all? { |d| d.is_a?(String) && d =~ /\AT-\d{3,}\z/ })
|
|
140
|
+
errors << "#{where}: `depends_on` must be a list of T-NNN ids."
|
|
141
|
+
end
|
|
117
142
|
end
|
|
118
143
|
errors
|
|
119
144
|
end
|
|
@@ -133,6 +158,52 @@ def marker(id)
|
|
|
133
158
|
"<!-- backlog-id: #{id} -->"
|
|
134
159
|
end
|
|
135
160
|
|
|
161
|
+
# Delimiters around the sync-owned section of an ADOPTED human issue. Text ABOVE
|
|
162
|
+
# the start delimiter (the author's original report) is never touched; only this
|
|
163
|
+
# block is upserted on each sync, so adoption is non-destructive.
|
|
164
|
+
MANAGED_START = '<!-- backlog-managed:start -->'
|
|
165
|
+
MANAGED_END = '<!-- backlog-managed:end -->'
|
|
166
|
+
|
|
167
|
+
def render_managed_block(task)
|
|
168
|
+
accept = (task['acceptance'] || []).map { |a| "- [ ] #{a}" }.join("\n")
|
|
169
|
+
roadmap = task.dig('links', 'roadmap')
|
|
170
|
+
meta_row = [
|
|
171
|
+
"**Priority:** #{task['priority']}",
|
|
172
|
+
"**Area:** #{task['area']}",
|
|
173
|
+
"**Risk:** #{task['risk']}",
|
|
174
|
+
"**Effort:** #{task['effort']}",
|
|
175
|
+
"**Source:** #{task['source']}"
|
|
176
|
+
].join(' · ')
|
|
177
|
+
|
|
178
|
+
<<~BLOCK.strip
|
|
179
|
+
#{MANAGED_START}
|
|
180
|
+
#{marker(task['id'])}
|
|
181
|
+
> Tracked from [`_data/backlog.yml`](../blob/main/_data/backlog.yml) by `scripts/sync-backlog.rb`.
|
|
182
|
+
> The report above is the author's; this block is auto-managed — edit the backlog, not here.
|
|
183
|
+
|
|
184
|
+
#{meta_row}#{roadmap ? " · **Roadmap:** v#{roadmap}" : ''}
|
|
185
|
+
|
|
186
|
+
## Acceptance criteria
|
|
187
|
+
|
|
188
|
+
#{accept}
|
|
189
|
+
|
|
190
|
+
Picked up by the IMPLEMENT routine; see [`docs/systems/continuous-evolution.md`](../blob/main/docs/systems/continuous-evolution.md).
|
|
191
|
+
#{MANAGED_END}
|
|
192
|
+
BLOCK
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Upsert the managed block into a (human-authored) issue body, preserving the
|
|
196
|
+
# author's text. Idempotent: replaces an existing block, else appends one.
|
|
197
|
+
def upsert_managed_block(body, task)
|
|
198
|
+
body = body.to_s
|
|
199
|
+
block = render_managed_block(task)
|
|
200
|
+
if body.include?(MANAGED_START) && body.include?(MANAGED_END)
|
|
201
|
+
body.sub(/#{Regexp.escape(MANAGED_START)}.*#{Regexp.escape(MANAGED_END)}/m, block)
|
|
202
|
+
else
|
|
203
|
+
"#{body.rstrip}\n\n#{block}\n"
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
136
207
|
def render_body(task)
|
|
137
208
|
accept = (task['acceptance'] || []).map { |a| "- [ ] #{a}" }.join("\n")
|
|
138
209
|
roadmap = task.dig('links', 'roadmap')
|
|
@@ -202,26 +273,52 @@ def ensure_labels(gh)
|
|
|
202
273
|
VALID_RISK.each { |r| gh.write(['label', 'create', "risk:#{r}", '--color', RISK_COLOR, '--force']) }
|
|
203
274
|
end
|
|
204
275
|
|
|
205
|
-
#
|
|
206
|
-
|
|
276
|
+
# Returns [index, link_state]:
|
|
277
|
+
# index — backlog id -> {number,state,labels,body,marker_id} for every
|
|
278
|
+
# marker-bearing agent-ready issue (the already-managed ones).
|
|
279
|
+
# link_state — issue number -> same record, for each issue referenced by a task
|
|
280
|
+
# `links.issue`. A human issue may carry neither the agent-ready
|
|
281
|
+
# label nor a marker yet, so it is fetched by number for adoption +
|
|
282
|
+
# provenance checks (is it already claimed by a foreign marker?).
|
|
283
|
+
def existing_issues(gh, link_numbers = [])
|
|
284
|
+
index = {}
|
|
285
|
+
link_state = {}
|
|
286
|
+
to_rec = lambda do |issue|
|
|
287
|
+
body = issue['body'].to_s
|
|
288
|
+
rec = {
|
|
289
|
+
'number' => issue['number'],
|
|
290
|
+
'state' => issue['state'].to_s.downcase,
|
|
291
|
+
'labels' => (issue['labels'] || []).map { |l| l['name'] },
|
|
292
|
+
'body' => body,
|
|
293
|
+
'marker_id' => body[/<!-- backlog-id: (T-\d+) -->/, 1]
|
|
294
|
+
}
|
|
295
|
+
index[rec['marker_id']] = rec if rec['marker_id']
|
|
296
|
+
rec
|
|
297
|
+
end
|
|
298
|
+
|
|
207
299
|
raw = gh.read(
|
|
208
300
|
['issue', 'list', '--label', 'agent-ready', '--state', 'all', '--limit', '500',
|
|
209
301
|
'--json', 'number,body,state,labels'],
|
|
210
302
|
default: '[]'
|
|
211
303
|
)
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
304
|
+
begin
|
|
305
|
+
JSON.parse(raw).each { |issue| to_rec.call(issue) }
|
|
306
|
+
rescue JSON::ParserError
|
|
307
|
+
# leave index empty; adoption/create still work off link_state
|
|
308
|
+
end
|
|
215
309
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
310
|
+
link_numbers.compact.map(&:to_i).uniq.each do |n|
|
|
311
|
+
raw1 = gh.read(['issue', 'view', n.to_s, '--json', 'number,body,state,labels'], default: '')
|
|
312
|
+
next if raw1.strip.empty?
|
|
313
|
+
|
|
314
|
+
begin
|
|
315
|
+
link_state[n] = to_rec.call(JSON.parse(raw1))
|
|
316
|
+
rescue JSON::ParserError
|
|
317
|
+
next
|
|
318
|
+
end
|
|
221
319
|
end
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
{}
|
|
320
|
+
|
|
321
|
+
[index, link_state]
|
|
225
322
|
end
|
|
226
323
|
|
|
227
324
|
# ---------------------------------------------------------------------------
|
|
@@ -241,29 +338,55 @@ end
|
|
|
241
338
|
|
|
242
339
|
def sync(data, gh)
|
|
243
340
|
ensure_labels(gh)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
341
|
+
tasks = data['tasks'] || []
|
|
342
|
+
link_numbers = tasks.map { |t| t.dig('links', 'issue') }.compact
|
|
343
|
+
index, link_state = existing_issues(gh, link_numbers)
|
|
344
|
+
created = adopted = updated = closed = 0
|
|
345
|
+
|
|
346
|
+
tasks.each do |task|
|
|
347
|
+
id = task['id']
|
|
348
|
+
title = task['title']
|
|
251
349
|
want_open = OPEN_STATUSES.include?(task['status'])
|
|
252
|
-
|
|
350
|
+
lnk = task.dig('links', 'issue')
|
|
351
|
+
issue = index[id] # already marker-tracked?
|
|
352
|
+
|
|
353
|
+
# Adoption: task points at an existing issue not yet marker-tracked. Adopt it
|
|
354
|
+
# (inject the managed block) instead of creating a duplicate — unless the
|
|
355
|
+
# issue already carries a DIFFERENT marker (foreign provenance → refuse).
|
|
356
|
+
if issue.nil? && lnk
|
|
357
|
+
ls = link_state[lnk.to_i]
|
|
358
|
+
if ls.nil?
|
|
359
|
+
warn "task #{id}: links.issue ##{lnk} not found — creating a new issue instead."
|
|
360
|
+
elsif ls['marker_id'] && ls['marker_id'] != id
|
|
361
|
+
warn "task #{id}: refusing to adopt ##{lnk} — already marked #{ls['marker_id']} (foreign). Skipping."
|
|
362
|
+
next
|
|
363
|
+
else
|
|
364
|
+
issue = ls.merge('adopt' => true)
|
|
365
|
+
end
|
|
366
|
+
end
|
|
253
367
|
|
|
254
368
|
if issue.nil?
|
|
255
369
|
next unless want_open # never create an issue for an already-done task
|
|
256
370
|
|
|
257
|
-
args = ['issue', 'create', '--title', title, '--body',
|
|
371
|
+
args = ['issue', 'create', '--title', title, '--body', render_body(task)]
|
|
258
372
|
managed_labels(task).each { |l| args.push('--label', l) }
|
|
259
373
|
created += 1 if gh.write(args)
|
|
260
374
|
next
|
|
261
375
|
end
|
|
262
376
|
|
|
263
377
|
number = issue['number'].to_s
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
378
|
+
if lnk
|
|
379
|
+
# Human-authored (adopted) issue: NEVER overwrite its title/body — only
|
|
380
|
+
# upsert the managed block (preserving the author's report) + labels.
|
|
381
|
+
gh.write(['issue', 'edit', number, '--body', upsert_managed_block(issue['body'], task)] +
|
|
382
|
+
label_args(managed_labels(task), issue['labels']))
|
|
383
|
+
issue['adopt'] ? (adopted += 1) : (updated += 1)
|
|
384
|
+
else
|
|
385
|
+
# Bot-created issue: fully sync-owned title + body.
|
|
386
|
+
gh.write(['issue', 'edit', number, '--title', title, '--body', render_body(task)] +
|
|
387
|
+
label_args(managed_labels(task), issue['labels']))
|
|
388
|
+
updated += 1
|
|
389
|
+
end
|
|
267
390
|
|
|
268
391
|
if want_open && issue['state'] != 'open'
|
|
269
392
|
gh.write(['issue', 'reopen', number])
|
|
@@ -273,7 +396,7 @@ def sync(data, gh)
|
|
|
273
396
|
end
|
|
274
397
|
end
|
|
275
398
|
|
|
276
|
-
puts "Backlog sync complete: #{created} created, #{updated} updated, #{closed} closed."
|
|
399
|
+
puts "Backlog sync complete: #{created} created, #{adopted} adopted, #{updated} updated, #{closed} closed."
|
|
277
400
|
0
|
|
278
401
|
end
|
|
279
402
|
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# sync-plan.rb
|
|
6
|
+
# =============================================================================
|
|
7
|
+
#
|
|
8
|
+
# Validates (and optionally mirrors) `_data/roadmap_plan.yml` — the ORDER-ONLY
|
|
9
|
+
# plan artifact produced by the /issue-plan committee. The plan sequences open
|
|
10
|
+
# backlog tasks into batches; it must NEVER re-encode anything the backlog owns
|
|
11
|
+
# (risk / priority / area / status / effort), which would create a second source
|
|
12
|
+
# of truth that drifts.
|
|
13
|
+
#
|
|
14
|
+
# ruby scripts/sync-plan.rb --check # validate the plan vs the backlog (CI/PR gate; stdlib only)
|
|
15
|
+
# ruby scripts/sync-plan.rb --dry-run # print the intended pinned-issue upsert
|
|
16
|
+
# ruby scripts/sync-plan.rb # upsert one pinned tracking issue via `gh`
|
|
17
|
+
#
|
|
18
|
+
# --check asserts:
|
|
19
|
+
# * every batch task id exists in _data/backlog.yml and is OPEN (not done),
|
|
20
|
+
# * batch `depends_on` references existing batch ids and the batch DAG is acyclic,
|
|
21
|
+
# * no plan entry carries a backlog-owned field (order-only invariant).
|
|
22
|
+
# =============================================================================
|
|
23
|
+
|
|
24
|
+
require 'yaml'
|
|
25
|
+
require 'date'
|
|
26
|
+
require 'json'
|
|
27
|
+
require 'optparse'
|
|
28
|
+
require 'open3'
|
|
29
|
+
|
|
30
|
+
ROOT = File.expand_path('..', __dir__)
|
|
31
|
+
PLAN_FILE = File.join(ROOT, '_data', 'roadmap_plan.yml')
|
|
32
|
+
BACKLOG = File.join(ROOT, '_data', 'backlog.yml')
|
|
33
|
+
|
|
34
|
+
PIN_MARKER = '<!-- roadmap-plan:pinned -->'
|
|
35
|
+
BACKLOG_OWNED = %w[risk priority area status effort source acceptance title].freeze
|
|
36
|
+
OPEN_TASK_STATUS = %w[open in-progress blocked].freeze
|
|
37
|
+
|
|
38
|
+
def load_yaml(path)
|
|
39
|
+
YAML.load_file(path, permitted_classes: [Date, Time])
|
|
40
|
+
rescue ArgumentError
|
|
41
|
+
YAML.safe_load(File.read(path, encoding: 'UTF-8'), permitted_classes: [Date, Time], aliases: false)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def backlog_status
|
|
45
|
+
tasks = (load_yaml(BACKLOG)['tasks'] || [])
|
|
46
|
+
tasks.each_with_object({}) { |t, h| h[t['id']] = t['status'] }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def validate(plan, statuses)
|
|
50
|
+
errors = []
|
|
51
|
+
unless plan.is_a?(Hash) && plan['batches'].is_a?(Array)
|
|
52
|
+
return ['plan must have a `batches:` list.']
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
batch_ids = plan['batches'].map { |b| b['id'] }.compact
|
|
56
|
+
seen = {}
|
|
57
|
+
batch_ids.each { |bid| seen[bid] ? (errors << "duplicate batch id #{bid}.") : (seen[bid] = true) }
|
|
58
|
+
|
|
59
|
+
plan['batches'].each_with_index do |batch, i|
|
|
60
|
+
where = batch['id'] ? "batch #{batch['id']}" : "batches[#{i}]"
|
|
61
|
+
errors << "#{where}: missing `id`." if batch['id'].to_s.empty?
|
|
62
|
+
|
|
63
|
+
# Order-only invariant: a batch (or its task entries) may not carry a field
|
|
64
|
+
# the backlog owns. Tasks must be bare id strings.
|
|
65
|
+
(BACKLOG_OWNED & batch.keys).each { |k| errors << "#{where}: must not carry backlog-owned field `#{k}`." }
|
|
66
|
+
Array(batch['tasks']).each do |t|
|
|
67
|
+
unless t.is_a?(String) && t =~ /\AT-\d{3,}\z/
|
|
68
|
+
errors << "#{where}: task entries must be bare T-NNN ids (got #{t.inspect})."
|
|
69
|
+
next
|
|
70
|
+
end
|
|
71
|
+
if !statuses.key?(t)
|
|
72
|
+
errors << "#{where}: task #{t} is not in the backlog."
|
|
73
|
+
elsif !OPEN_TASK_STATUS.include?(statuses[t])
|
|
74
|
+
errors << "#{where}: task #{t} is #{statuses[t]} (only open tasks may be planned)."
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
Array(batch['depends_on']).each do |dep|
|
|
79
|
+
errors << "#{where}: depends_on references unknown batch #{dep}." unless seen[dep]
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
errors.concat(cycle_errors(plan['batches']))
|
|
84
|
+
errors
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Kahn's algorithm over the batch DAG; any leftover nodes => a cycle.
|
|
88
|
+
def cycle_errors(batches)
|
|
89
|
+
deps = {}
|
|
90
|
+
batches.each { |b| deps[b['id']] = Array(b['depends_on']).dup }
|
|
91
|
+
indeg = Hash.new(0)
|
|
92
|
+
deps.each { |_n, ds| ds.each { |d| indeg[d] += 1 if deps.key?(d) } }
|
|
93
|
+
# Edge dep -> node (dep must precede node); compute order by repeatedly removing
|
|
94
|
+
# nodes whose deps are all satisfied.
|
|
95
|
+
remaining = deps.keys
|
|
96
|
+
progress = true
|
|
97
|
+
while progress
|
|
98
|
+
progress = false
|
|
99
|
+
ready = remaining.select { |n| deps[n].all? { |d| !remaining.include?(d) } }
|
|
100
|
+
unless ready.empty?
|
|
101
|
+
remaining -= ready
|
|
102
|
+
progress = true
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
remaining.empty? ? [] : ["batch dependency cycle among: #{remaining.sort.join(', ')}."]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def pinned_body(plan)
|
|
109
|
+
lines = ["#{PIN_MARKER}", '> Auto-managed by `scripts/sync-plan.rb` from `_data/roadmap_plan.yml`.',
|
|
110
|
+
'> Order only — risk/priority/area come from `_data/backlog.yml`.', '']
|
|
111
|
+
plan['batches'].each do |b|
|
|
112
|
+
lines << "### #{b['id']} — #{b['goal']}"
|
|
113
|
+
lines << "Tasks: #{Array(b['tasks']).join(', ')}"
|
|
114
|
+
lines << "Depends on: #{Array(b['depends_on']).join(', ')}" unless Array(b['depends_on']).empty?
|
|
115
|
+
lines << "Test framework: #{b['test_framework']}" if b['test_framework']
|
|
116
|
+
lines << ''
|
|
117
|
+
end
|
|
118
|
+
lines.join("\n").strip
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def upsert_pinned(plan, dry_run:)
|
|
122
|
+
title = '📋 Roadmap plan (auto-managed)'
|
|
123
|
+
body = pinned_body(plan)
|
|
124
|
+
found = `gh issue list --search #{PIN_MARKER.inspect}\\ in:body --state open --json number --jq '.[0].number' 2>/dev/null`.strip
|
|
125
|
+
args =
|
|
126
|
+
if found.empty?
|
|
127
|
+
['issue', 'create', '--title', title, '--body', body, '--label', 'agent-hold']
|
|
128
|
+
else
|
|
129
|
+
['issue', 'edit', found, '--title', title, '--body', body]
|
|
130
|
+
end
|
|
131
|
+
if dry_run
|
|
132
|
+
puts "DRY-RUN gh #{args.join(' ')}"
|
|
133
|
+
return
|
|
134
|
+
end
|
|
135
|
+
_o, err, st = Open3.capture3('gh', *args)
|
|
136
|
+
warn "gh failed: #{err}" unless st.success?
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def main
|
|
140
|
+
mode = :sync
|
|
141
|
+
OptionParser.new do |o|
|
|
142
|
+
o.on('--check') { mode = :check }
|
|
143
|
+
o.on('--dry-run') { mode = :dry_run }
|
|
144
|
+
end.parse!
|
|
145
|
+
|
|
146
|
+
return 0 unless File.exist?(PLAN_FILE) # no plan yet => nothing to validate
|
|
147
|
+
|
|
148
|
+
plan = load_yaml(PLAN_FILE)
|
|
149
|
+
errors = validate(plan, backlog_status)
|
|
150
|
+
unless errors.empty?
|
|
151
|
+
warn '✗ _data/roadmap_plan.yml failed validation:'
|
|
152
|
+
errors.each { |e| warn " - #{e}" }
|
|
153
|
+
return 1
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
if mode == :check
|
|
157
|
+
puts "✓ _data/roadmap_plan.yml is valid (#{plan['batches'].size} batches)."
|
|
158
|
+
return 0
|
|
159
|
+
end
|
|
160
|
+
upsert_pinned(plan, dry_run: mode == :dry_run)
|
|
161
|
+
0
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
exit main if $PROGRAM_NAME == __FILE__
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# sync-plan.sh
|
|
4
|
+
# =============================================================================
|
|
5
|
+
#
|
|
6
|
+
# Thin wrapper around scripts/sync-plan.rb.
|
|
7
|
+
#
|
|
8
|
+
# Validates (and optionally mirrors) `_data/roadmap_plan.yml` — the order-only
|
|
9
|
+
# plan artifact produced by the /issue-plan committee — against the backlog.
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# ./scripts/sync-plan.sh # upsert the pinned tracking issue via gh
|
|
13
|
+
# ./scripts/sync-plan.sh --check # validate plan vs backlog (CI/PR gate)
|
|
14
|
+
# ./scripts/sync-plan.sh --dry-run # print the intended gh call only
|
|
15
|
+
#
|
|
16
|
+
# =============================================================================
|
|
17
|
+
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
21
|
+
exec ruby "${SCRIPT_DIR}/sync-plan.rb" "$@"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jekyll-theme-zer0
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.22.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Amr Abdel
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-06-
|
|
11
|
+
date: 2026-06-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: jekyll
|
|
@@ -99,6 +99,8 @@ files:
|
|
|
99
99
|
- _data/prerequisites.yml
|
|
100
100
|
- _data/prompts.yml
|
|
101
101
|
- _data/roadmap.yml
|
|
102
|
+
- _data/roadmap_plan.yml
|
|
103
|
+
- _data/routing.yml
|
|
102
104
|
- _data/statistics_config.yml
|
|
103
105
|
- _data/theme-manifest.yml
|
|
104
106
|
- _data/theme_backgrounds.yml
|
|
@@ -374,6 +376,7 @@ files:
|
|
|
374
376
|
- scripts/analyze-commits.sh
|
|
375
377
|
- scripts/bin/audit-consumer
|
|
376
378
|
- scripts/bin/build
|
|
379
|
+
- scripts/bin/giscus-discussions
|
|
377
380
|
- scripts/bin/install
|
|
378
381
|
- scripts/bin/manifest
|
|
379
382
|
- scripts/bin/release
|
|
@@ -491,6 +494,8 @@ files:
|
|
|
491
494
|
- scripts/setup.sh
|
|
492
495
|
- scripts/sync-backlog.rb
|
|
493
496
|
- scripts/sync-backlog.sh
|
|
497
|
+
- scripts/sync-plan.rb
|
|
498
|
+
- scripts/sync-plan.sh
|
|
494
499
|
- scripts/test-auto-version.sh
|
|
495
500
|
- scripts/test-mermaid.sh
|
|
496
501
|
- scripts/test-notebook-conversion.sh
|