@zalom/plastic 2.0.0-alpha.9 → 2.0.1
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.
- package/PLASTIC.md +13 -136
- package/README.md +357 -133
- package/agents/plastic-enforcer.md +20 -15
- package/agents/plastic-executor.md +15 -4
- package/agents/plastic-node-research.md +30 -0
- package/agents/plastic-node-verify.md +28 -0
- package/agents/plastic-node-work.md +33 -0
- package/agents/{plastic-advisor.md → plastic-primary-advisor.md} +7 -9
- package/agents/{plastic-faux-advisor.md → plastic-secondary-advisor.md} +9 -12
- package/assets/plastic-logo.svg +1 -0
- package/bin/crap +4 -0
- package/bin/lib/context_budget.rb +43 -6
- package/bin/lib/skill_census.rb +839 -0
- package/bin/plastic +6 -0
- package/bin/plastic-skill-census +114 -0
- package/bin/test +24 -4
- package/bin/verify-change +345 -0
- package/config_asks.yml +4 -4
- package/deprecations.yml +1 -1
- package/{skills/agent-advisor/references → docs/help}/advisor-protocol.md +19 -24
- package/{skills/auto/references → docs/help}/agent-architecture.md +16 -14
- package/{skills/conventions/references → docs/help}/completion-and-done.md +15 -16
- package/docs/help/human-report-contract.md +152 -0
- package/{skills/conventions/references → docs/help}/knowledge-graph.md +9 -0
- package/{skills/conventions/references → docs/help}/locks-and-worktrees.md +11 -13
- package/{skills/conventions/references → docs/help}/maintenance-and-revisions.md +1 -1
- package/{skills/conventions/references → docs/help}/roadmaps.md +2 -2
- package/{skills/tutorial/references → docs/help}/track-1-guided.md +28 -47
- package/{skills/tutorial/references → docs/help}/track-2-auto.md +8 -8
- package/{skills/tutorial/references → docs/help}/track-3-projects-and-roadmaps.md +24 -17
- package/hooks/call-budget +4 -0
- package/hooks/hooks.json +24 -0
- package/hooks/message-display +55 -2
- package/hooks/session-start +5 -1
- package/hooks/statusline +32 -27
- package/hooks/stop +5 -0
- package/package.json +5 -3
- package/scripts/append-ledger +2 -1
- package/scripts/dashboard.rb +267 -16
- package/scripts/day-summary +2 -1
- package/scripts/doctor.rb +680 -39
- package/scripts/end-intent +153 -30
- package/scripts/exec-worktree +5 -5
- package/scripts/file-session-intent +2 -1
- package/scripts/graph-measure +249 -0
- package/scripts/hook-call-budget +222 -0
- package/scripts/hook-capture +24 -123
- package/scripts/hook-close +2 -1
- package/scripts/hook-message-display +7 -0
- package/scripts/hook-record +24 -16
- package/scripts/hook-savepoint +27 -3
- package/scripts/hook-session-start +359 -321
- package/scripts/hook-stop +58 -0
- package/scripts/index-projection +74 -0
- package/scripts/insight-append +17 -4
- package/scripts/install.rb +9 -7
- package/scripts/lib/action_graph_shim.rb +279 -0
- package/scripts/lib/active_delivery.rb +105 -0
- package/scripts/lib/agent_models.rb +63 -25
- package/scripts/lib/arm.rb +48 -83
- package/scripts/lib/atomic_write.rb +31 -0
- package/scripts/lib/backup.rb +65 -0
- package/scripts/lib/cli/command.rb +92 -0
- package/scripts/lib/cli/commands/auto.rb +18 -0
- package/scripts/lib/cli/commands/auto_brief.rb +44 -0
- package/scripts/lib/cli/commands/auto_lock.rb +99 -0
- package/scripts/lib/cli/commands/auto_report.rb +50 -0
- package/scripts/lib/cli/commands/auto_take.rb +31 -0
- package/scripts/lib/cli/commands/backup.rb +43 -0
- package/scripts/lib/cli/commands/checkout.rb +25 -0
- package/scripts/lib/cli/commands/continue.rb +66 -0
- package/scripts/lib/cli/commands/doctor.rb +81 -0
- package/scripts/lib/cli/commands/feedback.rb +40 -0
- package/scripts/lib/cli/commands/help.rb +69 -0
- package/scripts/lib/cli/commands/hook.rb +32 -0
- package/scripts/lib/cli/commands/index.rb +23 -0
- package/scripts/lib/cli/commands/install.rb +21 -0
- package/scripts/lib/cli/commands/installer_verb.rb +37 -0
- package/scripts/lib/cli/commands/intent.rb +19 -0
- package/scripts/lib/cli/commands/intent_answer.rb +37 -0
- package/scripts/lib/cli/commands/intent_command.rb +53 -0
- package/scripts/lib/cli/commands/intent_end.rb +62 -0
- package/scripts/lib/cli/commands/intent_new.rb +62 -0
- package/scripts/lib/cli/commands/intent_note.rb +43 -0
- package/scripts/lib/cli/commands/intent_rule.rb +36 -0
- package/scripts/lib/cli/commands/intent_show.rb +32 -0
- package/scripts/lib/cli/commands/intent_spec.rb +44 -0
- package/scripts/lib/cli/commands/intent_step.rb +71 -0
- package/scripts/lib/cli/commands/intent_verify.rb +26 -0
- package/scripts/lib/cli/commands/migrate.rb +16 -0
- package/scripts/lib/cli/commands/migrate_stores.rb +31 -0
- package/scripts/lib/cli/commands/next.rb +54 -0
- package/scripts/lib/cli/commands/project.rb +19 -0
- package/scripts/lib/cli/commands/project_links.rb +42 -0
- package/scripts/lib/cli/commands/project_list.rb +20 -0
- package/scripts/lib/cli/commands/project_new.rb +72 -0
- package/scripts/lib/cli/commands/query.rb +31 -0
- package/scripts/lib/cli/commands/render.rb +33 -0
- package/scripts/lib/cli/commands/roadmap.rb +19 -0
- package/scripts/lib/cli/commands/roadmap_check.rb +54 -0
- package/scripts/lib/cli/commands/roadmap_log.rb +54 -0
- package/scripts/lib/cli/commands/roadmap_migrate.rb +53 -0
- package/scripts/lib/cli/commands/roadmap_next.rb +70 -0
- package/scripts/lib/cli/commands/roadmap_show.rb +45 -0
- package/scripts/lib/cli/commands/rollback.rb +20 -0
- package/scripts/lib/cli/commands/search.rb +60 -0
- package/scripts/lib/cli/commands/session.rb +18 -0
- package/scripts/lib/cli/commands/session_commit.rb +42 -0
- package/scripts/lib/cli/commands/session_handoff.rb +34 -0
- package/scripts/lib/cli/commands/session_summary.rb +35 -0
- package/scripts/lib/cli/commands/status.rb +68 -0
- package/scripts/lib/cli/commands/subcommand_list.rb +36 -0
- package/scripts/lib/cli/commands/sync.rb +46 -0
- package/scripts/lib/cli/commands/uninstall.rb +20 -0
- package/scripts/lib/cli/commands/update.rb +20 -0
- package/scripts/lib/cli/commands/version.rb +53 -0
- package/scripts/lib/cli/frontier.rb +86 -0
- package/scripts/lib/cli/intent_progress.rb +36 -0
- package/scripts/lib/cli/legacy.rb +80 -0
- package/scripts/lib/cli/output.rb +137 -0
- package/scripts/lib/cli/scope.rb +134 -0
- package/scripts/lib/cli/table.rb +65 -0
- package/scripts/lib/cli.rb +94 -0
- package/scripts/lib/codex_adapter.rb +198 -0
- package/scripts/lib/compact_instructions.rb +13 -5
- package/scripts/lib/core_integrity.rb +71 -0
- package/scripts/lib/dashboard_screen.rb +40 -0
- package/scripts/lib/data_boundary.rb +132 -0
- package/scripts/lib/day_summary.rb +23 -14
- package/scripts/lib/doctor_core.rb +114 -38
- package/scripts/lib/doctor_session_ledger.rb +5 -53
- package/scripts/lib/engine_permissions.rb +88 -0
- package/scripts/lib/exec_worktree.rb +24 -21
- package/scripts/lib/feedback_report.rb +1 -1
- package/scripts/lib/graph_edges.rb +137 -0
- package/scripts/lib/graph_file.rb +246 -0
- package/scripts/lib/graph_measure.rb +645 -0
- package/scripts/lib/graph_measure_budget.rb +409 -0
- package/scripts/lib/graph_measure_cohorts.rb +487 -0
- package/scripts/lib/graph_measure_models.rb +413 -0
- package/scripts/lib/graph_measure_report.rb +532 -0
- package/scripts/lib/graph_tree.rb +98 -0
- package/scripts/lib/guarded_append.rb +155 -0
- package/scripts/lib/handoff.rb +40 -13
- package/scripts/lib/harness_adapter.rb +184 -0
- package/scripts/lib/hook_registry.rb +27 -3
- package/scripts/lib/hook_replay.rb +229 -0
- package/scripts/lib/index_entry.rb +62 -0
- package/scripts/lib/index_projection.rb +201 -0
- package/scripts/lib/insights.rb +1 -1
- package/scripts/lib/installer_core.rb +531 -71
- package/scripts/lib/intent_screen.rb +4 -4
- package/scripts/lib/intent_screen_ansi.rb +73 -12
- package/scripts/lib/intent_validator.rb +2 -2
- package/scripts/lib/lock.rb +10 -11
- package/scripts/lib/message_display.rb +350 -54
- package/scripts/lib/meter_watch.rb +185 -0
- package/scripts/lib/node_file.rb +234 -0
- package/scripts/lib/node_ids.rb +99 -0
- package/scripts/lib/node_input.rb +913 -0
- package/scripts/lib/node_input_compatibility.rb +62 -0
- package/scripts/lib/node_ledger.rb +386 -0
- package/scripts/lib/node_progress.rb +153 -0
- package/scripts/lib/node_return.rb +204 -0
- package/scripts/lib/node_worktree.rb +337 -0
- package/scripts/lib/outcome_report.rb +440 -0
- package/scripts/lib/preflight.rb +4 -6
- package/scripts/lib/project_config.rb +46 -0
- package/scripts/lib/project_validator.rb +3 -2
- package/scripts/lib/qmd_sync.rb +8 -7
- package/scripts/lib/ready_set.rb +462 -0
- package/scripts/lib/reference_archive.rb +45 -0
- package/scripts/lib/release_guard.rb +18 -0
- package/scripts/lib/report_screen.rb +1371 -47
- package/scripts/lib/rlm/corpus.rb +13 -0
- package/scripts/lib/rlm/probe.rb +29 -0
- package/scripts/lib/rlm/query.rb +22 -0
- package/scripts/lib/roadmap_graph.rb +210 -0
- package/scripts/lib/roadmap_migration.rb +95 -0
- package/scripts/lib/roadmap_queue.rb +158 -8
- package/scripts/lib/roadmap_render.rb +150 -0
- package/scripts/lib/roadmap_savepoint.rb +64 -14
- package/scripts/lib/runner_absorb.rb +703 -0
- package/scripts/lib/runner_answer.rb +206 -0
- package/scripts/lib/runner_core.rb +194 -0
- package/scripts/lib/runner_dispatch.rb +525 -0
- package/scripts/lib/runner_policy.rb +191 -0
- package/scripts/lib/runner_proposals.rb +275 -0
- package/scripts/lib/runner_rewind.rb +201 -0
- package/scripts/lib/runner_sweep.rb +231 -0
- package/scripts/lib/runner_until_empty.rb +252 -0
- package/scripts/lib/runner_watch.rb +389 -0
- package/scripts/lib/savepoint.rb +141 -19
- package/scripts/lib/scaffold_intent.rb +6 -3
- package/scripts/lib/screen_paint.rb +365 -28
- package/scripts/lib/screens/dashboard.rb +20 -0
- package/scripts/lib/screens/plan.rb +18 -0
- package/scripts/lib/screens/roadmap.rb +15 -0
- package/scripts/lib/search_index.rb +55 -0
- package/scripts/lib/session_close.rb +30 -28
- package/scripts/lib/session_git.rb +25 -18
- package/scripts/lib/session_ledger.rb +48 -4
- package/scripts/lib/session_usage.rb +190 -0
- package/scripts/lib/sqlite.rb +22 -0
- package/scripts/lib/stop_gate.rb +95 -0
- package/scripts/lib/store_discovery.rb +7 -6
- package/scripts/lib/store_layout.rb +54 -0
- package/scripts/lib/store_provisioning.rb +2 -1
- package/scripts/lib/store_sync.rb +85 -0
- package/scripts/lib/stores_move.rb +93 -0
- package/scripts/lib/verify_intent.rb +36 -8
- package/scripts/lib/version_number.rb +48 -0
- package/scripts/lib/work_graph.rb +59 -0
- package/scripts/lib/work_graph_validator.rb +201 -0
- package/scripts/lib/worktree.rb +27 -32
- package/scripts/lib/worktree_sweep.rb +6 -5
- package/scripts/link-suggest +2 -1
- package/scripts/meter-watch +57 -0
- package/scripts/migrate-to-global +1 -1
- package/scripts/new-intent +4 -13
- package/scripts/node-input +92 -0
- package/scripts/node-run +225 -0
- package/scripts/node-transition +291 -0
- package/scripts/outcome-report +74 -0
- package/scripts/plastic-lock +42 -33
- package/scripts/promote-session-item +3 -2
- package/scripts/read-config +53 -9
- package/scripts/ready-set +126 -0
- package/scripts/release-check +123 -0
- package/scripts/report-screen +177 -16
- package/scripts/roadmap-graph +119 -0
- package/scripts/roadmap-savepoint +7 -0
- package/scripts/runner +581 -0
- package/scripts/savepoint-note +11 -9
- package/scripts/session-commit +2 -1
- package/scripts/session-usage +56 -0
- package/scripts/skill-lint +115 -6
- package/scripts/spawn-preamble +2 -2
- package/scripts/update.rb +25 -4
- package/scripts/validate-work-graph +39 -0
- package/scripts/verify-intent +3 -2
- package/scripts/write-handoff +2 -1
- package/templates/agents.md +7 -7
- package/templates/config.yml +16 -9
- package/templates/dashboard-screen.md +22 -0
- package/templates/display-fixture.md +21 -0
- package/templates/graph.md +16 -0
- package/templates/index.md +1 -1
- package/templates/intent-screen.md +1 -1
- package/templates/node-decision.md +11 -0
- package/templates/node-research.md +13 -0
- package/templates/node-verify.md +13 -0
- package/templates/node-work.md +22 -0
- package/templates/outcome.md +8 -3
- package/templates/project.yml +1 -1
- package/templates/render.css +10 -0
- package/templates/report-plan.md +15 -0
- package/templates/report-roadmap-delivered.md +10 -0
- package/templates/report-roadmap-plan.md +9 -0
- package/templates/report-roadmap-state.md +9 -0
- package/templates/report-state.md +1 -1
- package/templates/roadmap.md +13 -0
- package/bin/plastic.js +0 -70
- package/scripts/lib/bridge.rb +0 -116
- package/skills/agent-advisor/SKILL.md +0 -92
- package/skills/auto/SKILL.md +0 -296
- package/skills/auto/evals/evals.json +0 -255
- package/skills/auto/references/end-tail.md +0 -66
- package/skills/auto/references/human-report-contract.md +0 -78
- package/skills/conventions/SKILL.md +0 -29
- package/skills/dashboard/SKILL.md +0 -169
- package/skills/dashboard/evals/evals.json +0 -38
- package/skills/dashboard/references/classification.md +0 -22
- package/skills/dashboard/templates/dashboard-global.md +0 -20
- package/skills/dashboard/templates/dashboard-project.md +0 -19
- package/skills/direct/SKILL.md +0 -66
- package/skills/direct/references/request-signals.md +0 -59
- package/skills/doctor/SKILL.md +0 -299
- package/skills/doctor/report.md +0 -102
- package/skills/feedback/SKILL.md +0 -98
- package/skills/feedback/references/transport-and-privacy.md +0 -65
- package/skills/feedback/report.md +0 -36
- package/skills/install/SKILL.md +0 -217
- package/skills/intent-continuing/SKILL.md +0 -154
- package/skills/intent-continuing/references/board-fill.md +0 -43
- package/skills/intent-continuing/references/boarding-matrix.md +0 -34
- package/skills/intent-continuing/references/context-management.md +0 -28
- package/skills/intent-continuing/references/liveness-ranking.md +0 -57
- package/skills/intent-creating/SKILL.md +0 -164
- package/skills/intent-creating/evals/evals.json +0 -72
- package/skills/intent-creating/references/lifecycle.md +0 -81
- package/skills/intent-creating/references/wikilinks.md +0 -8
- package/skills/intent-ending/SKILL.md +0 -176
- package/skills/intent-ending/evals/evals.json +0 -74
- package/skills/intent-executing/SKILL.md +0 -170
- package/skills/intent-executing/evals/evals.json +0 -66
- package/skills/intent-executing/implementer-prompt.md +0 -42
- package/skills/intent-executing/spec-reviewer-prompt.md +0 -27
- package/skills/intent-speccing/SKILL.md +0 -130
- package/skills/intent-speccing/evals/evals.json +0 -126
- package/skills/intent-speccing/references/design-principles.md +0 -44
- package/skills/intent-speccing/references/per-section-fill-rules.md +0 -92
- package/skills/intent-speccing/references/self-verify-checklist.md +0 -37
- package/skills/project-creating/SKILL.md +0 -162
- package/skills/project-creating/references/hubs-projects.md +0 -55
- package/skills/project-creating/references/project-scaffolding.md +0 -97
- package/skills/releasing/SKILL.md +0 -337
- package/skills/releasing/references/deprecations.md +0 -60
- package/skills/releasing/references/promotion-and-tagging.md +0 -66
- package/skills/releasing/references/release-lines.md +0 -105
- package/skills/roadmap/SKILL.md +0 -64
- package/skills/roadmap/references/file-format.md +0 -124
- package/skills/roadmap/references/operations.md +0 -112
- package/skills/rollback/SKILL.md +0 -91
- package/skills/tutorial/SKILL.md +0 -65
- package/skills/tutorial/evals/evals.json +0 -186
- package/skills/uninstall/SKILL.md +0 -75
- package/skills/update/SKILL.md +0 -126
- /package/{skills/auto/references → docs/help}/agent-report-contract.md +0 -0
- /package/{skills/intent-executing → docs/help}/code-quality-reviewer-prompt.md +0 -0
- /package/{skills/conventions/references → docs/help}/lifecycle-and-savepoints.md +0 -0
- /package/{skills/intent-executing → docs/help}/plan-reviewer-prompt.md +0 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: UTF-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# ready-set - the CLI over ReadySet (intent 336, G3). No computed field this
|
|
6
|
+
# intent adds ships without a script that reads it (327 D18, C19): prints the
|
|
7
|
+
# ready order, the blockers for every unready node, the batches, and every
|
|
8
|
+
# maximal-length critical path; emits JSON for a caller like the runner (G7).
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# ready-set <intent_dir> [--json] [--ranker finish-first|critical-path]
|
|
12
|
+
#
|
|
13
|
+
# Exit codes (matching scripts/validate-work-graph's shape):
|
|
14
|
+
# 0 - printed (or emitted as JSON)
|
|
15
|
+
# 1 - the graph is invalid (missing, cyclic, or a malformed node file)
|
|
16
|
+
# 2 - usage: not an intent directory, or an unknown --ranker value
|
|
17
|
+
|
|
18
|
+
require "json"
|
|
19
|
+
require_relative "lib/ready_set"
|
|
20
|
+
require_relative "lib/savepoint"
|
|
21
|
+
|
|
22
|
+
module ReadySetCli
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
RANKERS = {
|
|
26
|
+
"finish-first" => -> { ReadySet::FinishFirstRanker.new },
|
|
27
|
+
"critical-path" => -> { ReadySet::CriticalPathRanker.new },
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
def usage
|
|
31
|
+
warn "Usage: ready-set <intent_dir> [--json] [--ranker #{RANKERS.keys.join('|')}]"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def intent_directory?(dir)
|
|
35
|
+
dir && File.directory?(dir) && File.exist?(Savepoint.intent_file(dir))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def main(argv)
|
|
39
|
+
args = argv.dup
|
|
40
|
+
json = !!args.delete("--json")
|
|
41
|
+
|
|
42
|
+
ranker_name = nil
|
|
43
|
+
if (i = args.index("--ranker"))
|
|
44
|
+
ranker_name = args[i + 1]
|
|
45
|
+
args.slice!(i, 2)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
intent_dir_arg = args.shift
|
|
49
|
+
unless intent_directory?(intent_dir_arg && File.expand_path(intent_dir_arg))
|
|
50
|
+
warn "ready-set: #{intent_dir_arg.inspect} is not an intent directory"
|
|
51
|
+
usage
|
|
52
|
+
return 2
|
|
53
|
+
end
|
|
54
|
+
intent_dir = File.expand_path(intent_dir_arg)
|
|
55
|
+
|
|
56
|
+
ranker = nil
|
|
57
|
+
if ranker_name
|
|
58
|
+
factory = RANKERS[ranker_name]
|
|
59
|
+
unless factory
|
|
60
|
+
warn "ready-set: unknown ranker #{ranker_name.inspect} (use #{RANKERS.keys.join(', ')})"
|
|
61
|
+
return 2
|
|
62
|
+
end
|
|
63
|
+
ranker = factory.call
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
analysis = ReadySet.analyze(intent_dir, ranker: ranker)
|
|
67
|
+
unless analysis[:ok]
|
|
68
|
+
warn "ready-set: #{analysis[:errors].join('; ')}"
|
|
69
|
+
return 1
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
if json
|
|
73
|
+
puts JSON.pretty_generate(json_payload(analysis))
|
|
74
|
+
else
|
|
75
|
+
print_report(analysis)
|
|
76
|
+
end
|
|
77
|
+
0
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def json_payload(analysis)
|
|
81
|
+
unready = analysis[:nodes].reject { |_, v| v[:ready] }
|
|
82
|
+
{
|
|
83
|
+
"ready" => analysis[:ranked_ready].map { |r| r[:id] },
|
|
84
|
+
"ranker" => analysis[:ranker_name],
|
|
85
|
+
"batches" => analysis[:batches],
|
|
86
|
+
"critical_path" => analysis[:critical_path],
|
|
87
|
+
"paths" => analysis[:paths],
|
|
88
|
+
"hops" => analysis[:hops],
|
|
89
|
+
"max_paths_total" => analysis[:max_paths_total],
|
|
90
|
+
"blockers" => unready.transform_values { |v| v[:blockers] },
|
|
91
|
+
"errors" => analysis[:errors],
|
|
92
|
+
}
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def print_report(analysis)
|
|
96
|
+
unready = analysis[:nodes].reject { |_, v| v[:ready] }
|
|
97
|
+
|
|
98
|
+
puts "Ranker: #{analysis[:ranker_name]}"
|
|
99
|
+
puts
|
|
100
|
+
puts "Ready (#{analysis[:ranked_ready].length}):"
|
|
101
|
+
if analysis[:ranked_ready].empty?
|
|
102
|
+
puts " (none)"
|
|
103
|
+
else
|
|
104
|
+
analysis[:ranked_ready].each { |r| puts " #{r[:id]}" }
|
|
105
|
+
end
|
|
106
|
+
puts
|
|
107
|
+
puts "Blockers:"
|
|
108
|
+
if unready.empty?
|
|
109
|
+
puts " (none)"
|
|
110
|
+
else
|
|
111
|
+
unready.each do |id, view|
|
|
112
|
+
puts " #{id}:"
|
|
113
|
+
view[:blockers].each { |b| puts " - #{b}" }
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
puts
|
|
117
|
+
puts "Batches:"
|
|
118
|
+
analysis[:batches].each_with_index { |batch, i| puts " #{i + 1}: #{batch.join(', ')}" }
|
|
119
|
+
puts
|
|
120
|
+
puts "Critical path (#{analysis[:hops]} hops): #{(analysis[:critical_path] || []).join(' > ')}"
|
|
121
|
+
puts "Every maximal-length path (#{analysis[:max_paths_total]} total, #{analysis[:paths].length} shown):"
|
|
122
|
+
analysis[:paths].each { |p| puts " #{p.join(' > ')}" }
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
exit(ReadySetCli.main(ARGV)) if $PROGRAM_NAME == __FILE__
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: UTF-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# release-check - deterministic CLI over ReleaseGuard (intent 347).
|
|
6
|
+
#
|
|
7
|
+
# Guards a tagged publish before the workflow ever reaches `npm publish`:
|
|
8
|
+
# the pushed tag must equal `v` plus the version in package.json, the three
|
|
9
|
+
# repo version files must agree, and the runner's npm must meet the 11.5.1
|
|
10
|
+
# floor npm requires for OIDC trusted publishing. Writes the derived
|
|
11
|
+
# dist-tag and version to $GITHUB_OUTPUT, so the alpha/beta/latest rule has
|
|
12
|
+
# exactly one implementation, ReleaseGuard.dist_tag, shared with the test
|
|
13
|
+
# suite.
|
|
14
|
+
#
|
|
15
|
+
# Usage:
|
|
16
|
+
# release-check (--tag <tag> | --branch <branch>) --npm-version <version>
|
|
17
|
+
# [--github-output <path>] [--root <dir>]
|
|
18
|
+
#
|
|
19
|
+
# Exit codes: 0 (clean), 1 (violations; reported on stderr, one per line,
|
|
20
|
+
# each naming the specific thing that is wrong), 2 (usage).
|
|
21
|
+
|
|
22
|
+
require "json"
|
|
23
|
+
|
|
24
|
+
require_relative "lib/release_guard"
|
|
25
|
+
|
|
26
|
+
NPM_FLOOR = [11, 5, 1].freeze
|
|
27
|
+
KNOWN_FLAGS = %w[--tag --branch --npm-version --github-output --root].freeze
|
|
28
|
+
|
|
29
|
+
def usage
|
|
30
|
+
warn "usage: release-check (--tag <tag> | --branch <branch>) --npm-version <version> [--github-output <path>] [--root <dir>]"
|
|
31
|
+
exit 2
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def parse_args(argv)
|
|
35
|
+
opts = {}
|
|
36
|
+
i = 0
|
|
37
|
+
while i < argv.length
|
|
38
|
+
flag = argv[i]
|
|
39
|
+
usage unless KNOWN_FLAGS.include?(flag)
|
|
40
|
+
|
|
41
|
+
value = argv[i + 1]
|
|
42
|
+
usage if value.nil?
|
|
43
|
+
|
|
44
|
+
opts[flag] = value
|
|
45
|
+
i += 2
|
|
46
|
+
end
|
|
47
|
+
opts
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def read_version(path)
|
|
51
|
+
JSON.parse(File.read(path))["version"]
|
|
52
|
+
rescue Errno::ENOENT, JSON::ParserError
|
|
53
|
+
nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
opts = parse_args(ARGV)
|
|
57
|
+
usage unless (opts["--tag"] || opts["--branch"]) && opts["--npm-version"]
|
|
58
|
+
|
|
59
|
+
root = opts["--root"] ? File.expand_path(opts["--root"]) : File.expand_path("..", __dir__)
|
|
60
|
+
package_json_path = File.join(root, "package.json")
|
|
61
|
+
plugin_json_path = File.join(root, ".claude-plugin", "plugin.json")
|
|
62
|
+
marketplace_json_path = File.join(root, ".claude-plugin", "marketplace.json")
|
|
63
|
+
|
|
64
|
+
violations = []
|
|
65
|
+
|
|
66
|
+
canonical_version = read_version(package_json_path)
|
|
67
|
+
violations << "could not read a version from #{package_json_path}" if canonical_version.nil?
|
|
68
|
+
|
|
69
|
+
branch = opts["--branch"]
|
|
70
|
+
dist_tag = nil
|
|
71
|
+
|
|
72
|
+
if canonical_version
|
|
73
|
+
expected_tag = "v#{canonical_version}"
|
|
74
|
+
tag = opts.fetch("--tag", expected_tag).sub(%r{\Arefs/tags/}, "")
|
|
75
|
+
if tag != expected_tag
|
|
76
|
+
violations << "tag #{tag} does not match package.json version #{canonical_version} (expected #{expected_tag})"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
dist_tag = ReleaseGuard.dist_tag(canonical_version)
|
|
80
|
+
if branch && ReleaseGuard::CHANNELS[branch] != dist_tag
|
|
81
|
+
violations << "branch #{branch} publishes #{ReleaseGuard::CHANNELS[branch] || "nothing"}, and version #{canonical_version} is #{dist_tag}"
|
|
82
|
+
end
|
|
83
|
+
stable = dist_tag == "latest"
|
|
84
|
+
|
|
85
|
+
result = ReleaseGuard.check(
|
|
86
|
+
package_json: package_json_path,
|
|
87
|
+
plugin_json: plugin_json_path,
|
|
88
|
+
marketplace_json: marketplace_json_path,
|
|
89
|
+
stable: stable
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
unless result.ok?
|
|
93
|
+
result.mismatches.each do |file|
|
|
94
|
+
violations << "version file #{file} does not agree with package.json (#{canonical_version})"
|
|
95
|
+
end
|
|
96
|
+
if stable && result.prerelease_suffix
|
|
97
|
+
violations << "stable cut #{canonical_version} carries the pre-release suffix -#{result.prerelease_suffix}"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
npm_version = opts["--npm-version"].to_s.strip
|
|
103
|
+
if npm_version.empty? || !npm_version.match?(/\A\d+(\.\d+)*\z/)
|
|
104
|
+
violations << "npm --version reported #{opts["--npm-version"].inspect}, which is empty or not numeric"
|
|
105
|
+
elsif (npm_version.split(".").map(&:to_i) <=> NPM_FLOOR) < 0
|
|
106
|
+
violations << "npm #{npm_version} is below the 11.5.1 floor OIDC trusted publishing requires"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
if violations.empty?
|
|
110
|
+
if opts["--github-output"]
|
|
111
|
+
File.open(opts["--github-output"], "a") do |f|
|
|
112
|
+
f.puts "version=#{canonical_version}"
|
|
113
|
+
f.puts "dist_tag=#{dist_tag}"
|
|
114
|
+
f.puts "tag=#{tag}"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
puts "OK: tag #{tag}, version #{canonical_version}, dist-tag #{dist_tag}"
|
|
118
|
+
exit 0
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
warn "VIOLATIONS:"
|
|
122
|
+
violations.each { |v| warn "- #{v}" }
|
|
123
|
+
exit 1
|
package/scripts/report-screen
CHANGED
|
@@ -2,15 +2,22 @@
|
|
|
2
2
|
# encoding: UTF-8
|
|
3
3
|
# frozen_string_literal: true
|
|
4
4
|
|
|
5
|
-
# report-screen - the
|
|
6
|
-
# state, post-delivery delivered, and delay
|
|
5
|
+
# report-screen - the pre-delivery plan (intent 331b), the four delivery
|
|
6
|
+
# reports: mid-delivery state, post-delivery delivered, and delay (intent
|
|
7
|
+
# 317), plus session, the whole of one session's delivered work followed by
|
|
8
|
+
# the roster (intent 330). Each fills from the record via
|
|
7
9
|
# scripts/lib/report_screen.rb; no number here is written by eye.
|
|
8
10
|
#
|
|
9
11
|
# Usage:
|
|
12
|
+
# report-screen plan <intent_dir> [--ansi]
|
|
10
13
|
# report-screen state <intent_dir> [--changed "<text>"] [--ansi]
|
|
11
14
|
# report-screen state --all <store_root> [--changed "<text>"] [--ansi]
|
|
12
|
-
# report-screen delivered <intent_dir> [--ansi]
|
|
15
|
+
# report-screen delivered <intent_dir> [--ansi] [--repo <dir>]
|
|
13
16
|
# report-screen delay <intent_dir> [--ansi]
|
|
17
|
+
# report-screen session <tier_root> [--session <id>] [--since <iso>]
|
|
18
|
+
# [--ledger-root <dir>] [--ansi]
|
|
19
|
+
# report-screen roadmap <roadmap.md> plan|state|delivered [--ansi] [--store-root <dir>]
|
|
20
|
+
# report-screen archive <store_root> [--ansi]
|
|
14
21
|
#
|
|
15
22
|
# --ansi delegates to ScreenPaint (intent 317a, D1), the parser/re-layouter
|
|
16
23
|
# in the shared TUI core. Selection is by capability, never by harness:
|
|
@@ -23,24 +30,93 @@
|
|
|
23
30
|
# 2 - usage error, or the path/store is not what the verb expects (one line
|
|
24
31
|
# on stderr, stdout empty)
|
|
25
32
|
|
|
33
|
+
require "yaml"
|
|
34
|
+
require "shellwords"
|
|
26
35
|
require_relative "lib/report_screen"
|
|
27
36
|
require_relative "lib/intent_screen"
|
|
28
37
|
require_relative "lib/screen_paint"
|
|
38
|
+
require_relative "lib/session_ledger"
|
|
39
|
+
|
|
40
|
+
# Intent 331a (D6/R8): every caller-added screen kind file registers itself
|
|
41
|
+
# on load (ScreenPaint.register), so this glob is the ONLY wiring a new kind
|
|
42
|
+
# needs - no edit here, no edit to screen_paint.rb. Sorted for a
|
|
43
|
+
# deterministic load order; tolerates the directory being absent or empty
|
|
44
|
+
# (Dir.glob answers [] either way), which is this repo's own state today.
|
|
45
|
+
Dir.glob(File.join(__dir__, "lib", "screens", "*.rb")).sort.each { |f| require_relative f }
|
|
29
46
|
|
|
30
47
|
def usage_abort(message)
|
|
31
48
|
warn "report-screen: #{message}"
|
|
32
49
|
exit 2
|
|
33
50
|
end
|
|
34
51
|
|
|
35
|
-
# The one place allowed to shell out (D2/D20): resolves the
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
52
|
+
# The one place allowed to shell out (D2/D20): resolves the tag that CONTAINS
|
|
53
|
+
# the intent's merge commit, in the project's own repository, so the version
|
|
54
|
+
# on the delivered screen is the release that merge shipped in. The
|
|
55
|
+
# repository comes from --repo, else from projects.yml beside the store (the
|
|
56
|
+
# installed layout, <home>/projects/<slug>/store/<id>), else the repository
|
|
57
|
+
# this script lives in (the in-repo layout, `include_self: true`) - unless
|
|
58
|
+
# `include_self: false` (the branch reader, D23), in which case a global
|
|
59
|
+
# intent's repo never falls back to the script's own repository, since that
|
|
60
|
+
# is not the intent's repository at all. No merge sha, no repository, or no
|
|
61
|
+
# containing tag all answer nil, and the screen says "not recorded" rather
|
|
62
|
+
# than guessing from HEAD (D14). The pure module never reads git.
|
|
63
|
+
def resolve_repo(intent_dir, explicit_repo, script_dir, include_self: true)
|
|
64
|
+
candidates = []
|
|
65
|
+
candidates << File.expand_path(explicit_repo) if explicit_repo
|
|
66
|
+
if (m = intent_dir.match(%r{\A(.*)/(?:projects|stores)/([^/]+)/store/[^/]+\z}))
|
|
67
|
+
home, slug = m[1], m[2]
|
|
68
|
+
projects_yml = File.join(home, "projects.yml")
|
|
69
|
+
if File.exist?(projects_yml)
|
|
70
|
+
data = begin
|
|
71
|
+
YAML.safe_load(File.read(projects_yml))
|
|
72
|
+
rescue StandardError
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
entry = data.is_a?(Hash) && data["projects"].is_a?(Hash) ? data["projects"][slug] : nil
|
|
76
|
+
path = entry.is_a?(Hash) ? entry["path"].to_s : ""
|
|
77
|
+
candidates << File.expand_path(path) unless path.empty?
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
candidates << File.expand_path("..", script_dir) if include_self
|
|
81
|
+
candidates.find { |c| File.exist?(File.join(c, ".git")) }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def git_tag_reader(explicit_repo:, script_dir:)
|
|
85
|
+
lambda do |intent_dir|
|
|
86
|
+
sha = ReportScreen.merge_sha(intent_dir)
|
|
87
|
+
return nil unless sha
|
|
88
|
+
repo = resolve_repo(intent_dir, explicit_repo, script_dir)
|
|
89
|
+
return nil unless repo
|
|
90
|
+
tags = `git -C #{repo.shellescape} tag --contains #{sha.shellescape} 2>/dev/null`
|
|
91
|
+
versions = tags.lines.map(&:strip).reject(&:empty?).filter_map do |tag|
|
|
92
|
+
v = tag.sub(/\Av/, "")
|
|
93
|
+
Gem::Version.correct?(v) ? [Gem::Version.new(v), tag] : nil
|
|
94
|
+
end
|
|
95
|
+
versions.min_by(&:first)&.last
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# D9/D23: the injected branch reader. flow_base (a pure YAML read, never
|
|
100
|
+
# git) wins when the intent's project names a flow base; otherwise the
|
|
101
|
+
# repository's remote HEAD, else whichever of main/master exists. Resolves
|
|
102
|
+
# the repo with `include_self: false` (D23): a global-store intent must
|
|
103
|
+
# never fall back to the script's OWN repository (~/.plastic itself, on
|
|
104
|
+
# "main" in the installed layout) and print a confident, wrong branch.
|
|
105
|
+
def git_branch_reader(explicit_repo:, script_dir:)
|
|
106
|
+
lambda do |intent_dir|
|
|
107
|
+
flow = ReportScreen.flow_base(intent_dir)
|
|
108
|
+
next flow if flow && !flow.to_s.empty?
|
|
109
|
+
|
|
110
|
+
repo = resolve_repo(intent_dir, explicit_repo, script_dir, include_self: false)
|
|
111
|
+
next nil unless repo
|
|
112
|
+
|
|
113
|
+
branch = `git -C #{repo.shellescape} symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null`.strip
|
|
114
|
+
branch = branch.sub(%r{\Aorigin/}, "")
|
|
115
|
+
next branch unless branch.empty?
|
|
116
|
+
|
|
117
|
+
%w[main master].find do |b|
|
|
118
|
+
system("git", "-C", repo, "rev-parse", "--verify", "--quiet", b, out: File::NULL, err: File::NULL)
|
|
119
|
+
end
|
|
44
120
|
end
|
|
45
121
|
end
|
|
46
122
|
|
|
@@ -49,6 +125,11 @@ verb = args.shift
|
|
|
49
125
|
changed = nil
|
|
50
126
|
ansi = false
|
|
51
127
|
template_path = nil
|
|
128
|
+
repo_flag = nil
|
|
129
|
+
session_flag = nil
|
|
130
|
+
since_flag = nil
|
|
131
|
+
ledger_root_flag = nil
|
|
132
|
+
store_root_flag = nil
|
|
52
133
|
positional = []
|
|
53
134
|
|
|
54
135
|
while (arg = args.shift)
|
|
@@ -60,15 +141,25 @@ while (arg = args.shift)
|
|
|
60
141
|
ansi = true
|
|
61
142
|
when "--all"
|
|
62
143
|
positional << "--all"
|
|
144
|
+
when "--repo"
|
|
145
|
+
repo_flag = args.shift or usage_abort("--repo needs a path")
|
|
63
146
|
when "--template"
|
|
64
147
|
template_path = args.shift or usage_abort("--template needs a path")
|
|
148
|
+
when "--session"
|
|
149
|
+
session_flag = args.shift or usage_abort("--session needs a value")
|
|
150
|
+
when "--since"
|
|
151
|
+
since_flag = args.shift or usage_abort("--since needs a value")
|
|
152
|
+
when "--ledger-root"
|
|
153
|
+
ledger_root_flag = args.shift or usage_abort("--ledger-root needs a path")
|
|
154
|
+
when "--store-root"
|
|
155
|
+
store_root_flag = args.shift or usage_abort("--store-root needs a path")
|
|
65
156
|
else
|
|
66
157
|
usage_abort("unknown flag #{arg.inspect}") if arg.start_with?("--") && arg != "--all"
|
|
67
158
|
positional << arg
|
|
68
159
|
end
|
|
69
160
|
end
|
|
70
161
|
|
|
71
|
-
usage_abort("usage: report-screen state|delivered|delay <intent_dir> [--changed \"<text>\"] [--ansi]") unless verb
|
|
162
|
+
usage_abort("usage: report-screen plan|state|delivered|delay|session <intent_dir> [--changed \"<text>\"] [--ansi]") unless verb
|
|
72
163
|
|
|
73
164
|
all_mode = positional.delete("--all") ? true : false
|
|
74
165
|
target = positional.first
|
|
@@ -82,6 +173,16 @@ def paint(text, ansi_enabled, _renderer_path = nil)
|
|
|
82
173
|
end
|
|
83
174
|
|
|
84
175
|
case verb
|
|
176
|
+
when "plan"
|
|
177
|
+
usage_abort("usage: report-screen plan <intent_dir> [--ansi]") unless target
|
|
178
|
+
intent_dir = File.expand_path(target)
|
|
179
|
+
usage_abort("#{intent_dir} is not an intent directory") unless IntentScreen.intent_dir?(intent_dir)
|
|
180
|
+
store_root = File.expand_path("../..", intent_dir)
|
|
181
|
+
template_path ||= File.expand_path("../templates/report-plan.md", __dir__)
|
|
182
|
+
usage_abort("template not found at #{template_path}") unless File.exist?(template_path)
|
|
183
|
+
out = ReportScreen.render_plan(intent_dir: intent_dir, store_root: store_root,
|
|
184
|
+
template: File.read(template_path))
|
|
185
|
+
$stdout.write paint(out, ansi_enabled)
|
|
85
186
|
when "state"
|
|
86
187
|
if all_mode
|
|
87
188
|
usage_abort("usage: report-screen state --all <store_root>") unless target
|
|
@@ -104,8 +205,9 @@ when "delivered"
|
|
|
104
205
|
usage_abort("usage: report-screen delivered <intent_dir>") unless target
|
|
105
206
|
intent_dir = File.expand_path(target)
|
|
106
207
|
usage_abort("#{intent_dir} is not an intent directory") unless IntentScreen.intent_dir?(intent_dir)
|
|
107
|
-
|
|
108
|
-
|
|
208
|
+
out = ReportScreen.render_delivered(intent_dir: intent_dir,
|
|
209
|
+
tag_reader: git_tag_reader(explicit_repo: repo_flag, script_dir: __dir__),
|
|
210
|
+
branch_reader: git_branch_reader(explicit_repo: repo_flag, script_dir: __dir__))
|
|
109
211
|
$stdout.write paint(out, ansi_enabled)
|
|
110
212
|
when "delay"
|
|
111
213
|
usage_abort("usage: report-screen delay <intent_dir>") unless target
|
|
@@ -113,8 +215,67 @@ when "delay"
|
|
|
113
215
|
usage_abort("#{intent_dir} is not an intent directory") unless IntentScreen.intent_dir?(intent_dir)
|
|
114
216
|
out = ReportScreen.render_delay(intent_dir: intent_dir)
|
|
115
217
|
$stdout.write paint(out, ansi_enabled)
|
|
218
|
+
when "session"
|
|
219
|
+
usage_abort("usage: report-screen session <tier_root> [--session <id>] [--since <iso>] [--ledger-root <dir>] [--ansi]") unless target
|
|
220
|
+
store_root = File.expand_path(target)
|
|
221
|
+
usage_abort("#{store_root} is not a store (no INDEX.md)") unless File.exist?(File.join(store_root, "INDEX.md"))
|
|
222
|
+
|
|
223
|
+
now = Time.now
|
|
224
|
+
session_id = session_flag || ENV[SessionLedger::SESSION_ID_ENV_KEY]
|
|
225
|
+
ledger_root = ledger_root_flag ? File.expand_path(ledger_root_flag) : ReportScreen.default_ledger_root(store_root)
|
|
226
|
+
|
|
227
|
+
if since_flag
|
|
228
|
+
begin
|
|
229
|
+
Time.parse(since_flag)
|
|
230
|
+
rescue ArgumentError
|
|
231
|
+
usage_abort("--since needs an ISO timestamp, got #{since_flag.inspect}")
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
dirs, skipped = ReportScreen.session_delivered_dirs(ledger_root: ledger_root, tier_root: store_root,
|
|
236
|
+
session: session_id, since: since_flag, now: now)
|
|
237
|
+
# D17: the widened window is announced whether the id was missing or simply
|
|
238
|
+
# matched nothing. A resumed background job carries a session id that is not
|
|
239
|
+
# the one on the ledger lines, and that case must not answer silently. An
|
|
240
|
+
# explicit --since is the caller naming the window, so it needs no note.
|
|
241
|
+
note =
|
|
242
|
+
if since_flag
|
|
243
|
+
nil
|
|
244
|
+
elsif session_id.nil? || session_id.to_s.strip.empty?
|
|
245
|
+
ReportScreen.window_note(ReportScreen.fallback_day(ledger_root, SessionLedger.day_id(now)),
|
|
246
|
+
"no session id given")
|
|
247
|
+
elsif !ReportScreen.session_tagged?(ledger_root: ledger_root, session: session_id, now: now)
|
|
248
|
+
ReportScreen.window_note(ReportScreen.fallback_day(ledger_root, SessionLedger.day_id(now)),
|
|
249
|
+
"this session has no line in the day ledger")
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
out = ReportScreen.render_session(
|
|
253
|
+
dirs: dirs, skipped: skipped, store_root: store_root,
|
|
254
|
+
tag_reader: git_tag_reader(explicit_repo: nil, script_dir: __dir__),
|
|
255
|
+
branch_reader: git_branch_reader(explicit_repo: nil, script_dir: __dir__),
|
|
256
|
+
note: note, now: now, painter: ->(text) { paint(text, ansi_enabled) }
|
|
257
|
+
)
|
|
258
|
+
$stdout.write out
|
|
259
|
+
when "roadmap"
|
|
260
|
+
usage_abort("usage: report-screen roadmap <roadmap.md> plan|state|delivered [--ansi] [--store-root <dir>]") if positional.empty?
|
|
261
|
+
roadmap_path = File.expand_path(positional[0])
|
|
262
|
+
usage_abort("#{roadmap_path} does not exist") unless File.exist?(roadmap_path)
|
|
263
|
+
sub_verb = positional[1]
|
|
264
|
+
usage_abort("usage: report-screen roadmap <roadmap.md> plan|state|delivered") unless sub_verb
|
|
265
|
+
unless %w[plan state delivered].include?(sub_verb)
|
|
266
|
+
usage_abort("unknown sub-verb #{sub_verb.inspect} (use plan|state|delivered)")
|
|
267
|
+
end
|
|
268
|
+
roadmap_store_root = store_root_flag ? File.expand_path(store_root_flag) : nil
|
|
269
|
+
out = ReportScreen.render_roadmap(path: roadmap_path, verb: sub_verb, store_root: roadmap_store_root)
|
|
270
|
+
$stdout.write paint(out, ansi_enabled)
|
|
271
|
+
when "archive"
|
|
272
|
+
usage_abort("usage: report-screen archive <store_root>") unless target
|
|
273
|
+
store_root = File.expand_path(target)
|
|
274
|
+
usage_abort("#{store_root} is not a store (no INDEX.md)") unless File.exist?(File.join(store_root, "INDEX.md"))
|
|
275
|
+
out = ReportScreen.render_archive(store_root)
|
|
276
|
+
$stdout.write paint(out, ansi_enabled)
|
|
116
277
|
else
|
|
117
|
-
usage_abort("unknown verb #{verb.inspect} (use state|delivered|delay)")
|
|
278
|
+
usage_abort("unknown verb #{verb.inspect} (use plan|state|delivered|delay|session|roadmap|archive)")
|
|
118
279
|
end
|
|
119
280
|
|
|
120
281
|
exit 0
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: UTF-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# roadmap-graph - the CLI over the roadmap graph model (intent 337, G4):
|
|
6
|
+
# `check`, `render`, `migrate`, every verb honouring --dry-run.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# roadmap-graph check <roadmap.md> [--dry-run]
|
|
10
|
+
# roadmap-graph render <roadmap.md> [--dry-run]
|
|
11
|
+
# roadmap-graph migrate <roadmap.md> [--dry-run]
|
|
12
|
+
#
|
|
13
|
+
# Exit codes:
|
|
14
|
+
# 0 - printed / rendered / migrated cleanly (or a migrate skip: the
|
|
15
|
+
# roadmap already carries a graph)
|
|
16
|
+
# 1 - a real finding: a cyclic graph, a dangling id, or a render/migrate
|
|
17
|
+
# refusal (no ## Graph, no grouping heading)
|
|
18
|
+
# 2 - usage: no argument, an unknown verb, or a missing/non-file path
|
|
19
|
+
|
|
20
|
+
require_relative "lib/roadmap_graph"
|
|
21
|
+
require_relative "lib/roadmap_render"
|
|
22
|
+
require_relative "lib/roadmap_migration"
|
|
23
|
+
|
|
24
|
+
module RoadmapGraphCli
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
VERBS = %w[check render migrate].freeze
|
|
28
|
+
|
|
29
|
+
def usage
|
|
30
|
+
warn "Usage: roadmap-graph <#{VERBS.join('|')}> <roadmap.md> [--dry-run]"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def main(argv)
|
|
34
|
+
args = argv.dup
|
|
35
|
+
dry_run = !!args.delete("--dry-run")
|
|
36
|
+
|
|
37
|
+
verb = args.shift
|
|
38
|
+
unless verb && VERBS.include?(verb)
|
|
39
|
+
warn "roadmap-graph: unknown verb #{verb.inspect} (use #{VERBS.join(', ')})" if verb
|
|
40
|
+
usage
|
|
41
|
+
return 2
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
path = args.shift
|
|
45
|
+
unless path && File.file?(path)
|
|
46
|
+
warn "roadmap-graph: #{path.inspect} is not a file"
|
|
47
|
+
return 2
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
case verb
|
|
51
|
+
when "check" then check(path)
|
|
52
|
+
when "render" then render(path, dry_run)
|
|
53
|
+
when "migrate" then migrate(path, dry_run)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def check(path)
|
|
58
|
+
result = RoadmapGraph.analyze(path)
|
|
59
|
+
unless result[:has_graph]
|
|
60
|
+
warn "roadmap-graph check: #{result[:reason]}"
|
|
61
|
+
return 1
|
|
62
|
+
end
|
|
63
|
+
if result[:cycle]
|
|
64
|
+
warn "roadmap-graph check: cyclic graph: #{result[:cycle].join(' > ')}"
|
|
65
|
+
return 1
|
|
66
|
+
end
|
|
67
|
+
if result[:reason]
|
|
68
|
+
warn "roadmap-graph check: #{result[:reason]}"
|
|
69
|
+
return 1
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
puts "Batches: #{result[:batches].map { |b| b.join(', ') }.join(' | ')}"
|
|
73
|
+
puts "Ready: #{result[:ready].join(', ')}"
|
|
74
|
+
puts "Dead ends: #{result[:dead_ends].join(', ')}" unless result[:dead_ends].empty?
|
|
75
|
+
|
|
76
|
+
unless result[:dangling].empty?
|
|
77
|
+
warn "roadmap-graph check: graph names #{result[:dangling].map(&:inspect).join(', ')}, no batch entry"
|
|
78
|
+
return 1
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
0
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def render(path, dry_run)
|
|
85
|
+
result = RoadmapRender.write(path, dry_run: dry_run)
|
|
86
|
+
unless result[:ok]
|
|
87
|
+
warn "roadmap-graph render: #{result[:error]}"
|
|
88
|
+
return 1
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
if dry_run
|
|
92
|
+
puts result[:content]
|
|
93
|
+
else
|
|
94
|
+
puts "roadmap-graph render: wrote #{path}"
|
|
95
|
+
end
|
|
96
|
+
0
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def migrate(path, dry_run)
|
|
100
|
+
result = RoadmapMigration.write(path, dry_run: dry_run)
|
|
101
|
+
unless result[:ok]
|
|
102
|
+
if result[:skipped]
|
|
103
|
+
puts "roadmap-graph migrate: #{result[:reason]}"
|
|
104
|
+
return 0
|
|
105
|
+
end
|
|
106
|
+
warn "roadmap-graph migrate: #{result[:reason]}"
|
|
107
|
+
return 1
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
if dry_run
|
|
111
|
+
puts result[:content]
|
|
112
|
+
else
|
|
113
|
+
puts "roadmap-graph migrate: wrote #{path}"
|
|
114
|
+
end
|
|
115
|
+
0
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
exit(RoadmapGraphCli.main(ARGV)) if $PROGRAM_NAME == __FILE__
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
# rebuilds an empty ledger.
|
|
19
19
|
|
|
20
20
|
require_relative "lib/roadmap_savepoint"
|
|
21
|
+
require_relative "lib/guarded_append"
|
|
21
22
|
|
|
22
23
|
def opt(args, name)
|
|
23
24
|
(i = args.index(name)) && args[i + 1]
|
|
@@ -47,6 +48,12 @@ when "append"
|
|
|
47
48
|
rescue ArgumentError => e
|
|
48
49
|
warn "append: #{e.message}"
|
|
49
50
|
exit 2
|
|
51
|
+
rescue GuardedAppend::Unavailable => e
|
|
52
|
+
# Intent 335 (spec D14): the write guard could not be taken; nothing was written. Exit 4
|
|
53
|
+
# rather than let a backtrace reach the six skill recipes that drive this CLI - it rescues
|
|
54
|
+
# ArgumentError alone today and would otherwise print one straight to the terminal.
|
|
55
|
+
warn "append: #{e.message}"
|
|
56
|
+
exit 4
|
|
50
57
|
end
|
|
51
58
|
|
|
52
59
|
when "rebuild"
|