@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,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Plastic::CLI::TABLE (intent 363) - the whole command line in one frozen hash:
|
|
4
|
+
# a command name mapped to the file that holds it, the class inside
|
|
5
|
+
# Plastic::CLI::Commands, and the one line `plastic help` prints beside it.
|
|
6
|
+
#
|
|
7
|
+
# Adding a command is one row here plus one file. `plastic help` reads this
|
|
8
|
+
# hash and loads nothing, so listing the commands costs no command file.
|
|
9
|
+
# test/cli/table_test.rb walks every row, so a half-wired command cannot ship.
|
|
10
|
+
module Plastic
|
|
11
|
+
class CLI
|
|
12
|
+
TABLE = {
|
|
13
|
+
"auto brief" => ["commands/auto_brief", "AutoBrief", "Print an intent's spawn preamble, and the advisor shapes for --role advisor"],
|
|
14
|
+
"auto lock" => ["commands/auto_lock", "AutoLock", "Inspect, repair, or release an intent's delivery lock"],
|
|
15
|
+
"auto report" => ["commands/auto_report", "AutoReport", "Print an intent's completion report, then the review-by-risk rules"],
|
|
16
|
+
"auto take" => ["commands/auto_take", "AutoTake", "Arm an intent's delivery lock for this session"],
|
|
17
|
+
"auto" => ["commands/auto", "Auto", "List the auto subcommands"],
|
|
18
|
+
"backup" => ["commands/backup", "Backup", "Write one archive of the three databases and the config"],
|
|
19
|
+
"checkout" => ["commands/checkout", "Checkout", "Restore missing store files from the databases"],
|
|
20
|
+
"continue" => ["commands/continue", "Continue", "Show where one project stands and what runs next"],
|
|
21
|
+
"doctor" => ["commands/doctor", "Doctor", "Diagnose Plastic installation health"],
|
|
22
|
+
"feedback" => ["commands/feedback", "Feedback", "File a feedback report, body on standard input"],
|
|
23
|
+
"help" => ["commands/help", "Help", "List the commands and help topics, or show one usage"],
|
|
24
|
+
"hook" => ["commands/hook", "Hook", "Run one harness hook event through its launcher"],
|
|
25
|
+
"index" => ["commands/index", "Index", "Rebuild the search index from every markdown file in the stores"],
|
|
26
|
+
"install" => ["commands/install", "Install", "Install Plastic into this machine's agents"],
|
|
27
|
+
"intent" => ["commands/intent", "Intent", "List the intent subcommands"],
|
|
28
|
+
"intent answer" => ["commands/intent_answer", "IntentAnswer", "Answer an intent's needs_decision node"],
|
|
29
|
+
"intent end" => ["commands/intent_end", "IntentEnd", "Close an intent as delivered or abandoned"],
|
|
30
|
+
"intent new" => ["commands/intent_new", "IntentNew", "Create a new intent"],
|
|
31
|
+
"intent note" => ["commands/intent_note", "IntentNote", "Append a savepoint note to an intent"],
|
|
32
|
+
"intent rule" => ["commands/intent_rule", "IntentRule", "Record a ruling in an intent's Insights"],
|
|
33
|
+
"intent show" => ["commands/intent_show", "IntentShow", "Print an intent's state screen"],
|
|
34
|
+
"intent spec" => ["commands/intent_spec", "IntentSpec", "Print an intent's state screen, then the speccing rules"],
|
|
35
|
+
"intent step" => ["commands/intent_step", "IntentStep", "Run the next ready step of an intent's graph"],
|
|
36
|
+
"intent verify" => ["commands/intent_verify", "IntentVerify", "Run an intent's merge-gate checks"],
|
|
37
|
+
"migrate" => ["commands/migrate", "Migrate", "List the migrate subcommands"],
|
|
38
|
+
"migrate stores" => ["commands/migrate_stores", "MigrateStores", "Move the stores under stores/, behind a full copy of the home"],
|
|
39
|
+
"next" => ["commands/next", "Next", "Print the next action in one line"],
|
|
40
|
+
"project" => ["commands/project", "Project", "List the project subcommands"],
|
|
41
|
+
"project links" => ["commands/project_links", "ProjectLinks", "Project every store's Links sections from frontmatter"],
|
|
42
|
+
"project list" => ["commands/project_list", "ProjectList", "List every store this machine holds"],
|
|
43
|
+
"project new" => ["commands/project_new", "ProjectNew", "Register, provision and validate a new project"],
|
|
44
|
+
"query" => ["commands/query", "Query", "Run one read-only SQL statement on the index"],
|
|
45
|
+
"render" => ["commands/render", "Render", "Print one markdown file as an HTML page"],
|
|
46
|
+
"roadmap" => ["commands/roadmap", "Roadmap", "List the roadmap subcommands"],
|
|
47
|
+
"roadmap check" => ["commands/roadmap_check", "RoadmapCheck", "Check a roadmap's graph for cycles and dangling ids"],
|
|
48
|
+
"roadmap log" => ["commands/roadmap_log", "RoadmapLog", "Append a savepoint line to a roadmap's ledger"],
|
|
49
|
+
"roadmap next" => ["commands/roadmap_next", "RoadmapNext", "Name the roadmap most worth continuing"],
|
|
50
|
+
"roadmap show" => ["commands/roadmap_show", "RoadmapShow", "Print a roadmap's state screen"],
|
|
51
|
+
"rollback" => ["commands/rollback", "Rollback", "Move to a Plastic version this machine has run before"],
|
|
52
|
+
"search" => ["commands/search", "Search", "Find text in the stores, ranked, with one excerpt each"],
|
|
53
|
+
"session commit" => ["commands/session_commit", "SessionCommit", "Commit one verified checklist item"],
|
|
54
|
+
"session handoff" => ["commands/session_handoff", "SessionHandoff", "Write this session's hand-off into the day ledger"],
|
|
55
|
+
"session summary" => ["commands/session_summary", "SessionSummary", "Print the day ledger's open items and recent activity"],
|
|
56
|
+
"session" => ["commands/session", "Session", "List the session subcommands"],
|
|
57
|
+
"status" => ["commands/status", "Status", "Show active work in every store"],
|
|
58
|
+
"sync" => ["commands/sync", "Sync", "Bring the store files and the three databases level"],
|
|
59
|
+
"uninstall" => ["commands/uninstall", "Uninstall", "Remove Plastic from this machine's agents"],
|
|
60
|
+
"update" => ["commands/update", "Update", "Move Plastic to the next version on its channel"],
|
|
61
|
+
"version" => ["commands/version", "Version", "Print the installed Plastic version"]
|
|
62
|
+
}.freeze
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "did_you_mean"
|
|
5
|
+
require_relative "cli/table"
|
|
6
|
+
require_relative "cli/command"
|
|
7
|
+
require_relative "cli/output"
|
|
8
|
+
|
|
9
|
+
# Plastic::CLI (intent 363) - the dispatcher behind bin/plastic. It reads the
|
|
10
|
+
# command table, matches the longest command name the arguments begin with,
|
|
11
|
+
# requires that one file, and calls it. Nothing else is loaded, which is what
|
|
12
|
+
# keeps a call at about 20 milliseconds with RubyGems off.
|
|
13
|
+
#
|
|
14
|
+
# No arguments, or --help, print the command list. --version prints the
|
|
15
|
+
# version. A command's own --help prints that command's usage line, read from
|
|
16
|
+
# the class without building it. An unknown name exits 2 and names the closest
|
|
17
|
+
# command, so a typo never becomes a stack trace.
|
|
18
|
+
module Plastic
|
|
19
|
+
class CLI
|
|
20
|
+
FLAGS = {"--version" => "version", "-v" => "version",
|
|
21
|
+
"--help" => "help", "-h" => "help"}.freeze
|
|
22
|
+
|
|
23
|
+
def self.call(...)
|
|
24
|
+
new(...).call
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def initialize(argv, out: $stdout, err: $stderr, env: ENV, home: Dir.home,
|
|
28
|
+
directory: Dir.pwd, table: TABLE)
|
|
29
|
+
@argv = argv.dup
|
|
30
|
+
@out = out
|
|
31
|
+
@err = err
|
|
32
|
+
@env = env
|
|
33
|
+
@home = home
|
|
34
|
+
@directory = directory
|
|
35
|
+
@table = table
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def call
|
|
39
|
+
matched = match(expanded)
|
|
40
|
+
return unknown(expanded.first) unless matched
|
|
41
|
+
|
|
42
|
+
name, rest = matched
|
|
43
|
+
return usage(name, rest) if help?(rest)
|
|
44
|
+
|
|
45
|
+
command(name).call(rest, out: @out, err: @err, env: @env, home: @home,
|
|
46
|
+
directory: @directory)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def match(argv)
|
|
50
|
+
[2, 1].each do |width|
|
|
51
|
+
name = argv.first(width).join(" ")
|
|
52
|
+
return [name, argv.drop(width)] if @table.key?(name)
|
|
53
|
+
end
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def expanded
|
|
60
|
+
return ["help"] if @argv.empty?
|
|
61
|
+
|
|
62
|
+
[FLAGS.fetch(@argv.first, @argv.first)] + @argv.drop(1)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def help?(rest)
|
|
66
|
+
rest.any? { |argument| FLAGS[argument] == "help" }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def usage(name, rest)
|
|
70
|
+
Output.new(out: @out, err: @err)
|
|
71
|
+
.row("usage", command(name)::USAGE_LINE)
|
|
72
|
+
.flush(json: rest.include?("--json"))
|
|
73
|
+
Command::OK
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def command(name)
|
|
77
|
+
file, const, = @table.fetch(name)
|
|
78
|
+
require_relative "cli/#{file}"
|
|
79
|
+
Commands.const_get(const)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def unknown(name)
|
|
83
|
+
suggestions = closest(name)
|
|
84
|
+
@err.puts "plastic: no command named #{name.inspect}"
|
|
85
|
+
@err.puts "Closest: #{suggestions.join(", ")}" unless suggestions.empty?
|
|
86
|
+
@err.puts "Run `plastic help` for the command list."
|
|
87
|
+
Command::USAGE
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def closest(name)
|
|
91
|
+
DidYouMean::SpellChecker.new(dictionary: @table.keys).correct(name)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -28,7 +28,15 @@ module CompactInstructions
|
|
|
28
28
|
# as stale, so the block states the shipped numbers and names the two keys as the
|
|
29
29
|
# override instead. It names the hand-off in words and never by path, so it reads
|
|
30
30
|
# correctly whether or not the hand-off writer is installed.
|
|
31
|
+
# The `@` line is a Claude Code import: the harness reads the named file into the
|
|
32
|
+
# session. It is the whole of ruling D43, that PLASTIC.md is the only instruction text
|
|
33
|
+
# Plastic carries. It names the installed copy under the Plastic home, because a bare
|
|
34
|
+
# `@PLASTIC.md` would resolve against ~/.claude, where no such file exists.
|
|
31
35
|
BODY = <<~MD.freeze
|
|
36
|
+
Plastic runs from one command, `plastic`. Its instructions are one page, imported here:
|
|
37
|
+
|
|
38
|
+
@~/.plastic/PLASTIC.md
|
|
39
|
+
|
|
32
40
|
Plastic watches this session's context. When the harness reports how much of the
|
|
33
41
|
window is used:
|
|
34
42
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
# heartbeat. Never the raw ledger (296 D36). No environment reads; every
|
|
9
9
|
# path is injected.
|
|
10
10
|
|
|
11
|
+
require_relative "store_layout"
|
|
11
12
|
require "time"
|
|
12
13
|
require_relative "session_ledger"
|
|
13
14
|
require_relative "handoff"
|
|
@@ -82,10 +83,10 @@ module DaySummary
|
|
|
82
83
|
# The global store plus every projects/<slug>/store, each with its INDEX
|
|
83
84
|
# one level up, the way doctor enumerates them.
|
|
84
85
|
def stores(home)
|
|
85
|
-
list = [[File.join(home, "INDEX.md"),
|
|
86
|
-
projects_root =
|
|
86
|
+
list = [[File.join(Plastic::StoreLayout.global_root(home), "INDEX.md"), Plastic::StoreLayout.global_store(home)]]
|
|
87
|
+
projects_root = Plastic::StoreLayout.projects_root(home)
|
|
87
88
|
if File.directory?(projects_root)
|
|
88
|
-
|
|
89
|
+
Plastic::StoreLayout.project_slugs(home).each do |slug|
|
|
89
90
|
list << [File.join(projects_root, slug, "INDEX.md"), File.join(projects_root, slug, "store")]
|
|
90
91
|
end
|
|
91
92
|
end
|
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
# the store, conventions, intent and CLI halves, so Doctor stays one class with
|
|
9
9
|
# one public surface and one definition of which checks are core (intent 36a).
|
|
10
10
|
|
|
11
|
+
require_relative "store_layout"
|
|
11
12
|
require "json"
|
|
12
13
|
require "yaml"
|
|
13
14
|
require "time"
|
|
14
15
|
require "digest"
|
|
15
|
-
require "rubygems"
|
|
16
16
|
|
|
17
|
+
require_relative "version_number"
|
|
17
18
|
require_relative "hook_registry"
|
|
18
19
|
require_relative "compact_instructions"
|
|
19
20
|
|
|
@@ -301,7 +302,7 @@ class Doctor
|
|
|
301
302
|
return checks
|
|
302
303
|
end
|
|
303
304
|
|
|
304
|
-
index_path = File.join(plastic_home, "INDEX.md")
|
|
305
|
+
index_path = File.join(Plastic::StoreLayout.global_root(plastic_home), "INDEX.md")
|
|
305
306
|
if File.exist?(index_path)
|
|
306
307
|
checks << check(
|
|
307
308
|
category: "global_store", name: "global_index_reachable", status: "pass",
|
|
@@ -517,9 +518,6 @@ class Doctor
|
|
|
517
518
|
checks << hooks_entries_owned_check(settings)
|
|
518
519
|
end
|
|
519
520
|
|
|
520
|
-
# skills_exist — flat, hyphen-namespaced personal skills (plastic-<name>/)
|
|
521
|
-
checks << flat_skills_check(agent_dir, "--claude")
|
|
522
|
-
|
|
523
521
|
# stray_skills — installed plastic-* skill dir with no manifest entry (a leftover,
|
|
524
522
|
# e.g. an old-name copy after a rename; intent 158a AC15)
|
|
525
523
|
checks << stray_skills_check(agent_dir, "--claude", File.join(agent_dir, "plastic", "manifest.json"))
|
|
@@ -630,24 +628,6 @@ class Doctor
|
|
|
630
628
|
)
|
|
631
629
|
end
|
|
632
630
|
|
|
633
|
-
def flat_skills_check(agent_dir, installer_flag)
|
|
634
|
-
skills_root = File.join(agent_dir, "skills")
|
|
635
|
-
found = Dir.glob(File.join(skills_root, "plastic-*", "SKILL.md"))
|
|
636
|
-
|
|
637
|
-
if !found.empty?
|
|
638
|
-
check(
|
|
639
|
-
category: "agent_registration", name: "skills_exist", status: "pass",
|
|
640
|
-
message: "#{found.size} plastic-* skill(s) installed in #{tilde(skills_root)}"
|
|
641
|
-
)
|
|
642
|
-
else
|
|
643
|
-
check(
|
|
644
|
-
category: "agent_registration", name: "skills_exist", status: "fail",
|
|
645
|
-
message: "No plastic-* skills found in #{tilde(skills_root)}",
|
|
646
|
-
fixable: true, fix_hint: "Re-run the Plastic installer: npx @zalom/plastic@latest #{installer_flag}"
|
|
647
|
-
)
|
|
648
|
-
end
|
|
649
|
-
end
|
|
650
|
-
|
|
651
631
|
# Manifest-diff stray-skill check (intent 158a), extended (intent 276) to
|
|
652
632
|
# never vanish on a missing manifest and to name the reserved-prefix rule.
|
|
653
633
|
def stray_skills_check(agent_dir, installer_flag, manifest_path)
|
|
@@ -707,9 +687,6 @@ class Doctor
|
|
|
707
687
|
def check_flat_skills_and_stray(agent_key, agent_dir)
|
|
708
688
|
checks = []
|
|
709
689
|
|
|
710
|
-
# For codex/hermes: just check skills exist (no settings.json hooks)
|
|
711
|
-
checks << flat_skills_check(agent_dir, "--#{agent_key}")
|
|
712
|
-
|
|
713
690
|
# stray_skills — installed plastic-* skill dir with no manifest entry (a leftover,
|
|
714
691
|
# e.g. an old-name copy after a rename; intent 158a AC15)
|
|
715
692
|
checks << stray_skills_check(agent_dir, "--#{agent_key}", File.join(agent_dir, "plastic", "manifest.json"))
|
|
@@ -1061,9 +1038,7 @@ class Doctor
|
|
|
1061
1038
|
end
|
|
1062
1039
|
|
|
1063
1040
|
def safe_version(str)
|
|
1064
|
-
|
|
1065
|
-
rescue ArgumentError
|
|
1066
|
-
nil
|
|
1041
|
+
VersionNumber.parse(str)
|
|
1067
1042
|
end
|
|
1068
1043
|
|
|
1069
1044
|
def check_core_files(agent_key, include_drift: true)
|
|
@@ -1167,7 +1142,7 @@ class Doctor
|
|
|
1167
1142
|
"#{tilde(agent_version_path)}: #{agent_version}",
|
|
1168
1143
|
],
|
|
1169
1144
|
fixable: true,
|
|
1170
|
-
fix_hint: "Re-sync the stale harness: npx @zalom/plastic@latest install --reinstall <flag>, or plastic
|
|
1145
|
+
fix_hint: "Re-sync the stale harness: npx @zalom/plastic@latest install --reinstall <flag>, or `plastic rollback` to a prior version"
|
|
1171
1146
|
)
|
|
1172
1147
|
end
|
|
1173
1148
|
else
|
|
@@ -1175,7 +1150,7 @@ class Doctor
|
|
|
1175
1150
|
category: "core_files", name: "version_match", status: "warn",
|
|
1176
1151
|
message: "Agent-side VERSION file not found at #{tilde(agent_version_path)}",
|
|
1177
1152
|
fixable: true,
|
|
1178
|
-
fix_hint: "Re-sync the stale harness: npx @zalom/plastic@latest install --reinstall <flag>, or plastic
|
|
1153
|
+
fix_hint: "Re-sync the stale harness: npx @zalom/plastic@latest install --reinstall <flag>, or `plastic rollback` to a prior version"
|
|
1179
1154
|
)
|
|
1180
1155
|
end
|
|
1181
1156
|
end
|
|
@@ -1280,7 +1255,7 @@ class Doctor
|
|
|
1280
1255
|
|
|
1281
1256
|
DISPLAY_HOOK_FIX_HINT = "Re-run the Plastic installer to repair the hook registration: " \
|
|
1282
1257
|
"npx @zalom/plastic@<channel> install --reinstall --claude " \
|
|
1283
|
-
"(plastic
|
|
1258
|
+
"(plastic install --reinstall)".freeze
|
|
1284
1259
|
|
|
1285
1260
|
# display_hook_registered (intent 331e, D1, category "display"): the Claude
|
|
1286
1261
|
# settings carry the plastic-message-display command, on-disk, executable.
|
|
@@ -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 "time"
|
|
5
6
|
require_relative "session_ledger"
|
|
6
7
|
|
|
@@ -38,7 +39,7 @@ module DoctorSessionLedger
|
|
|
38
39
|
def check_session_ledger(scopes: nil, now: Time.now)
|
|
39
40
|
return [] unless scopes.nil? || scopes.include?("global")
|
|
40
41
|
|
|
41
|
-
store_dir =
|
|
42
|
+
store_dir = Plastic::StoreLayout.global_store(plastic_home)
|
|
42
43
|
orphans = orphaned_session_dirs(store_dir, now)
|
|
43
44
|
shape = day_ledger_shape_problems(store_dir)
|
|
44
45
|
|
|
@@ -18,7 +18,7 @@ require "fileutils"
|
|
|
18
18
|
# Trust model: this class never sends anything anywhere. `compose` returns a
|
|
19
19
|
# Result carrying a local file path and a browser URL; only the human, in
|
|
20
20
|
# their own authenticated browser, submits it. There is no send method here
|
|
21
|
-
# and there must never be one (see
|
|
21
|
+
# and there must never be one (see SECURITY.md).
|
|
22
22
|
class FeedbackReport
|
|
23
23
|
GITHUB_REPO = "zalom/plastic"
|
|
24
24
|
CAP_BYTES = 7500
|
|
@@ -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 "tmpdir"
|
|
6
7
|
require_relative "node_ledger"
|
|
@@ -112,7 +113,8 @@ module GraphMeasureModels
|
|
|
112
113
|
# purpose - two different silences over the same store, agreeing with
|
|
113
114
|
# each other only by accident.
|
|
114
115
|
def project_config_path(store_dir)
|
|
115
|
-
|
|
116
|
+
home, slug = Plastic::StoreLayout.locate(store_dir.to_s)
|
|
117
|
+
File.join((slug == Plastic::StoreLayout::GLOBAL) ? home : File.dirname(store_dir.to_s), "config.yml")
|
|
116
118
|
end
|
|
117
119
|
|
|
118
120
|
# --- rendering ---------------------------------------------------------------
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
# plain hyphen as the id/title separator on READ; every WRITE still emits the
|
|
11
11
|
# real em dash (D10). The separator is built from the codepoint, not a
|
|
12
12
|
# literal byte, so this file stays em-dash free.
|
|
13
|
+
require_relative "atomic_write"
|
|
14
|
+
|
|
13
15
|
module IndexEntry
|
|
14
16
|
module_function
|
|
15
17
|
|
|
@@ -22,6 +24,13 @@ module IndexEntry
|
|
|
22
24
|
line.to_s.match(ENTRY_RE)
|
|
23
25
|
end
|
|
24
26
|
|
|
27
|
+
def add_active(index_path, dir_name:, title:)
|
|
28
|
+
id = dir_name.split("--").first
|
|
29
|
+
line = "- [#{id} #{EM_DASH} #{title}](store/#{dir_name}/#{dir_name}.md)\n"
|
|
30
|
+
text = File.read(index_path, encoding: "UTF-8")
|
|
31
|
+
AtomicWrite.write(index_path, text.sub(/^## Active\n/) { |heading| heading + line })
|
|
32
|
+
end
|
|
33
|
+
|
|
25
34
|
# True iff the intent is Active in its store's INDEX.md, which lives at the
|
|
26
35
|
# PARENT of the store/ dir. Non-raising: any failure (missing or unreadable
|
|
27
36
|
# INDEX, bad arg) returns false. `index_active_ids` is a pure-data test
|
|
@@ -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 "fileutils"
|
|
@@ -12,6 +13,7 @@ require_relative "agent_models"
|
|
|
12
13
|
require_relative "harness_text"
|
|
13
14
|
require_relative "compact_instructions"
|
|
14
15
|
require_relative "engine_permissions"
|
|
16
|
+
require_relative "qmd_sync"
|
|
15
17
|
|
|
16
18
|
# Shared installer machinery, instantiable with injected package root / store / agent
|
|
17
19
|
# map so the verb scripts (install/update/uninstall/rollback) and their tests can run
|
|
@@ -45,23 +47,21 @@ class InstallerCore
|
|
|
45
47
|
CLAUDE_SECTION_END = "<!-- END PLASTIC COMPACT -->"
|
|
46
48
|
CLAUDE_SECTION_RE = /^<!-- BEGIN PLASTIC COMPACT.*?-->\n.*?\n<!-- END PLASTIC COMPACT -->\n?/m
|
|
47
49
|
|
|
48
|
-
# Curated essentials plus a pointer to ~/.plastic/PLASTIC.md and the
|
|
49
|
-
#
|
|
50
|
-
# a shared file Codex merges from multiple sources,
|
|
51
|
-
# hand-curated pointer rather than embedding the core
|
|
52
|
-
# because it only ever points, never duplicates.
|
|
50
|
+
# Curated essentials plus a pointer to ~/.plastic/PLASTIC.md and the help topics
|
|
51
|
+
# `plastic help TOPIC` prints, injected into ~/.codex/AGENTS.md. Not a slice of
|
|
52
|
+
# PLASTIC.md itself: AGENTS.md is a shared file Codex merges from multiple sources,
|
|
53
|
+
# so this block stays a small, hand-curated pointer rather than embedding the core
|
|
54
|
+
# wholesale, and it never drifts because it only ever points, never duplicates.
|
|
53
55
|
CODEX_AGENTS_MD_BODY = <<~MD.freeze
|
|
54
56
|
Plastic is installed for this agent. Plastic is intent-driven state management: work runs
|
|
55
57
|
in one of three modes, direct, thinking, or auto (a team drives the runner loop:
|
|
56
58
|
`runner step`, `status`, `answer`). Do not jump straight to code.
|
|
57
59
|
|
|
58
60
|
Standing rules:
|
|
59
|
-
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
- Operational procedures are
|
|
63
|
-
plastic-<name>/SKILL.md). Invoke one explicitly as $plastic-<name> (for example
|
|
64
|
-
$plastic-doctor), or let Codex pick one implicitly by matching its description.
|
|
61
|
+
- The command line is ~/.plastic/PLASTIC.md. Read it and follow it exactly. The
|
|
62
|
+
conventions are chapters `plastic help TOPIC` prints on demand. Both are
|
|
63
|
+
generated and overwritten on Plastic updates, so never edit them.
|
|
64
|
+
- Operational procedures are the `plastic` command line itself, run directly.
|
|
65
65
|
- Intents, specs, plans, checklists, and outcomes live under ~/.plastic/, never in
|
|
66
66
|
the project tree.
|
|
67
67
|
|
|
@@ -352,12 +352,36 @@ class InstallerCore
|
|
|
352
352
|
end
|
|
353
353
|
end
|
|
354
354
|
|
|
355
|
+
# The command line (intent 363). Glob-derived for the reason screen_files is:
|
|
356
|
+
# a command is one row in scripts/lib/cli/table.rb plus one file, and a
|
|
357
|
+
# hand-written manifest would make it two files and a diff. The launcher
|
|
358
|
+
# bin/plastic requires scripts/lib/cli.rb by a relative path, so the whole
|
|
359
|
+
# subtree travels together or an installed copy LoadErrors on the first call.
|
|
360
|
+
def cli_files
|
|
361
|
+
Dir.glob(File.join(package_root, "scripts", "lib", "{cli,rlm}", "**", "*.rb")).each_with_object({}) do |path, acc|
|
|
362
|
+
rel = path.sub("#{package_root}/", "")
|
|
363
|
+
acc[rel] = rel
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# `plastic help TOPIC` (intent 372, family 4) reads docs/help/TOPIC.md from
|
|
368
|
+
# package_root the same way it reads a command's file, so the chapter has to
|
|
369
|
+
# ship the same way a template does: glob-derived, one file added and it
|
|
370
|
+
# installs with no diff here.
|
|
371
|
+
def help_files
|
|
372
|
+
Dir.glob(File.join(package_root, "docs", "help", "*.md")).each_with_object({}) do |path, acc|
|
|
373
|
+
rel = File.join("docs", "help", File.basename(path))
|
|
374
|
+
acc[rel] = rel
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
355
378
|
# Files copied into ~/.plastic on install/update. Every verb script + the shared lib
|
|
356
379
|
# must be here so the installed ~/.plastic/scripts copy is self-complete (sync-guarded
|
|
357
380
|
# by install_sync_test). The templates and screen-kind halves are glob-derived
|
|
358
381
|
# (template_files, screen_files above); the rest stays a hand-written literal.
|
|
359
382
|
def core_files
|
|
360
|
-
hand_registered_files.merge(template_files).merge(hook_files).merge(screen_files)
|
|
383
|
+
hand_registered_files.merge(template_files).merge(hook_files).merge(screen_files).merge(cli_files)
|
|
384
|
+
.merge(help_files)
|
|
361
385
|
end
|
|
362
386
|
|
|
363
387
|
def hand_registered_files
|
|
@@ -391,6 +415,9 @@ class InstallerCore
|
|
|
391
415
|
"scripts/plastic-lock" => "scripts/plastic-lock",
|
|
392
416
|
"scripts/lib/hook_registry.rb" => "scripts/lib/hook_registry.rb",
|
|
393
417
|
"scripts/lib/compact_instructions.rb" => "scripts/lib/compact_instructions.rb",
|
|
418
|
+
"scripts/lib/version_number.rb" => "scripts/lib/version_number.rb",
|
|
419
|
+
"scripts/lib/cli.rb" => "scripts/lib/cli.rb",
|
|
420
|
+
"bin/plastic" => "bin/plastic",
|
|
394
421
|
"scripts/agent-report" => "scripts/agent-report",
|
|
395
422
|
"scripts/lib/insights.rb" => "scripts/lib/insights.rb",
|
|
396
423
|
"scripts/insight-append" => "scripts/insight-append",
|
|
@@ -406,6 +433,14 @@ class InstallerCore
|
|
|
406
433
|
"scripts/lib/intent_validator.rb" => "scripts/lib/intent_validator.rb",
|
|
407
434
|
"scripts/lib/graph_rebuild.rb" => "scripts/lib/graph_rebuild.rb",
|
|
408
435
|
"scripts/lib/store_discovery.rb" => "scripts/lib/store_discovery.rb",
|
|
436
|
+
"scripts/lib/store_layout.rb" => "scripts/lib/store_layout.rb",
|
|
437
|
+
"scripts/lib/stores_move.rb" => "scripts/lib/stores_move.rb",
|
|
438
|
+
"scripts/lib/sqlite.rb" => "scripts/lib/sqlite.rb",
|
|
439
|
+
"scripts/lib/search_index.rb" => "scripts/lib/search_index.rb",
|
|
440
|
+
"scripts/lib/store_sync.rb" => "scripts/lib/store_sync.rb",
|
|
441
|
+
"scripts/lib/work_graph.rb" => "scripts/lib/work_graph.rb",
|
|
442
|
+
"scripts/lib/reference_archive.rb" => "scripts/lib/reference_archive.rb",
|
|
443
|
+
"scripts/lib/backup.rb" => "scripts/lib/backup.rb",
|
|
409
444
|
"scripts/lib/frontmatter_writer.rb" => "scripts/lib/frontmatter_writer.rb",
|
|
410
445
|
"scripts/lib/links_projection.rb" => "scripts/lib/links_projection.rb",
|
|
411
446
|
"scripts/lib/links_section.rb" => "scripts/lib/links_section.rb",
|
|
@@ -624,7 +659,7 @@ class InstallerCore
|
|
|
624
659
|
def bootstrap
|
|
625
660
|
puts " \u{1f331} First install \u{2014} bootstrapping store..."
|
|
626
661
|
|
|
627
|
-
FileUtils.mkdir_p(
|
|
662
|
+
FileUtils.mkdir_p(Plastic::StoreLayout.global_store(plastic_home))
|
|
628
663
|
FileUtils.mkdir_p(File.join(plastic_home, "projects"))
|
|
629
664
|
|
|
630
665
|
write_if_missing(File.join(plastic_home, "config.yml"), <<~YAML)
|
|
@@ -643,7 +678,7 @@ class InstallerCore
|
|
|
643
678
|
|
|
644
679
|
write_if_missing(File.join(plastic_home, "projects.yml"), "---\nprojects: {}\n")
|
|
645
680
|
|
|
646
|
-
write_if_missing(File.join(plastic_home, "INDEX.md"), <<~MD)
|
|
681
|
+
write_if_missing(File.join(Plastic::StoreLayout.global_root(plastic_home), "INDEX.md"), <<~MD)
|
|
647
682
|
# Index
|
|
648
683
|
|
|
649
684
|
## Active
|
|
@@ -661,7 +696,7 @@ class InstallerCore
|
|
|
661
696
|
# Plastic: Agent Instructions
|
|
662
697
|
|
|
663
698
|
Read `PLASTIC.md` in this directory for the core conventions; deeper doctrine lives
|
|
664
|
-
in the `plastic
|
|
699
|
+
in the chapters `plastic help TOPIC` prints. Follow it exactly. Never modify it:
|
|
665
700
|
it is overwritten on plugin updates.
|
|
666
701
|
|
|
667
702
|
This file (`AGENTS.md`) is where project-specific rules live.
|
|
@@ -672,6 +707,25 @@ class InstallerCore
|
|
|
672
707
|
puts " \u{2705} Store bootstrapped"
|
|
673
708
|
end
|
|
674
709
|
|
|
710
|
+
# Intent 372 (former install skill, lines 119-127): `~/.plastic` becomes its own
|
|
711
|
+
# git repository the first time it exists, so `update` can later commit the core
|
|
712
|
+
# files it re-syncs. Leaves an existing repository alone.
|
|
713
|
+
def git_init_if_absent(runner: ->(cmd) { system(*cmd) })
|
|
714
|
+
FileUtils.mkdir_p(plastic_home)
|
|
715
|
+
return if File.directory?(File.join(plastic_home, ".git"))
|
|
716
|
+
runner.call(["git", "-C", plastic_home, "init", "-q"])
|
|
717
|
+
end
|
|
718
|
+
|
|
719
|
+
# Intent 372 (former install skill, lines 167-179): register every Plastic store as
|
|
720
|
+
# a QMD collection. QmdSync.register already no-ops per store when QMD is absent;
|
|
721
|
+
# the detector is injected too so a run stays hermetic on a machine that happens to
|
|
722
|
+
# have the real `qmd` binary on PATH.
|
|
723
|
+
def register_with_qmd(runner: QmdSync.default_runner, detector: QmdSync.method(:detect))
|
|
724
|
+
QmdSync.enumerate_stores(plastic_home: plastic_home).each do |store|
|
|
725
|
+
QmdSync.register(collection: store[:collection], dir: store[:dir], runner: runner, detector: detector)
|
|
726
|
+
end
|
|
727
|
+
end
|
|
728
|
+
|
|
675
729
|
# --- Agent adapters ---
|
|
676
730
|
|
|
677
731
|
# Uniform per-agent install record dir: <agent-dir>/plastic/ holds VERSION and
|
|
@@ -943,9 +997,9 @@ class InstallerCore
|
|
|
943
997
|
# Every store root (the global store, plus each project store) whose real
|
|
944
998
|
# path is reached with no symlink between it and plastic_home.
|
|
945
999
|
def real_store_roots(plastic_home)
|
|
946
|
-
roots = [
|
|
947
|
-
Dir.glob(File.join(plastic_home, "
|
|
948
|
-
roots.select { |root| real_store_root?(plastic_home, root) }
|
|
1000
|
+
roots = [Plastic::StoreLayout.global_store(plastic_home)] +
|
|
1001
|
+
Dir.glob(File.join(Plastic::StoreLayout.projects_root(plastic_home), "*", "store"))
|
|
1002
|
+
roots.uniq.select { |root| real_store_root?(plastic_home, root) }
|
|
949
1003
|
end
|
|
950
1004
|
|
|
951
1005
|
def real_store_root?(plastic_home, store_root)
|
|
@@ -1020,7 +1074,7 @@ class InstallerCore
|
|
|
1020
1074
|
# fails milder than the bug it prevents, independent of what any caller's `root:` is or
|
|
1021
1075
|
# what a hand-edited manifest claims.
|
|
1022
1076
|
def store_roots
|
|
1023
|
-
[
|
|
1077
|
+
%w[store projects stores].map { |name| File.expand_path(File.join(plastic_home, name)) }
|
|
1024
1078
|
end
|
|
1025
1079
|
|
|
1026
1080
|
# `install_skills_flat` relocates any top-level underscore-prefixed markdown
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# encoding: UTF-8
|
|
2
|
+
require_relative "store_layout"
|
|
2
3
|
# frozen_string_literal: true
|
|
3
4
|
# IntentScreen (intent 316) - fills templates/intent-screen.md from one intent's
|
|
4
5
|
# record: the intent file, the tier's INDEX.md, savepoint.md and checklist.md.
|
|
@@ -101,9 +102,8 @@ module IntentScreen
|
|
|
101
102
|
# --- store ---------------------------------------------------------------------
|
|
102
103
|
|
|
103
104
|
def self.store_fields(store_root)
|
|
104
|
-
|
|
105
|
-
if
|
|
106
|
-
slug = File.basename(store_root)
|
|
105
|
+
slug = Plastic::StoreLayout.locate(File.join(store_root, "store")).last
|
|
106
|
+
if slug != Plastic::StoreLayout::GLOBAL
|
|
107
107
|
{ "store" => "project:#{slug}", "store.note" => "the #{slug} project store" }
|
|
108
108
|
else
|
|
109
109
|
{ "store" => "global", "store.note" => "the global store" }
|
package/scripts/lib/lock.rb
CHANGED
|
@@ -368,8 +368,8 @@ module Lock
|
|
|
368
368
|
# file is inert: nothing reads it, it stays zero bytes, and
|
|
369
369
|
# scripts/end-intent:558 keys its exit contract on Lock.path alone. This is
|
|
370
370
|
# a requirement, not a guarantee the code enforces: the curator stray-file
|
|
371
|
-
# rule (
|
|
372
|
-
#
|
|
371
|
+
# rule (docs/help/maintenance-and-revisions.md:155) has no *.lock
|
|
372
|
+
# carve-out today, so a wrongful delete there degrades one
|
|
373
373
|
# write window to the pre-fix (unguarded) behavior, not a hard failure.
|
|
374
374
|
def with_write_guard(intent_dir, type: "delivery",
|
|
375
375
|
guard_timeout: WRITE_GUARD_TIMEOUT_SECONDS,
|
|
@@ -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 "yaml"
|
|
5
6
|
require "date"
|
|
6
7
|
require "digest"
|
|
@@ -70,7 +71,7 @@ module NodeInput
|
|
|
70
71
|
status.success? ? truncate_landed_commits(out) : nil
|
|
71
72
|
end
|
|
72
73
|
|
|
73
|
-
PROJECT_LAYOUT_RE = %r{\A(.*)/projects/([^/]+)/store/[^/]+\z}.freeze
|
|
74
|
+
PROJECT_LAYOUT_RE = %r{\A(.*)/(?:projects|stores)/([^/]+)/store/[^/]+\z}.freeze
|
|
74
75
|
|
|
75
76
|
# --- paths ---------------------------------------------------------------
|
|
76
77
|
|
|
@@ -399,7 +400,7 @@ module NodeInput
|
|
|
399
400
|
return nil unless m
|
|
400
401
|
|
|
401
402
|
home, slug = m[1], m[2]
|
|
402
|
-
path = File.join(home,
|
|
403
|
+
path = File.join(Plastic::StoreLayout.project_root(home, slug), "project.yml")
|
|
403
404
|
return nil unless File.exist?(path)
|
|
404
405
|
|
|
405
406
|
# `permitted_classes` (post-execution review finding B3): the same bug
|
package/scripts/lib/preflight.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# encoding: UTF-8
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
require_relative "version_number"
|
|
5
5
|
|
|
6
6
|
# Pure, dependency-injected pre-flight checks for Plastic's runtime dependencies
|
|
7
7
|
# (intent 38). Takes injected probes (ruby version, node version, git presence,
|
|
@@ -14,9 +14,9 @@ require "rubygems"
|
|
|
14
14
|
module Preflight
|
|
15
15
|
module_function
|
|
16
16
|
|
|
17
|
-
RUBY_FLOOR = "
|
|
17
|
+
RUBY_FLOOR = "4.0.0"
|
|
18
18
|
NODE_FLOOR = 18
|
|
19
|
-
RUBY_PIN = "
|
|
19
|
+
RUBY_PIN = "4.0"
|
|
20
20
|
|
|
21
21
|
def check(ruby_version:, node_version:, git_present:, mise_present:)
|
|
22
22
|
messages = []
|
|
@@ -63,9 +63,7 @@ module Preflight
|
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def safe_version(str)
|
|
66
|
-
|
|
67
|
-
rescue ArgumentError
|
|
68
|
-
nil
|
|
66
|
+
VersionNumber.parse(str)
|
|
69
67
|
end
|
|
70
68
|
|
|
71
69
|
def strip_leading_v(str)
|