@zalom/plastic 2.0.0-alpha.21 → 2.0.0-alpha.23
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 +20 -18
- package/agents/plastic-enforcer.md +6 -3
- package/agents/plastic-executor.md +4 -0
- package/agents/plastic-node-research.md +28 -0
- package/agents/plastic-node-verify.md +27 -0
- package/agents/plastic-node-work.md +32 -0
- package/bin/lib/context_budget.rb +1 -1
- package/hooks/hooks.json +12 -0
- package/hooks/statusline +28 -0
- package/hooks/stop +5 -0
- package/package.json +1 -1
- package/scripts/doctor.rb +159 -10
- package/scripts/end-intent +3 -3
- package/scripts/graph-measure +249 -0
- package/scripts/hook-capture +1 -0
- package/scripts/hook-savepoint +24 -2
- package/scripts/hook-session-start +333 -321
- package/scripts/hook-stop +57 -0
- package/scripts/insight-append +18 -4
- package/scripts/lib/active_delivery.rb +61 -0
- package/scripts/lib/agent_models.rb +10 -1
- package/scripts/lib/codex_adapter.rb +197 -0
- package/scripts/lib/doctor_core.rb +8 -3
- package/scripts/lib/engine_permissions.rb +88 -0
- package/scripts/lib/graph_edges.rb +4 -4
- package/scripts/lib/graph_file.rb +4 -4
- package/scripts/lib/graph_measure.rb +645 -0
- package/scripts/lib/graph_measure_budget.rb +408 -0
- package/scripts/lib/graph_measure_cohorts.rb +487 -0
- package/scripts/lib/graph_measure_models.rb +411 -0
- package/scripts/lib/graph_measure_report.rb +532 -0
- package/scripts/lib/graph_tree.rb +2 -2
- package/scripts/lib/handoff.rb +36 -5
- package/scripts/lib/harness_adapter.rb +184 -0
- package/scripts/lib/hook_registry.rb +13 -1
- package/scripts/lib/hook_replay.rb +23 -5
- package/scripts/lib/index_projection.rb +1 -1
- package/scripts/lib/installer_core.rb +112 -6
- package/scripts/lib/intent_screen.rb +1 -1
- package/scripts/lib/intent_validator.rb +2 -2
- package/scripts/lib/meter_watch.rb +15 -9
- package/scripts/lib/node_file.rb +3 -3
- package/scripts/lib/node_ledger.rb +8 -1
- package/scripts/lib/node_progress.rb +153 -0
- package/scripts/lib/outcome_report.rb +1 -1
- package/scripts/lib/report_screen.rb +10 -6
- package/scripts/lib/roadmap_graph.rb +1 -1
- package/scripts/lib/roadmap_queue.rb +1 -1
- package/scripts/lib/roadmap_render.rb +1 -1
- package/scripts/lib/runner_absorb.rb +31 -5
- package/scripts/lib/runner_dispatch.rb +26 -11
- package/scripts/lib/runner_until_empty.rb +252 -0
- package/scripts/lib/runner_watch.rb +389 -0
- package/scripts/lib/savepoint.rb +3 -3
- package/scripts/lib/session_git.rb +2 -2
- package/scripts/lib/stop_gate.rb +95 -0
- package/scripts/lib/verify_intent.rb +2 -2
- package/scripts/lib/work_graph_validator.rb +6 -6
- package/scripts/new-intent +1 -1
- package/scripts/node-run +224 -0
- package/scripts/read-config +6 -0
- package/scripts/runner +203 -19
- package/scripts/skill-lint +115 -6
- package/scripts/verify-intent +1 -1
- package/skills/auto/SKILL.md +54 -56
- package/skills/auto/references/agent-architecture.md +10 -8
- package/skills/auto/references/human-report-contract.md +1 -1
- package/skills/conventions/references/completion-and-done.md +7 -7
- package/skills/conventions/references/knowledge-graph.md +9 -0
- package/skills/conventions/references/locks-and-worktrees.md +3 -3
- package/skills/conventions/references/maintenance-and-revisions.md +1 -1
- package/skills/doctor/SKILL.md +3 -3
- package/skills/doctor/report.md +1 -1
- package/skills/intent-continuing/references/boarding-matrix.md +2 -2
- package/skills/intent-creating/SKILL.md +58 -133
- package/skills/intent-creating/evals/evals.json +1 -1
- package/skills/intent-ending/SKILL.md +48 -56
- package/skills/intent-ending/evals/evals.json +1 -1
- package/skills/intent-executing/SKILL.md +43 -136
- package/skills/intent-speccing/SKILL.md +3 -0
- package/skills/releasing/SKILL.md +1 -1
- package/skills/releasing/references/release-lines.md +1 -1
- package/skills/tutorial/SKILL.md +2 -1
- package/skills/tutorial/references/track-1-guided.md +21 -40
- package/skills/tutorial/references/track-2-auto.md +3 -3
- package/templates/agents.md +2 -2
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: UTF-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
require "json"
|
|
6
|
+
require "yaml"
|
|
7
|
+
require "date"
|
|
8
|
+
require_relative "lib/savepoint"
|
|
9
|
+
|
|
10
|
+
# graph-measure - the one read-only executable over the node ledger (intent
|
|
11
|
+
# 343, G10, n2). A subcommand table over one library, following
|
|
12
|
+
# scripts/runner's shape (327 spec): the public verbs are named up front even
|
|
13
|
+
# before every module lands, a flag outside a verb's own vocabulary is
|
|
14
|
+
# refused by name, and each verb's module is required LAZILY inside its own
|
|
15
|
+
# branch so a verb whose module has not landed yet reports plainly instead of
|
|
16
|
+
# taking `intent`, the verb an operator runs constantly, down with it.
|
|
17
|
+
#
|
|
18
|
+
# Usage:
|
|
19
|
+
# graph-measure <intent|budget|cohorts> <dir> [--format text|json]
|
|
20
|
+
#
|
|
21
|
+
# `intent` (n2) and `budget` (n4) take an INTENT directory. `cohorts` (n5,
|
|
22
|
+
# its model section; n6 adds the rates, the latency and the bar to the same
|
|
23
|
+
# verb) takes a STORE directory instead - the directory holding `id--slug/`
|
|
24
|
+
# intent folders for one project - and is validated against that shape, not
|
|
25
|
+
# `intent_directory?`.
|
|
26
|
+
#
|
|
27
|
+
# Exit codes:
|
|
28
|
+
# 0 - a report rendered (anomalies never change this: a ledger with torn
|
|
29
|
+
# lines still renders, lists them, and exits 0)
|
|
30
|
+
# 1 - the directory has no savepoint.md to measure at all, named
|
|
31
|
+
# 2 - usage: no args, an unknown subcommand, an unknown flag, or the path
|
|
32
|
+
# is not the right shape for its verb (an intent directory for
|
|
33
|
+
# `intent`/`budget`, a store directory for `cohorts`)
|
|
34
|
+
# 3 - the verb's module has not been delivered yet
|
|
35
|
+
module GraphMeasureCLI
|
|
36
|
+
module_function
|
|
37
|
+
|
|
38
|
+
VERBS = %w[intent budget cohorts].freeze
|
|
39
|
+
SAVEPOINT_FILE = "savepoint.md"
|
|
40
|
+
|
|
41
|
+
# verb -> [[ModuleName, lib_file], ...], required lazily inside the verb's
|
|
42
|
+
# own branch (row 2.6). "budget" and "cohorts" name libraries n4 and n5
|
|
43
|
+
# ship; requiring them today raises LoadError, caught below.
|
|
44
|
+
LAZY_MODULES = {
|
|
45
|
+
"intent" => [["GraphMeasure", "graph_measure"], ["GraphMeasureReport", "graph_measure_report"]],
|
|
46
|
+
"budget" => [["GraphMeasureBudget", "graph_measure_budget"]],
|
|
47
|
+
"cohorts" => [["GraphMeasureModels", "graph_measure_models"], ["GraphMeasureCohorts", "graph_measure_cohorts"]],
|
|
48
|
+
}.freeze
|
|
49
|
+
|
|
50
|
+
KNOWN_FLAGS = {
|
|
51
|
+
"intent" => %w[--format],
|
|
52
|
+
"budget" => %w[--format],
|
|
53
|
+
"cohorts" => %w[--format],
|
|
54
|
+
}.freeze
|
|
55
|
+
|
|
56
|
+
# "intent" and "budget" take one intent directory; "cohorts" takes the
|
|
57
|
+
# store directory that holds many of them (row 5.15).
|
|
58
|
+
DIR_KIND = { "intent" => :intent, "budget" => :intent, "cohorts" => :store }.freeze
|
|
59
|
+
|
|
60
|
+
def usage
|
|
61
|
+
warn "Usage: graph-measure <#{VERBS.join('|')}> <dir> [--format text|json]"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def intent_directory?(dir)
|
|
65
|
+
dir && File.directory?(dir) && File.exist?(Savepoint.intent_file(dir))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def store_directory?(dir)
|
|
69
|
+
dir && File.directory?(dir)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def valid_dir?(verb, dir)
|
|
73
|
+
DIR_KIND.fetch(verb, :intent) == :store ? store_directory?(dir) : intent_directory?(dir)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def opt(args, name)
|
|
77
|
+
(i = args.index(name)) && args[i + 1]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def unrecognized_flag(verb, args)
|
|
81
|
+
allowed = KNOWN_FLAGS.fetch(verb, [])
|
|
82
|
+
args.find { |a| a.is_a?(String) && a.start_with?("--") && !allowed.include?(a) }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def main(argv)
|
|
86
|
+
args = argv.dup
|
|
87
|
+
verb = args.shift
|
|
88
|
+
|
|
89
|
+
unless verb
|
|
90
|
+
usage
|
|
91
|
+
return 2
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
unless VERBS.include?(verb)
|
|
95
|
+
warn "graph-measure: unknown subcommand #{verb.inspect}"
|
|
96
|
+
usage
|
|
97
|
+
return 2
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
dir_arg = args.shift
|
|
101
|
+
unless valid_dir?(verb, dir_arg && File.expand_path(dir_arg))
|
|
102
|
+
label = DIR_KIND.fetch(verb, :intent) == :store ? "not a store directory" : "not an intent directory"
|
|
103
|
+
warn "graph-measure: #{dir_arg.inspect} is #{label}"
|
|
104
|
+
usage
|
|
105
|
+
return 2
|
|
106
|
+
end
|
|
107
|
+
dir = File.expand_path(dir_arg)
|
|
108
|
+
|
|
109
|
+
bad_flag = unrecognized_flag(verb, args)
|
|
110
|
+
if bad_flag
|
|
111
|
+
warn "graph-measure: unknown flag #{bad_flag.inspect} for #{verb}"
|
|
112
|
+
usage
|
|
113
|
+
return 2
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
format = opt(args, "--format") || "text"
|
|
117
|
+
unless %w[text json].include?(format)
|
|
118
|
+
warn "graph-measure: unknown --format #{format.inspect} (use text or json)"
|
|
119
|
+
usage
|
|
120
|
+
return 2
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
run_lazy(verb, dir, format)
|
|
124
|
+
rescue StandardError => e
|
|
125
|
+
# Row 2.22: a malformed graph.md, a torn ledger, or invalid UTF-8 never
|
|
126
|
+
# raises out of GraphMeasure or GraphMeasureReport (both are built to
|
|
127
|
+
# resolve broken data to an anomaly, never an exception) - this is
|
|
128
|
+
# defence in depth so a defect in either never reaches the owner as a
|
|
129
|
+
# bare Ruby backtrace.
|
|
130
|
+
warn "graph-measure: internal error: #{e.message}"
|
|
131
|
+
1
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def run_lazy(verb, dir, format)
|
|
135
|
+
LAZY_MODULES.fetch(verb, []).each { |(_name, file)| require_relative(File.join("lib", file)) }
|
|
136
|
+
rescue LoadError
|
|
137
|
+
warn "graph-measure: #{verb} is not yet delivered (its module has not landed)"
|
|
138
|
+
3
|
|
139
|
+
else
|
|
140
|
+
dispatch_lazy(verb, dir, format)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def dispatch_lazy(verb, dir, format)
|
|
144
|
+
case verb
|
|
145
|
+
when "intent"
|
|
146
|
+
run_intent(dir, format)
|
|
147
|
+
when "budget"
|
|
148
|
+
run_budget(dir, format)
|
|
149
|
+
when "cohorts"
|
|
150
|
+
run_cohorts(dir, format)
|
|
151
|
+
else
|
|
152
|
+
warn "graph-measure: #{verb}'s module loaded but no dispatcher is wired up yet"
|
|
153
|
+
3
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# M1: the boundary's own config resolution, never GraphMeasureModels' or
|
|
158
|
+
# GraphMeasureCohorts' (spec D2, both modules' own DI convention). Mirrors
|
|
159
|
+
# scripts/doctor.rb's own `PLASTIC_HOME` fallback and `load_yaml_safe`
|
|
160
|
+
# shape exactly, so a missing or malformed config.yml/project.yml resolves
|
|
161
|
+
# to `{}` (the shipped-default chain), never an exception.
|
|
162
|
+
def plastic_home
|
|
163
|
+
ENV.fetch("PLASTIC_HOME") { File.join(Dir.home, ".plastic") }
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def load_yaml_safe(path)
|
|
167
|
+
return {} unless path && File.exist?(path)
|
|
168
|
+
|
|
169
|
+
YAML.safe_load(File.read(path), permitted_classes: [Date, Time]) || {}
|
|
170
|
+
rescue StandardError
|
|
171
|
+
{}
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def missing_savepoint(dir)
|
|
175
|
+
savepoint_path = File.join(dir, SAVEPOINT_FILE)
|
|
176
|
+
return nil if File.exist?(savepoint_path)
|
|
177
|
+
|
|
178
|
+
warn "graph-measure: #{savepoint_path} does not exist, nothing to measure"
|
|
179
|
+
savepoint_path
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def run_intent(dir, format)
|
|
183
|
+
# Row 2.5: checked directly, ahead of GraphMeasure.read, which resolves a
|
|
184
|
+
# missing file to an empty (not absent) record by design (D3) - the CLI
|
|
185
|
+
# boundary is the only place that can still tell "nothing to measure"
|
|
186
|
+
# apart from "measured, and it is empty".
|
|
187
|
+
return 1 if missing_savepoint(dir)
|
|
188
|
+
|
|
189
|
+
record = GraphMeasure.read(dir)
|
|
190
|
+
puts format == "json" ? GraphMeasureReport.render_json(record) : GraphMeasureReport.render_text(record)
|
|
191
|
+
0
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def run_budget(dir, format)
|
|
195
|
+
return 1 if missing_savepoint(dir)
|
|
196
|
+
|
|
197
|
+
record = GraphMeasureBudget.read(dir)
|
|
198
|
+
puts format == "json" ? GraphMeasureBudget.render_json(record) : GraphMeasureBudget.render_text(record)
|
|
199
|
+
0
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Row 2.5's "missing savepoint.md" check does not apply here: `dir` is a
|
|
203
|
+
# STORE directory (spec D1's "the directory holding id--slug/ intent
|
|
204
|
+
# folders"), which never carries a savepoint.md of its own.
|
|
205
|
+
#
|
|
206
|
+
# n6 adds the rates, the latency and the bar to this same verb, sourced
|
|
207
|
+
# from `GraphMeasureCohorts`. Its bar section reuses the one
|
|
208
|
+
# `GraphMeasureModels` walk's `qualified` counts (passed in here) rather
|
|
209
|
+
# than triggering a second store walk for the same numbers.
|
|
210
|
+
#
|
|
211
|
+
# M1 (v1 review): this call used to pass neither `project_config:` nor
|
|
212
|
+
# `global_config:`, so the model comparison always resolved against
|
|
213
|
+
# RunnerPolicy's shipped defaults only, never the real project-then-
|
|
214
|
+
# global-then-default chain the CHANGELOG describes and
|
|
215
|
+
# `scripts/doctor.rb`'s own `model_requalification_checks` actually reads.
|
|
216
|
+
# Reproduced: with a `plastic-executor:` override present in global
|
|
217
|
+
# config, `GraphMeasureModels.read` reports real drift rows while this
|
|
218
|
+
# verb (unpatched) always reported zero. `GraphMeasure`'s own modules stay
|
|
219
|
+
# config-free by design (D2's DI convention: they never read disk or ENV
|
|
220
|
+
# themselves) - this executable is the boundary that is allowed to, the
|
|
221
|
+
# same role `scripts/doctor.rb` already plays for the same two config
|
|
222
|
+
# files.
|
|
223
|
+
#
|
|
224
|
+
# NEW-4 (v2 review, D22): the project-scope half named `<store>/../project.yml`,
|
|
225
|
+
# a file whose template carries no `agents:` key at all, so a real
|
|
226
|
+
# operator override at the file that DOES exist in the wild - the store's
|
|
227
|
+
# sibling `config.yml` - was silently ignored. The path now comes from
|
|
228
|
+
# `GraphMeasureModels.project_config_path`, the one function
|
|
229
|
+
# `scripts/doctor.rb`'s own rule calls too, so the two commands cannot
|
|
230
|
+
# resolve a different file for the same store.
|
|
231
|
+
def run_cohorts(dir, format)
|
|
232
|
+
global_config = load_yaml_safe(File.join(plastic_home, "config.yml"))
|
|
233
|
+
project_config = load_yaml_safe(GraphMeasureModels.project_config_path(dir))
|
|
234
|
+
|
|
235
|
+
models_record = GraphMeasureModels.read(dir, project_config: project_config, global_config: global_config)
|
|
236
|
+
cohorts_record = GraphMeasureCohorts.read(dir, qualified: models_record[:qualified],
|
|
237
|
+
project_config: project_config, global_config: global_config)
|
|
238
|
+
if format == "json"
|
|
239
|
+
combined = GraphMeasureModels.model(models_record).merge(GraphMeasureCohorts.model(cohorts_record))
|
|
240
|
+
puts JSON.generate(combined)
|
|
241
|
+
else
|
|
242
|
+
puts GraphMeasureModels.render_text(models_record)
|
|
243
|
+
puts GraphMeasureCohorts.render_text(cohorts_record)
|
|
244
|
+
end
|
|
245
|
+
0
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
exit GraphMeasureCLI.main(ARGV) if $PROGRAM_NAME == __FILE__
|
package/scripts/hook-capture
CHANGED
package/scripts/hook-savepoint
CHANGED
|
@@ -15,12 +15,31 @@
|
|
|
15
15
|
require "json"
|
|
16
16
|
require_relative "lib/session_ledger"
|
|
17
17
|
require_relative "lib/handoff"
|
|
18
|
+
require_relative "lib/active_delivery"
|
|
18
19
|
|
|
19
20
|
MESSAGE = "PLASTIC SAVEPOINT - context is being compacted. The hand-off for this " \
|
|
20
21
|
"session is written in today's day ledger " \
|
|
21
22
|
"(store/.sessions/<day>/handoff--<session>.md). " \
|
|
22
23
|
"After compaction say continue to resume from it."
|
|
23
24
|
|
|
25
|
+
# The intent this session is delivering, resolved through the same walk the
|
|
26
|
+
# Stop gate uses (row 4.38): read-config's project_roots, plus the global
|
|
27
|
+
# store, glob for a live delivery.lock this session holds. Never raises
|
|
28
|
+
# (ActiveDelivery.resolve rescues its own reads); a lookup failure just
|
|
29
|
+
# means the Runner section is omitted, never that the hand-off is lost.
|
|
30
|
+
def resolve_intent(plastic_home, session_id, read_config_path)
|
|
31
|
+
return nil if session_id.strip.empty?
|
|
32
|
+
|
|
33
|
+
global_store = File.join(plastic_home, "store")
|
|
34
|
+
roots_raw = IO.popen({ "PLASTIC_HOME" => plastic_home }, [read_config_path, "project_roots"],
|
|
35
|
+
err: File::NULL, &:read).to_s.strip
|
|
36
|
+
project_roots = roots_raw.empty? ? [] : Array(JSON.parse(roots_raw))
|
|
37
|
+
project_roots = project_roots.map { |root| File.expand_path(root.to_s) }
|
|
38
|
+
ActiveDelivery.resolve(global_store: global_store, project_roots: project_roots, session: session_id)
|
|
39
|
+
rescue StandardError
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
|
|
24
43
|
written = nil
|
|
25
44
|
begin
|
|
26
45
|
plastic_home = ARGV[0].to_s
|
|
@@ -30,11 +49,14 @@ begin
|
|
|
30
49
|
session_id = payload["session_id"].to_s
|
|
31
50
|
|
|
32
51
|
unless plastic_home.empty? || session_id.strip.empty?
|
|
33
|
-
|
|
52
|
+
home = File.expand_path(plastic_home)
|
|
53
|
+
store = File.join(home, "store")
|
|
34
54
|
session = SessionLedger.short_session_id(session_id, nil)
|
|
35
55
|
day = Handoff.day_for(store, session, today: SessionLedger.day_id)
|
|
56
|
+
read_config_path = File.expand_path("read-config", __dir__)
|
|
57
|
+
intent = resolve_intent(home, session_id, read_config_path)
|
|
36
58
|
written = Handoff.write(store: store, day: day, session: session, trigger: "precompact",
|
|
37
|
-
templates: File.expand_path("../templates", __dir__))
|
|
59
|
+
templates: File.expand_path("../templates", __dir__), intent: intent)
|
|
38
60
|
end
|
|
39
61
|
rescue StandardError, JSON::ParserError
|
|
40
62
|
nil
|