@zalom/plastic 1.14.1 → 2.0.0-alpha.10
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 +101 -294
- package/README.md +5 -6
- package/agents/plastic-advisor.md +18 -17
- package/agents/plastic-enforcer.md +57 -69
- package/agents/plastic-executor.md +34 -20
- package/agents/plastic-faux-advisor.md +18 -18
- package/bin/lib/context_budget.rb +453 -0
- package/bin/plastic-bench +78 -0
- package/config_asks.yml +38 -0
- package/hooks/capture +4 -0
- package/hooks/close +5 -0
- package/hooks/hooks.json +14 -42
- package/hooks/message-display +81 -0
- package/hooks/record +4 -0
- package/hooks/savepoint +5 -5
- package/package.json +3 -2
- package/scripts/agent-report +15 -10
- package/scripts/append-ledger +208 -0
- package/scripts/codex-hook +78 -90
- package/scripts/dashboard.rb +43 -14
- package/scripts/day-summary +53 -0
- package/scripts/doctor.rb +269 -43
- package/scripts/end-intent +170 -76
- package/scripts/exec-worktree +11 -20
- package/scripts/file-session-intent +61 -0
- package/scripts/hook-capture +255 -0
- package/scripts/hook-close +36 -0
- package/scripts/hook-message-display +74 -0
- package/scripts/hook-record +157 -0
- package/scripts/hook-savepoint +45 -0
- package/scripts/hook-session-start +128 -12
- package/scripts/install.rb +8 -6
- package/scripts/intent-screen +77 -0
- package/scripts/lib/agent_models.rb +1 -7
- package/scripts/lib/arm.rb +308 -0
- package/scripts/lib/backfill_intent.rb +316 -0
- package/scripts/lib/bridge.rb +24 -1597
- package/scripts/lib/compact_instructions.rb +56 -0
- package/scripts/lib/day_summary.rb +211 -0
- package/scripts/lib/doctor_core.rb +66 -88
- package/scripts/lib/doctor_session_ledger.rb +158 -0
- package/scripts/lib/exec_worktree.rb +14 -60
- package/scripts/lib/handoff.rb +184 -0
- package/scripts/lib/harness_text.rb +5 -4
- package/scripts/lib/hook_registry.rb +57 -106
- package/scripts/lib/insights.rb +3 -4
- package/scripts/lib/installer_core.rb +136 -41
- package/scripts/lib/intent_screen.rb +309 -0
- package/scripts/lib/intent_screen_ansi.rb +262 -0
- package/scripts/lib/lock.rb +6 -26
- package/scripts/lib/message_display.rb +290 -0
- package/scripts/lib/outcome_guard.rb +7 -5
- package/scripts/lib/project_validator.rb +30 -1
- package/scripts/lib/report_screen.rb +648 -0
- package/scripts/lib/roadmap_queue.rb +3 -3
- package/scripts/lib/roadmap_savepoint.rb +1 -1
- package/scripts/lib/rule_catalog.rb +6 -3
- package/scripts/lib/savepoint.rb +334 -0
- package/scripts/lib/scaffold_intent.rb +13 -179
- package/scripts/lib/screen_paint.rb +276 -0
- package/scripts/lib/session_backfill.rb +184 -0
- package/scripts/lib/session_close.rb +104 -0
- package/scripts/lib/session_git.rb +500 -0
- package/scripts/lib/session_ledger.rb +621 -0
- package/scripts/lib/store_discovery.rb +1 -1
- package/scripts/lib/store_provisioning.rb +1 -1
- package/scripts/maintenance-run +7 -7
- package/scripts/new-intent +63 -4
- package/scripts/plastic-lock +101 -41
- package/scripts/promote-session-item +150 -0
- package/scripts/read-config +3 -0
- package/scripts/rebuild-graph +1 -1
- package/scripts/report-screen +120 -0
- package/scripts/roadmap-next +2 -2
- package/scripts/rollback.rb +48 -3
- package/scripts/savepoint-note +67 -0
- package/scripts/scaffold-intent +37 -63
- package/scripts/session-commit +116 -0
- package/scripts/spawn-preamble +14 -7
- package/scripts/update.rb +7 -3
- package/scripts/write-handoff +60 -0
- package/skills/agent-advisor/SKILL.md +3 -3
- package/skills/agent-advisor/references/advisor-protocol.md +20 -20
- package/skills/auto/SKILL.md +223 -317
- package/skills/auto/evals/evals.json +10 -10
- package/skills/auto/references/agent-architecture.md +102 -90
- package/skills/auto/references/agent-report-contract.md +4 -21
- package/skills/auto/references/end-tail.md +47 -53
- package/skills/auto/references/human-report-contract.md +62 -56
- package/skills/conventions/SKILL.md +1 -3
- package/skills/conventions/references/lifecycle-and-savepoints.md +35 -1
- package/skills/conventions/references/locks-and-worktrees.md +62 -58
- package/skills/conventions/references/maintenance-and-revisions.md +12 -12
- package/skills/dashboard/SKILL.md +1 -1
- package/skills/direct/SKILL.md +66 -0
- package/skills/direct/references/request-signals.md +59 -0
- package/skills/doctor/SKILL.md +58 -11
- package/skills/doctor/report.md +3 -1
- package/skills/install/SKILL.md +11 -9
- package/skills/intent-continuing/SKILL.md +142 -91
- package/skills/intent-continuing/references/boarding-matrix.md +34 -0
- package/skills/intent-continuing/references/context-management.md +3 -3
- package/skills/intent-creating/SKILL.md +2 -2
- package/skills/intent-creating/references/lifecycle.md +1 -1
- package/skills/intent-ending/SKILL.md +34 -37
- package/skills/intent-ending/evals/evals.json +4 -4
- package/skills/intent-executing/SKILL.md +15 -46
- package/skills/intent-executing/plan-reviewer-prompt.md +39 -0
- package/skills/intent-speccing/SKILL.md +108 -51
- package/skills/intent-speccing/evals/evals.json +5 -5
- package/skills/intent-speccing/references/per-section-fill-rules.md +4 -4
- package/skills/intent-speccing/references/self-verify-checklist.md +1 -2
- package/skills/project-creating/SKILL.md +1 -1
- package/skills/releasing/SKILL.md +7 -8
- package/skills/tutorial/SKILL.md +2 -2
- package/skills/tutorial/references/track-1-guided.md +16 -18
- package/skills/tutorial/references/track-2-auto.md +12 -13
- package/skills/tutorial/references/track-3-projects-and-roadmaps.md +2 -2
- package/templates/agents.md +4 -7
- package/templates/config.yml +5 -0
- package/templates/intent-screen.md +17 -0
- package/templates/outcome.md +14 -1
- package/templates/project.yml +7 -0
- package/templates/report-state.md +11 -0
- package/templates/roadmap.md +1 -1
- package/templates/savepoint.md +2 -2
- package/templates/session-intent.md +29 -0
- package/agents/plastic-brainstorming.md +0 -48
- package/agents/plastic-future-intent-researcher.md +0 -35
- package/agents/plastic-intent-curator.md +0 -47
- package/agents/plastic-intent-discovery.md +0 -43
- package/agents/plastic-planner.md +0 -56
- package/agents/plastic-spec-specialist.md +0 -53
- package/hooks/auto-arm +0 -5
- package/hooks/bash-gate +0 -3
- package/hooks/continue +0 -31
- package/hooks/edit-gates +0 -3
- package/hooks/future-intent-check +0 -25
- package/hooks/gate-check +0 -12
- package/hooks/power-tools +0 -8
- package/scripts/hook-auto-arm +0 -51
- package/scripts/hook-bash-gate +0 -78
- package/scripts/hook-code-gate +0 -38
- package/scripts/hook-continue +0 -48
- package/scripts/hook-create-gate +0 -51
- package/scripts/hook-edit-gates +0 -58
- package/scripts/hook-future-intent-check +0 -90
- package/scripts/hook-gate-check +0 -169
- package/scripts/hook-links-gate +0 -54
- package/scripts/hook-lock-gate +0 -47
- package/scripts/hook-power-tools +0 -38
- package/scripts/hook-savepoint-pre +0 -32
- package/scripts/lib/codex_edit_gates.rb +0 -138
- package/scripts/lib/edit_gates.rb +0 -398
- package/scripts/lib/links_gate.rb +0 -140
- package/scripts/lib/qmd_hook.rb +0 -41
- package/scripts/lib/spec_header.rb +0 -83
- package/scripts/lib/start_intent.rb +0 -296
- package/scripts/start-intent +0 -89
- package/skills/_active-intent-gate.md +0 -26
- package/skills/auto/references/tiers.md +0 -100
- package/skills/continuing/SKILL.md +0 -34
- package/skills/continuing/evals/evals.json +0 -91
- package/skills/conventions/references/gates-and-enforcement.md +0 -53
- package/skills/conventions/references/tiers-and-dispatch.md +0 -135
- package/skills/doctor/references/gates-stuck-detection.md +0 -43
- package/skills/intent-brainstorming/SKILL.md +0 -118
- package/skills/intent-brainstorming/evals/evals.json +0 -67
- package/skills/intent-continuing/evals/evals.json +0 -145
- package/skills/intent-discovering/SKILL.md +0 -56
- package/skills/intent-grilling/SKILL.md +0 -108
- package/skills/intent-linking/SKILL.md +0 -128
- package/skills/intent-linking/evals/evals.json +0 -22
- package/skills/intent-linking/references/zettelkasten.md +0 -45
- package/skills/intent-locking/SKILL.md +0 -64
- package/skills/intent-planning/SKILL.md +0 -145
- package/skills/intent-planning/evals/evals.json +0 -81
- package/skills/intent-planning/references/plan-format.md +0 -130
- package/skills/intent-researching/SKILL.md +0 -123
- package/skills/intent-researching/evals/evals.json +0 -22
- package/skills/intent-savepoint/SKILL.md +0 -83
- package/skills/intent-savepoint/references/context-management.md +0 -32
- package/skills/intent-starting/SKILL.md +0 -151
- package/skills/intent-starting/evals/evals.json +0 -117
- package/skills/intent-starting/references/boarding-matrix.md +0 -35
- package/skills/project-continuing/SKILL.md +0 -119
- package/skills/project-continuing/evals/evals.json +0 -100
- package/skills/roadmap-continuing/SKILL.md +0 -89
- package/skills/roadmap-continuing/evals/evals.json +0 -82
- package/skills/skill-creating/SKILL.md +0 -75
- package/skills/skill-creating/evals/evals.json +0 -108
- package/skills/skill-creating/references/agents.md +0 -168
- package/skills/skill-creating/references/defaults-first.md +0 -23
- package/skills/skill-creating/references/evals.md +0 -41
- package/skills/skill-creating/references/hooks.md +0 -251
- package/skills/skill-creating/references/progressive-disclosure.md +0 -176
- package/skills/skill-creating/references/scripts.md +0 -166
- package/skills/skill-creating/references/skills.md +0 -169
- package/skills/skill-creating/scripts/scaffold.rb +0 -313
- package/skills/skill-evaluating/SKILL.md +0 -141
- package/skills/skill-evaluating/assets/eval-template.json +0 -12
- package/skills/skill-evaluating/evals/evals.json +0 -75
- package/skills/skill-evaluating/references/convention-checks.md +0 -76
- package/skills/skill-evaluating/references/eval-methodology.md +0 -154
- package/skills/store-curating/SKILL.md +0 -64
- package/skills/store-curating/evals/evals.json +0 -38
- package/skills/store-indexing/SKILL.md +0 -93
- package/skills/store-indexing/evals/evals.json +0 -22
- package/skills/store-indexing/references/zettelkasten-linking.md +0 -32
- package/skills/store-provisioning/SKILL.md +0 -55
- /package/skills/{project-continuing → intent-continuing}/references/board-fill.md +0 -0
- /package/skills/{roadmap-continuing → intent-continuing}/references/liveness-ranking.md +0 -0
- /package/skills/{intent-brainstorming → intent-speccing}/references/design-principles.md +0 -0
package/scripts/rollback.rb
CHANGED
|
@@ -82,8 +82,6 @@ class Rollback < InstallerCore
|
|
|
82
82
|
File.exist?(path) ? File.read(path).strip : nil
|
|
83
83
|
end
|
|
84
84
|
|
|
85
|
-
private
|
|
86
|
-
|
|
87
85
|
def flag_value(argv, name)
|
|
88
86
|
i = argv.index(name)
|
|
89
87
|
return nil unless i && argv[i + 1]
|
|
@@ -93,11 +91,58 @@ class Rollback < InstallerCore
|
|
|
93
91
|
def switch_to(target, current)
|
|
94
92
|
action = action_for(target, current)
|
|
95
93
|
puts "#{action == "downgrade" ? "\u{23ea}" : "\u{23e9}"} #{current} \u{2192} #{target} (#{action})"
|
|
96
|
-
|
|
94
|
+
prepare_switch(target, current)
|
|
95
|
+
cmd = ["npx", "#{PKG}@#{target}", "install", "--reinstall", "--ledger-action", action, *harness_flags]
|
|
97
96
|
puts " $ #{cmd.join(" ")}"
|
|
98
97
|
system(*cmd) ? 0 : 1
|
|
99
98
|
end
|
|
100
99
|
|
|
100
|
+
# Before handing off to an older package (a downgrade), strip Plastic's own current hook
|
|
101
|
+
# registrations from every installed harness (intent 310, cut-inventory R2). The older
|
|
102
|
+
# installer purges only the names its own registry knows, so an entry this version
|
|
103
|
+
# registered would otherwise survive while the older manifest diff deletes its launcher,
|
|
104
|
+
# and the older doctor would then report an unowned plastic- command. The older installer
|
|
105
|
+
# registers its own set fresh on the cleaned file. Returns the paths it stripped.
|
|
106
|
+
def prepare_switch(target, current)
|
|
107
|
+
return [] unless action_for(target, current) == "downgrade"
|
|
108
|
+
|
|
109
|
+
stripped = []
|
|
110
|
+
installed_agents.each do |key|
|
|
111
|
+
agent = agents.find { |a| a[:key] == key }
|
|
112
|
+
next unless agent
|
|
113
|
+
|
|
114
|
+
case key
|
|
115
|
+
when "claude"
|
|
116
|
+
path = File.join(agent[:dir], "settings.json")
|
|
117
|
+
if File.exist?(path)
|
|
118
|
+
remove_claude_hooks(path)
|
|
119
|
+
stripped << path
|
|
120
|
+
end
|
|
121
|
+
# The compact-instructions block (intent 312), for the same reason: no older
|
|
122
|
+
# package knows the section exists, so nothing there would ever replace or
|
|
123
|
+
# remove it. The Codex AGENTS.md section needs no equivalent: every older
|
|
124
|
+
# package knows that one and rewrites it on the downgrade install.
|
|
125
|
+
claude_md = strip_claude_compact_section(File.join(agent[:dir], "CLAUDE.md"))
|
|
126
|
+
stripped << claude_md if claude_md
|
|
127
|
+
when "codex"
|
|
128
|
+
path = agent[:home_dir] && File.join(agent[:home_dir], "hooks.json")
|
|
129
|
+
if path && File.exist?(path)
|
|
130
|
+
remove_codex_hooks(path)
|
|
131
|
+
stripped << path
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
stripped
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# The install flags for every harness with a per-agent record (intent 310); Claude alone
|
|
139
|
+
# when nothing is recorded, matching the first-install default.
|
|
140
|
+
def harness_flags
|
|
141
|
+
keys = installed_agents
|
|
142
|
+
keys = ["claude"] if keys.empty?
|
|
143
|
+
keys.map { |k| "--#{k}" }
|
|
144
|
+
end
|
|
145
|
+
|
|
101
146
|
def print_table(ledger, current)
|
|
102
147
|
puts "\nPlastic version history (versions.json — append-only):\n\n"
|
|
103
148
|
puts " %-22s %-10s %s" % ["version", "action", "at"]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: UTF-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# savepoint-note - the writer CLI for the two savepoint kinds intent 317 adds:
|
|
6
|
+
# Review (one line per plan-review or post-execution-review verdict) and
|
|
7
|
+
# Commit (one line per commit landing during Exec). D17: there is no automatic
|
|
8
|
+
# seam for either (session-commit writes the day ledger, not the intent
|
|
9
|
+
# ledger; the executor's own commits are plain git), so this is an explicit
|
|
10
|
+
# thin wrapper on Savepoint.append_review_savepoint / append_commit_savepoint.
|
|
11
|
+
#
|
|
12
|
+
# Usage:
|
|
13
|
+
# savepoint-note <intent_dir> --kind Review|Commit --text "<text>"
|
|
14
|
+
#
|
|
15
|
+
# --text is normalized (D21): runs of two or more spaces collapse to one, so a
|
|
16
|
+
# free-text kind that happens to contain a double space cannot key the dedup
|
|
17
|
+
# primitive's split(/\s{2,}/) on the prefix alone and silently drop a later
|
|
18
|
+
# line. A newline in --text is refused outright (exit 2, ledger unchanged)
|
|
19
|
+
# rather than silently splitting one entry into two malformed ledger lines.
|
|
20
|
+
#
|
|
21
|
+
# Exit codes:
|
|
22
|
+
# 0 - the line was appended (or was already recorded; both are success)
|
|
23
|
+
# 2 - usage error, bad --kind, missing --text, a newline in --text, or the
|
|
24
|
+
# path is not an intent directory (one line on stderr, ledger unchanged)
|
|
25
|
+
|
|
26
|
+
require_relative "lib/savepoint"
|
|
27
|
+
|
|
28
|
+
def usage_abort(message)
|
|
29
|
+
warn "savepoint-note: #{message}"
|
|
30
|
+
exit 2
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
args = ARGV.dup
|
|
34
|
+
kind = nil
|
|
35
|
+
text = nil
|
|
36
|
+
positional = []
|
|
37
|
+
while (arg = args.shift)
|
|
38
|
+
case arg
|
|
39
|
+
when "--kind"
|
|
40
|
+
kind = args.shift
|
|
41
|
+
when "--text"
|
|
42
|
+
text = args.shift
|
|
43
|
+
else
|
|
44
|
+
usage_abort("unknown flag #{arg.inspect}") if arg.start_with?("--")
|
|
45
|
+
positional << arg
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
usage_abort("usage: savepoint-note <intent_dir> --kind Review|Commit --text \"<text>\"") unless positional.length == 1
|
|
50
|
+
|
|
51
|
+
intent_dir = File.expand_path(positional.first)
|
|
52
|
+
usage_abort("#{intent_dir} is not an intent directory") unless File.exist?(Savepoint.intent_file(intent_dir))
|
|
53
|
+
|
|
54
|
+
KINDS = %w[Review Commit].freeze
|
|
55
|
+
usage_abort("--kind must be one of #{KINDS.join(', ')}, got #{kind.inspect}") unless KINDS.include?(kind)
|
|
56
|
+
usage_abort("--text is required") if text.nil? || text.empty?
|
|
57
|
+
usage_abort("--text must not contain a newline") if text.include?("\n")
|
|
58
|
+
|
|
59
|
+
normalized = text.gsub(/ {2,}/, " ").strip
|
|
60
|
+
usage_abort("--text is empty after normalization") if normalized.empty?
|
|
61
|
+
|
|
62
|
+
case kind
|
|
63
|
+
when "Review" then Savepoint.append_review_savepoint(intent_dir, normalized)
|
|
64
|
+
when "Commit" then Savepoint.append_commit_savepoint(intent_dir, normalized)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
exit 0
|
package/scripts/scaffold-intent
CHANGED
|
@@ -2,46 +2,38 @@
|
|
|
2
2
|
# encoding: UTF-8
|
|
3
3
|
# frozen_string_literal: true
|
|
4
4
|
|
|
5
|
-
# scaffold-intent (intent 213
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# text
|
|
5
|
+
# scaffold-intent (intent 213; one verb since intent 308) - write an intent's judgment
|
|
6
|
+
# documents from its record. `backfill` fills spec.md, plan.md, actions/ACTION_1.md, and
|
|
7
|
+
# outcome.md wherever the file is missing or still the scaffold placeholder, from the
|
|
8
|
+
# intent file, checklist.md, and the diff (BackfillIntent). Every section it writes is a
|
|
9
|
+
# verbatim copy or a mechanical rendering of an already-committed artifact; a section with
|
|
10
|
+
# no source keeps the template's own stub text. A hand-written file is never touched, and
|
|
11
|
+
# there is no force flag: real content is edited by hand or not at all.
|
|
11
12
|
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# [--test-summary <path>] (outcome subcommand only)
|
|
13
|
+
# `scripts/end-intent` runs the same writer at every close, so this CLI exists for the
|
|
14
|
+
# doctor fix hint (backfilled_complete) and for filling an intent by hand before its close.
|
|
15
15
|
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# flags exist: --store, --id, --force, and --test-summary (outcome only) are the whole
|
|
20
|
-
# shipped surface (D5, a one-way door).
|
|
16
|
+
# Usage:
|
|
17
|
+
# scaffold-intent backfill --store <path> --id <intent_id> --disposition delivered|abandoned
|
|
18
|
+
# [--summary <text>]
|
|
21
19
|
#
|
|
22
20
|
# Exit codes:
|
|
23
|
-
# 0
|
|
21
|
+
# 0 written, or nothing left to write
|
|
24
22
|
# 1 usage or path-resolution failure
|
|
25
|
-
#
|
|
26
|
-
# 3 a prerequisite committed artifact is missing or still sentinel
|
|
27
|
-
#
|
|
28
|
-
# The actions/ rule (intent 133a, D11): scaffold-intent does not scaffold actions/ in any
|
|
29
|
-
# form. Actions require judgment and stay entirely with the planner. No subcommand
|
|
30
|
-
# creates the directory, writes a .gitkeep, or writes a sentinel-only ACTION_*.md.
|
|
23
|
+
# 3 a source is unusable (the intent file is missing)
|
|
31
24
|
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
# presence). This does NOT purge a bridge:
|
|
36
|
-
# Bridge.purge_done_bridges keys on INDEX Active status plus lock presence, never on the
|
|
37
|
-
# derived stage.
|
|
25
|
+
# The spec, checklist, and outcome subcommands, --force, and --test-summary were removed
|
|
26
|
+
# in 2.0 (intent 308): the backfill writer covers their two derivable targets and the
|
|
27
|
+
# outcome diffstat, and nothing else called them.
|
|
38
28
|
|
|
39
29
|
require_relative "lib/scaffold_intent"
|
|
30
|
+
require_relative "lib/backfill_intent"
|
|
40
31
|
|
|
41
|
-
SUBCOMMANDS = %w[
|
|
32
|
+
SUBCOMMANDS = %w[backfill].freeze
|
|
33
|
+
DISPOSITIONS = %w[delivered abandoned].freeze
|
|
42
34
|
|
|
43
35
|
def parse_args(argv)
|
|
44
|
-
opts = { subcommand: nil, store: nil, id: nil,
|
|
36
|
+
opts = { subcommand: nil, store: nil, id: nil, disposition: nil, summary: nil }
|
|
45
37
|
args = argv.dup
|
|
46
38
|
opts[:subcommand] = args.shift
|
|
47
39
|
|
|
@@ -49,10 +41,10 @@ def parse_args(argv)
|
|
|
49
41
|
while i < args.length
|
|
50
42
|
arg = args[i]
|
|
51
43
|
case arg
|
|
52
|
-
when "--store"
|
|
53
|
-
when "--id"
|
|
54
|
-
when "--
|
|
55
|
-
when "--
|
|
44
|
+
when "--store" then opts[:store] = args[i += 1]
|
|
45
|
+
when "--id" then opts[:id] = args[i += 1]
|
|
46
|
+
when "--disposition" then opts[:disposition] = args[i += 1]
|
|
47
|
+
when "--summary" then opts[:summary] = args[i += 1]
|
|
56
48
|
else
|
|
57
49
|
usage_abort("unknown argument #{arg.inspect}")
|
|
58
50
|
end
|
|
@@ -62,8 +54,8 @@ def parse_args(argv)
|
|
|
62
54
|
end
|
|
63
55
|
|
|
64
56
|
def usage
|
|
65
|
-
"usage: scaffold-intent
|
|
66
|
-
"
|
|
57
|
+
"usage: scaffold-intent backfill --store <store_path> --id <intent_id> " \
|
|
58
|
+
"--disposition delivered|abandoned [--summary <text>]"
|
|
67
59
|
end
|
|
68
60
|
|
|
69
61
|
def usage_abort(message)
|
|
@@ -75,11 +67,11 @@ end
|
|
|
75
67
|
def main(argv)
|
|
76
68
|
opts = parse_args(argv)
|
|
77
69
|
|
|
78
|
-
usage_abort("subcommand must be
|
|
70
|
+
usage_abort("subcommand must be #{SUBCOMMANDS.join('|')}") unless SUBCOMMANDS.include?(opts[:subcommand])
|
|
79
71
|
usage_abort("--store is required") if opts[:store].nil? || opts[:store].empty?
|
|
80
72
|
usage_abort("--id is required") if opts[:id].nil? || opts[:id].empty?
|
|
81
|
-
|
|
82
|
-
usage_abort("--
|
|
73
|
+
unless DISPOSITIONS.include?(opts[:disposition])
|
|
74
|
+
usage_abort("--disposition must be one of #{DISPOSITIONS.join('|')}")
|
|
83
75
|
end
|
|
84
76
|
|
|
85
77
|
store = ScaffoldIntent.expand(opts[:store])
|
|
@@ -88,33 +80,15 @@ def main(argv)
|
|
|
88
80
|
intent_dir, err = ScaffoldIntent.resolve_intent_dir(store, opts[:id])
|
|
89
81
|
usage_abort(err) if intent_dir.nil?
|
|
90
82
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
test_summary_path = ScaffoldIntent.expand(opts[:test_summary])
|
|
94
|
-
usage_abort("--test-summary path does not exist: #{test_summary_path}") unless File.exist?(test_summary_path)
|
|
95
|
-
end
|
|
83
|
+
result = BackfillIntent.run(intent_dir: intent_dir, store: store, id: opts[:id],
|
|
84
|
+
disposition: opts[:disposition], summary: opts[:summary])
|
|
96
85
|
|
|
97
|
-
result
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
when "checklist"
|
|
102
|
-
ScaffoldIntent.scaffold_checklist(intent_dir: intent_dir, force: opts[:force])
|
|
103
|
-
when "outcome"
|
|
104
|
-
ScaffoldIntent.scaffold_outcome(intent_dir: intent_dir, force: opts[:force], store: store,
|
|
105
|
-
id: opts[:id], test_summary: test_summary_path)
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
if result[:message]
|
|
109
|
-
if result[:code] == 0
|
|
110
|
-
puts result[:message]
|
|
111
|
-
else
|
|
112
|
-
warn "scaffold-intent: #{result[:message]}"
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
puts result[:path] if result[:path]
|
|
86
|
+
result[:written].each { |rel| puts "scaffold-intent: backfilled #{File.join(intent_dir, rel)}" }
|
|
87
|
+
result[:skipped].each { |rel| puts "scaffold-intent: kept #{rel} (real content)" }
|
|
88
|
+
result[:notes].each { |note| warn "scaffold-intent: #{note}" }
|
|
89
|
+
puts "scaffold-intent: nothing to backfill" if result[:written].empty? && result[:notes].empty?
|
|
116
90
|
|
|
117
|
-
exit
|
|
91
|
+
exit(result[:notes].any? { |n| n.include?("intent file is missing") } ? 3 : 0)
|
|
118
92
|
end
|
|
119
93
|
|
|
120
94
|
main(ARGV) if $PROGRAM_NAME == __FILE__
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: UTF-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# session-commit (intent 300) - makes one commit for a verified checklist
|
|
6
|
+
# item on the right branch of the repo that contains `--cwd`, applying the
|
|
7
|
+
# branch model and per-repo flow settings in `scripts/lib/session_git.rb`.
|
|
8
|
+
# Idempotent and fail-open: no repository, a detached HEAD, a clean tree, an
|
|
9
|
+
# agent-owned branch, a refused push, a missing `gh`, or a rejected
|
|
10
|
+
# commit-msg hook all degrade to no commit plus a Note savepoint line,
|
|
11
|
+
# never a non-zero exit (spec D1).
|
|
12
|
+
#
|
|
13
|
+
# Usage:
|
|
14
|
+
# session-commit --cwd <dir> --summary <text> [--session <id>] [--day <YYYYMMDD>]
|
|
15
|
+
# [--store <dir>] [--plastic-home <dir>]
|
|
16
|
+
#
|
|
17
|
+
# Exit codes:
|
|
18
|
+
# 0 - always, except a usage error
|
|
19
|
+
# 2 - usage error (missing --cwd or --summary); nothing is written
|
|
20
|
+
|
|
21
|
+
require "fileutils"
|
|
22
|
+
require_relative "lib/session_git"
|
|
23
|
+
require_relative "lib/session_ledger"
|
|
24
|
+
|
|
25
|
+
def usage_abort(message)
|
|
26
|
+
warn "session-commit: #{message}"
|
|
27
|
+
exit 2
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def next_value!(argv, i, flag)
|
|
31
|
+
usage_abort("#{flag} requires a value") if i + 1 >= argv.length
|
|
32
|
+
argv[i + 1]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def parse_args(argv)
|
|
36
|
+
opts = { cwd: nil, summary: nil, session: nil, day: nil, store: nil, plastic_home: nil }
|
|
37
|
+
i = 0
|
|
38
|
+
while i < argv.length
|
|
39
|
+
arg = argv[i]
|
|
40
|
+
case arg
|
|
41
|
+
when "--cwd" then opts[:cwd] = next_value!(argv, i, arg); i += 1
|
|
42
|
+
when "--summary" then opts[:summary] = next_value!(argv, i, arg); i += 1
|
|
43
|
+
when "--session" then opts[:session] = next_value!(argv, i, arg); i += 1
|
|
44
|
+
when "--day" then opts[:day] = next_value!(argv, i, arg); i += 1
|
|
45
|
+
when "--store" then opts[:store] = next_value!(argv, i, arg); i += 1
|
|
46
|
+
when "--plastic-home" then opts[:plastic_home] = next_value!(argv, i, arg); i += 1
|
|
47
|
+
else
|
|
48
|
+
usage_abort("unknown flag #{arg.inspect}")
|
|
49
|
+
end
|
|
50
|
+
i += 1
|
|
51
|
+
end
|
|
52
|
+
opts
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def expand(path)
|
|
56
|
+
File.expand_path(path.to_s.sub(/\A~/, Dir.home))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Sibling of this script's dir, matching append-ledger's own resolution:
|
|
60
|
+
# in-repo (<repo>/scripts/session-commit -> <repo>/templates) and installed
|
|
61
|
+
# (~/.plastic/scripts/session-commit -> ~/.plastic/templates).
|
|
62
|
+
def default_templates_dir
|
|
63
|
+
File.expand_path("../templates", __dir__)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The fail-open guarantee (spec D1: exit 0 always, except a usage error)
|
|
67
|
+
# must survive a store or ledger failure too (review R2), not only a git
|
|
68
|
+
# failure: a read-only store, a damaged install missing templates/, or any
|
|
69
|
+
# other write failure used to sit outside every rescue, so the CLI could
|
|
70
|
+
# exit 1 with a raw Ruby backtrace on stderr and zero savepoint lines, even
|
|
71
|
+
# after the git commit itself had already landed. `main`'s own body is
|
|
72
|
+
# wrapped so nothing here can turn into a crash; the ledger write is a
|
|
73
|
+
# second, inner rescue so a lost savepoint line never also swallows the
|
|
74
|
+
# result the caller already needs on stdout.
|
|
75
|
+
def main(argv)
|
|
76
|
+
opts = parse_args(argv)
|
|
77
|
+
usage_abort("--cwd is required") if opts[:cwd].nil?
|
|
78
|
+
usage_abort("--summary is required") if opts[:summary].nil?
|
|
79
|
+
|
|
80
|
+
plastic_home = expand(opts[:plastic_home] || ENV.fetch("PLASTIC_HOME", "~/.plastic"))
|
|
81
|
+
store = opts[:store] ? expand(opts[:store]) : File.join(plastic_home, "store")
|
|
82
|
+
cwd = expand(opts[:cwd])
|
|
83
|
+
|
|
84
|
+
day = opts[:day] || SessionLedger.day_id
|
|
85
|
+
session = SessionLedger.short_session_id(opts[:session], ENV["CLAUDE_CODE_SESSION_ID"])
|
|
86
|
+
project = SessionLedger.project_slug(cwd, plastic_home: plastic_home)
|
|
87
|
+
|
|
88
|
+
begin
|
|
89
|
+
templates = default_templates_dir
|
|
90
|
+
SessionLedger.open_day(store: store, day: day, templates: templates, author: session) if Dir.exist?(templates)
|
|
91
|
+
rescue StandardError => e
|
|
92
|
+
warn "session-commit: could not open the day ledger: #{e.message}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
result = SessionGit.commit!(cwd: cwd, summary: opts[:summary], day: day, session: session,
|
|
96
|
+
plastic_home: plastic_home, store: store)
|
|
97
|
+
|
|
98
|
+
begin
|
|
99
|
+
# Does not depend on open_day having run: a damaged install with no
|
|
100
|
+
# templates/, or a store error above, must not also block the append.
|
|
101
|
+
savepoint_path = SessionLedger.savepoint_path(store, day)
|
|
102
|
+
FileUtils.mkdir_p(File.dirname(savepoint_path))
|
|
103
|
+
line = SessionLedger.savepoint_line(result.event, session, project, result.message, now: Time.now)
|
|
104
|
+
SessionLedger.append_line(savepoint_path, line, header: nil)
|
|
105
|
+
rescue StandardError => e
|
|
106
|
+
warn "session-commit: could not write the savepoint line: #{e.message}"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
puts result.message
|
|
110
|
+
exit 0
|
|
111
|
+
rescue StandardError => e
|
|
112
|
+
warn "session-commit: #{e.message}"
|
|
113
|
+
exit 0
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
main(ARGV) if $PROGRAM_NAME == __FILE__
|
package/scripts/spawn-preamble
CHANGED
|
@@ -22,8 +22,9 @@
|
|
|
22
22
|
#
|
|
23
23
|
# Exit codes: 0 (preamble emitted), 2 (usage).
|
|
24
24
|
|
|
25
|
-
require_relative "lib/
|
|
25
|
+
require_relative "lib/savepoint"
|
|
26
26
|
require_relative "lib/worktree"
|
|
27
|
+
require_relative "lib/intent_screen"
|
|
27
28
|
|
|
28
29
|
# Verbatim honoring instruction. Kept as one constant so the contract doc and the
|
|
29
30
|
# test assert against the exact same string.
|
|
@@ -56,7 +57,7 @@ REPORT_CONTRACT =
|
|
|
56
57
|
"insights field carrying 0..N durable nuggets (what I discovered worth keeping, " \
|
|
57
58
|
"the most interesting residue of this turn; none if there were none); plus a " \
|
|
58
59
|
"role-specific payload that fulfils your place in the What, Why, How, Exec cycle " \
|
|
59
|
-
"(for example the
|
|
60
|
+
"(for example the executor reports what was built and the test result). Populate " \
|
|
60
61
|
"the insights field even when you cannot write the intent file yourself: a " \
|
|
61
62
|
"background or dispatched agent carries each nugget home in the report and the " \
|
|
62
63
|
"orchestrator persists it via scripts/insight-append, so an insight never " \
|
|
@@ -90,7 +91,7 @@ end
|
|
|
90
91
|
# id/intent we report match what the rest of Plastic sees. Returns a Hash (possibly
|
|
91
92
|
# empty) — never raises.
|
|
92
93
|
def frontmatter_for(intent_dir)
|
|
93
|
-
ifile =
|
|
94
|
+
ifile = Savepoint.intent_file(intent_dir)
|
|
94
95
|
return {} unless File.exist?(ifile)
|
|
95
96
|
|
|
96
97
|
content = File.read(ifile)
|
|
@@ -115,13 +116,19 @@ STAGE_LABELS = {
|
|
|
115
116
|
"exec" => "Exec", "done" => "Done"
|
|
116
117
|
}.freeze
|
|
117
118
|
|
|
119
|
+
# Intent 317, D6: a dispatched agent's "Current stage:" line must name the last
|
|
120
|
+
# LIFECYCLE line (What/Why/How/Exec/Done), never a trailing Lock/Review/Commit
|
|
121
|
+
# line - the bug spawn-preamble shared with IntentScreen.savepoint_fields
|
|
122
|
+
# before this guard (plan review finding B1).
|
|
118
123
|
def current_stage(intent_dir)
|
|
119
|
-
ledger = File.join(intent_dir,
|
|
124
|
+
ledger = File.join(intent_dir, Savepoint::SAVEPOINT_FILE)
|
|
120
125
|
if File.exist?(ledger)
|
|
121
|
-
|
|
122
|
-
|
|
126
|
+
lines = File.read(ledger).each_line.map(&:strip).reject(&:empty?)
|
|
127
|
+
lifecycle = lines.select { |l| IntentScreen.lifecycle_line?(l) }
|
|
128
|
+
return lifecycle.last if lifecycle.any?
|
|
129
|
+
return lines.last if lines.any?
|
|
123
130
|
end
|
|
124
|
-
STAGE_LABELS.fetch(
|
|
131
|
+
STAGE_LABELS.fetch(Savepoint.derive_stage(intent_dir), Savepoint.derive_stage(intent_dir))
|
|
125
132
|
end
|
|
126
133
|
|
|
127
134
|
intent_dir_arg, role, step = parse_args(ARGV)
|
package/scripts/update.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# frozen_string_literal: true
|
|
4
4
|
|
|
5
5
|
# Plastic — `update` verb. Runs via `npx @zalom/plastic update` (bin/plastic.js) or directly.
|
|
6
|
-
# Usage: ruby scripts/update.rb [--beta|--latest|--alpha] [--claude|--codex|--hermes|--all] [--help]
|
|
6
|
+
# Usage: ruby scripts/update.rb [--beta|--latest|--alpha] [--yes] [--claude|--codex|--hermes|--all] [--help]
|
|
7
7
|
#
|
|
8
8
|
# Forward-only version transitions, sourced from npm dist-tags:
|
|
9
9
|
# - no flag = next version on the CURRENT channel (derived from VERSION)
|
|
@@ -62,7 +62,7 @@ class Update < InstallerCore
|
|
|
62
62
|
warn "No published version on the #{requested} channel."
|
|
63
63
|
return 1
|
|
64
64
|
when :ok
|
|
65
|
-
if res[:kind] == :cross_bleeding && !confirm_bleeding(iv, res[:target])
|
|
65
|
+
if res[:kind] == :cross_bleeding && !confirm_bleeding(iv, res[:target], argv)
|
|
66
66
|
puts "Aborted."
|
|
67
67
|
return 1
|
|
68
68
|
end
|
|
@@ -222,7 +222,10 @@ class Update < InstallerCore
|
|
|
222
222
|
nil
|
|
223
223
|
end
|
|
224
224
|
|
|
225
|
-
|
|
225
|
+
# --yes (intent 310) answers the bleeding-edge question without a tty, so a script or an
|
|
226
|
+
# agent can run `update --alpha --yes`; without it a non-tty run aborts as before.
|
|
227
|
+
def confirm_bleeding(current, target, argv = [])
|
|
228
|
+
return true if argv.include?("--yes")
|
|
226
229
|
return false unless $stdin.tty?
|
|
227
230
|
print "Switch from #{current} to the less-stable #{target}? [y/N]: "
|
|
228
231
|
($stdin.gets&.strip || "").downcase.start_with?("y")
|
|
@@ -248,6 +251,7 @@ class Update < InstallerCore
|
|
|
248
251
|
--latest Switch to / advance the stable channel
|
|
249
252
|
--beta Switch to / advance the beta channel
|
|
250
253
|
--alpha Switch to / advance the alpha channel (bleeding edge — confirmed)
|
|
254
|
+
--yes Confirm a switch toward bleeding edge without a prompt (scripts, agents)
|
|
251
255
|
|
|
252
256
|
Agent options (default: --claude):
|
|
253
257
|
--claude --codex --hermes --all
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: UTF-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# write-handoff (intent 311): render and write one session's hand-off into
|
|
6
|
+
# the day directory (.sessions/<day>/handoff--<session>.md). Derived from the
|
|
7
|
+
# day ledger, so it can be regenerated at any time. Prints the path.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# write-handoff --trigger tick|precompact|close [--store <dir>] [--day <YYYYMMDD>]
|
|
11
|
+
# [--session <id>] [--templates <dir>]
|
|
12
|
+
#
|
|
13
|
+
# Defaults match append-ledger: the store is $PLASTIC_HOME/store, the day is
|
|
14
|
+
# today (local wall clock), the session is $CLAUDE_CODE_SESSION_ID, and the
|
|
15
|
+
# templates dir is the one shipped next to this script.
|
|
16
|
+
#
|
|
17
|
+
# Exit codes: 0 done; 2 usage error.
|
|
18
|
+
|
|
19
|
+
require_relative "lib/session_ledger"
|
|
20
|
+
require_relative "lib/handoff"
|
|
21
|
+
|
|
22
|
+
def usage_abort(message)
|
|
23
|
+
warn "write-handoff: #{message}"
|
|
24
|
+
exit 2
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def expand(path)
|
|
28
|
+
File.expand_path(path.to_s.sub(/\A~/, Dir.home))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def parse_args(argv)
|
|
32
|
+
opts = {}
|
|
33
|
+
i = 0
|
|
34
|
+
while i < argv.length
|
|
35
|
+
flag = argv[i]
|
|
36
|
+
usage_abort("unknown argument #{flag.inspect}") unless flag.start_with?("--")
|
|
37
|
+
usage_abort("#{flag} requires a value") if i + 1 >= argv.length
|
|
38
|
+
key = flag.delete_prefix("--").to_sym
|
|
39
|
+
usage_abort("unknown flag #{flag.inspect}") unless %i[store day session trigger templates].include?(key)
|
|
40
|
+
opts[key] = argv[i + 1]
|
|
41
|
+
i += 2
|
|
42
|
+
end
|
|
43
|
+
opts
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
opts = parse_args(ARGV)
|
|
47
|
+
usage_abort("--trigger is required (#{Handoff::TRIGGERS.join(', ')})") unless opts[:trigger]
|
|
48
|
+
usage_abort("--trigger must be one of #{Handoff::TRIGGERS.join(', ')}") unless Handoff::TRIGGERS.include?(opts[:trigger])
|
|
49
|
+
|
|
50
|
+
plastic_home = expand(ENV.fetch("PLASTIC_HOME", "~/.plastic"))
|
|
51
|
+
store = opts[:store] ? expand(opts[:store]) : File.join(plastic_home, "store")
|
|
52
|
+
templates = opts[:templates] ? expand(opts[:templates]) : File.expand_path("../templates", __dir__)
|
|
53
|
+
usage_abort("templates dir not found: #{templates}") unless Dir.exist?(templates)
|
|
54
|
+
|
|
55
|
+
day = opts[:day] || SessionLedger.day_id
|
|
56
|
+
usage_abort("--day must be eight digits parsing as a real date, got #{day.inspect}") unless SessionLedger.valid_day_id?(day)
|
|
57
|
+
session = SessionLedger.short_session_id(opts[:session], ENV["CLAUDE_CODE_SESSION_ID"])
|
|
58
|
+
|
|
59
|
+
puts Handoff.write(store: store, day: day, session: session, trigger: opts[:trigger], templates: templates)
|
|
60
|
+
exit 0
|
|
@@ -33,7 +33,7 @@ when the ordering decides where you spend the next day.
|
|
|
33
33
|
Never buy a consultation for: anything a tool can answer (search, reading code, running
|
|
34
34
|
tests, documentation), writing code at volume, confirming a decision you already made,
|
|
35
35
|
style or naming a linter would settle, or anything reversible and cheap you have not
|
|
36
|
-
tried first. The full buy/never-buy list, the
|
|
36
|
+
tried first. The full buy/never-buy list, the effort table, and the entry test live in
|
|
37
37
|
`references/advisor-protocol.md`; read it before writing a brief for the first time in
|
|
38
38
|
a session.
|
|
39
39
|
|
|
@@ -50,7 +50,7 @@ a session.
|
|
|
50
50
|
advisor is disabled and point at "Setting the default" below.
|
|
51
51
|
4. Dispatch the resolved agent with a brief built per `references/advisor-protocol.md`
|
|
52
52
|
section 4 (natural prose, the block is a completeness check, not a form to fill).
|
|
53
|
-
State
|
|
53
|
+
State EFFORT and the answer shape explicitly; classify low and prove your way up,
|
|
54
54
|
never open high "to be safe".
|
|
55
55
|
5. Consume the answer per the protocol's section 5: run the Operating Manual's
|
|
56
56
|
five-question self-test on the advisor's plan before executing it. Advice is input,
|
|
@@ -86,7 +86,7 @@ the new default back to the user in one line.
|
|
|
86
86
|
## References
|
|
87
87
|
|
|
88
88
|
- `references/advisor-protocol.md`: the full shipped Advisor Protocol (what to buy,
|
|
89
|
-
|
|
89
|
+
effort and answer shape, the entry test, how to write a brief that earns its cost, the
|
|
90
90
|
answer contract, session economics, anti-patterns). Read it before the first
|
|
91
91
|
consultation in a session; the second consultation in the same advisor thread costs a
|
|
92
92
|
fraction of the first, so keep follow-ups on one thread rather than opening a new one.
|