@zalom/plastic 2.0.0-alpha.27 → 2.0.0-alpha.29
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 -139
- package/README.md +345 -133
- package/agents/plastic-enforcer.md +9 -10
- package/agents/plastic-executor.md +1 -1
- package/bin/crap +4 -0
- package/bin/lib/context_budget.rb +35 -1
- package/bin/lib/skill_census.rb +839 -0
- package/bin/plastic +6 -0
- package/bin/plastic-skill-census +114 -0
- package/bin/verify-change +345 -0
- package/deprecations.yml +1 -1
- package/{skills/agent-advisor/references → docs/help}/advisor-protocol.md +4 -7
- package/{skills/auto/references → docs/help}/agent-architecture.md +7 -7
- package/{skills/conventions/references → docs/help}/completion-and-done.md +1 -1
- package/{skills/auto/references → docs/help}/human-report-contract.md +17 -19
- package/{skills/conventions/references → docs/help}/roadmaps.md +2 -2
- package/{skills/tutorial/references → docs/help}/track-1-guided.md +8 -8
- package/{skills/tutorial/references → docs/help}/track-2-auto.md +5 -5
- package/{skills/tutorial/references → docs/help}/track-3-projects-and-roadmaps.md +24 -17
- package/package.json +3 -2
- package/scripts/append-ledger +2 -1
- package/scripts/dashboard.rb +10 -9
- package/scripts/day-summary +2 -1
- package/scripts/doctor.rb +48 -42
- package/scripts/end-intent +2 -8
- package/scripts/file-session-intent +2 -1
- package/scripts/hook-capture +4 -3
- package/scripts/hook-close +2 -1
- package/scripts/hook-record +3 -2
- package/scripts/hook-savepoint +3 -2
- package/scripts/hook-session-start +5 -4
- package/scripts/hook-stop +2 -1
- package/scripts/insight-append +1 -2
- package/scripts/install.rb +3 -1
- package/scripts/lib/active_delivery.rb +1 -1
- package/scripts/lib/arm.rb +2 -1
- package/scripts/lib/backup.rb +65 -0
- package/scripts/lib/cli/command.rb +85 -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 +60 -0
- package/scripts/lib/cli/commands/auto_report.rb +50 -0
- package/scripts/lib/cli/commands/auto_take.rb +25 -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 +20 -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 +61 -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 +25 -0
- package/scripts/lib/cli/commands/intent_spec.rb +44 -0
- package/scripts/lib/cli/commands/intent_step.rb +43 -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 +49 -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 +25 -0
- package/scripts/lib/cli/commands/roadmap.rb +19 -0
- package/scripts/lib/cli/commands/roadmap_check.rb +44 -0
- package/scripts/lib/cli/commands/roadmap_log.rb +54 -0
- package/scripts/lib/cli/commands/roadmap_next.rb +34 -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 +84 -0
- package/scripts/lib/cli/legacy.rb +50 -0
- package/scripts/lib/cli/output.rb +102 -0
- package/scripts/lib/cli/scope.rb +127 -0
- package/scripts/lib/cli/table.rb +64 -0
- package/scripts/lib/cli.rb +94 -0
- package/scripts/lib/compact_instructions.rb +8 -0
- package/scripts/lib/day_summary.rb +4 -3
- package/scripts/lib/doctor_core.rb +7 -32
- package/scripts/lib/doctor_session_ledger.rb +2 -1
- package/scripts/lib/feedback_report.rb +1 -1
- package/scripts/lib/graph_measure_models.rb +3 -1
- package/scripts/lib/index_entry.rb +9 -0
- package/scripts/lib/installer_core.rb +73 -19
- package/scripts/lib/intent_screen.rb +3 -3
- package/scripts/lib/lock.rb +2 -2
- package/scripts/lib/node_input.rb +3 -2
- package/scripts/lib/preflight.rb +4 -6
- package/scripts/lib/project_config.rb +2 -1
- package/scripts/lib/project_validator.rb +3 -2
- package/scripts/lib/qmd_sync.rb +8 -7
- package/scripts/lib/reference_archive.rb +45 -0
- package/scripts/lib/release_guard.rb +2 -0
- package/scripts/lib/report_screen.rb +4 -3
- 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_queue.rb +2 -2
- package/scripts/lib/roadmap_savepoint.rb +1 -1
- package/scripts/lib/runner_absorb.rb +3 -2
- package/scripts/lib/search_index.rb +55 -0
- package/scripts/lib/session_git.rb +4 -3
- package/scripts/lib/sqlite.rb +22 -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 +2 -7
- package/scripts/lib/version_number.rb +48 -0
- package/scripts/lib/work_graph.rb +59 -0
- package/scripts/lib/worktree.rb +3 -8
- package/scripts/lib/worktree_sweep.rb +3 -2
- package/scripts/link-suggest +2 -1
- package/scripts/migrate-to-global +1 -1
- package/scripts/new-intent +3 -12
- package/scripts/plastic-lock +3 -2
- package/scripts/promote-session-item +3 -2
- package/scripts/release-check +10 -5
- package/scripts/report-screen +1 -1
- package/scripts/session-commit +2 -1
- package/scripts/spawn-preamble +2 -2
- package/scripts/update.rb +25 -4
- package/scripts/write-handoff +2 -1
- package/templates/agents.md +6 -6
- package/templates/render.css +10 -0
- package/bin/plastic.js +0 -70
- package/skills/agent-advisor/SKILL.md +0 -84
- package/skills/auto/SKILL.md +0 -297
- package/skills/auto/evals/evals.json +0 -255
- package/skills/auto/references/end-tail.md +0 -64
- package/skills/conventions/SKILL.md +0 -29
- package/skills/dashboard/SKILL.md +0 -180
- 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 -305
- 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 -215
- package/skills/intent-continuing/SKILL.md +0 -156
- package/skills/intent-continuing/references/board-fill.md +0 -52
- package/skills/intent-continuing/references/boarding-matrix.md +0 -35
- 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 -89
- 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 -182
- package/skills/intent-ending/evals/evals.json +0 -74
- package/skills/intent-executing/SKILL.md +0 -87
- package/skills/intent-executing/evals/evals.json +0 -66
- package/skills/intent-executing/implementer-prompt.md +0 -47
- package/skills/intent-executing/spec-reviewer-prompt.md +0 -27
- package/skills/intent-speccing/SKILL.md +0 -136
- 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 -376
- package/skills/releasing/references/deprecations.md +0 -60
- package/skills/releasing/references/promotion-and-tagging.md +0 -70
- package/skills/releasing/references/release-lines.md +0 -105
- package/skills/roadmap/SKILL.md +0 -90
- package/skills/roadmap/references/file-format.md +0 -134
- package/skills/roadmap/references/operations.md +0 -112
- package/skills/rollback/SKILL.md +0 -91
- package/skills/tutorial/SKILL.md +0 -66
- 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}/knowledge-graph.md +0 -0
- /package/{skills/conventions/references → docs/help}/lifecycle-and-savepoints.md +0 -0
- /package/{skills/conventions/references → docs/help}/locks-and-worktrees.md +0 -0
- /package/{skills/conventions/references → docs/help}/maintenance-and-revisions.md +0 -0
- /package/{skills/intent-executing → docs/help}/plan-reviewer-prompt.md +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require_relative "store_layout"
|
|
5
|
+
require_relative "sqlite"
|
|
6
|
+
require_relative "lock"
|
|
7
|
+
|
|
8
|
+
module Plastic
|
|
9
|
+
class StoresMove
|
|
10
|
+
Refused = Class.new(StandardError)
|
|
11
|
+
|
|
12
|
+
GLOBAL_ENTRIES = %w[store INDEX.md roadmaps].freeze
|
|
13
|
+
PATH_COLUMNS = {"knowledge_graph.db" => %w[doc path], "references.db" => %w[sqlar name]}.freeze
|
|
14
|
+
LOCKS = "{store,projects/*/store}/*/delivery.lock"
|
|
15
|
+
|
|
16
|
+
def initialize(home, qmd_index:, dry_run: false)
|
|
17
|
+
@home = home
|
|
18
|
+
@qmd_index = qmd_index
|
|
19
|
+
@dry_run = dry_run
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def call
|
|
23
|
+
refuse
|
|
24
|
+
plan = [["copy", copy]] + moves.map { |from, to| ["move", "#{from} -> #{to}"] } + rewrites.map { |file| ["rewrite", file] }
|
|
25
|
+
run unless @dry_run
|
|
26
|
+
plan
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def copy
|
|
30
|
+
"#{@home}-before-stores-move"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def refuse
|
|
36
|
+
raise Refused, "#{File.join(@home, "stores")}/ exists, so this home has moved already" if StoreLayout.moved?(@home)
|
|
37
|
+
raise Refused, "a delivery lock is held: #{held.join(", ")}" unless held.empty?
|
|
38
|
+
raise Refused, "#{copy} exists; remove it or move it away first" if File.exist?(copy)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def held
|
|
42
|
+
Dir.glob(LOCKS, base: @home).select { |lock| Time.now - File.mtime(File.join(@home, lock)) < Lock::TTL_SECONDS }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def moves
|
|
46
|
+
@moves ||= GLOBAL_ENTRIES.select { |name| File.exist?(File.join(@home, name)) }.map { |name| [name, "stores/global/#{name}"] } +
|
|
47
|
+
project_entries.map { |entry| ["projects/#{entry}", "stores/#{entry}"] }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def project_entries
|
|
51
|
+
Dir.exist?(projects) ? Dir.children(projects).sort : []
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def projects
|
|
55
|
+
File.join(@home, "projects")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def rewrites
|
|
59
|
+
[File.join(@home, "config.yml"), @qmd_index, *PATH_COLUMNS.keys.map { |name| File.join(@home, name) }].select { |file| File.file?(file) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def run
|
|
63
|
+
files = rewrites
|
|
64
|
+
FileUtils.cp_r(@home, copy, preserve: true)
|
|
65
|
+
moves.each { |from, to| move(from, to) }
|
|
66
|
+
FileUtils.rmdir(projects) if Dir.exist?(projects)
|
|
67
|
+
files.each { |file| PATH_COLUMNS.key?(File.basename(file)) ? rewrite_rows(file) : rewrite_text(file) }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def move(from, to)
|
|
71
|
+
FileUtils.mkdir_p(File.dirname(File.join(@home, to)))
|
|
72
|
+
FileUtils.mv(File.join(@home, from), File.join(@home, to))
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def rewrite_text(file)
|
|
76
|
+
FileUtils.cp(file, "#{file}.before-stores-move", preserve: true)
|
|
77
|
+
File.write(file, [@home, "~/.plastic"].inject(File.read(file)) { |text, base| relocate(text, base) })
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def relocate(text, base)
|
|
81
|
+
text.gsub(%r{#{Regexp.escape(base)}/(store|roadmaps|INDEX\.md)\b}) { "#{base}/stores/global/#{Regexp.last_match(1)}" }
|
|
82
|
+
.gsub(%r{#{Regexp.escape(base)}/projects\b}) { "#{base}/stores" }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def rewrite_rows(file)
|
|
86
|
+
table, column = PATH_COLUMNS.fetch(File.basename(file))
|
|
87
|
+
Sqlite.call(file, <<~SQL)
|
|
88
|
+
UPDATE #{table} SET #{column} = 'stores/' || substr(#{column}, 10) WHERE #{column} LIKE 'projects/%';
|
|
89
|
+
UPDATE #{table} SET #{column} = 'stores/global/' || #{column} WHERE #{column} LIKE 'store/%';
|
|
90
|
+
SQL
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# encoding: UTF-8
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require_relative "store_layout"
|
|
4
5
|
require_relative "worktree"
|
|
5
6
|
require_relative "scaffold_intent"
|
|
6
7
|
|
|
@@ -96,13 +97,7 @@ module VerifyIntent
|
|
|
96
97
|
# shapes are told apart by whether store's grandparent is literally named "projects" (the
|
|
97
98
|
# StoreDiscovery convention), never by hardcoding ".plastic" as a name.
|
|
98
99
|
def resolve_plastic_home_and_scope(store)
|
|
99
|
-
|
|
100
|
-
grandparent = File.dirname(parent)
|
|
101
|
-
if File.basename(grandparent) == "projects"
|
|
102
|
-
[File.dirname(grandparent), "project:#{File.basename(parent)}"]
|
|
103
|
-
else
|
|
104
|
-
[parent, "global"]
|
|
105
|
-
end
|
|
100
|
+
Plastic::StoreLayout.home_and_scope(store)
|
|
106
101
|
end
|
|
107
102
|
|
|
108
103
|
# --- check 1: doctor, scoped to the intent --------------------------------------
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# VersionNumber - dotted version comparison without RubyGems (intent 363).
|
|
5
|
+
#
|
|
6
|
+
# The launcher starts as `ruby --disable-gems`, which saves about 26 of the 40
|
|
7
|
+
# milliseconds a plain Ruby process costs, so no file a command loads may reach
|
|
8
|
+
# for Gem::Version. Parses the leading run of dotted integers, ignoring a
|
|
9
|
+
# leading "v" and anything after the digits, and compares two of them segment by
|
|
10
|
+
# segment with the shorter one zero-padded. A string with no leading integer
|
|
11
|
+
# parses to nil, so a caller answers "not found" instead of raising.
|
|
12
|
+
#
|
|
13
|
+
# It deliberately does not order pre-release suffixes: 2.0.0-alpha.27 and 2.0.0
|
|
14
|
+
# compare equal here. A caller that needs RubyGems' pre-release order, such as
|
|
15
|
+
# the release-tag reader in scripts/report-screen, keeps Gem::Version.
|
|
16
|
+
class VersionNumber
|
|
17
|
+
include Comparable
|
|
18
|
+
|
|
19
|
+
LEADING_DIGITS = /\A(\d+(?:\.\d+)*)/
|
|
20
|
+
|
|
21
|
+
attr_reader :segments
|
|
22
|
+
|
|
23
|
+
def self.parse(value)
|
|
24
|
+
match = LEADING_DIGITS.match(value.to_s.strip.sub(/\Av/, ""))
|
|
25
|
+
match && new(match[1].split(".").map(&:to_i))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def initialize(segments)
|
|
29
|
+
@segments = segments
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def <=>(other)
|
|
33
|
+
return nil unless other.is_a?(VersionNumber)
|
|
34
|
+
|
|
35
|
+
width = [segments.length, other.segments.length].max
|
|
36
|
+
padded(width) <=> other.padded(width)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def to_s
|
|
40
|
+
segments.join(".")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
protected
|
|
44
|
+
|
|
45
|
+
def padded(width)
|
|
46
|
+
segments + Array.new(width - segments.length, 0)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require_relative "sqlite"
|
|
5
|
+
require_relative "index_entry"
|
|
6
|
+
require_relative "store_discovery"
|
|
7
|
+
|
|
8
|
+
module Plastic
|
|
9
|
+
module WorkGraph
|
|
10
|
+
NAME = "work_graph.db"
|
|
11
|
+
SCHEMA = <<~SQL
|
|
12
|
+
CREATE TABLE intent(id TEXT, store TEXT, slug TEXT, status TEXT, title TEXT);
|
|
13
|
+
CREATE TABLE ledger(intent_id TEXT, store TEXT, at TEXT, stage TEXT, text TEXT);
|
|
14
|
+
SQL
|
|
15
|
+
STATUSES = %w[active future completed abandoned].freeze
|
|
16
|
+
LEDGER_LINE = /\A(\S+)\s{2,}(\S+)\s{2,}(.+)\z/
|
|
17
|
+
|
|
18
|
+
def self.path(home)
|
|
19
|
+
File.join(home, NAME)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.build(home)
|
|
23
|
+
draft = "#{path(home)}.tmp"
|
|
24
|
+
FileUtils.rm_f(draft)
|
|
25
|
+
rows = StoreDiscovery.discover(home)[:stores].flat_map { |store| intents(store) + ledger(store) }
|
|
26
|
+
Sqlite.call(draft, [SCHEMA, "BEGIN;", *rows, "COMMIT;"].join("\n"))
|
|
27
|
+
File.rename(draft, path(home))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.intents(store)
|
|
31
|
+
return [] unless File.exist?(store[:index])
|
|
32
|
+
|
|
33
|
+
status = nil
|
|
34
|
+
File.readlines(store[:index], chomp: true, encoding: "UTF-8").filter_map do |line|
|
|
35
|
+
status = line.delete_prefix("## ").downcase if line.start_with?("## ")
|
|
36
|
+
entry = STATUSES.include?(status) && IndexEntry.match(line)
|
|
37
|
+
entry && insert("intent", entry[1], store[:slug], File.basename(File.dirname(entry[3])).split("--", 2).last, status, entry[2])
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.ledger(store)
|
|
42
|
+
Dir.glob("*/savepoint.md", base: store[:store]).sort.flat_map do |file|
|
|
43
|
+
id = file.split("--").first
|
|
44
|
+
File.readlines(File.join(store[:store], file), chomp: true, encoding: "UTF-8").filter_map do |line|
|
|
45
|
+
entry = line.match(LEDGER_LINE)
|
|
46
|
+
entry && insert("ledger", id, store[:slug], entry[1], entry[2], entry[3])
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.insert(table, *values)
|
|
52
|
+
"INSERT INTO #{table} VALUES(#{values.map { |value| Sqlite.quote(value.to_s) }.join(", ")});"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.known(home)
|
|
56
|
+
Sqlite.call(path(home), "SELECT store, id FROM intent;", readonly: true).map { |row| "#{row["store"]}:#{row["id"]}" }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
package/scripts/lib/worktree.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# encoding: UTF-8
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require_relative "store_layout"
|
|
4
5
|
require "json"
|
|
5
6
|
require "yaml"
|
|
6
7
|
require "socket"
|
|
@@ -377,14 +378,8 @@ module Worktree
|
|
|
377
378
|
# (no project repo). Mirrors qmd_sync's slug_for_store fallback.
|
|
378
379
|
def slug_for_store(store_dir, home: Dir.home)
|
|
379
380
|
return nil if blank?(store_dir)
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
return nil if store_dir == File.join(plastic_home, "store")
|
|
383
|
-
|
|
384
|
-
parts = store_dir.split(File::SEPARATOR)
|
|
385
|
-
idx = parts.rindex("projects")
|
|
386
|
-
return parts[idx + 1] if idx && parts[idx + 1] && parts[idx + 2] == "store"
|
|
387
|
-
nil
|
|
381
|
+
slug = Plastic::StoreLayout.locate(store_dir).last
|
|
382
|
+
(slug == Plastic::StoreLayout::GLOBAL) ? nil : slug
|
|
388
383
|
end
|
|
389
384
|
|
|
390
385
|
# Best-effort slug for the worktree dir-name from an intent dir/store path:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# encoding: UTF-8
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require_relative "store_layout"
|
|
4
5
|
require_relative "worktree"
|
|
5
6
|
require_relative "index_entry"
|
|
6
7
|
|
|
@@ -55,8 +56,8 @@ module WorktreeSweep
|
|
|
55
56
|
# Matching on the full name, not just the numeric id, sidesteps id
|
|
56
57
|
# collisions across projects (ids are only unique WITHIN one store).
|
|
57
58
|
def resolve_intent_dir(plastic_home, name)
|
|
58
|
-
candidates = [File.join(plastic_home,
|
|
59
|
-
Dir.glob(File.join(plastic_home, "
|
|
59
|
+
candidates = [File.join(Plastic::StoreLayout.global_store(plastic_home), name)] +
|
|
60
|
+
Dir.glob(File.join(Plastic::StoreLayout.projects_root(plastic_home), "*", "store", name))
|
|
60
61
|
candidates.find { |d| Dir.exist?(d) }
|
|
61
62
|
end
|
|
62
63
|
|
package/scripts/link-suggest
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
# nothing. --edge is explicit and required (sources is decided by origin, never
|
|
27
27
|
# inferred); --rating is required when --edge is chain.
|
|
28
28
|
|
|
29
|
+
require_relative "lib/store_layout"
|
|
29
30
|
require_relative "lib/link_suggestions"
|
|
30
31
|
|
|
31
32
|
module LinkSuggestCLI
|
|
@@ -69,7 +70,7 @@ module LinkSuggestCLI
|
|
|
69
70
|
i += 1
|
|
70
71
|
end
|
|
71
72
|
end
|
|
72
|
-
opts[:store_dir] ||=
|
|
73
|
+
opts[:store_dir] ||= Plastic::StoreLayout.global_store(opts[:home])
|
|
73
74
|
opts
|
|
74
75
|
end
|
|
75
76
|
|
|
@@ -17,7 +17,7 @@ GLOBAL_ROOT="$HOME/.plastic"
|
|
|
17
17
|
LOCAL_PLASTIC="$PROJECT_PATH/.plastic"
|
|
18
18
|
|
|
19
19
|
if [ ! -d "$GLOBAL_ROOT" ]; then
|
|
20
|
-
echo "Error: Global Plastic not installed. Run
|
|
20
|
+
echo "Error: Global Plastic not installed. Run 'plastic install' first." >&2
|
|
21
21
|
exit 1
|
|
22
22
|
fi
|
|
23
23
|
|
package/scripts/new-intent
CHANGED
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
# exactly one line, `created <dir>` or `joined <dir>`; exit 0 either way,
|
|
36
36
|
# exit 1 on bad input or failed self-validation.
|
|
37
37
|
|
|
38
|
+
require_relative "lib/store_layout"
|
|
38
39
|
require "fileutils"
|
|
39
40
|
require "date"
|
|
40
41
|
require_relative "lib/savepoint"
|
|
@@ -167,17 +168,7 @@ end
|
|
|
167
168
|
# [nil, "global"] only if the layout is unrecognized, in which case Links
|
|
168
169
|
# projection falls back to single-store resolution rooted at this store.
|
|
169
170
|
def store_context(store)
|
|
170
|
-
|
|
171
|
-
parent = File.dirname(store) # `<home>` or `<home>/projects/<slug>`
|
|
172
|
-
if File.basename(store) == "store" && File.basename(File.dirname(parent)) == "projects"
|
|
173
|
-
slug = File.basename(parent)
|
|
174
|
-
home = File.dirname(File.dirname(parent)) # strip projects/<slug>
|
|
175
|
-
[home, "project:#{slug}"]
|
|
176
|
-
elsif File.basename(store) == "store"
|
|
177
|
-
[parent, "global"] # `<home>/store`
|
|
178
|
-
else
|
|
179
|
-
[parent, "global"]
|
|
180
|
-
end
|
|
171
|
+
Plastic::StoreLayout.home_and_scope(store)
|
|
181
172
|
end
|
|
182
173
|
|
|
183
174
|
# The in-scope stores under `plastic_home`, each { key:, store: }. Delegates to
|
|
@@ -283,7 +274,7 @@ def run_tmp(opts)
|
|
|
283
274
|
abort "new-intent: --sources is not valid with --tmp" unless opts[:sources].empty?
|
|
284
275
|
abort "new-intent: --tags is not valid with --tmp" unless opts[:tags].empty?
|
|
285
276
|
|
|
286
|
-
store = opts[:store] ? expand(opts[:store]) : expand(
|
|
277
|
+
store = opts[:store] ? expand(opts[:store]) : Plastic::StoreLayout.global_store(expand(ENV.fetch("PLASTIC_HOME", "~/.plastic")))
|
|
287
278
|
templates = opts[:templates] ? expand(opts[:templates]) : default_templates_dir
|
|
288
279
|
abort "new-intent: templates dir not found: #{templates}" unless Dir.exist?(templates)
|
|
289
280
|
|
package/scripts/plastic-lock
CHANGED
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
# file; that path was retired in 2.0 (intent 307) and 344 G11.
|
|
39
39
|
# Exit 0 on success/report; exit 1 when the verb is blocked.
|
|
40
40
|
|
|
41
|
+
require_relative "lib/store_layout"
|
|
41
42
|
require "json"
|
|
42
43
|
require "yaml"
|
|
43
44
|
require_relative "lib/lock"
|
|
@@ -96,7 +97,7 @@ if dir.nil? || dir.strip.empty?
|
|
|
96
97
|
exit 1
|
|
97
98
|
end
|
|
98
99
|
roots = ActiveDelivery.project_roots(plastic_home)
|
|
99
|
-
global_store =
|
|
100
|
+
global_store = Plastic::StoreLayout.global_store(plastic_home)
|
|
100
101
|
dir = ActiveDelivery.resolve(global_store: global_store, project_roots: roots, session: session)
|
|
101
102
|
if dir.nil?
|
|
102
103
|
candidates = ActiveDelivery.candidate_intent_dirs(global_store: global_store, project_roots: roots).uniq
|
|
@@ -188,7 +189,7 @@ when "arm"
|
|
|
188
189
|
end
|
|
189
190
|
when "status"
|
|
190
191
|
lock = Lock.read(dir)
|
|
191
|
-
resolved = ActiveDelivery.resolve(global_store:
|
|
192
|
+
resolved = ActiveDelivery.resolve(global_store: Plastic::StoreLayout.global_store(plastic_home),
|
|
192
193
|
project_roots: ActiveDelivery.project_roots(plastic_home),
|
|
193
194
|
session: key)
|
|
194
195
|
delivering = !resolved.nil? && File.expand_path(resolved) == File.expand_path(dir)
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
# Exit codes: 0 promoted (prints the intent dir last), 1 new-intent failed,
|
|
22
22
|
# 2 usage error or nothing to promote.
|
|
23
23
|
|
|
24
|
+
require_relative "lib/store_layout"
|
|
24
25
|
require "open3"
|
|
25
26
|
require "yaml"
|
|
26
27
|
require_relative "lib/session_ledger"
|
|
@@ -61,7 +62,7 @@ usage_abort("--day must be a real calendar date, YYYYMMDD") unless SessionLedger
|
|
|
61
62
|
usage_abort("--match is required") if opts[:match].nil? || opts[:match].empty?
|
|
62
63
|
|
|
63
64
|
plastic_home = File.expand_path(opts[:plastic_home] || ENV.fetch("PLASTIC_HOME", "~/.plastic"))
|
|
64
|
-
store = opts[:store] ||
|
|
65
|
+
store = opts[:store] || Plastic::StoreLayout.global_store(plastic_home)
|
|
65
66
|
templates = opts[:templates] || File.expand_path("../templates", __dir__)
|
|
66
67
|
session = opts[:session] ? SessionLedger.short_session_id(opts[:session]) : nil
|
|
67
68
|
|
|
@@ -69,7 +70,7 @@ target_store = store
|
|
|
69
70
|
if opts[:project]
|
|
70
71
|
projects = StoreProvisioning.load_projects(plastic_home)
|
|
71
72
|
usage_abort("--project #{opts[:project]} is not a registered project") unless projects.key?(opts[:project])
|
|
72
|
-
target_store = File.join(plastic_home,
|
|
73
|
+
target_store = File.join(Plastic::StoreLayout.project_root(plastic_home, opts[:project]), "store")
|
|
73
74
|
end
|
|
74
75
|
|
|
75
76
|
# --- pick the target line ---------------------------------------------------
|
package/scripts/release-check
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
# suite.
|
|
14
14
|
#
|
|
15
15
|
# Usage:
|
|
16
|
-
# release-check --tag <tag> --npm-version <version>
|
|
16
|
+
# release-check (--tag <tag> | --branch <branch>) --npm-version <version>
|
|
17
17
|
# [--github-output <path>] [--root <dir>]
|
|
18
18
|
#
|
|
19
19
|
# Exit codes: 0 (clean), 1 (violations; reported on stderr, one per line,
|
|
@@ -24,10 +24,10 @@ require "json"
|
|
|
24
24
|
require_relative "lib/release_guard"
|
|
25
25
|
|
|
26
26
|
NPM_FLOOR = [11, 5, 1].freeze
|
|
27
|
-
KNOWN_FLAGS = %w[--tag --npm-version --github-output --root].freeze
|
|
27
|
+
KNOWN_FLAGS = %w[--tag --branch --npm-version --github-output --root].freeze
|
|
28
28
|
|
|
29
29
|
def usage
|
|
30
|
-
warn "usage: release-check --tag <tag> --npm-version <version> [--github-output <path>] [--root <dir>]"
|
|
30
|
+
warn "usage: release-check (--tag <tag> | --branch <branch>) --npm-version <version> [--github-output <path>] [--root <dir>]"
|
|
31
31
|
exit 2
|
|
32
32
|
end
|
|
33
33
|
|
|
@@ -54,7 +54,7 @@ rescue Errno::ENOENT, JSON::ParserError
|
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
opts = parse_args(ARGV)
|
|
57
|
-
usage unless opts["--tag"] && opts["--npm-version"]
|
|
57
|
+
usage unless (opts["--tag"] || opts["--branch"]) && opts["--npm-version"]
|
|
58
58
|
|
|
59
59
|
root = opts["--root"] ? File.expand_path(opts["--root"]) : File.expand_path("..", __dir__)
|
|
60
60
|
package_json_path = File.join(root, "package.json")
|
|
@@ -66,16 +66,20 @@ violations = []
|
|
|
66
66
|
canonical_version = read_version(package_json_path)
|
|
67
67
|
violations << "could not read a version from #{package_json_path}" if canonical_version.nil?
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
branch = opts["--branch"]
|
|
70
70
|
dist_tag = nil
|
|
71
71
|
|
|
72
72
|
if canonical_version
|
|
73
73
|
expected_tag = "v#{canonical_version}"
|
|
74
|
+
tag = opts.fetch("--tag", expected_tag).sub(%r{\Arefs/tags/}, "")
|
|
74
75
|
if tag != expected_tag
|
|
75
76
|
violations << "tag #{tag} does not match package.json version #{canonical_version} (expected #{expected_tag})"
|
|
76
77
|
end
|
|
77
78
|
|
|
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
|
|
79
83
|
stable = dist_tag == "latest"
|
|
80
84
|
|
|
81
85
|
result = ReleaseGuard.check(
|
|
@@ -107,6 +111,7 @@ if violations.empty?
|
|
|
107
111
|
File.open(opts["--github-output"], "a") do |f|
|
|
108
112
|
f.puts "version=#{canonical_version}"
|
|
109
113
|
f.puts "dist_tag=#{dist_tag}"
|
|
114
|
+
f.puts "tag=#{tag}"
|
|
110
115
|
end
|
|
111
116
|
end
|
|
112
117
|
puts "OK: tag #{tag}, version #{canonical_version}, dist-tag #{dist_tag}"
|
package/scripts/report-screen
CHANGED
|
@@ -63,7 +63,7 @@ end
|
|
|
63
63
|
def resolve_repo(intent_dir, explicit_repo, script_dir, include_self: true)
|
|
64
64
|
candidates = []
|
|
65
65
|
candidates << File.expand_path(explicit_repo) if explicit_repo
|
|
66
|
-
if (m = intent_dir.match(%r{\A(.*)/projects/([^/]+)/store/[^/]+\z}))
|
|
66
|
+
if (m = intent_dir.match(%r{\A(.*)/(?:projects|stores)/([^/]+)/store/[^/]+\z}))
|
|
67
67
|
home, slug = m[1], m[2]
|
|
68
68
|
projects_yml = File.join(home, "projects.yml")
|
|
69
69
|
if File.exist?(projects_yml)
|
package/scripts/session-commit
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
# 0 - always, except a usage error
|
|
19
19
|
# 2 - usage error (missing --cwd or --summary); nothing is written
|
|
20
20
|
|
|
21
|
+
require_relative "lib/store_layout"
|
|
21
22
|
require "fileutils"
|
|
22
23
|
require_relative "lib/session_git"
|
|
23
24
|
require_relative "lib/session_ledger"
|
|
@@ -78,7 +79,7 @@ def main(argv)
|
|
|
78
79
|
usage_abort("--summary is required") if opts[:summary].nil?
|
|
79
80
|
|
|
80
81
|
plastic_home = expand(opts[:plastic_home] || ENV.fetch("PLASTIC_HOME", "~/.plastic"))
|
|
81
|
-
store = opts[:store] ? expand(opts[:store]) :
|
|
82
|
+
store = opts[:store] ? expand(opts[:store]) : Plastic::StoreLayout.global_store(plastic_home)
|
|
82
83
|
cwd = expand(opts[:cwd])
|
|
83
84
|
|
|
84
85
|
day = opts[:day] || SessionLedger.day_id
|
package/scripts/spawn-preamble
CHANGED
|
@@ -44,7 +44,7 @@ WORKTREE_FALLBACK_INSTRUCTION =
|
|
|
44
44
|
"directory, cd directly to the code worktree path above before editing project code."
|
|
45
45
|
|
|
46
46
|
# Verbatim completion-report contract (intent 74). Kept as one constant so the
|
|
47
|
-
# contract doc (
|
|
47
|
+
# contract doc (docs/help/agent-report-contract.md), the role prompts,
|
|
48
48
|
# and the test assert against the exact same string. SINGLE SOURCE OF TRUTH for the
|
|
49
49
|
# report wording: the work output is lifecycle artifacts, the final message is a
|
|
50
50
|
# structured report about them. Both are required and they do not contradict.
|
|
@@ -64,7 +64,7 @@ REPORT_CONTRACT =
|
|
|
64
64
|
"depends on the discovering session's file-write access. Keep the report " \
|
|
65
65
|
"prose-stripped: the envelope and payload only, no greeting, no preamble, " \
|
|
66
66
|
"no end-recap, no restating of the task; reasoning stays in the thinking channel. " \
|
|
67
|
-
"See
|
|
67
|
+
"See docs/help/agent-report-contract.md for the per-role format."
|
|
68
68
|
|
|
69
69
|
def parse_args(argv)
|
|
70
70
|
role = nil
|
package/scripts/update.rb
CHANGED
|
@@ -231,12 +231,33 @@ class Update < InstallerCore
|
|
|
231
231
|
($stdin.gets&.strip || "").downcase.start_with?("y")
|
|
232
232
|
end
|
|
233
233
|
|
|
234
|
-
# Thin npx-exec glue (
|
|
235
|
-
# the target version's install verb, recording the ledger action as `update`.
|
|
236
|
-
|
|
234
|
+
# Thin npx-exec glue (the decision above is unit-tested). Delegates the file-sync to
|
|
235
|
+
# the target version's install verb, recording the ledger action as `update`. On
|
|
236
|
+
# success, commits the re-synced core files and clears the update-check cache
|
|
237
|
+
# (former update skill, lines 124-125).
|
|
238
|
+
def perform_switch(target, agent_flags, switch_runner: ->(cmd) { system(*cmd) })
|
|
237
239
|
cmd = ["npx", "#{PKG}@#{target}", "install", "--reinstall", "--ledger-action", "update", *agent_flags]
|
|
238
240
|
puts " $ #{cmd.join(" ")}"
|
|
239
|
-
|
|
241
|
+
return 1 unless switch_runner.call(cmd)
|
|
242
|
+
|
|
243
|
+
commit_core_files(target)
|
|
244
|
+
clear_update_check_cache
|
|
245
|
+
0
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Intent 372 (former update skill, lines 124-125): `~/.plastic` is its own git
|
|
249
|
+
# repository (install.rb's git_init_if_absent), so a successful update commits the
|
|
250
|
+
# core files it just re-synced. Empty commits are allowed: a same-version repair
|
|
251
|
+
# re-syncs nothing new, and that is still worth a ledger-adjacent commit.
|
|
252
|
+
def commit_core_files(target, runner: ->(cmd) { system(*cmd) })
|
|
253
|
+
runner.call(["git", "-C", plastic_home, "add", "PLASTIC.md", "scripts", "AGENTS.md", "VERSION",
|
|
254
|
+
"versions.json", "deprecations.yml", "config_asks.yml"])
|
|
255
|
+
runner.call(["git", "-C", plastic_home, "commit", "-m", "chore: update Plastic to #{target}",
|
|
256
|
+
"--allow-empty"])
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def clear_update_check_cache
|
|
260
|
+
FileUtils.rm_f(File.join(plastic_home, ".cache", "update-check.json"))
|
|
240
261
|
end
|
|
241
262
|
|
|
242
263
|
def show_help
|
package/scripts/write-handoff
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
#
|
|
17
17
|
# Exit codes: 0 done; 2 usage error.
|
|
18
18
|
|
|
19
|
+
require_relative "lib/store_layout"
|
|
19
20
|
require_relative "lib/session_ledger"
|
|
20
21
|
require_relative "lib/handoff"
|
|
21
22
|
|
|
@@ -48,7 +49,7 @@ usage_abort("--trigger is required (#{Handoff::TRIGGERS.join(', ')})") unless op
|
|
|
48
49
|
usage_abort("--trigger must be one of #{Handoff::TRIGGERS.join(', ')}") unless Handoff::TRIGGERS.include?(opts[:trigger])
|
|
49
50
|
|
|
50
51
|
plastic_home = expand(ENV.fetch("PLASTIC_HOME", "~/.plastic"))
|
|
51
|
-
store = opts[:store] ? expand(opts[:store]) :
|
|
52
|
+
store = opts[:store] ? expand(opts[:store]) : Plastic::StoreLayout.global_store(plastic_home)
|
|
52
53
|
templates = opts[:templates] ? expand(opts[:templates]) : File.expand_path("../templates", __dir__)
|
|
53
54
|
usage_abort("templates dir not found: #{templates}") unless Dir.exist?(templates)
|
|
54
55
|
|
package/templates/agents.md
CHANGED
|
@@ -42,7 +42,7 @@ Active/Future/Completed placement is managed in INDEX.md, not in frontmatter.
|
|
|
42
42
|
|
|
43
43
|
## Creating Tactical Intents
|
|
44
44
|
|
|
45
|
-
Create through the `plastic
|
|
45
|
+
Create through the `plastic intent new` command, which scaffolds with one call:
|
|
46
46
|
`ruby ~/.plastic/scripts/new-intent --store .plastic/store --intent "<one-line>" --slug <slug> [--sources <governing-id>]`.
|
|
47
47
|
It allocates the Folgezettel ID, creates `.plastic/store/ID--slug/ID--slug.md` born
|
|
48
48
|
complete, and wires the links. Never hand-author the files: `new-intent` validates the
|
|
@@ -54,11 +54,11 @@ Plastic has its own lifecycle skills. When a Plastic skill exists for the curren
|
|
|
54
54
|
|
|
55
55
|
| Phase | Skill | Produces |
|
|
56
56
|
|-------|-------|----------|
|
|
57
|
-
| What | `plastic
|
|
58
|
-
| Why | `plastic
|
|
59
|
-
| How | `plastic
|
|
60
|
-
| Exec | `plastic
|
|
61
|
-
| End | `plastic
|
|
57
|
+
| What | `plastic intent new` | Intent file |
|
|
58
|
+
| Why | `plastic intent spec` | Rulings as insights, `resources/*.md`, `spec.md` |
|
|
59
|
+
| How | `plastic intent spec` | `actions/`, `plan.md`, `checklist.md` |
|
|
60
|
+
| Exec | `plastic intent step` | Code + `outcome.md` |
|
|
61
|
+
| End | `plastic intent end` | Lifecycle transition |
|
|
62
62
|
|
|
63
63
|
**Artifact convention:** ALL lifecycle artifacts go to the active intent directory (`store/{id}--{slug}/`). Never write specs to `docs/superpowers/specs/` or plans to `docs/superpowers/plans/`.
|
|
64
64
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
body { max-width: 46rem; margin: 2rem auto; padding: 0 1rem; font: 16px/1.6 system-ui, sans-serif; color: #1f2933; background: #f4f5f7; }
|
|
2
|
+
h1, h2, h3 { line-height: 1.25; }
|
|
3
|
+
table { border-collapse: collapse; display: block; overflow-x: auto; }
|
|
4
|
+
th, td { border: 1px solid #9aa5b1; padding: 0.3rem 0.6rem; text-align: left; }
|
|
5
|
+
pre { background: #e4e7eb; padding: 0.8rem; overflow-x: auto; }
|
|
6
|
+
code { font-family: ui-monospace, monospace; }
|
|
7
|
+
@media (prefers-color-scheme: dark) {
|
|
8
|
+
body { color: #e4e7eb; background: #1f2933; }
|
|
9
|
+
pre { background: #323f4b; }
|
|
10
|
+
}
|
package/bin/plastic.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// Thin shim — npx entry point that dispatches a subcommand to the matching Ruby verb script.
|
|
4
|
-
// All logic lives in scripts/<verb>.rb. JS is only the distribution + dispatch mechanism.
|
|
5
|
-
//
|
|
6
|
-
// npx @zalom/plastic install [flags]
|
|
7
|
-
// npx @zalom/plastic update [flags]
|
|
8
|
-
// npx @zalom/plastic uninstall [flags]
|
|
9
|
-
// npx @zalom/plastic rollback [flags]
|
|
10
|
-
//
|
|
11
|
-
// Back-compat: a bare `--uninstall` (no subcommand) routes to uninstall with a deprecation
|
|
12
|
-
// warning; no subcommand at all defaults to install (legacy behaviour, one release).
|
|
13
|
-
|
|
14
|
-
import { execFileSync } from 'node:child_process'
|
|
15
|
-
import { resolve } from 'node:path'
|
|
16
|
-
import { existsSync } from 'node:fs'
|
|
17
|
-
|
|
18
|
-
const VERBS = ['install', 'update', 'uninstall', 'rollback']
|
|
19
|
-
const packageRoot = new URL('..', import.meta.url).pathname
|
|
20
|
-
const argv = process.argv.slice(2)
|
|
21
|
-
|
|
22
|
-
let verb
|
|
23
|
-
let rest
|
|
24
|
-
|
|
25
|
-
if (VERBS.includes(argv[0])) {
|
|
26
|
-
verb = argv[0]
|
|
27
|
-
rest = argv.slice(1)
|
|
28
|
-
} else if (argv.includes('--uninstall')) {
|
|
29
|
-
// Deprecated: `--uninstall` as a flag instead of the `uninstall` subcommand.
|
|
30
|
-
console.error('! plastic: `--uninstall` is deprecated — use `npx @zalom/plastic uninstall`. (works for now)')
|
|
31
|
-
verb = 'uninstall'
|
|
32
|
-
rest = argv.filter((a) => a !== '--uninstall')
|
|
33
|
-
} else {
|
|
34
|
-
// No subcommand: legacy default to install (one release of grace).
|
|
35
|
-
verb = 'install'
|
|
36
|
-
rest = argv
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const script = resolve(packageRoot, 'scripts', `${verb}.rb`)
|
|
40
|
-
|
|
41
|
-
if (!existsSync(script)) {
|
|
42
|
-
console.error(`Error: scripts/${verb}.rb not found in package.`)
|
|
43
|
-
process.exit(1)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
execFileSync('ruby', [script, ...rest], {
|
|
48
|
-
stdio: 'inherit',
|
|
49
|
-
// Clear RUBYOPT last, after the spread, so a global setting (for example --yjit)
|
|
50
|
-
// cannot reach a ruby that does not know the flag. A machine with an old ruby
|
|
51
|
-
// must get preflight's real message, not a crash.
|
|
52
|
-
env: { ...process.env, PLASTIC_PACKAGE_ROOT: packageRoot, RUBYOPT: '' },
|
|
53
|
-
})
|
|
54
|
-
} catch (err) {
|
|
55
|
-
if (err.status) process.exit(err.status)
|
|
56
|
-
// We only get here when ruby could not be executed at all. A ruby that runs but is
|
|
57
|
-
// too old exits with a status, handled on the line above, and prints its own real
|
|
58
|
-
// message from scripts/lib/preflight.rb naming the version actually found.
|
|
59
|
-
if (err.code === 'ENOENT') {
|
|
60
|
-
console.error('Plastic needs Ruby 3.0.0 or newer to run its scripts (ruby was not found on PATH).')
|
|
61
|
-
console.error('Install a pinned Ruby with mise:')
|
|
62
|
-
console.error(' curl https://mise.run | sh # only if mise is not installed yet')
|
|
63
|
-
console.error(' mise use --global ruby@3.3')
|
|
64
|
-
console.error('Then re-run the Plastic installer.')
|
|
65
|
-
} else {
|
|
66
|
-
console.error(`Plastic could not run ruby: ${err.message}`)
|
|
67
|
-
console.error('Check that `ruby -v` works in this shell, then re-run the Plastic installer.')
|
|
68
|
-
}
|
|
69
|
-
process.exit(1)
|
|
70
|
-
}
|