@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/new-intent
CHANGED
|
@@ -21,22 +21,37 @@
|
|
|
21
21
|
# [--tags tag,tag] [--templates <dir>]
|
|
22
22
|
#
|
|
23
23
|
# Exit codes: 0 (born complete), 1 (scaffold failed self-validation or bad input).
|
|
24
|
+
#
|
|
25
|
+
# A second, unrelated mode (intent 297):
|
|
26
|
+
# new-intent --tmp [--store <dir>] [--day YYYYMMDD] [--author <name>] \
|
|
27
|
+
# [--templates <dir>]
|
|
28
|
+
#
|
|
29
|
+
# `--tmp` scaffolds the shared session intent day ledger under the global
|
|
30
|
+
# store's `.sessions/<YYYYMMDD>/` instead of allocating a real intent id. It
|
|
31
|
+
# is a thin CLI over SessionLedger.open_day (the single scaffold
|
|
32
|
+
# implementation, spec D12) and never touches the id-allocation path above:
|
|
33
|
+
# no folgezettel-id call, no actions/ or resources/ dirs, no sentinel
|
|
34
|
+
# placeholders, no born savepoint stamp, no Links projection. Stdout is
|
|
35
|
+
# exactly one line, `created <dir>` or `joined <dir>`; exit 0 either way,
|
|
36
|
+
# exit 1 on bad input or failed self-validation.
|
|
24
37
|
|
|
25
38
|
require "fileutils"
|
|
26
39
|
require "date"
|
|
27
|
-
require_relative "lib/
|
|
40
|
+
require_relative "lib/savepoint"
|
|
28
41
|
require_relative "lib/intent_validator"
|
|
29
42
|
require_relative "lib/graph_rebuild"
|
|
30
43
|
require_relative "lib/links_projection"
|
|
31
44
|
require_relative "lib/links_section"
|
|
32
45
|
require_relative "lib/store_discovery"
|
|
46
|
+
require_relative "lib/session_ledger"
|
|
33
47
|
|
|
34
48
|
# --- Explicit flag parsing (no eval, no global injection) ------------------
|
|
35
49
|
|
|
36
50
|
def parse_args(argv)
|
|
37
51
|
opts = {
|
|
38
52
|
store: nil, intent: nil, slug: nil, parent: nil,
|
|
39
|
-
author: "claude-code", sources: [], tags: [], templates: nil
|
|
53
|
+
author: "claude-code", sources: [], tags: [], templates: nil,
|
|
54
|
+
tmp: false, day: nil
|
|
40
55
|
}
|
|
41
56
|
i = 0
|
|
42
57
|
while i < argv.length
|
|
@@ -50,6 +65,8 @@ def parse_args(argv)
|
|
|
50
65
|
when "--sources" then opts[:sources] = split_list(argv[i += 1])
|
|
51
66
|
when "--tags" then opts[:tags] = split_list(argv[i += 1])
|
|
52
67
|
when "--templates" then opts[:templates] = argv[i += 1]
|
|
68
|
+
when "--tmp" then opts[:tmp] = true
|
|
69
|
+
when "--day" then opts[:day] = argv[i += 1]
|
|
53
70
|
else
|
|
54
71
|
abort "new-intent: unknown argument #{arg.inspect}"
|
|
55
72
|
end
|
|
@@ -253,8 +270,50 @@ def project_links_for(file_path, referer_store_key, maps)
|
|
|
253
270
|
true
|
|
254
271
|
end
|
|
255
272
|
|
|
273
|
+
# The --tmp branch (intent 297, spec D1): scaffold the shared session intent
|
|
274
|
+
# day ledger instead of a real intent. A thin CLI over
|
|
275
|
+
# SessionLedger.open_day, the single scaffold implementation (spec D12); this
|
|
276
|
+
# method never allocates a folgezettel id, never creates actions/ or
|
|
277
|
+
# resources/, never writes a sentinel placeholder, never stamps a savepoint
|
|
278
|
+
# line, and never runs the Links projection build.
|
|
279
|
+
def run_tmp(opts)
|
|
280
|
+
{ intent: "--intent", slug: "--slug", parent: "--parent" }.each do |key, flag|
|
|
281
|
+
abort "new-intent: #{flag} is not valid with --tmp" if opts[key] && !opts[key].to_s.empty?
|
|
282
|
+
end
|
|
283
|
+
abort "new-intent: --sources is not valid with --tmp" unless opts[:sources].empty?
|
|
284
|
+
abort "new-intent: --tags is not valid with --tmp" unless opts[:tags].empty?
|
|
285
|
+
|
|
286
|
+
store = opts[:store] ? expand(opts[:store]) : expand(File.join(ENV.fetch("PLASTIC_HOME", "~/.plastic"), "store"))
|
|
287
|
+
templates = opts[:templates] ? expand(opts[:templates]) : default_templates_dir
|
|
288
|
+
abort "new-intent: templates dir not found: #{templates}" unless Dir.exist?(templates)
|
|
289
|
+
|
|
290
|
+
day = opts[:day] || SessionLedger.day_id
|
|
291
|
+
unless SessionLedger.valid_day_id?(day)
|
|
292
|
+
abort "new-intent: --day must be eight digits parsing as a real date, got #{opts[:day].inspect}"
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
result = SessionLedger.open_day(store: store, day: day, templates: templates, author: opts[:author], now: Time.now)
|
|
296
|
+
|
|
297
|
+
plastic_home, = store_context(store)
|
|
298
|
+
known_stores = StoreDiscovery.known_slugs(plastic_home)
|
|
299
|
+
validation = IntentValidator.validate(result[:dir], known_stores: known_stores)
|
|
300
|
+
unless validation[:ok]
|
|
301
|
+
warn "new-intent: scaffolded day ledger is NOT born complete:"
|
|
302
|
+
validation[:missing].each { |f| warn " missing field: #{f}" }
|
|
303
|
+
validation[:errors].each { |e| warn " #{e}" }
|
|
304
|
+
exit 1
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
puts "#{result[:created] ? "created" : "joined"} #{result[:dir]}"
|
|
308
|
+
exit 0
|
|
309
|
+
end
|
|
310
|
+
|
|
256
311
|
def main(argv)
|
|
257
312
|
opts = parse_args(argv)
|
|
313
|
+
return run_tmp(opts) if opts[:tmp]
|
|
314
|
+
|
|
315
|
+
abort "new-intent: --day is only valid with --tmp" if opts[:day]
|
|
316
|
+
|
|
258
317
|
abort "new-intent: --store is required" if opts[:store].nil? || opts[:store].empty?
|
|
259
318
|
abort "new-intent: --intent is required" if opts[:intent].nil? || opts[:intent].empty?
|
|
260
319
|
abort "new-intent: --slug is required" if opts[:slug].nil? || opts[:slug].empty?
|
|
@@ -338,7 +397,7 @@ def main(argv)
|
|
|
338
397
|
template_path = File.join(templates, name)
|
|
339
398
|
body = File.exist?(template_path) ? File.read(template_path) : ""
|
|
340
399
|
body = render_tokens(body, { "INTENT_NAME" => opts[:intent] })
|
|
341
|
-
File.write(File.join(intent_dir, name), "#{
|
|
400
|
+
File.write(File.join(intent_dir, name), "#{Savepoint::PLACEHOLDER_SENTINEL}\n#{body}")
|
|
342
401
|
end
|
|
343
402
|
|
|
344
403
|
# 6. Stamp the born savepoint line (intent 81). The first ledger line is the
|
|
@@ -347,7 +406,7 @@ def main(argv)
|
|
|
347
406
|
# sessions / harnesses). The intent file is never a sentinel placeholder, so
|
|
348
407
|
# append_savepoint records `What {id}--{slug}.md`. Idempotent: a later gate
|
|
349
408
|
# fire adds nothing.
|
|
350
|
-
|
|
409
|
+
Savepoint.append_savepoint(intent_dir, intent_file)
|
|
351
410
|
|
|
352
411
|
# 7. Self-validate (frontmatter + sanctioned sections), including that every
|
|
353
412
|
# sources/chain cross-store token names a real store (intent 189 D3).
|
package/scripts/plastic-lock
CHANGED
|
@@ -2,19 +2,29 @@
|
|
|
2
2
|
# encoding: UTF-8
|
|
3
3
|
# frozen_string_literal: true
|
|
4
4
|
#
|
|
5
|
-
# plastic-lock: inspect and
|
|
6
|
-
# and take/free per-artifact claim tokens
|
|
5
|
+
# plastic-lock: take, inspect, repair, and give back the durable delivery lock
|
|
6
|
+
# (intent 108, D5; intent 307), and take/free per-artifact claim tokens
|
|
7
|
+
# (intent 111 D1/D5).
|
|
7
8
|
#
|
|
8
|
-
# Usage: plastic-lock <status|who|fix|release|reclaim|delegate|claim|release-claim>
|
|
9
|
+
# Usage: plastic-lock <arm|status|who|fix|release|reclaim|delegate|claim|release-claim>
|
|
9
10
|
# [--intent-dir DIR] [--session SID] [--delegate SID] [--artifact NAME]
|
|
11
|
+
# [--harness claude|codex] [--agent NAME] [--model MODEL] [--thread ID]
|
|
12
|
+
# [--mode auto|guided] [--status finished|failed]
|
|
10
13
|
#
|
|
11
14
|
# Verbs:
|
|
15
|
+
# arm take the intent for this session (intent 307): acquire
|
|
16
|
+
# delivery.lock stamped with --mode (default auto) and the
|
|
17
|
+
# provenance flags, provision the code worktree, and point the
|
|
18
|
+
# session at the intent; exit 1 on a held, stale, excluded, or
|
|
19
|
+
# corrupt lock with the resolving doctor hint
|
|
12
20
|
# who compact, read-only durable owner/activity view
|
|
13
|
-
# status report the lock file,
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
21
|
+
# status report the lock file, its freshness, the derived worktree,
|
|
22
|
+
# whether this session's pointer names the intent, and any
|
|
23
|
+
# live per-artifact claims
|
|
24
|
+
# fix idempotent repair of the lock for the current session; never
|
|
25
|
+
# touches a fresh foreign lock
|
|
26
|
+
# release owner clears the lock and points the session back at the day
|
|
27
|
+
# ledger (End tail / abandoning a boarding)
|
|
18
28
|
# reclaim explicit takeover of a stale lock, audited in savepoint.md
|
|
19
29
|
# delegate owner registers a subagent session under the lock (D4)
|
|
20
30
|
# claim acquire the named --artifact's claim token (intent 111);
|
|
@@ -22,15 +32,23 @@
|
|
|
22
32
|
# including this same session; takes over a stale claim
|
|
23
33
|
# release-claim free the named --artifact's claim token
|
|
24
34
|
#
|
|
25
|
-
# Without --intent-dir the intent is resolved from this session's
|
|
26
|
-
#
|
|
35
|
+
# Without --intent-dir the intent is resolved from this session's pointer
|
|
36
|
+
# (~/.plastic/store/.tmp/<session>/current) when it names an intent id, looked
|
|
37
|
+
# up in the working directory's project store and then the global store. The
|
|
38
|
+
# /tmp bridge JSON this CLI used to resolve through was removed in 2.0
|
|
39
|
+
# (intent 307). Exit 0 on success/report; exit 1 when the verb is blocked.
|
|
27
40
|
|
|
28
41
|
require "json"
|
|
42
|
+
require "yaml"
|
|
29
43
|
require_relative "lib/bridge"
|
|
30
44
|
require_relative "lib/lock"
|
|
45
|
+
require_relative "lib/arm"
|
|
46
|
+
require_relative "lib/session_ledger"
|
|
47
|
+
|
|
48
|
+
VERBS = %w[arm status who fix release reclaim delegate claim release-claim].freeze
|
|
31
49
|
|
|
32
50
|
def usage!
|
|
33
|
-
warn "usage: plastic-lock
|
|
51
|
+
warn "usage: plastic-lock <#{VERBS.join('|')}> " \
|
|
34
52
|
"[--intent-dir DIR] [--session SID] [--delegate SID] [--artifact NAME] " \
|
|
35
53
|
"[--harness claude|codex] [--agent NAME] [--model MODEL] [--thread ID] " \
|
|
36
54
|
"[--mode auto|guided] [--status finished|failed]"
|
|
@@ -38,7 +56,7 @@ def usage!
|
|
|
38
56
|
end
|
|
39
57
|
|
|
40
58
|
verb = ARGV.shift
|
|
41
|
-
usage! unless
|
|
59
|
+
usage! unless VERBS.include?(verb)
|
|
42
60
|
|
|
43
61
|
opts = {}
|
|
44
62
|
until ARGV.empty?
|
|
@@ -52,6 +70,7 @@ until ARGV.empty?
|
|
|
52
70
|
when "--model" then opts[:model] = ARGV.shift
|
|
53
71
|
when "--thread" then opts[:thread] = ARGV.shift
|
|
54
72
|
when "--mode" then opts[:mode] = ARGV.shift
|
|
73
|
+
when "--allow-inline" then opts[:allow_inline] = true
|
|
55
74
|
when "--status" then opts[:status] = ARGV.shift
|
|
56
75
|
else
|
|
57
76
|
warn "unknown flag #{flag}"
|
|
@@ -64,15 +83,29 @@ session = opts[:session]
|
|
|
64
83
|
session = ENV["CLAUDE_CODE_SESSION_ID"] if session.nil? || session.strip.empty?
|
|
65
84
|
harness = opts[:harness]
|
|
66
85
|
hint_harness = harness || "claude"
|
|
86
|
+
# The plastic home every hook uses (PLASTIC_HOME, else ~/.plastic); Arm takes the
|
|
87
|
+
# OS home above it, the same convention Worktree resolves projects.yml from.
|
|
88
|
+
plastic_home_env = ENV["PLASTIC_HOME"].to_s.strip
|
|
89
|
+
home = plastic_home_env.empty? ? Dir.home : File.dirname(File.expand_path(plastic_home_env))
|
|
90
|
+
|
|
91
|
+
# The stores this session's pointer is resolved against: the project store of
|
|
92
|
+
# the working directory (projects.yml, longest path match), then the global one.
|
|
93
|
+
def candidate_stores(home, cwd)
|
|
94
|
+
plastic_home = File.join(home, ".plastic")
|
|
95
|
+
stores = []
|
|
96
|
+
slug = SessionLedger.project_slug(cwd, plastic_home: plastic_home)
|
|
97
|
+
stores << File.join(plastic_home, "projects", slug, "store") if slug && slug != "global"
|
|
98
|
+
stores << File.join(plastic_home, "store")
|
|
99
|
+
stores
|
|
100
|
+
end
|
|
67
101
|
|
|
68
102
|
dir = opts[:dir]
|
|
69
103
|
if dir.nil? || dir.strip.empty?
|
|
70
|
-
if verb
|
|
71
|
-
warn "plastic-lock:
|
|
104
|
+
if %w[who arm].include?(verb)
|
|
105
|
+
warn "plastic-lock: #{verb} needs --intent-dir <intent dir>"
|
|
72
106
|
exit 1
|
|
73
107
|
end
|
|
74
|
-
|
|
75
|
-
dir = Bridge.bridge_intent_dir(bridge)
|
|
108
|
+
dir = Arm.intent_dir_from_pointer(session, home: home, stores: candidate_stores(home, Dir.pwd))
|
|
76
109
|
end
|
|
77
110
|
if dir.nil?
|
|
78
111
|
warn "plastic-lock: no intent resolved; pass --intent-dir <intent dir>"
|
|
@@ -85,7 +118,7 @@ if verb == "who"
|
|
|
85
118
|
basename = File.basename(dir)
|
|
86
119
|
intent_id, slug = basename.split("--", 2)
|
|
87
120
|
display = ->(value) { value.nil? || value.to_s.strip.empty? || value == "unknown" ? "Unknown" : value.to_s }
|
|
88
|
-
|
|
121
|
+
harness_name = ->(value) {
|
|
89
122
|
shown = display.call(value)
|
|
90
123
|
shown == "Unknown" ? shown : shown.sub(/\A./) { |char| char.upcase }
|
|
91
124
|
}
|
|
@@ -99,12 +132,12 @@ if verb == "who"
|
|
|
99
132
|
|
|
100
133
|
puts "#{intent_id} · #{slug || basename}"
|
|
101
134
|
puts "State: #{view['state']}"
|
|
102
|
-
puts "Controller: #{display.call(owner['agent'])} via #{
|
|
135
|
+
puts "Controller: #{display.call(owner['agent'])} via #{harness_name.call(owner['harness'])}"
|
|
103
136
|
puts "Session: #{display.call(view['owner_session'])}"
|
|
104
137
|
puts "Heartbeat: #{view['heartbeat_at'] || 'none'}"
|
|
105
138
|
puts "Claims: #{claim_text.empty? ? 'none' : claim_text.join(', ')}"
|
|
106
139
|
if delegate
|
|
107
|
-
puts "Delegate: #{display.call(delegate['agent'])} via #{
|
|
140
|
+
puts "Delegate: #{display.call(delegate['agent'])} via #{harness_name.call(delegate['harness'])}, #{display.call(delegate['status'])}"
|
|
108
141
|
else
|
|
109
142
|
puts "Delegate: none"
|
|
110
143
|
end
|
|
@@ -113,31 +146,65 @@ end
|
|
|
113
146
|
|
|
114
147
|
intent_id = Bridge.intent_id_from_dir(dir)
|
|
115
148
|
store = File.dirname(dir)
|
|
116
|
-
|
|
117
|
-
|
|
149
|
+
key = Arm.resolve_session(session, store: store, intent_id: intent_id)
|
|
150
|
+
|
|
151
|
+
def doctor_hint(verb_text, harness)
|
|
152
|
+
"run #{Lock.skill_ref('plastic-doctor', harness: harness)} #{verb_text}"
|
|
153
|
+
end
|
|
118
154
|
|
|
119
155
|
case verb
|
|
156
|
+
when "arm"
|
|
157
|
+
result = Arm.arm(intent_dir: dir, session: key, mode: opts[:mode] || "auto", home: home,
|
|
158
|
+
harness: harness, agent: opts[:agent], model: opts[:model], thread: opts[:thread],
|
|
159
|
+
allow_inline: opts[:allow_inline] || false)
|
|
160
|
+
case result[:status]
|
|
161
|
+
when :acquired, :owned
|
|
162
|
+
wt = result[:worktree] || {}
|
|
163
|
+
puts JSON.pretty_generate(
|
|
164
|
+
"intent_dir" => dir, "session" => result[:session], "status" => result[:status].to_s,
|
|
165
|
+
"run_mode" => result[:lock] && result[:lock]["run_mode"],
|
|
166
|
+
"worktree" => wt, "pointer" => result[:pointer]
|
|
167
|
+
)
|
|
168
|
+
when :held
|
|
169
|
+
warn "plastic-lock: delivery lock for intent #{intent_id} is held by session " \
|
|
170
|
+
"#{result[:lock] && result[:lock]['owner_session']}; #{doctor_hint('check the lock status', hint_harness)}"
|
|
171
|
+
exit 1
|
|
172
|
+
when :stale
|
|
173
|
+
warn "plastic-lock: delivery lock for intent #{intent_id} is stale (owner " \
|
|
174
|
+
"#{result[:lock] && result[:lock]['owner_session']}); #{doctor_hint('reclaim the lock to take it over with an audit', hint_harness)}"
|
|
175
|
+
exit 1
|
|
176
|
+
when :excluded
|
|
177
|
+
warn "plastic-lock: a #{result[:lock] && result[:lock]['type']} lock is active on intent " \
|
|
178
|
+
"#{intent_id}; #{doctor_hint('check the lock status', hint_harness)}"
|
|
179
|
+
exit 1
|
|
180
|
+
when :corrupt
|
|
181
|
+
warn "plastic-lock: delivery.lock for intent #{intent_id} is unreadable; #{doctor_hint('fix the lock', hint_harness)}"
|
|
182
|
+
exit 1
|
|
183
|
+
when :inline_refused
|
|
184
|
+
warn "plastic-lock: refusing to arm intent #{intent_id} from a conversation session " \
|
|
185
|
+
"(owner rule 2026-08-31: no inline intent delivery). Dispatch the delivery team " \
|
|
186
|
+
"instead (plastic-auto), or pass --allow-inline with explicit owner approval."
|
|
187
|
+
exit 1
|
|
188
|
+
end
|
|
120
189
|
when "status"
|
|
121
190
|
lock = Lock.read(dir)
|
|
122
|
-
|
|
191
|
+
pointer = Arm.read_pointer(key, home: home)
|
|
123
192
|
report = {
|
|
124
193
|
"intent_dir" => dir,
|
|
125
194
|
"session" => key,
|
|
126
195
|
"lock" => lock,
|
|
127
196
|
"lock_fresh" => Lock.fresh?(dir),
|
|
128
197
|
"lock_corrupt" => Lock.corrupt?(dir),
|
|
129
|
-
"
|
|
130
|
-
"
|
|
131
|
-
|
|
198
|
+
"worktree" => Arm.worktree_block(intent_dir: dir, home: home),
|
|
199
|
+
"pointer" => pointer,
|
|
200
|
+
"pointer_names_intent" => pointer == intent_id,
|
|
132
201
|
"claims" => Claim.claims_status(dir),
|
|
133
202
|
}
|
|
134
203
|
puts JSON.pretty_generate(report)
|
|
135
204
|
when "fix"
|
|
136
|
-
report =
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
run_mode: opts[:mode],
|
|
140
|
-
hint_harness: hint_harness)
|
|
205
|
+
report = Arm.repair(intent_dir: dir, session: key, home: home, harness: harness,
|
|
206
|
+
agent: opts[:agent], model: opts[:model], thread: opts[:thread],
|
|
207
|
+
run_mode: opts[:mode], hint_harness: hint_harness)
|
|
141
208
|
puts JSON.pretty_generate(report)
|
|
142
209
|
unless report["status"] == "repaired"
|
|
143
210
|
warn "plastic-lock: #{report['status']} by #{report['owner']}" \
|
|
@@ -150,12 +217,7 @@ when "release"
|
|
|
150
217
|
warn "plastic-lock: not the owner; run plastic-lock status"
|
|
151
218
|
exit 1
|
|
152
219
|
end
|
|
153
|
-
|
|
154
|
-
if data
|
|
155
|
-
data["lock"] = { "owner_session" => nil, "acquired_at" => nil,
|
|
156
|
-
"host" => nil, "type" => nil, "delegates" => [] }
|
|
157
|
-
Bridge.write(key, data)
|
|
158
|
-
end
|
|
220
|
+
Arm.reset_pointer(key, intent_id, home: home)
|
|
159
221
|
puts "released (#{result})"
|
|
160
222
|
when "reclaim"
|
|
161
223
|
status, lock_data = Lock.takeover(dir, session: key, harness: harness,
|
|
@@ -166,11 +228,9 @@ when "reclaim"
|
|
|
166
228
|
"back off (no silent reclaim)"
|
|
167
229
|
exit 1
|
|
168
230
|
end
|
|
169
|
-
report =
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
run_mode: opts[:mode],
|
|
173
|
-
hint_harness: hint_harness)
|
|
231
|
+
report = Arm.repair(intent_dir: dir, session: key, home: home, harness: harness,
|
|
232
|
+
agent: opts[:agent], model: opts[:model], thread: opts[:thread],
|
|
233
|
+
run_mode: opts[:mode], hint_harness: hint_harness)
|
|
174
234
|
puts JSON.pretty_generate(report)
|
|
175
235
|
when "delegate"
|
|
176
236
|
usage! if opts[:delegate].nil?
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: UTF-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# promote-session-item (intent 301): turn one session day-ledger line into a
|
|
6
|
+
# registered intent through scripts/new-intent (never hand-authored), record
|
|
7
|
+
# the day of origin in the new intent, and mark the line promoted.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# promote-session-item --day <YYYYMMDD> --match <substring> [--session <id>]
|
|
11
|
+
# [--store <dir>] [--project <slug>] [--templates <dir>]
|
|
12
|
+
# [--plastic-home <dir>] [--no-reindex]
|
|
13
|
+
#
|
|
14
|
+
# Target: the newest open line, else the newest pending line, whose summary
|
|
15
|
+
# contains --match, tagged with --session when given, any session otherwise.
|
|
16
|
+
# The intent lands in the project's store when --project names a registered
|
|
17
|
+
# project, else in --store ($PLASTIC_HOME/store by default). No graph edge to
|
|
18
|
+
# the day id is written (the day ledger is not a store node); the origin is
|
|
19
|
+
# the `session_day:` frontmatter key plus a line under ## Context.
|
|
20
|
+
#
|
|
21
|
+
# Exit codes: 0 promoted (prints the intent dir last), 1 new-intent failed,
|
|
22
|
+
# 2 usage error or nothing to promote.
|
|
23
|
+
|
|
24
|
+
require "open3"
|
|
25
|
+
require "yaml"
|
|
26
|
+
require_relative "lib/session_ledger"
|
|
27
|
+
require_relative "lib/session_backfill"
|
|
28
|
+
require_relative "lib/store_provisioning"
|
|
29
|
+
|
|
30
|
+
def usage_abort(message)
|
|
31
|
+
warn "promote-session-item: #{message}"
|
|
32
|
+
exit 2
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def next_value!(argv, i, flag)
|
|
36
|
+
value = argv[i + 1]
|
|
37
|
+
usage_abort("#{flag} needs a value") if value.nil? || value.start_with?("--")
|
|
38
|
+
value
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
opts = { day: nil, match: nil, session: nil, store: nil, project: nil, templates: nil,
|
|
42
|
+
plastic_home: nil, reindex: true }
|
|
43
|
+
i = 0
|
|
44
|
+
while i < ARGV.length
|
|
45
|
+
case ARGV[i]
|
|
46
|
+
when "--day" then opts[:day] = next_value!(ARGV, i, "--day"); i += 1
|
|
47
|
+
when "--match" then opts[:match] = next_value!(ARGV, i, "--match"); i += 1
|
|
48
|
+
when "--session" then opts[:session] = next_value!(ARGV, i, "--session"); i += 1
|
|
49
|
+
when "--store" then opts[:store] = next_value!(ARGV, i, "--store"); i += 1
|
|
50
|
+
when "--project" then opts[:project] = next_value!(ARGV, i, "--project"); i += 1
|
|
51
|
+
when "--templates" then opts[:templates] = next_value!(ARGV, i, "--templates"); i += 1
|
|
52
|
+
when "--plastic-home" then opts[:plastic_home] = next_value!(ARGV, i, "--plastic-home"); i += 1
|
|
53
|
+
when "--no-reindex" then opts[:reindex] = false
|
|
54
|
+
else usage_abort("unknown argument #{ARGV[i].inspect}")
|
|
55
|
+
end
|
|
56
|
+
i += 1
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
usage_abort("--day is required") if opts[:day].nil?
|
|
60
|
+
usage_abort("--day must be a real calendar date, YYYYMMDD") unless SessionLedger.valid_day_id?(opts[:day])
|
|
61
|
+
usage_abort("--match is required") if opts[:match].nil? || opts[:match].empty?
|
|
62
|
+
|
|
63
|
+
plastic_home = File.expand_path(opts[:plastic_home] || ENV.fetch("PLASTIC_HOME", "~/.plastic"))
|
|
64
|
+
store = opts[:store] || File.join(plastic_home, "store")
|
|
65
|
+
templates = opts[:templates] || File.expand_path("../templates", __dir__)
|
|
66
|
+
session = opts[:session] ? SessionLedger.short_session_id(opts[:session]) : nil
|
|
67
|
+
|
|
68
|
+
target_store = store
|
|
69
|
+
if opts[:project]
|
|
70
|
+
projects = StoreProvisioning.load_projects(plastic_home)
|
|
71
|
+
usage_abort("--project #{opts[:project]} is not a registered project") unless projects.key?(opts[:project])
|
|
72
|
+
target_store = File.join(plastic_home, "projects", opts[:project], "store")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# --- pick the target line ---------------------------------------------------
|
|
76
|
+
|
|
77
|
+
day_dir = SessionLedger.day_dir(store, opts[:day])
|
|
78
|
+
checklist = SessionLedger.checklist_path(store, opts[:day])
|
|
79
|
+
items = SessionBackfill.read_items(day_dir)
|
|
80
|
+
candidates = items.select do |item|
|
|
81
|
+
(session.nil? || item[:session] == session) && item[:summary].include?(opts[:match])
|
|
82
|
+
end
|
|
83
|
+
target = candidates.reverse.find { |item| item[:state] == :open } ||
|
|
84
|
+
candidates.reverse.find { |item| item[:state] == :pending }
|
|
85
|
+
usage_abort("nothing to promote in #{opts[:day]} matching #{opts[:match].inspect}") if target.nil?
|
|
86
|
+
|
|
87
|
+
# --- scaffold through new-intent ---------------------------------------------
|
|
88
|
+
|
|
89
|
+
def slug_for(summary)
|
|
90
|
+
words = summary.downcase.gsub(/[^a-z0-9]+/, "-").gsub(/\A-+|-+\z/, "").split("-").reject(&:empty?)
|
|
91
|
+
slug = words.first(5).join("-")
|
|
92
|
+
slug.empty? ? "item" : slug
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
new_intent = File.expand_path("new-intent", __dir__)
|
|
96
|
+
out, err, status = Open3.capture3({ "RUBYOPT" => nil }, RbConfig.ruby, new_intent,
|
|
97
|
+
"--store", target_store, "--intent", target[:summary],
|
|
98
|
+
"--slug", slug_for(target[:summary]), "--templates", templates)
|
|
99
|
+
unless status.success?
|
|
100
|
+
warn "promote-session-item: new-intent failed:"
|
|
101
|
+
warn err
|
|
102
|
+
warn out
|
|
103
|
+
exit 1
|
|
104
|
+
end
|
|
105
|
+
intent_dir = out.lines.last.to_s.strip
|
|
106
|
+
usage_abort("new-intent printed no intent dir") if intent_dir.empty? || !Dir.exist?(intent_dir)
|
|
107
|
+
intent_id = File.basename(intent_dir).split("--", 2).first
|
|
108
|
+
|
|
109
|
+
# --- record the origin in the new intent (one atomic rewrite) -----------------
|
|
110
|
+
|
|
111
|
+
intent_file = File.join(intent_dir, "#{File.basename(intent_dir)}.md")
|
|
112
|
+
content = File.read(intent_file)
|
|
113
|
+
parts = content.split(/^(---\s*\n)/, 3)
|
|
114
|
+
if parts.length >= 4
|
|
115
|
+
parts[2] += "session_day: \"#{opts[:day]}\"\n"
|
|
116
|
+
content = parts[0] + parts[1] + parts[2] + parts[3] + parts[4].to_s
|
|
117
|
+
end
|
|
118
|
+
origin = "Promoted from the session ledger of #{opts[:day]}: #{target[:summary]}\n"
|
|
119
|
+
content = content.sub(/^## Context\n/) { |heading| heading + origin }
|
|
120
|
+
SessionBackfill.write_atomic(intent_dir, File.basename(intent_file), content)
|
|
121
|
+
|
|
122
|
+
# --- register the intent under ## Future in the store's INDEX.md ----------------
|
|
123
|
+
|
|
124
|
+
def register_in_index(index_path, intent_id, intent_dir, summary)
|
|
125
|
+
return unless File.exist?(index_path)
|
|
126
|
+
|
|
127
|
+
relative = "store/#{File.basename(intent_dir)}/#{File.basename(intent_dir)}.md"
|
|
128
|
+
entry = "- [#{intent_id} - #{summary}](#{relative}) - promoted from a session ledger\n"
|
|
129
|
+
content = File.read(index_path)
|
|
130
|
+
return if content.include?("(#{relative})")
|
|
131
|
+
|
|
132
|
+
updated = content.sub(/^## Future\n(?:\(no future intents\)\n)?/) { |heading| heading.sub(/\(no future intents\)\n/, "") + entry }
|
|
133
|
+
SessionBackfill.write_atomic(File.dirname(index_path), File.basename(index_path), updated)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
register_in_index(File.join(File.dirname(target_store), "INDEX.md"), intent_id, intent_dir, target[:summary])
|
|
137
|
+
|
|
138
|
+
# --- mark the line, note it, reindex ---------------------------------------------
|
|
139
|
+
|
|
140
|
+
SessionLedger.set_state(checklist, from: target[:state], to: :promoted, session: target[:session],
|
|
141
|
+
match: target[:summary])
|
|
142
|
+
SessionLedger.append_line(SessionLedger.savepoint_path(store, opts[:day]),
|
|
143
|
+
SessionLedger.savepoint_line("Note", target[:session], target[:project],
|
|
144
|
+
"promoted \"#{target[:summary]}\" to intent #{intent_id}",
|
|
145
|
+
now: Time.now))
|
|
146
|
+
if opts[:reindex]
|
|
147
|
+
qmd_sync = File.expand_path("qmd-sync", __dir__)
|
|
148
|
+
system({ "RUBYOPT" => nil }, RbConfig.ruby, qmd_sync, "reindex", out: File::NULL, err: File::NULL) if File.exist?(qmd_sync)
|
|
149
|
+
end
|
|
150
|
+
puts intent_dir
|
package/scripts/read-config
CHANGED
|
@@ -14,6 +14,9 @@ require_relative "lib/agent_models"
|
|
|
14
14
|
DEFAULTS = {
|
|
15
15
|
"version" => 3,
|
|
16
16
|
"stale_threshold_days" => 3,
|
|
17
|
+
# Absolute token counts for a 1M window, 35 and 50 percent (intent 312, 296 D38).
|
|
18
|
+
"context_offer_tokens" => 350_000,
|
|
19
|
+
"context_insist_tokens" => 500_000,
|
|
17
20
|
"execution_mode" => "subagent-driven",
|
|
18
21
|
"hash_length" => 6,
|
|
19
22
|
"hash_algorithm" => "sha256-base36",
|
package/scripts/rebuild-graph
CHANGED
|
@@ -82,7 +82,7 @@ class RebuildGraph
|
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
# Registered projects.yml slugs with no store directory on disk (legal;
|
|
85
|
-
# plastic-
|
|
85
|
+
# the plastic-doctor provisioning section exists for exactly this state). Reported in the audit
|
|
86
86
|
# (intent 189 D5) so this never looks like a silent empty store.
|
|
87
87
|
def missing_stores
|
|
88
88
|
discovery[:missing]
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: UTF-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# report-screen - the three delivery-report screens (intent 317): mid-delivery
|
|
6
|
+
# state, post-delivery delivered, and delay. Each fills from the record via
|
|
7
|
+
# scripts/lib/report_screen.rb; no number here is written by eye.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# report-screen state <intent_dir> [--changed "<text>"] [--ansi]
|
|
11
|
+
# report-screen state --all <store_root> [--changed "<text>"] [--ansi]
|
|
12
|
+
# report-screen delivered <intent_dir> [--ansi]
|
|
13
|
+
# report-screen delay <intent_dir> [--ansi]
|
|
14
|
+
#
|
|
15
|
+
# --ansi delegates to ScreenPaint (intent 317a, D1), the parser/re-layouter
|
|
16
|
+
# in the shared TUI core. Selection is by capability, never by harness:
|
|
17
|
+
# NO_COLOR forces plain, a non-TTY stdout stays plain unless
|
|
18
|
+
# PLASTIC_FORCE_COLOR=1 (the test seam), and an unparseable screen falls
|
|
19
|
+
# open to the plain text untouched.
|
|
20
|
+
#
|
|
21
|
+
# Exit codes:
|
|
22
|
+
# 0 - the screen is on stdout
|
|
23
|
+
# 2 - usage error, or the path/store is not what the verb expects (one line
|
|
24
|
+
# on stderr, stdout empty)
|
|
25
|
+
|
|
26
|
+
require_relative "lib/report_screen"
|
|
27
|
+
require_relative "lib/intent_screen"
|
|
28
|
+
require_relative "lib/screen_paint"
|
|
29
|
+
|
|
30
|
+
def usage_abort(message)
|
|
31
|
+
warn "report-screen: #{message}"
|
|
32
|
+
exit 2
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# The one place allowed to shell out (D2/D20): resolves the nearest git tag
|
|
36
|
+
# reachable from the repo this script lives in, so `render_delivered`'s
|
|
37
|
+
# version field and the `ship` evidence row are never invented. The pure
|
|
38
|
+
# module never reads git itself; this is injected as `tag_reader:`.
|
|
39
|
+
def git_tag_reader(repo_root)
|
|
40
|
+
lambda do |_intent_dir|
|
|
41
|
+
return nil unless File.exist?(File.join(repo_root, ".git"))
|
|
42
|
+
tag = `git -C #{repo_root} describe --tags --abbrev=0 2>/dev/null`.strip
|
|
43
|
+
tag.empty? ? nil : tag
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
args = ARGV.dup
|
|
48
|
+
verb = args.shift
|
|
49
|
+
changed = nil
|
|
50
|
+
ansi = false
|
|
51
|
+
template_path = nil
|
|
52
|
+
positional = []
|
|
53
|
+
|
|
54
|
+
while (arg = args.shift)
|
|
55
|
+
case arg
|
|
56
|
+
when "--changed"
|
|
57
|
+
usage_abort("--changed needs a value") if args.empty?
|
|
58
|
+
changed = args.shift
|
|
59
|
+
when "--ansi"
|
|
60
|
+
ansi = true
|
|
61
|
+
when "--all"
|
|
62
|
+
positional << "--all"
|
|
63
|
+
when "--template"
|
|
64
|
+
template_path = args.shift or usage_abort("--template needs a path")
|
|
65
|
+
else
|
|
66
|
+
usage_abort("unknown flag #{arg.inspect}") if arg.start_with?("--") && arg != "--all"
|
|
67
|
+
positional << arg
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
usage_abort("usage: report-screen state|delivered|delay <intent_dir> [--changed \"<text>\"] [--ansi]") unless verb
|
|
72
|
+
|
|
73
|
+
all_mode = positional.delete("--all") ? true : false
|
|
74
|
+
target = positional.first
|
|
75
|
+
|
|
76
|
+
ansi_enabled = ansi && ENV["NO_COLOR"].to_s.empty? &&
|
|
77
|
+
($stdout.tty? || ENV["PLASTIC_FORCE_COLOR"] == "1")
|
|
78
|
+
|
|
79
|
+
def paint(text, ansi_enabled, _renderer_path = nil)
|
|
80
|
+
return text unless ansi_enabled
|
|
81
|
+
ScreenPaint.paint(text, color: true) || text
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
case verb
|
|
85
|
+
when "state"
|
|
86
|
+
if all_mode
|
|
87
|
+
usage_abort("usage: report-screen state --all <store_root>") unless target
|
|
88
|
+
store_root = File.expand_path(target)
|
|
89
|
+
usage_abort("#{store_root} is not a store (no INDEX.md)") unless File.exist?(File.join(store_root, "INDEX.md"))
|
|
90
|
+
out = ReportScreen.render_roster(store_root, changed: changed)
|
|
91
|
+
$stdout.write paint(out, ansi_enabled)
|
|
92
|
+
else
|
|
93
|
+
usage_abort("usage: report-screen state <intent_dir> [--changed \"<text>\"]") unless target
|
|
94
|
+
intent_dir = File.expand_path(target)
|
|
95
|
+
usage_abort("#{intent_dir} is not an intent directory") unless IntentScreen.intent_dir?(intent_dir)
|
|
96
|
+
store_root = File.expand_path("../..", intent_dir)
|
|
97
|
+
template_path ||= File.expand_path("../templates/report-state.md", __dir__)
|
|
98
|
+
usage_abort("template not found at #{template_path}") unless File.exist?(template_path)
|
|
99
|
+
out = ReportScreen.render_state(intent_dir: intent_dir, store_root: store_root, changed: changed,
|
|
100
|
+
template: File.read(template_path))
|
|
101
|
+
$stdout.write paint(out, ansi_enabled)
|
|
102
|
+
end
|
|
103
|
+
when "delivered"
|
|
104
|
+
usage_abort("usage: report-screen delivered <intent_dir>") unless target
|
|
105
|
+
intent_dir = File.expand_path(target)
|
|
106
|
+
usage_abort("#{intent_dir} is not an intent directory") unless IntentScreen.intent_dir?(intent_dir)
|
|
107
|
+
repo_root = File.expand_path("..", __dir__)
|
|
108
|
+
out = ReportScreen.render_delivered(intent_dir: intent_dir, tag_reader: git_tag_reader(repo_root))
|
|
109
|
+
$stdout.write paint(out, ansi_enabled)
|
|
110
|
+
when "delay"
|
|
111
|
+
usage_abort("usage: report-screen delay <intent_dir>") unless target
|
|
112
|
+
intent_dir = File.expand_path(target)
|
|
113
|
+
usage_abort("#{intent_dir} is not an intent directory") unless IntentScreen.intent_dir?(intent_dir)
|
|
114
|
+
out = ReportScreen.render_delay(intent_dir: intent_dir)
|
|
115
|
+
$stdout.write paint(out, ansi_enabled)
|
|
116
|
+
else
|
|
117
|
+
usage_abort("unknown verb #{verb.inspect} (use state|delivered|delay)")
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
exit 0
|
package/scripts/roadmap-next
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
# roadmap-next - thin CLI over RoadmapQueue (intent 148).
|
|
6
6
|
#
|
|
7
|
-
# The auto loop and plastic-
|
|
7
|
+
# The auto loop and plastic-intent-continuing both call this instead of ranking roadmap
|
|
8
8
|
# liveness in prose. Queue mode (default) is for the auto loop: it breaks a tie
|
|
9
9
|
# deterministically and reports the winner's frontier state. Which mode (--which) is for
|
|
10
|
-
# plastic-
|
|
10
|
+
# plastic-intent-continuing: it returns tie_candidates so the skill's roadmap route can resolve
|
|
11
11
|
# a genuine tie.
|
|
12
12
|
#
|
|
13
13
|
# Usage:
|