@zalom/plastic 2.0.0-alpha.17 → 2.0.0-alpha.18
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/package.json +1 -1
- package/scripts/hook-capture +4 -105
package/package.json
CHANGED
package/scripts/hook-capture
CHANGED
|
@@ -5,12 +5,11 @@
|
|
|
5
5
|
# Usage: hook-capture (reads the UserPromptSubmit stdin JSON payload)
|
|
6
6
|
# Intent 298. One UserPromptSubmit process replacing hook-continue,
|
|
7
7
|
# hook-future-intent-check, and hook-auto-arm: it appends a pending line to
|
|
8
|
-
# the session day ledger
|
|
9
|
-
#
|
|
10
|
-
#
|
|
8
|
+
# the session day ledger and detects "continue" and "auto" prompts. Every job
|
|
9
|
+
# runs in its own rescue and the hook always exits 0 (spec D2). Intent 345
|
|
10
|
+
# (D7, 323) removed the per-prompt Future-intent hint step entirely.
|
|
11
11
|
|
|
12
12
|
require "json"
|
|
13
|
-
require "yaml"
|
|
14
13
|
require "open3"
|
|
15
14
|
require "fileutils"
|
|
16
15
|
require_relative "lib/session_ledger"
|
|
@@ -42,87 +41,6 @@ templates = File.expand_path("../templates", __dir__)
|
|
|
42
41
|
sid = SessionLedger.short_session_id(nil, session_id)
|
|
43
42
|
today = SessionLedger.day_id
|
|
44
43
|
|
|
45
|
-
# --- Extracted matching logic, shared by both the global and project store
|
|
46
|
-
# passes of job (f) below (mirrors hook-future-intent-check verbatim). ---
|
|
47
|
-
def future_intent_matches(store_root, message)
|
|
48
|
-
index_path = File.join(store_root, "INDEX.md")
|
|
49
|
-
return [] unless File.exist?(index_path)
|
|
50
|
-
|
|
51
|
-
lines = File.readlines(index_path)
|
|
52
|
-
future_dirs = []
|
|
53
|
-
section = nil
|
|
54
|
-
|
|
55
|
-
lines.each do |line|
|
|
56
|
-
section = :future if line.start_with?("## Future")
|
|
57
|
-
section = nil if line.start_with?("## ") && !line.start_with?("## Future")
|
|
58
|
-
next unless section == :future && line.strip.start_with?("- [")
|
|
59
|
-
|
|
60
|
-
future_dirs << $1 if line =~ /store\/([\w-]+)\//
|
|
61
|
-
end
|
|
62
|
-
return [] if future_dirs.empty?
|
|
63
|
-
|
|
64
|
-
matches = []
|
|
65
|
-
message_words = message.split(/\W+/).reject { |w| w.length < 4 }
|
|
66
|
-
|
|
67
|
-
future_dirs.each do |dir|
|
|
68
|
-
intent_path = File.join(store_root, "store", dir, "#{dir}.md")
|
|
69
|
-
next unless File.exist?(intent_path)
|
|
70
|
-
|
|
71
|
-
content = File.read(intent_path)
|
|
72
|
-
|
|
73
|
-
tags = []
|
|
74
|
-
tags = $1.split(",").map(&:strip).map(&:downcase) if content =~ /^tags:\s*\[([^\]]+)\]/
|
|
75
|
-
|
|
76
|
-
intent_name = ""
|
|
77
|
-
intent_name = $1.downcase if content =~ /^intent:\s*["']?(.+?)["']?\s*$/
|
|
78
|
-
|
|
79
|
-
keywords = (tags + intent_name.split(/\W+/).reject { |w| w.length < 4 }).map(&:downcase).uniq
|
|
80
|
-
|
|
81
|
-
matched_keywords = keywords.select { |kw| message.include?(kw) }
|
|
82
|
-
name_matches = message_words.select { |w| intent_name.include?(w) }
|
|
83
|
-
matched_keywords = (matched_keywords + name_matches).uniq
|
|
84
|
-
|
|
85
|
-
next if matched_keywords.empty?
|
|
86
|
-
|
|
87
|
-
index_line = lines.find { |l| l.include?(dir) }&.strip || "#{dir} - #{intent_name}"
|
|
88
|
-
matches << { "index_line" => index_line, "keywords" => matched_keywords }
|
|
89
|
-
end
|
|
90
|
-
matches
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# The store_root that carries the current project's own store, resolved from
|
|
94
|
-
# cwd through projects.yml, mirroring SessionLedger.project_slug's matching
|
|
95
|
-
# but returning nil (not "global") when nothing matches, since there is then
|
|
96
|
-
# no distinct second store to hint against.
|
|
97
|
-
def resolve_project_store_root(cwd, plastic_home)
|
|
98
|
-
projects_path = File.join(plastic_home, "projects.yml")
|
|
99
|
-
return nil unless File.exist?(projects_path)
|
|
100
|
-
|
|
101
|
-
data = begin
|
|
102
|
-
YAML.safe_load(File.read(projects_path))
|
|
103
|
-
rescue StandardError
|
|
104
|
-
nil
|
|
105
|
-
end
|
|
106
|
-
data = {} unless data.is_a?(Hash)
|
|
107
|
-
projects = data["projects"].is_a?(Hash) ? data["projects"] : {}
|
|
108
|
-
expanded_cwd = File.expand_path(cwd)
|
|
109
|
-
|
|
110
|
-
matches = projects.filter_map do |slug, info|
|
|
111
|
-
next unless info.is_a?(Hash) && slug.is_a?(String)
|
|
112
|
-
|
|
113
|
-
path = info["path"]
|
|
114
|
-
next unless path
|
|
115
|
-
|
|
116
|
-
root = File.expand_path(path)
|
|
117
|
-
next unless expanded_cwd == root || expanded_cwd.start_with?("#{root}#{File::SEPARATOR}")
|
|
118
|
-
|
|
119
|
-
[root.length, slug]
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
best = matches.max_by { |(length, _slug)| length }
|
|
123
|
-
best ? File.join(plastic_home, "projects", best[1]) : nil
|
|
124
|
-
end
|
|
125
|
-
|
|
126
44
|
def truncate(text, max)
|
|
127
45
|
return text if text.length <= max
|
|
128
46
|
|
|
@@ -183,7 +101,7 @@ system_message = nil
|
|
|
183
101
|
|
|
184
102
|
# --- (d) "continue" cockpit -------------------------------------------------
|
|
185
103
|
begin
|
|
186
|
-
if prompt.
|
|
104
|
+
if prompt.to_s.strip.downcase == "continue"
|
|
187
105
|
dashboard = File.expand_path("dashboard.rb", __dir__)
|
|
188
106
|
if File.exist?(dashboard)
|
|
189
107
|
cockpit, _err, status = Open3.capture3({ "RUBYOPT" => nil }, "ruby", dashboard, "continue")
|
|
@@ -224,25 +142,6 @@ rescue StandardError
|
|
|
224
142
|
nil
|
|
225
143
|
end
|
|
226
144
|
|
|
227
|
-
# --- (f) Future-intent hint, global store then the project's own store -----
|
|
228
|
-
begin
|
|
229
|
-
message = prompt.to_s.downcase
|
|
230
|
-
if message.strip.length >= 10 && message.strip != "continue"
|
|
231
|
-
matches = future_intent_matches(plastic_home, message)
|
|
232
|
-
project_root = resolve_project_store_root(cwd, plastic_home)
|
|
233
|
-
matches += future_intent_matches(project_root, message) if project_root
|
|
234
|
-
|
|
235
|
-
if matches.any?
|
|
236
|
-
parts = ["PLASTIC - Future intents related to this message:\n"]
|
|
237
|
-
matches.each { |m| parts << "#{m["index_line"]} (matched: #{m["keywords"].join(", ")})" }
|
|
238
|
-
parts << "\nConsider asking the user if they want to activate any of these, or note the connection."
|
|
239
|
-
context_parts << parts.join("\n")
|
|
240
|
-
end
|
|
241
|
-
end
|
|
242
|
-
rescue StandardError
|
|
243
|
-
nil
|
|
244
|
-
end
|
|
245
|
-
|
|
246
145
|
exit 0 if context_parts.empty?
|
|
247
146
|
|
|
248
147
|
payload_out = {
|