@zalom/plastic 2.0.0-alpha.8 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PLASTIC.md +13 -136
- package/README.md +357 -133
- package/agents/plastic-enforcer.md +20 -15
- package/agents/plastic-executor.md +15 -4
- package/agents/plastic-node-research.md +30 -0
- package/agents/plastic-node-verify.md +28 -0
- package/agents/plastic-node-work.md +33 -0
- package/agents/{plastic-advisor.md → plastic-primary-advisor.md} +7 -9
- package/agents/{plastic-faux-advisor.md → plastic-secondary-advisor.md} +9 -12
- package/assets/plastic-logo.svg +1 -0
- package/bin/crap +4 -0
- package/bin/lib/context_budget.rb +43 -6
- package/bin/lib/skill_census.rb +839 -0
- package/bin/plastic +6 -0
- package/bin/plastic-skill-census +114 -0
- package/bin/test +24 -4
- package/bin/verify-change +345 -0
- package/config_asks.yml +4 -4
- package/deprecations.yml +1 -1
- package/{skills/agent-advisor/references → docs/help}/advisor-protocol.md +19 -24
- package/{skills/auto/references → docs/help}/agent-architecture.md +16 -14
- package/{skills/conventions/references → docs/help}/completion-and-done.md +15 -16
- package/docs/help/human-report-contract.md +152 -0
- package/{skills/conventions/references → docs/help}/knowledge-graph.md +9 -0
- package/{skills/conventions/references → docs/help}/locks-and-worktrees.md +23 -13
- package/{skills/conventions/references → docs/help}/maintenance-and-revisions.md +1 -1
- package/{skills/conventions/references → docs/help}/roadmaps.md +2 -2
- package/{skills/tutorial/references → docs/help}/track-1-guided.md +28 -47
- package/{skills/tutorial/references → docs/help}/track-2-auto.md +8 -8
- package/{skills/tutorial/references → docs/help}/track-3-projects-and-roadmaps.md +24 -17
- package/hooks/call-budget +4 -0
- package/hooks/hooks.json +24 -0
- package/hooks/message-display +62 -1
- package/hooks/session-start +5 -1
- package/hooks/statusline +32 -27
- package/hooks/stop +5 -0
- package/package.json +5 -3
- package/scripts/append-ledger +2 -1
- package/scripts/dashboard.rb +267 -16
- package/scripts/day-summary +2 -1
- package/scripts/doctor.rb +680 -39
- package/scripts/end-intent +238 -22
- package/scripts/exec-worktree +5 -5
- package/scripts/file-session-intent +2 -1
- package/scripts/graph-measure +249 -0
- package/scripts/hook-call-budget +222 -0
- package/scripts/hook-capture +24 -123
- package/scripts/hook-close +2 -1
- package/scripts/hook-message-display +19 -5
- package/scripts/hook-record +24 -16
- package/scripts/hook-savepoint +27 -3
- package/scripts/hook-session-start +359 -321
- package/scripts/hook-stop +58 -0
- package/scripts/index-projection +74 -0
- package/scripts/insight-append +17 -4
- package/scripts/install.rb +9 -7
- package/scripts/lib/action_graph_shim.rb +279 -0
- package/scripts/lib/active_delivery.rb +105 -0
- package/scripts/lib/agent_models.rb +63 -25
- package/scripts/lib/arm.rb +48 -83
- package/scripts/lib/atomic_write.rb +31 -0
- package/scripts/lib/backup.rb +65 -0
- package/scripts/lib/cli/command.rb +92 -0
- package/scripts/lib/cli/commands/auto.rb +18 -0
- package/scripts/lib/cli/commands/auto_brief.rb +44 -0
- package/scripts/lib/cli/commands/auto_lock.rb +99 -0
- package/scripts/lib/cli/commands/auto_report.rb +50 -0
- package/scripts/lib/cli/commands/auto_take.rb +31 -0
- package/scripts/lib/cli/commands/backup.rb +43 -0
- package/scripts/lib/cli/commands/checkout.rb +25 -0
- package/scripts/lib/cli/commands/continue.rb +66 -0
- package/scripts/lib/cli/commands/doctor.rb +81 -0
- package/scripts/lib/cli/commands/feedback.rb +40 -0
- package/scripts/lib/cli/commands/help.rb +69 -0
- package/scripts/lib/cli/commands/hook.rb +32 -0
- package/scripts/lib/cli/commands/index.rb +23 -0
- package/scripts/lib/cli/commands/install.rb +21 -0
- package/scripts/lib/cli/commands/installer_verb.rb +37 -0
- package/scripts/lib/cli/commands/intent.rb +19 -0
- package/scripts/lib/cli/commands/intent_answer.rb +37 -0
- package/scripts/lib/cli/commands/intent_command.rb +53 -0
- package/scripts/lib/cli/commands/intent_end.rb +62 -0
- package/scripts/lib/cli/commands/intent_new.rb +62 -0
- package/scripts/lib/cli/commands/intent_note.rb +43 -0
- package/scripts/lib/cli/commands/intent_rule.rb +36 -0
- package/scripts/lib/cli/commands/intent_show.rb +32 -0
- package/scripts/lib/cli/commands/intent_spec.rb +44 -0
- package/scripts/lib/cli/commands/intent_step.rb +71 -0
- package/scripts/lib/cli/commands/intent_verify.rb +26 -0
- package/scripts/lib/cli/commands/migrate.rb +16 -0
- package/scripts/lib/cli/commands/migrate_stores.rb +31 -0
- package/scripts/lib/cli/commands/next.rb +54 -0
- package/scripts/lib/cli/commands/project.rb +19 -0
- package/scripts/lib/cli/commands/project_links.rb +42 -0
- package/scripts/lib/cli/commands/project_list.rb +20 -0
- package/scripts/lib/cli/commands/project_new.rb +72 -0
- package/scripts/lib/cli/commands/query.rb +31 -0
- package/scripts/lib/cli/commands/render.rb +33 -0
- package/scripts/lib/cli/commands/roadmap.rb +19 -0
- package/scripts/lib/cli/commands/roadmap_check.rb +54 -0
- package/scripts/lib/cli/commands/roadmap_log.rb +54 -0
- package/scripts/lib/cli/commands/roadmap_migrate.rb +53 -0
- package/scripts/lib/cli/commands/roadmap_next.rb +70 -0
- package/scripts/lib/cli/commands/roadmap_show.rb +45 -0
- package/scripts/lib/cli/commands/rollback.rb +20 -0
- package/scripts/lib/cli/commands/search.rb +60 -0
- package/scripts/lib/cli/commands/session.rb +18 -0
- package/scripts/lib/cli/commands/session_commit.rb +42 -0
- package/scripts/lib/cli/commands/session_handoff.rb +34 -0
- package/scripts/lib/cli/commands/session_summary.rb +35 -0
- package/scripts/lib/cli/commands/status.rb +68 -0
- package/scripts/lib/cli/commands/subcommand_list.rb +36 -0
- package/scripts/lib/cli/commands/sync.rb +46 -0
- package/scripts/lib/cli/commands/uninstall.rb +20 -0
- package/scripts/lib/cli/commands/update.rb +20 -0
- package/scripts/lib/cli/commands/version.rb +53 -0
- package/scripts/lib/cli/frontier.rb +86 -0
- package/scripts/lib/cli/intent_progress.rb +36 -0
- package/scripts/lib/cli/legacy.rb +80 -0
- package/scripts/lib/cli/output.rb +137 -0
- package/scripts/lib/cli/scope.rb +134 -0
- package/scripts/lib/cli/table.rb +65 -0
- package/scripts/lib/cli.rb +94 -0
- package/scripts/lib/codex_adapter.rb +198 -0
- package/scripts/lib/compact_instructions.rb +13 -5
- package/scripts/lib/core_integrity.rb +71 -0
- package/scripts/lib/dashboard_screen.rb +40 -0
- package/scripts/lib/data_boundary.rb +132 -0
- package/scripts/lib/day_summary.rb +30 -16
- package/scripts/lib/doctor_core.rb +114 -38
- package/scripts/lib/doctor_session_ledger.rb +5 -53
- package/scripts/lib/engine_permissions.rb +88 -0
- package/scripts/lib/exec_worktree.rb +24 -21
- package/scripts/lib/feedback_report.rb +1 -1
- package/scripts/lib/graph_edges.rb +137 -0
- package/scripts/lib/graph_file.rb +246 -0
- package/scripts/lib/graph_measure.rb +645 -0
- package/scripts/lib/graph_measure_budget.rb +409 -0
- package/scripts/lib/graph_measure_cohorts.rb +487 -0
- package/scripts/lib/graph_measure_models.rb +413 -0
- package/scripts/lib/graph_measure_report.rb +532 -0
- package/scripts/lib/graph_tree.rb +98 -0
- package/scripts/lib/guarded_append.rb +155 -0
- package/scripts/lib/handoff.rb +40 -13
- package/scripts/lib/harness_adapter.rb +184 -0
- package/scripts/lib/hook_registry.rb +27 -3
- package/scripts/lib/hook_replay.rb +229 -0
- package/scripts/lib/index_entry.rb +62 -0
- package/scripts/lib/index_projection.rb +201 -0
- package/scripts/lib/insights.rb +1 -1
- package/scripts/lib/installer_core.rb +532 -71
- package/scripts/lib/intent_screen.rb +9 -6
- package/scripts/lib/intent_screen_ansi.rb +73 -12
- package/scripts/lib/intent_validator.rb +2 -2
- package/scripts/lib/lock.rb +10 -11
- package/scripts/lib/message_display.rb +377 -173
- package/scripts/lib/meter_watch.rb +185 -0
- package/scripts/lib/node_file.rb +234 -0
- package/scripts/lib/node_ids.rb +99 -0
- package/scripts/lib/node_input.rb +913 -0
- package/scripts/lib/node_input_compatibility.rb +62 -0
- package/scripts/lib/node_ledger.rb +386 -0
- package/scripts/lib/node_progress.rb +153 -0
- package/scripts/lib/node_return.rb +204 -0
- package/scripts/lib/node_worktree.rb +337 -0
- package/scripts/lib/outcome_report.rb +440 -0
- package/scripts/lib/preflight.rb +4 -6
- package/scripts/lib/project_config.rb +46 -0
- package/scripts/lib/project_validator.rb +3 -2
- package/scripts/lib/qmd_sync.rb +8 -7
- package/scripts/lib/ready_set.rb +462 -0
- package/scripts/lib/reference_archive.rb +45 -0
- package/scripts/lib/release_guard.rb +18 -0
- package/scripts/lib/report_screen.rb +1460 -74
- package/scripts/lib/rlm/corpus.rb +13 -0
- package/scripts/lib/rlm/probe.rb +29 -0
- package/scripts/lib/rlm/query.rb +22 -0
- package/scripts/lib/roadmap_graph.rb +210 -0
- package/scripts/lib/roadmap_migration.rb +95 -0
- package/scripts/lib/roadmap_queue.rb +158 -8
- package/scripts/lib/roadmap_render.rb +150 -0
- package/scripts/lib/roadmap_savepoint.rb +64 -14
- package/scripts/lib/runner_absorb.rb +703 -0
- package/scripts/lib/runner_answer.rb +206 -0
- package/scripts/lib/runner_core.rb +194 -0
- package/scripts/lib/runner_dispatch.rb +525 -0
- package/scripts/lib/runner_policy.rb +191 -0
- package/scripts/lib/runner_proposals.rb +275 -0
- package/scripts/lib/runner_rewind.rb +201 -0
- package/scripts/lib/runner_sweep.rb +231 -0
- package/scripts/lib/runner_until_empty.rb +252 -0
- package/scripts/lib/runner_watch.rb +389 -0
- package/scripts/lib/savepoint.rb +141 -19
- package/scripts/lib/scaffold_intent.rb +6 -3
- package/scripts/lib/screen_paint.rb +555 -0
- package/scripts/lib/screens/dashboard.rb +20 -0
- package/scripts/lib/screens/plan.rb +18 -0
- package/scripts/lib/screens/roadmap.rb +15 -0
- package/scripts/lib/search_index.rb +55 -0
- package/scripts/lib/session_close.rb +30 -28
- package/scripts/lib/session_git.rb +25 -18
- package/scripts/lib/session_ledger.rb +48 -4
- package/scripts/lib/session_usage.rb +190 -0
- package/scripts/lib/sqlite.rb +22 -0
- package/scripts/lib/stop_gate.rb +95 -0
- package/scripts/lib/store_discovery.rb +7 -6
- package/scripts/lib/store_layout.rb +54 -0
- package/scripts/lib/store_provisioning.rb +2 -1
- package/scripts/lib/store_sync.rb +85 -0
- package/scripts/lib/stores_move.rb +93 -0
- package/scripts/lib/verify_intent.rb +36 -8
- package/scripts/lib/version_number.rb +48 -0
- package/scripts/lib/work_graph.rb +59 -0
- package/scripts/lib/work_graph_validator.rb +201 -0
- package/scripts/lib/worktree.rb +27 -32
- package/scripts/lib/worktree_sweep.rb +6 -5
- package/scripts/link-suggest +2 -1
- package/scripts/meter-watch +57 -0
- package/scripts/migrate-to-global +1 -1
- package/scripts/new-intent +4 -13
- package/scripts/node-input +92 -0
- package/scripts/node-run +225 -0
- package/scripts/node-transition +291 -0
- package/scripts/outcome-report +74 -0
- package/scripts/plastic-lock +42 -33
- package/scripts/promote-session-item +3 -2
- package/scripts/read-config +53 -9
- package/scripts/ready-set +126 -0
- package/scripts/release-check +123 -0
- package/scripts/report-screen +192 -30
- package/scripts/roadmap-graph +119 -0
- package/scripts/roadmap-savepoint +7 -0
- package/scripts/runner +581 -0
- package/scripts/savepoint-note +11 -9
- package/scripts/session-commit +2 -1
- package/scripts/session-usage +56 -0
- package/scripts/skill-lint +115 -6
- package/scripts/spawn-preamble +2 -2
- package/scripts/update.rb +25 -4
- package/scripts/validate-work-graph +39 -0
- package/scripts/verify-intent +3 -2
- package/scripts/write-handoff +2 -1
- package/templates/agents.md +7 -7
- package/templates/config.yml +16 -9
- package/templates/dashboard-screen.md +22 -0
- package/templates/display-fixture.md +21 -0
- package/templates/graph.md +16 -0
- package/templates/index.md +1 -1
- package/templates/intent-screen.md +1 -1
- package/templates/node-decision.md +11 -0
- package/templates/node-research.md +13 -0
- package/templates/node-verify.md +13 -0
- package/templates/node-work.md +22 -0
- package/templates/outcome.md +15 -2
- package/templates/project.yml +1 -1
- package/templates/render.css +10 -0
- package/templates/report-plan.md +15 -0
- package/templates/report-roadmap-delivered.md +10 -0
- package/templates/report-roadmap-plan.md +9 -0
- package/templates/report-roadmap-state.md +9 -0
- package/templates/report-state.md +1 -1
- package/templates/roadmap.md +13 -0
- package/bin/plastic.js +0 -70
- package/scripts/lib/bridge.rb +0 -116
- package/skills/agent-advisor/SKILL.md +0 -92
- package/skills/auto/SKILL.md +0 -295
- package/skills/auto/evals/evals.json +0 -255
- package/skills/auto/references/end-tail.md +0 -66
- package/skills/auto/references/human-report-contract.md +0 -78
- package/skills/conventions/SKILL.md +0 -29
- package/skills/dashboard/SKILL.md +0 -169
- package/skills/dashboard/evals/evals.json +0 -38
- package/skills/dashboard/references/classification.md +0 -22
- package/skills/dashboard/templates/dashboard-global.md +0 -20
- package/skills/dashboard/templates/dashboard-project.md +0 -19
- package/skills/direct/SKILL.md +0 -66
- package/skills/direct/references/request-signals.md +0 -59
- package/skills/doctor/SKILL.md +0 -299
- package/skills/doctor/report.md +0 -102
- package/skills/feedback/SKILL.md +0 -98
- package/skills/feedback/references/transport-and-privacy.md +0 -65
- package/skills/feedback/report.md +0 -36
- package/skills/install/SKILL.md +0 -217
- package/skills/intent-continuing/SKILL.md +0 -154
- package/skills/intent-continuing/references/board-fill.md +0 -43
- package/skills/intent-continuing/references/boarding-matrix.md +0 -34
- package/skills/intent-continuing/references/context-management.md +0 -28
- package/skills/intent-continuing/references/liveness-ranking.md +0 -57
- package/skills/intent-creating/SKILL.md +0 -164
- package/skills/intent-creating/evals/evals.json +0 -72
- package/skills/intent-creating/references/lifecycle.md +0 -81
- package/skills/intent-creating/references/wikilinks.md +0 -8
- package/skills/intent-ending/SKILL.md +0 -173
- package/skills/intent-ending/evals/evals.json +0 -74
- package/skills/intent-executing/SKILL.md +0 -170
- package/skills/intent-executing/evals/evals.json +0 -66
- package/skills/intent-executing/implementer-prompt.md +0 -42
- package/skills/intent-executing/spec-reviewer-prompt.md +0 -27
- package/skills/intent-speccing/SKILL.md +0 -130
- package/skills/intent-speccing/evals/evals.json +0 -126
- package/skills/intent-speccing/references/design-principles.md +0 -44
- package/skills/intent-speccing/references/per-section-fill-rules.md +0 -92
- package/skills/intent-speccing/references/self-verify-checklist.md +0 -37
- package/skills/project-creating/SKILL.md +0 -162
- package/skills/project-creating/references/hubs-projects.md +0 -55
- package/skills/project-creating/references/project-scaffolding.md +0 -97
- package/skills/releasing/SKILL.md +0 -337
- package/skills/releasing/references/deprecations.md +0 -60
- package/skills/releasing/references/promotion-and-tagging.md +0 -66
- package/skills/releasing/references/release-lines.md +0 -105
- package/skills/roadmap/SKILL.md +0 -64
- package/skills/roadmap/references/file-format.md +0 -124
- package/skills/roadmap/references/operations.md +0 -112
- package/skills/rollback/SKILL.md +0 -91
- package/skills/tutorial/SKILL.md +0 -65
- package/skills/tutorial/evals/evals.json +0 -186
- package/skills/uninstall/SKILL.md +0 -75
- package/skills/update/SKILL.md +0 -126
- /package/{skills/auto/references → docs/help}/agent-report-contract.md +0 -0
- /package/{skills/intent-executing → docs/help}/code-quality-reviewer-prompt.md +0 -0
- /package/{skills/conventions/references → docs/help}/lifecycle-and-savepoints.md +0 -0
- /package/{skills/intent-executing → docs/help}/plan-reviewer-prompt.md +0 -0
package/scripts/dashboard.rb
CHANGED
|
@@ -21,12 +21,26 @@
|
|
|
21
21
|
#
|
|
22
22
|
# Read-only. Never modifies files.
|
|
23
23
|
|
|
24
|
+
require_relative "lib/store_layout"
|
|
24
25
|
require "json"
|
|
25
26
|
require "yaml"
|
|
26
27
|
require "date"
|
|
27
28
|
require_relative "doctor"
|
|
28
29
|
require_relative "lib/savepoint"
|
|
29
30
|
require_relative "lib/lock"
|
|
31
|
+
require_relative "lib/dashboard_screen"
|
|
32
|
+
require_relative "lib/intent_screen"
|
|
33
|
+
require_relative "lib/report_screen"
|
|
34
|
+
require_relative "lib/roadmap_queue"
|
|
35
|
+
require_relative "lib/day_summary"
|
|
36
|
+
require_relative "lib/screen_paint"
|
|
37
|
+
require_relative "lib/ready_set"
|
|
38
|
+
|
|
39
|
+
# Intent 331a (D6/R8): every caller-added screen kind file registers itself on
|
|
40
|
+
# load, so this glob is the only wiring a new kind needs (scripts/report-screen:42
|
|
41
|
+
# does the identical glob for the same reason). Sorted for a deterministic load
|
|
42
|
+
# order; tolerates the directory being absent or empty.
|
|
43
|
+
Dir.glob(File.join(__dir__, "lib", "screens", "*.rb")).sort.each { |f| require_relative f }
|
|
30
44
|
|
|
31
45
|
PLASTIC_HOME = ENV.fetch("PLASTIC_HOME") { File.join(Dir.home, ".plastic") }
|
|
32
46
|
|
|
@@ -87,7 +101,7 @@ end
|
|
|
87
101
|
# instead, with no end-of-line requirement, so trailing note prose is irrelevant. The
|
|
88
102
|
# separator there may be a real em dash (U+2014, INDEX.md's normal on-write convention,
|
|
89
103
|
# built from the codepoint so this source line stays em-dash free) or a plain hyphen
|
|
90
|
-
# (end-intent's own
|
|
104
|
+
# (end-intent's own IndexEntry.match accepts either on read). Because regex
|
|
91
105
|
# alternation is leftmost-first, this only ever matches the date immediately after the
|
|
92
106
|
# link. It never continues scanning into the note prose, so a second date mentioned
|
|
93
107
|
# later in a note's free text cannot be mistaken for the completion date. The id class
|
|
@@ -130,13 +144,13 @@ def completion_dates(index_path)
|
|
|
130
144
|
end
|
|
131
145
|
|
|
132
146
|
# All stores: global + every registered project. -> [{scope, store, index}]
|
|
133
|
-
def stores
|
|
147
|
+
def stores(home = PLASTIC_HOME)
|
|
134
148
|
list = []
|
|
135
|
-
global =
|
|
136
|
-
list << { scope: "global", store: global, index: File.join(
|
|
137
|
-
projects_root =
|
|
149
|
+
global = Plastic::StoreLayout.global_store(home)
|
|
150
|
+
list << { scope: "global", store: global, index: File.join(Plastic::StoreLayout.global_root(home), "INDEX.md") } if File.directory?(global)
|
|
151
|
+
projects_root = Plastic::StoreLayout.projects_root(home)
|
|
138
152
|
if File.directory?(projects_root)
|
|
139
|
-
|
|
153
|
+
Plastic::StoreLayout.project_slugs(home).each do |proj|
|
|
140
154
|
store = File.join(projects_root, proj, "store")
|
|
141
155
|
next unless File.directory?(store)
|
|
142
156
|
list << { scope: "project:#{proj}", store: store, index: File.join(projects_root, proj, "INDEX.md") }
|
|
@@ -256,9 +270,27 @@ def parse_intent(store_info, dir_name, status_index)
|
|
|
256
270
|
# durable lock beside the intent. intent_line deliberately does not expose
|
|
257
271
|
# this filesystem path in --data or any rendered surface.
|
|
258
272
|
intent_dir: File.expand_path(dir),
|
|
273
|
+
# Intent 336 (G3, D14): the ready node count for a graph-shaped intent,
|
|
274
|
+
# read through ReadySet, nil for every other intent. Explicitly advisory,
|
|
275
|
+
# entirely separate from the sources-derived "unblocked" flag below,
|
|
276
|
+
# which is the cross-intent knowledge graph and stays exactly as it was
|
|
277
|
+
# (327 D40 forbids that field from gating work). ReadySet.analyze is
|
|
278
|
+
# called only when real.("graph.md") is true, so the cost of this read
|
|
279
|
+
# is zero for every intent that has no graph.
|
|
280
|
+
ready_node_count: real.("graph.md") ? ready_node_count_for(dir) : nil,
|
|
259
281
|
}
|
|
260
282
|
end
|
|
261
283
|
|
|
284
|
+
# The count of currently-ready nodes in a graph-shaped intent's own graph.md,
|
|
285
|
+
# or nil when ReadySet cannot analyze it (a malformed or cyclic graph never
|
|
286
|
+
# raises out of the dashboard).
|
|
287
|
+
def ready_node_count_for(dir)
|
|
288
|
+
analysis = ReadySet.analyze(dir)
|
|
289
|
+
return nil unless analysis[:ok]
|
|
290
|
+
|
|
291
|
+
analysis[:nodes].count { |_, view| view[:ready] }
|
|
292
|
+
end
|
|
293
|
+
|
|
262
294
|
def checklist_partially_done?(path)
|
|
263
295
|
txt = File.read(path)
|
|
264
296
|
checked = txt.scan(/^\s*- \[x\]/i).size
|
|
@@ -561,7 +593,7 @@ def render_continue(records)
|
|
|
561
593
|
out.concat(matrix(open, scope_tag: true))
|
|
562
594
|
out << ""
|
|
563
595
|
out << LEGEND
|
|
564
|
-
out << "ask for the <slug> project board · plastic
|
|
596
|
+
out << "ask for the <slug> project board · plastic auto take ID (works the dispatchable queue)"
|
|
565
597
|
out.join("\n") + "\n"
|
|
566
598
|
end
|
|
567
599
|
|
|
@@ -670,15 +702,10 @@ end
|
|
|
670
702
|
# recent_delivery_summary (D1 fix) uses this twice: once per intent label, so a
|
|
671
703
|
# paragraph-long `intent` field collapses to a short name, and once on the fully assembled
|
|
672
704
|
# summary string, the hard budget cap that holds no matter how the per-label math adds up.
|
|
705
|
+
# Intent 331f: the one implementation now lives on ReportScreen; this delegates so dashboard.rb
|
|
706
|
+
# and every ReportScreen render entry point share it.
|
|
673
707
|
def truncate_on_word_boundary(text, max_chars)
|
|
674
|
-
|
|
675
|
-
return t if t.length <= max_chars
|
|
676
|
-
ellipsis = "…"
|
|
677
|
-
limit = [max_chars - ellipsis.length, 0].max
|
|
678
|
-
slice = t[0, limit]
|
|
679
|
-
cut = slice.rindex(/\s/)
|
|
680
|
-
slice = slice[0, cut] if cut && cut.positive?
|
|
681
|
-
"#{slice.rstrip}#{ellipsis}"
|
|
708
|
+
ReportScreen.truncate_on_word_boundary(text, max_chars)
|
|
682
709
|
end
|
|
683
710
|
|
|
684
711
|
# D3 fix (intent 202 gate review): completion dates only carry day granularity, and many
|
|
@@ -821,6 +848,7 @@ def next_work(records, cap: NEXT_WORK_CAP)
|
|
|
821
848
|
{ id: r[:id], intent: r[:intent], scope: r[:scope], lifecycle: r[:lifecycle],
|
|
822
849
|
value: r[:value].to_s, disposition: r[:disposition], flags: r[:flags],
|
|
823
850
|
what: cell(text), flags_label: cell(Array(r[:flags]).join(", ")),
|
|
851
|
+
ready_node_count: r[:ready_node_count],
|
|
824
852
|
line: "#{r[:id]} #{text}" }
|
|
825
853
|
end
|
|
826
854
|
end
|
|
@@ -828,7 +856,7 @@ end
|
|
|
828
856
|
def short_description(scope)
|
|
829
857
|
return "" unless scope.start_with?("project:")
|
|
830
858
|
slug = scope.sub("project:", "")
|
|
831
|
-
agents = File.join(PLASTIC_HOME,
|
|
859
|
+
agents = File.join(Plastic::StoreLayout.project_root(PLASTIC_HOME, slug), "AGENTS.md")
|
|
832
860
|
if File.exist?(agents)
|
|
833
861
|
File.readlines(agents).each do |l|
|
|
834
862
|
t = l.strip
|
|
@@ -969,6 +997,211 @@ def render_json(records, scope_label)
|
|
|
969
997
|
}
|
|
970
998
|
end
|
|
971
999
|
|
|
1000
|
+
# ---------------------------------------------------------------------------
|
|
1001
|
+
# Screen renderer (intent 331d) - dashboard.rb continue|project <slug> --screen.
|
|
1002
|
+
#
|
|
1003
|
+
# Sources every fact through the same helpers report-screen and DaySummary
|
|
1004
|
+
# already use (Resolved contract, ACTION_1): a missing source prints "not
|
|
1005
|
+
# recorded" or "none", never a guess or a crash (R1). The classification
|
|
1006
|
+
# pipeline above (classify, rank_key, actionable?, QUADRANTS, disposition_of)
|
|
1007
|
+
# is read here, never edited: the screen is a new renderer over the same
|
|
1008
|
+
# records (D3), so every count and rank matches what --json already reports
|
|
1009
|
+
# for the identical scope.
|
|
1010
|
+
# ---------------------------------------------------------------------------
|
|
1011
|
+
|
|
1012
|
+
SCREEN_ACTIVE_CAP = 8
|
|
1013
|
+
SCREEN_NEXT_CAP = 6
|
|
1014
|
+
SCREEN_NOT_RECORDED = "not recorded"
|
|
1015
|
+
|
|
1016
|
+
def screen_scope_slug(scope)
|
|
1017
|
+
scope.sub(/\Aproject:/, "")
|
|
1018
|
+
end
|
|
1019
|
+
|
|
1020
|
+
# Tier root: PLASTIC_HOME for "global", PLASTIC_HOME/projects/<slug> for
|
|
1021
|
+
# "project:<slug>" (Resolved contract) - the same tier scripts/lib/qmd_sync.rb
|
|
1022
|
+
# already derives from a store path. Global's own roadmaps/INDEX.md sit
|
|
1023
|
+
# directly under PLASTIC_HOME, with no intervening "store" segment.
|
|
1024
|
+
def screen_tier_root(plastic_home, scope)
|
|
1025
|
+
return plastic_home if scope == "global"
|
|
1026
|
+
Plastic::StoreLayout.project_root(plastic_home, screen_scope_slug(scope))
|
|
1027
|
+
end
|
|
1028
|
+
|
|
1029
|
+
# "global" spans every store (the same aggregate render_continue and --json's
|
|
1030
|
+
# continue-mode subset already read); a project scope narrows to its own
|
|
1031
|
+
# records. Never a project-only helper on the unscoped path (D10).
|
|
1032
|
+
def screen_scoped_records(records, scope)
|
|
1033
|
+
return records if scope == "global"
|
|
1034
|
+
records.select { |r| r[:scope] == scope }
|
|
1035
|
+
end
|
|
1036
|
+
|
|
1037
|
+
def screen_active_count(scoped)
|
|
1038
|
+
scoped.count { |r| r[:status] == "active" }
|
|
1039
|
+
end
|
|
1040
|
+
|
|
1041
|
+
# D2: a stale or absent lock never counts as delivering.
|
|
1042
|
+
def screen_in_delivery_count(scoped, now:)
|
|
1043
|
+
scoped.count do |r|
|
|
1044
|
+
r[:status] == "active" && Lock.who(r[:intent_dir], now: now)["state"] == "fresh"
|
|
1045
|
+
end
|
|
1046
|
+
end
|
|
1047
|
+
|
|
1048
|
+
# D3: completed_on (the INDEX.md completion date) is the source of truth,
|
|
1049
|
+
# rec[:done_at] (the savepoint's own Done timestamp) the fallback when a
|
|
1050
|
+
# completed intent has no dated INDEX entry yet. `created` is never read.
|
|
1051
|
+
def screen_completion_date(rec)
|
|
1052
|
+
raw = rec[:completed_on].to_s
|
|
1053
|
+
raw = rec[:done_at].to_s if raw.empty?
|
|
1054
|
+
return nil if raw.empty?
|
|
1055
|
+
begin
|
|
1056
|
+
Date.parse(raw)
|
|
1057
|
+
rescue ArgumentError, TypeError
|
|
1058
|
+
nil
|
|
1059
|
+
end
|
|
1060
|
+
end
|
|
1061
|
+
|
|
1062
|
+
def screen_delivered_count(scoped, now:)
|
|
1063
|
+
today_date = now.to_date
|
|
1064
|
+
scoped.count do |r|
|
|
1065
|
+
next false unless r[:status] == "completed"
|
|
1066
|
+
date = screen_completion_date(r)
|
|
1067
|
+
next false unless date
|
|
1068
|
+
diff = (today_date - date).to_i
|
|
1069
|
+
diff >= 0 && diff <= 7
|
|
1070
|
+
end
|
|
1071
|
+
end
|
|
1072
|
+
|
|
1073
|
+
# "none" on state none/tie/exhausted or a missing roadmaps dir (R1, D4); the
|
|
1074
|
+
# live frontier otherwise (D17). RoadmapQueue itself tolerates an absent
|
|
1075
|
+
# directory (Dir.exist? guard), so this never crashes on a bare tier.
|
|
1076
|
+
def screen_roadmap_field(plastic_home, scope, now:)
|
|
1077
|
+
tier = screen_tier_root(plastic_home, scope)
|
|
1078
|
+
reader = RoadmapQueue.new(roadmaps_dir: File.join(tier, "roadmaps"),
|
|
1079
|
+
index_path: File.join(tier, "INDEX.md"), now: now)
|
|
1080
|
+
payload = reader.which
|
|
1081
|
+
return "none" if %w[none tie exhausted].include?(payload["state"])
|
|
1082
|
+
"#{payload['roadmap']} · #{payload['frontier_wave']}"
|
|
1083
|
+
end
|
|
1084
|
+
|
|
1085
|
+
# Sessions are global, never per-project (Resolved contract): always the
|
|
1086
|
+
# PLASTIC_HOME/store tmp root, and `session: nil` (A6) so the calling
|
|
1087
|
+
# session's own live heartbeat counts rather than being excluded as "self".
|
|
1088
|
+
def screen_sessions_count(plastic_home, now:)
|
|
1089
|
+
store = Plastic::StoreLayout.global_store(plastic_home)
|
|
1090
|
+
DaySummary.active_sessions(store, nil, now: now, ttl: DaySummary::HEARTBEAT_TTL).size
|
|
1091
|
+
end
|
|
1092
|
+
|
|
1093
|
+
def screen_changed_field(scoped)
|
|
1094
|
+
latest = scoped.map { |r| r[:last_accessed_at].to_s }.reject(&:empty?).max
|
|
1095
|
+
return SCREEN_NOT_RECORDED unless latest
|
|
1096
|
+
t = begin
|
|
1097
|
+
Time.parse(latest)
|
|
1098
|
+
rescue ArgumentError, TypeError
|
|
1099
|
+
nil
|
|
1100
|
+
end
|
|
1101
|
+
return SCREEN_NOT_RECORDED unless t
|
|
1102
|
+
t.utc.strftime("%Y-%m-%d %H:%M UTC")
|
|
1103
|
+
end
|
|
1104
|
+
|
|
1105
|
+
# D6, intent 331f: the one Lead freshness rule every screen shares - fresh prints
|
|
1106
|
+
# "agent · key", a stale lock prints "stale · N min" (never "not recorded" or a bare "idle",
|
|
1107
|
+
# which would either hide the staleness or contradict a live In-delivery count with a dead
|
|
1108
|
+
# lead), and no lock (or one that will not read) prints "idle".
|
|
1109
|
+
def screen_lead_field(rec, now:)
|
|
1110
|
+
ReportScreen.lead_cell(rec[:intent_dir], now: now)
|
|
1111
|
+
end
|
|
1112
|
+
|
|
1113
|
+
# D3 (331d1): the owner ruled no rendered row exceeds 115 visible columns.
|
|
1114
|
+
# The bound is on the WHOLE pipe-delimited row, not on one cell, so the
|
|
1115
|
+
# Intent cell gets whatever the other cells leave it. A cell short enough on
|
|
1116
|
+
# its own still drifts the row past the bound once the bar, the lead and the
|
|
1117
|
+
# scaffolding are added, which is the failure this measures away.
|
|
1118
|
+
SCREEN_ROW_MAX_COLUMNS = 115
|
|
1119
|
+
|
|
1120
|
+
# D2 (331d1): the Intent cell carries the intent line up to but not including
|
|
1121
|
+
# its first colon. A Plastic intent line opens with a short name and then
|
|
1122
|
+
# explains itself after a colon, so the lead IS the name; a line with no
|
|
1123
|
+
# colon is already a name and passes through whole. Escaping happens here, so
|
|
1124
|
+
# the budget below measures what actually reaches the row.
|
|
1125
|
+
def screen_intent_title(rec)
|
|
1126
|
+
# D8: one colon rule for every screen, on ReportScreen. `max:` is the whole line here
|
|
1127
|
+
# because screen_fit_intent does this cell's own width budgeting a moment later.
|
|
1128
|
+
cell(ReportScreen.title_before_colon(rec[:intent], max: rec[:intent].to_s.length + 1))
|
|
1129
|
+
end
|
|
1130
|
+
|
|
1131
|
+
# The Intent cell fitted to what the row has left. `others` are the already
|
|
1132
|
+
# rendered sibling cells; the scaffolding is the leading "| ", a " | " between
|
|
1133
|
+
# every pair of cells, and the trailing " |". Intent 331f1: the one implementation now lives
|
|
1134
|
+
# on ReportScreen (fit_row_cell), measured in DISPLAY columns rather than String#length - an
|
|
1135
|
+
# `others` cell carrying a progress bar costs two columns per glyph, not one (RC1) - so
|
|
1136
|
+
# roadmap_state_entries_table's own Intent cell spends the same budget by the same rule.
|
|
1137
|
+
def screen_fit_intent(title, others)
|
|
1138
|
+
ReportScreen.fit_row_cell(title, others, max: SCREEN_ROW_MAX_COLUMNS)
|
|
1139
|
+
end
|
|
1140
|
+
|
|
1141
|
+
# D6: last_accessed_at descending, then id, capped at SCREEN_ACTIVE_CAP.
|
|
1142
|
+
def screen_where_we_are(scoped, now:)
|
|
1143
|
+
active = scoped.select { |r| r[:status] == "active" }
|
|
1144
|
+
ordered = active.sort_by { |r| [invert_ts(r[:last_accessed_at]), r[:id]] }
|
|
1145
|
+
ordered.first(SCREEN_ACTIVE_CAP).map do |r|
|
|
1146
|
+
items = IntentScreen.checklist_items(r[:intent_dir])
|
|
1147
|
+
progress = IntentScreen.progress_fields(items)
|
|
1148
|
+
graph_id = r[:id].to_s
|
|
1149
|
+
stage = r[:lifecycle].to_s.capitalize
|
|
1150
|
+
bar = "#{progress['progress.bar']} #{progress['progress.done']} / #{progress['progress.total']}"
|
|
1151
|
+
lead = screen_lead_field(r, now: now)
|
|
1152
|
+
{
|
|
1153
|
+
graph_id: graph_id,
|
|
1154
|
+
intent: screen_fit_intent(screen_intent_title(r), [graph_id, stage, bar, lead]),
|
|
1155
|
+
stage: stage,
|
|
1156
|
+
progress: bar,
|
|
1157
|
+
lead: lead,
|
|
1158
|
+
}
|
|
1159
|
+
end
|
|
1160
|
+
end
|
|
1161
|
+
|
|
1162
|
+
# A2: the exact pool render_json's dispatchable_queue ranks - actionable?
|
|
1163
|
+
# records, rank_key order, filtered to disposition defer/research (D12
|
|
1164
|
+
# excludes drive/triage). Rank is that queue's own 1-based position, so a
|
|
1165
|
+
# capped display row's rank always agrees with the uncapped --json contract.
|
|
1166
|
+
def screen_dispatchable_pool(scope_records)
|
|
1167
|
+
ranked = scope_records.select { |r| actionable?(r) }.sort_by { |r| rank_key(r) }
|
|
1168
|
+
ranked.select { |r| %w[defer research].include?(r[:disposition]) }
|
|
1169
|
+
end
|
|
1170
|
+
|
|
1171
|
+
def screen_where_we_go_next(scope_records)
|
|
1172
|
+
pool = screen_dispatchable_pool(scope_records)
|
|
1173
|
+
pool.each_with_index.map do |r, i|
|
|
1174
|
+
rank = i + 1
|
|
1175
|
+
graph_id = r[:id].to_s
|
|
1176
|
+
reason = r[:quadrant].to_s
|
|
1177
|
+
{
|
|
1178
|
+
rank: rank,
|
|
1179
|
+
graph_id: graph_id,
|
|
1180
|
+
intent: screen_fit_intent(screen_intent_title(r), [rank.to_s, graph_id, reason]),
|
|
1181
|
+
reason: reason,
|
|
1182
|
+
}
|
|
1183
|
+
end.first(SCREEN_NEXT_CAP)
|
|
1184
|
+
end
|
|
1185
|
+
|
|
1186
|
+
def screen_fields(records, scope, plastic_home:, now: Time.now)
|
|
1187
|
+
scoped = screen_scoped_records(records, scope)
|
|
1188
|
+
{
|
|
1189
|
+
scope: scope,
|
|
1190
|
+
active: screen_active_count(scoped),
|
|
1191
|
+
in_delivery: screen_in_delivery_count(scoped, now: now),
|
|
1192
|
+
delivered: screen_delivered_count(scoped, now: now),
|
|
1193
|
+
roadmap: screen_roadmap_field(plastic_home, scope, now: now),
|
|
1194
|
+
sessions: screen_sessions_count(plastic_home, now: now),
|
|
1195
|
+
changed: screen_changed_field(scoped),
|
|
1196
|
+
where_we_are: screen_where_we_are(scoped, now: now),
|
|
1197
|
+
where_we_go_next: screen_where_we_go_next(scoped),
|
|
1198
|
+
}
|
|
1199
|
+
end
|
|
1200
|
+
|
|
1201
|
+
def render_screen(records, scope, plastic_home: PLASTIC_HOME, now: Time.now)
|
|
1202
|
+
DashboardScreen.render(screen_fields(records, scope, plastic_home: plastic_home, now: now))
|
|
1203
|
+
end
|
|
1204
|
+
|
|
972
1205
|
# ---------------------------------------------------------------------------
|
|
973
1206
|
# CLI
|
|
974
1207
|
# ---------------------------------------------------------------------------
|
|
@@ -997,6 +1230,8 @@ def main(argv)
|
|
|
997
1230
|
json = argv.delete("--json")
|
|
998
1231
|
data = argv.delete("--data")
|
|
999
1232
|
plain = argv.delete("--plain")
|
|
1233
|
+
screen = argv.delete("--screen")
|
|
1234
|
+
ansi = argv.delete("--ansi")
|
|
1000
1235
|
all = argv.delete("--all")
|
|
1001
1236
|
limit_active = extract_flag_value(argv, "--limit-active")
|
|
1002
1237
|
limit_next = extract_flag_value(argv, "--limit-next")
|
|
@@ -1047,6 +1282,22 @@ def main(argv)
|
|
|
1047
1282
|
return 0
|
|
1048
1283
|
end
|
|
1049
1284
|
|
|
1285
|
+
if screen
|
|
1286
|
+
if mode == "project" && (slug.nil? || slug.empty?)
|
|
1287
|
+
warn "usage: dashboard.rb project <slug> --screen"
|
|
1288
|
+
return 2
|
|
1289
|
+
end
|
|
1290
|
+
scope = mode == "project" ? "project:#{slug}" : "global"
|
|
1291
|
+
text = render_screen(records, scope, plastic_home: PLASTIC_HOME, now: Time.now)
|
|
1292
|
+
# A4: the identical capability guard scripts/report-screen:161 applies -
|
|
1293
|
+
# NO_COLOR always wins to plain; a non-tty stdout stays plain unless the
|
|
1294
|
+
# PLASTIC_FORCE_COLOR test seam is set.
|
|
1295
|
+
ansi_enabled = ansi && ENV["NO_COLOR"].to_s.empty? &&
|
|
1296
|
+
($stdout.tty? || ENV["PLASTIC_FORCE_COLOR"] == "1")
|
|
1297
|
+
print(ansi_enabled ? (ScreenPaint.paint(text, color: true) || text) : text)
|
|
1298
|
+
return 0
|
|
1299
|
+
end
|
|
1300
|
+
|
|
1050
1301
|
case mode
|
|
1051
1302
|
when "continue" then print render_continue(records)
|
|
1052
1303
|
when "project"
|
package/scripts/day-summary
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
#
|
|
15
15
|
# Exit codes: 0 done; 2 usage error.
|
|
16
16
|
|
|
17
|
+
require_relative "lib/store_layout"
|
|
17
18
|
require_relative "lib/session_ledger"
|
|
18
19
|
require_relative "lib/day_summary"
|
|
19
20
|
|
|
@@ -43,7 +44,7 @@ end
|
|
|
43
44
|
|
|
44
45
|
opts = parse_args(ARGV)
|
|
45
46
|
home = opts[:home] ? expand(opts[:home]) : expand(ENV.fetch("PLASTIC_HOME", "~/.plastic"))
|
|
46
|
-
store = opts[:store] ? expand(opts[:store]) :
|
|
47
|
+
store = opts[:store] ? expand(opts[:store]) : Plastic::StoreLayout.global_store(home)
|
|
47
48
|
day = opts[:day] || SessionLedger.day_id
|
|
48
49
|
usage_abort("--day must be eight digits parsing as a real date, got #{day.inspect}") unless SessionLedger.valid_day_id?(day)
|
|
49
50
|
session = SessionLedger.short_session_id(opts[:session], ENV["CLAUDE_CODE_SESSION_ID"])
|