@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
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: UTF-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
require "time"
|
|
6
|
+
require_relative "lib/savepoint"
|
|
7
|
+
require_relative "lib/node_ledger"
|
|
8
|
+
require_relative "lib/ready_set"
|
|
9
|
+
require_relative "lib/guarded_append"
|
|
10
|
+
require_relative "lib/lock"
|
|
11
|
+
require_relative "lib/arm"
|
|
12
|
+
|
|
13
|
+
# node-transition - the CLI over NodeLedger that REFUSES what the graph does not
|
|
14
|
+
# allow (intent 335, G2). An orchestrator can run this by hand today, before the
|
|
15
|
+
# runner (intent 340) exists.
|
|
16
|
+
#
|
|
17
|
+
# Usage:
|
|
18
|
+
# node-transition <intent_dir> --node <id|Intent> --state <state> [--field k=v]...
|
|
19
|
+
# [--comment "..."] [--needs a,b] [--session SID] [--now ISO8601]
|
|
20
|
+
# node-transition report <intent_dir>
|
|
21
|
+
#
|
|
22
|
+
# Exit codes (spec D18, matching the tree: scripts/append-ledger:125 uses 3 for a
|
|
23
|
+
# guard that could not be taken, scripts/end-intent:675 uses 4 for a lock-ownership
|
|
24
|
+
# refusal):
|
|
25
|
+
# 0 - the line was appended
|
|
26
|
+
# 2 - usage: not an intent directory, bad subject, unknown state, missing
|
|
27
|
+
# required field, a tab or newline in a value
|
|
28
|
+
# 3 - the ledger guard was unavailable; nothing was written
|
|
29
|
+
# 4 - running, and the caller does not hold delivery.lock
|
|
30
|
+
# 5 - running, and the subject is not ready
|
|
31
|
+
# 6 - reclaimed, and the subject's last running line has not expired
|
|
32
|
+
module NodeTransition
|
|
33
|
+
module_function
|
|
34
|
+
|
|
35
|
+
# The readiness precondition for `running` (intent 336, G3): ReadySet.ready?
|
|
36
|
+
# applied to `subject` against `edges`/`nodes` already parsed once, before
|
|
37
|
+
# the guard is ever taken (spec: "parsed once, before the guard"). Takes
|
|
38
|
+
# `content` (the ledger's raw text), never a path, so this method can be run
|
|
39
|
+
# twice: once as a cheap unguarded pre-check for a fast, readable refusal,
|
|
40
|
+
# and once - the AUTHORITATIVE decision - as the guard's precondition,
|
|
41
|
+
# evaluated against the exact content GuardedAppend just read under its
|
|
42
|
+
# lock hold (post-execution review row 7.1, carried forward by 336). A
|
|
43
|
+
# path-based re-read here would defeat the guard: two callers could each
|
|
44
|
+
# open their own handle, both see "planned", and both go on to append
|
|
45
|
+
# `running`.
|
|
46
|
+
def running_ready?(content, subject, edges, nodes, caps: ReadySet::DEFAULT_CAPS)
|
|
47
|
+
ReadySet.ready?(content: content, subject: subject, graph: { edges: edges }, nodes: nodes, caps: caps)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# graph.md and nodes/ for `subject`'s readiness (intent 336, G3): a genuinely
|
|
51
|
+
# missing graph.md degrades gracefully to no known needs/kind/files (the
|
|
52
|
+
# same fallback the temporary needs_from_graph reader gave, so a legacy
|
|
53
|
+
# intent with no graph.md still dispatches ad hoc node ids), while a
|
|
54
|
+
# PRESENT but broken graph.md (no ## Graph section, a cycle) is a clean
|
|
55
|
+
# refusal, never a raise - {edges:, nodes:, error:}.
|
|
56
|
+
def resolve_graph_and_nodes(intent_dir)
|
|
57
|
+
graph_path = File.join(intent_dir, "graph.md")
|
|
58
|
+
return { edges: {}, nodes: {}, error: nil } unless File.exist?(graph_path)
|
|
59
|
+
|
|
60
|
+
loaded = ReadySet.load_graph(intent_dir)
|
|
61
|
+
return { edges: loaded[:edges] || {}, nodes: loaded[:nodes] || {}, error: nil } if loaded[:ok]
|
|
62
|
+
|
|
63
|
+
{ edges: {}, nodes: {}, error: loaded[:errors].join("; ") }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Session resolution per spec D16: an explicit --session is authoritative and
|
|
67
|
+
# NEVER falls through (an explicit non-owner is refused outright, never
|
|
68
|
+
# silently granted ownership through a fallback). Without it, try the
|
|
69
|
+
# environment's CLAUDE_CODE_SESSION_ID, then the derived `auto-` key, and keep
|
|
70
|
+
# the first candidate that actually HOLDS the lock (owner or delegate,
|
|
71
|
+
# Lock.holds?) - so the fallback can never grant ownership to a session that
|
|
72
|
+
# does not have it.
|
|
73
|
+
def resolve_owning_session(intent_dir, explicit:, env_session:)
|
|
74
|
+
if explicit && !explicit.to_s.strip.empty?
|
|
75
|
+
return Lock.holds?(intent_dir, session: explicit) ? explicit : nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
store = Arm.store_for(intent_dir)
|
|
79
|
+
intent_id = Arm.intent_id_for(intent_dir)
|
|
80
|
+
candidates = [env_session, Arm.derive_key(store, intent_id)].reject { |c| c.to_s.strip.empty? }
|
|
81
|
+
candidates.find { |c| Lock.holds?(intent_dir, session: c) }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def intent_directory?(dir)
|
|
85
|
+
dir && File.directory?(dir) && File.exist?(Savepoint.intent_file(dir))
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def valid_subject?(subject)
|
|
89
|
+
subject == Savepoint::INTENT_SUBJECT || subject.to_s.match?(Savepoint::NODE_SUBJECT_RE)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def opt(args, name)
|
|
93
|
+
(i = args.index(name)) && args[i + 1]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def opt_all(args, name)
|
|
97
|
+
args.each_index.select { |i| args[i] == name }.map { |i| args[i + 1] }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def parse_fields(args)
|
|
101
|
+
opt_all(args, "--field").each_with_object({}) do |pair, memo|
|
|
102
|
+
key, value = pair.to_s.split("=", 2)
|
|
103
|
+
memo[key] = value if key && !key.empty?
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def usage
|
|
108
|
+
warn "Usage: node-transition <intent_dir> --node <id|Intent> --state <state> " \
|
|
109
|
+
"[--field k=v]... [--comment \"...\"] [--needs a,b] [--session SID] [--now ISO8601]"
|
|
110
|
+
warn " node-transition report <intent_dir>"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def run_report(intent_dir)
|
|
114
|
+
unless intent_directory?(intent_dir)
|
|
115
|
+
warn "node-transition: #{intent_dir.inspect} is not an intent directory"
|
|
116
|
+
usage
|
|
117
|
+
exit 2
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
savepoint_path = File.join(intent_dir, "savepoint.md")
|
|
121
|
+
anomalies = NodeLedger.anomalies(savepoint_path)
|
|
122
|
+
if anomalies.empty?
|
|
123
|
+
puts "node-transition report: no torn or unattributed transition lines"
|
|
124
|
+
else
|
|
125
|
+
anomalies.each { |a| puts "#{a[:reason]}: #{a[:line]}" }
|
|
126
|
+
end
|
|
127
|
+
exit 0
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def main(argv)
|
|
131
|
+
args = argv.dup
|
|
132
|
+
head = args.shift
|
|
133
|
+
|
|
134
|
+
if head == "report"
|
|
135
|
+
report_dir_arg = args.shift
|
|
136
|
+
run_report(report_dir_arg && File.expand_path(report_dir_arg))
|
|
137
|
+
return
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
intent_dir_arg = head
|
|
141
|
+
unless intent_directory?(intent_dir_arg && File.expand_path(intent_dir_arg))
|
|
142
|
+
warn "node-transition: #{intent_dir_arg.inspect} is not an intent directory"
|
|
143
|
+
usage
|
|
144
|
+
exit 2
|
|
145
|
+
end
|
|
146
|
+
intent_dir = File.expand_path(intent_dir_arg)
|
|
147
|
+
|
|
148
|
+
subject = opt(args, "--node")
|
|
149
|
+
state = opt(args, "--state")
|
|
150
|
+
comment = opt(args, "--comment")
|
|
151
|
+
needs_flag = opt(args, "--needs")
|
|
152
|
+
session_flag = opt(args, "--session")
|
|
153
|
+
now_flag = opt(args, "--now")
|
|
154
|
+
fields = parse_fields(args)
|
|
155
|
+
|
|
156
|
+
if subject.nil? || state.nil?
|
|
157
|
+
warn "node-transition: --node and --state are required"
|
|
158
|
+
usage
|
|
159
|
+
exit 2
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
unless valid_subject?(subject)
|
|
163
|
+
warn "node-transition: #{subject.inspect} is neither #{Savepoint::INTENT_SUBJECT} nor a valid node id"
|
|
164
|
+
exit 2
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
unless NodeLedger::STATES.include?(state)
|
|
168
|
+
warn "node-transition: unknown state #{state.inspect}"
|
|
169
|
+
exit 2
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
now = begin
|
|
173
|
+
now_flag ? Time.iso8601(now_flag) : Time.now
|
|
174
|
+
rescue ArgumentError
|
|
175
|
+
warn "node-transition: --now must be ISO 8601"
|
|
176
|
+
exit 2
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
begin
|
|
180
|
+
fields.each_value { |v| NodeLedger.normalize_value(v) }
|
|
181
|
+
NodeLedger.normalize_value(comment) if comment
|
|
182
|
+
rescue ArgumentError => e
|
|
183
|
+
warn "node-transition: #{e.message}"
|
|
184
|
+
exit 2
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
missing = NodeLedger.missing_fields(state, fields)
|
|
188
|
+
unless missing.empty?
|
|
189
|
+
warn "node-transition: state #{state} requires #{missing.join(', ')}"
|
|
190
|
+
exit 2
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
savepoint_path = File.join(intent_dir, "savepoint.md")
|
|
194
|
+
precondition = nil
|
|
195
|
+
|
|
196
|
+
case state
|
|
197
|
+
when "running"
|
|
198
|
+
owning_session = resolve_owning_session(intent_dir, explicit: session_flag,
|
|
199
|
+
env_session: ENV["CLAUDE_CODE_SESSION_ID"])
|
|
200
|
+
unless owning_session
|
|
201
|
+
warn "node-transition: the caller does not hold delivery.lock for #{intent_dir}"
|
|
202
|
+
exit 4
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
if subject == Savepoint::INTENT_SUBJECT
|
|
206
|
+
# The node readiness rule (four conditions over the work graph) is
|
|
207
|
+
# scoped to node subjects; the intent-scope subject is never gated by
|
|
208
|
+
# it, so a legacy intent with no graph.md at all can still write an
|
|
209
|
+
# Intent line (spec Acceptance Criteria; matrix "Transition the
|
|
210
|
+
# Intent subject").
|
|
211
|
+
precondition = nil
|
|
212
|
+
else
|
|
213
|
+
resolved = resolve_graph_and_nodes(intent_dir)
|
|
214
|
+
if resolved[:error]
|
|
215
|
+
warn "node-transition: #{resolved[:error]}"
|
|
216
|
+
exit 5
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
needs_override = needs_flag ? needs_flag.split(",").map(&:strip).reject(&:empty?) : nil
|
|
220
|
+
edges = needs_override ? resolved[:edges].merge(subject => needs_override) : resolved[:edges]
|
|
221
|
+
nodes = resolved[:nodes]
|
|
222
|
+
|
|
223
|
+
# Fast, unguarded pre-check: a cheap, readable refusal for the common
|
|
224
|
+
# case, but NEVER the authoritative decision (post-execution review
|
|
225
|
+
# row 7.1, carried forward by 336). The authoritative check is the
|
|
226
|
+
# `precondition` below, run inside GuardedAppend's lock hold against
|
|
227
|
+
# the content it actually read. `edges` and `nodes` are parsed once,
|
|
228
|
+
# above, before the guard is ever taken (spec D1).
|
|
229
|
+
precheck_content = File.exist?(savepoint_path) ? File.read(savepoint_path) : ""
|
|
230
|
+
result = running_ready?(precheck_content, subject, edges, nodes)
|
|
231
|
+
unless result[:ready]
|
|
232
|
+
warn "node-transition: #{result[:blockers].join('; ')}"
|
|
233
|
+
exit 5
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
precondition = ->(content) { running_ready?(content, subject, edges, nodes)[:ready] }
|
|
237
|
+
end
|
|
238
|
+
when "reclaimed"
|
|
239
|
+
running_entry = NodeLedger.last_running(savepoint_path, subject)
|
|
240
|
+
unless running_entry
|
|
241
|
+
warn "node-transition: #{subject} has no running line to reclaim"
|
|
242
|
+
exit 6
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Post-execution review row 7.4: expiry alone is not enough. A `done`
|
|
246
|
+
# node whose last `running` line's expires= happens to be in the past
|
|
247
|
+
# must not revert to `planned` and become re-dispatchable - reclaim is
|
|
248
|
+
# deliberately lock-free (spec D43), so this status check is the only
|
|
249
|
+
# thing that stops it. Row 7.5 pins that a genuinely running, expired
|
|
250
|
+
# subject is still reclaimable, so the crash sweep keeps working.
|
|
251
|
+
current_status = NodeLedger.status_for(savepoint_path, subject)
|
|
252
|
+
unless current_status == "running"
|
|
253
|
+
warn "node-transition: #{subject} is #{current_status}, not running; nothing to reclaim"
|
|
254
|
+
exit 6
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
expires_raw = (running_entry[:fields] || {})["expires"]
|
|
258
|
+
expiry_time = begin
|
|
259
|
+
expires_raw && Time.iso8601(expires_raw)
|
|
260
|
+
rescue ArgumentError
|
|
261
|
+
nil
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
unless expiry_time && now > expiry_time
|
|
265
|
+
warn "node-transition: #{subject}'s running line has not expired yet"
|
|
266
|
+
exit 6
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
result = begin
|
|
271
|
+
NodeLedger.append_transition(savepoint_path, subject: subject, state: state, fields: fields,
|
|
272
|
+
comment: comment, now: now, precondition: precondition)
|
|
273
|
+
rescue GuardedAppend::Unavailable => e
|
|
274
|
+
warn "node-transition: #{e.message}"
|
|
275
|
+
exit 3
|
|
276
|
+
rescue ArgumentError => e
|
|
277
|
+
warn "node-transition: #{e.message}"
|
|
278
|
+
exit 2
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
if result == :refused
|
|
282
|
+
warn "node-transition: #{subject} was no longer ready by the time the write lock was taken"
|
|
283
|
+
exit 5
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
puts "node-transition: appended #{state} for #{subject}"
|
|
287
|
+
exit 0
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
NodeTransition.main(ARGV) if $PROGRAM_NAME == __FILE__
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: UTF-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# outcome-report - the CLI over scripts/lib/outcome_report.rb (intent 339, G6).
|
|
6
|
+
# Prints the generated outcome.md by default; --write puts it on disk through
|
|
7
|
+
# AtomicWrite. `end-intent` calls the library directly (never shells out to
|
|
8
|
+
# this), so this command is for a human or an agent checking the generated
|
|
9
|
+
# report before or instead of a close.
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# outcome-report <intent_dir> [--write] [--disposition delivered|abandoned]
|
|
13
|
+
#
|
|
14
|
+
# Exit codes:
|
|
15
|
+
# 0 - printed (or written) successfully
|
|
16
|
+
# 2 - usage error: no path given, an unknown flag, or the path is not an
|
|
17
|
+
# intent directory
|
|
18
|
+
# 3 - the report model could not be built (a malformed or absent graph.md)
|
|
19
|
+
|
|
20
|
+
require_relative "lib/outcome_report"
|
|
21
|
+
require_relative "lib/intent_screen"
|
|
22
|
+
require_relative "lib/outcome_guard"
|
|
23
|
+
|
|
24
|
+
def usage_abort(message)
|
|
25
|
+
warn "outcome-report: #{message}"
|
|
26
|
+
exit 2
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
args = ARGV.dup
|
|
30
|
+
write_flag = false
|
|
31
|
+
disposition = "delivered"
|
|
32
|
+
positional = []
|
|
33
|
+
|
|
34
|
+
while (arg = args.shift)
|
|
35
|
+
case arg
|
|
36
|
+
when "--write"
|
|
37
|
+
write_flag = true
|
|
38
|
+
when "--disposition"
|
|
39
|
+
disposition = args.shift or usage_abort("--disposition needs a value")
|
|
40
|
+
else
|
|
41
|
+
usage_abort("unknown flag #{arg.inspect}") if arg.start_with?("--")
|
|
42
|
+
positional << arg
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
target = positional.first
|
|
47
|
+
usage_abort("usage: outcome-report <intent_dir> [--write] [--disposition delivered|abandoned]") unless target
|
|
48
|
+
# Row v1f.9 (N3): refuse an unknown --disposition here rather than write a
|
|
49
|
+
# record OutcomeGuard then refuses. OutcomeGuard::DISPOSITIONS is already
|
|
50
|
+
# loaded in-process, so this is one comparison, not a duplicated list.
|
|
51
|
+
unless OutcomeGuard::DISPOSITIONS.include?(disposition)
|
|
52
|
+
usage_abort("--disposition must be one of #{OutcomeGuard::DISPOSITIONS.join('|')}, got #{disposition.inspect}")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
intent_dir = File.expand_path(target)
|
|
56
|
+
usage_abort("#{intent_dir} is not an intent directory") unless IntentScreen.intent_dir?(intent_dir)
|
|
57
|
+
|
|
58
|
+
model = OutcomeReport.model(intent_dir)
|
|
59
|
+
unless model[:ok]
|
|
60
|
+
warn "outcome-report: the report model could not be built: #{model[:errors].join('; ')}"
|
|
61
|
+
exit 3
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
if write_flag
|
|
65
|
+
OutcomeReport.write(intent_dir, disposition: disposition)
|
|
66
|
+
puts File.join(intent_dir, "outcome.md")
|
|
67
|
+
else
|
|
68
|
+
outcome_path = File.join(intent_dir, "outcome.md")
|
|
69
|
+
existing = File.exist?(outcome_path) ? File.read(outcome_path) : nil
|
|
70
|
+
puts OutcomeReport.render(model, disposition: disposition, existing: existing,
|
|
71
|
+
findings: OutcomeReport.findings(intent_dir), intent_dir: intent_dir)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
exit 0
|
package/scripts/plastic-lock
CHANGED
|
@@ -19,12 +19,11 @@
|
|
|
19
19
|
# corrupt lock with the resolving doctor hint
|
|
20
20
|
# who compact, read-only durable owner/activity view
|
|
21
21
|
# status report the lock file, its freshness, the derived worktree,
|
|
22
|
-
# whether this session
|
|
23
|
-
# live per-artifact claims
|
|
22
|
+
# whether this session is the one delivering the intent, and
|
|
23
|
+
# any live per-artifact claims
|
|
24
24
|
# fix idempotent repair of the lock for the current session; never
|
|
25
25
|
# touches a fresh foreign lock
|
|
26
|
-
# release owner clears the lock
|
|
27
|
-
# ledger (End tail / abandoning a boarding)
|
|
26
|
+
# release owner clears the lock (End tail / abandoning a boarding)
|
|
28
27
|
# reclaim explicit takeover of a stale lock, audited in savepoint.md
|
|
29
28
|
# delegate owner registers a subagent session under the lock (D4)
|
|
30
29
|
# claim acquire the named --artifact's claim token (intent 111);
|
|
@@ -32,18 +31,19 @@
|
|
|
32
31
|
# including this same session; takes over a stale claim
|
|
33
32
|
# release-claim free the named --artifact's claim token
|
|
34
33
|
#
|
|
35
|
-
# Without --intent-dir the intent is resolved from this session's
|
|
36
|
-
# (
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
34
|
+
# Without --intent-dir the intent is resolved from this session's held
|
|
35
|
+
# delivery lock (ActiveDelivery.resolve over the global store and the
|
|
36
|
+
# configured project roots): exactly one fresh lock this session owns or
|
|
37
|
+
# delegates for. Session resolution no longer reads any per-session state
|
|
38
|
+
# file; that path was retired in 2.0 (intent 307) and 344 G11.
|
|
39
|
+
# Exit 0 on success/report; exit 1 when the verb is blocked.
|
|
40
40
|
|
|
41
|
+
require_relative "lib/store_layout"
|
|
41
42
|
require "json"
|
|
42
43
|
require "yaml"
|
|
43
|
-
require_relative "lib/bridge"
|
|
44
44
|
require_relative "lib/lock"
|
|
45
45
|
require_relative "lib/arm"
|
|
46
|
-
require_relative "lib/
|
|
46
|
+
require_relative "lib/active_delivery"
|
|
47
47
|
|
|
48
48
|
VERBS = %w[arm status who fix release reclaim delegate claim release-claim].freeze
|
|
49
49
|
|
|
@@ -70,6 +70,7 @@ until ARGV.empty?
|
|
|
70
70
|
when "--model" then opts[:model] = ARGV.shift
|
|
71
71
|
when "--thread" then opts[:thread] = ARGV.shift
|
|
72
72
|
when "--mode" then opts[:mode] = ARGV.shift
|
|
73
|
+
when "--allow-inline" then opts[:allow_inline] = true
|
|
73
74
|
when "--status" then opts[:status] = ARGV.shift
|
|
74
75
|
else
|
|
75
76
|
warn "unknown flag #{flag}"
|
|
@@ -87,16 +88,7 @@ hint_harness = harness || "claude"
|
|
|
87
88
|
plastic_home_env = ENV["PLASTIC_HOME"].to_s.strip
|
|
88
89
|
home = plastic_home_env.empty? ? Dir.home : File.dirname(File.expand_path(plastic_home_env))
|
|
89
90
|
|
|
90
|
-
|
|
91
|
-
# the working directory (projects.yml, longest path match), then the global one.
|
|
92
|
-
def candidate_stores(home, cwd)
|
|
93
|
-
plastic_home = File.join(home, ".plastic")
|
|
94
|
-
stores = []
|
|
95
|
-
slug = SessionLedger.project_slug(cwd, plastic_home: plastic_home)
|
|
96
|
-
stores << File.join(plastic_home, "projects", slug, "store") if slug && slug != "global"
|
|
97
|
-
stores << File.join(plastic_home, "store")
|
|
98
|
-
stores
|
|
99
|
-
end
|
|
91
|
+
plastic_home = File.join(home, ".plastic")
|
|
100
92
|
|
|
101
93
|
dir = opts[:dir]
|
|
102
94
|
if dir.nil? || dir.strip.empty?
|
|
@@ -104,11 +96,21 @@ if dir.nil? || dir.strip.empty?
|
|
|
104
96
|
warn "plastic-lock: #{verb} needs --intent-dir <intent dir>"
|
|
105
97
|
exit 1
|
|
106
98
|
end
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
99
|
+
roots = ActiveDelivery.project_roots(plastic_home)
|
|
100
|
+
global_store = Plastic::StoreLayout.global_store(plastic_home)
|
|
101
|
+
dir = ActiveDelivery.resolve(global_store: global_store, project_roots: roots, session: session)
|
|
102
|
+
if dir.nil?
|
|
103
|
+
candidates = ActiveDelivery.candidate_intent_dirs(global_store: global_store, project_roots: roots).uniq
|
|
104
|
+
held = candidates.select do |candidate|
|
|
105
|
+
Lock.fresh?(candidate) && Lock.holds?(candidate, session: session)
|
|
106
|
+
end
|
|
107
|
+
if held.size > 1
|
|
108
|
+
warn "plastic-lock: more than one held delivery lock names this session; pass --intent-dir <intent dir>"
|
|
109
|
+
else
|
|
110
|
+
warn "plastic-lock: no intent resolved; pass --intent-dir <intent dir>"
|
|
111
|
+
end
|
|
112
|
+
exit 1
|
|
113
|
+
end
|
|
112
114
|
end
|
|
113
115
|
dir = File.expand_path(dir)
|
|
114
116
|
|
|
@@ -143,7 +145,7 @@ if verb == "who"
|
|
|
143
145
|
exit 0
|
|
144
146
|
end
|
|
145
147
|
|
|
146
|
-
intent_id =
|
|
148
|
+
intent_id = Arm.intent_id_for(dir)
|
|
147
149
|
store = File.dirname(dir)
|
|
148
150
|
key = Arm.resolve_session(session, store: store, intent_id: intent_id)
|
|
149
151
|
|
|
@@ -154,14 +156,15 @@ end
|
|
|
154
156
|
case verb
|
|
155
157
|
when "arm"
|
|
156
158
|
result = Arm.arm(intent_dir: dir, session: key, mode: opts[:mode] || "auto", home: home,
|
|
157
|
-
harness: harness, agent: opts[:agent], model: opts[:model], thread: opts[:thread]
|
|
159
|
+
harness: harness, agent: opts[:agent], model: opts[:model], thread: opts[:thread],
|
|
160
|
+
allow_inline: opts[:allow_inline] || false)
|
|
158
161
|
case result[:status]
|
|
159
162
|
when :acquired, :owned
|
|
160
163
|
wt = result[:worktree] || {}
|
|
161
164
|
puts JSON.pretty_generate(
|
|
162
165
|
"intent_dir" => dir, "session" => result[:session], "status" => result[:status].to_s,
|
|
163
166
|
"run_mode" => result[:lock] && result[:lock]["run_mode"],
|
|
164
|
-
"worktree" => wt
|
|
167
|
+
"worktree" => wt
|
|
165
168
|
)
|
|
166
169
|
when :held
|
|
167
170
|
warn "plastic-lock: delivery lock for intent #{intent_id} is held by session " \
|
|
@@ -178,10 +181,18 @@ when "arm"
|
|
|
178
181
|
when :corrupt
|
|
179
182
|
warn "plastic-lock: delivery.lock for intent #{intent_id} is unreadable; #{doctor_hint('fix the lock', hint_harness)}"
|
|
180
183
|
exit 1
|
|
184
|
+
when :inline_refused
|
|
185
|
+
warn "plastic-lock: refusing to arm intent #{intent_id} from a conversation session " \
|
|
186
|
+
"(owner rule 2026-08-31: no inline intent delivery). Dispatch the delivery team " \
|
|
187
|
+
"instead (plastic-auto), or pass --allow-inline with explicit owner approval."
|
|
188
|
+
exit 3
|
|
181
189
|
end
|
|
182
190
|
when "status"
|
|
183
191
|
lock = Lock.read(dir)
|
|
184
|
-
|
|
192
|
+
resolved = ActiveDelivery.resolve(global_store: Plastic::StoreLayout.global_store(plastic_home),
|
|
193
|
+
project_roots: ActiveDelivery.project_roots(plastic_home),
|
|
194
|
+
session: key)
|
|
195
|
+
delivering = !resolved.nil? && File.expand_path(resolved) == File.expand_path(dir)
|
|
185
196
|
report = {
|
|
186
197
|
"intent_dir" => dir,
|
|
187
198
|
"session" => key,
|
|
@@ -189,8 +200,7 @@ when "status"
|
|
|
189
200
|
"lock_fresh" => Lock.fresh?(dir),
|
|
190
201
|
"lock_corrupt" => Lock.corrupt?(dir),
|
|
191
202
|
"worktree" => Arm.worktree_block(intent_dir: dir, home: home),
|
|
192
|
-
"
|
|
193
|
-
"pointer_names_intent" => pointer == intent_id,
|
|
203
|
+
"delivering" => delivering,
|
|
194
204
|
"claims" => Claim.claims_status(dir),
|
|
195
205
|
}
|
|
196
206
|
puts JSON.pretty_generate(report)
|
|
@@ -210,7 +220,6 @@ when "release"
|
|
|
210
220
|
warn "plastic-lock: not the owner; run plastic-lock status"
|
|
211
221
|
exit 1
|
|
212
222
|
end
|
|
213
|
-
Arm.reset_pointer(key, intent_id, home: home)
|
|
214
223
|
puts "released (#{result})"
|
|
215
224
|
when "reclaim"
|
|
216
225
|
status, lock_data = Lock.takeover(dir, session: key, harness: harness,
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
# Exit codes: 0 promoted (prints the intent dir last), 1 new-intent failed,
|
|
22
22
|
# 2 usage error or nothing to promote.
|
|
23
23
|
|
|
24
|
+
require_relative "lib/store_layout"
|
|
24
25
|
require "open3"
|
|
25
26
|
require "yaml"
|
|
26
27
|
require_relative "lib/session_ledger"
|
|
@@ -61,7 +62,7 @@ usage_abort("--day must be a real calendar date, YYYYMMDD") unless SessionLedger
|
|
|
61
62
|
usage_abort("--match is required") if opts[:match].nil? || opts[:match].empty?
|
|
62
63
|
|
|
63
64
|
plastic_home = File.expand_path(opts[:plastic_home] || ENV.fetch("PLASTIC_HOME", "~/.plastic"))
|
|
64
|
-
store = opts[:store] ||
|
|
65
|
+
store = opts[:store] || Plastic::StoreLayout.global_store(plastic_home)
|
|
65
66
|
templates = opts[:templates] || File.expand_path("../templates", __dir__)
|
|
66
67
|
session = opts[:session] ? SessionLedger.short_session_id(opts[:session]) : nil
|
|
67
68
|
|
|
@@ -69,7 +70,7 @@ target_store = store
|
|
|
69
70
|
if opts[:project]
|
|
70
71
|
projects = StoreProvisioning.load_projects(plastic_home)
|
|
71
72
|
usage_abort("--project #{opts[:project]} is not a registered project") unless projects.key?(opts[:project])
|
|
72
|
-
target_store = File.join(plastic_home,
|
|
73
|
+
target_store = File.join(Plastic::StoreLayout.project_root(plastic_home, opts[:project]), "store")
|
|
73
74
|
end
|
|
74
75
|
|
|
75
76
|
# --- pick the target line ---------------------------------------------------
|
package/scripts/read-config
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
-
# Usage: read-config <key> [--default VALUE] [--project PATH]
|
|
2
|
+
# Usage: read-config <key> [--default VALUE] [--project PATH] [--harness claude|codex]
|
|
3
3
|
# Resolves a config value: project -> global -> built-in defaults.
|
|
4
4
|
# Nested keys use dot notation: agent.type, architect.style
|
|
5
5
|
# Returns scalar values as strings, hash/array values as JSON.
|
|
@@ -14,9 +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,
|
|
18
|
-
"context_offer_tokens" =>
|
|
19
|
-
"context_insist_tokens" =>
|
|
17
|
+
# Absolute token counts for a 1M window, 15 and 25 percent (intent 355, n5, D7).
|
|
18
|
+
"context_offer_tokens" => 150_000,
|
|
19
|
+
"context_insist_tokens" => 250_000,
|
|
20
20
|
"execution_mode" => "subagent-driven",
|
|
21
21
|
"hash_length" => 6,
|
|
22
22
|
"hash_algorithm" => "sha256-base36",
|
|
@@ -33,6 +33,12 @@ DEFAULTS = {
|
|
|
33
33
|
},
|
|
34
34
|
"architect" => {
|
|
35
35
|
"style" => nil
|
|
36
|
+
},
|
|
37
|
+
# Intent 340b (G7c, n4, D9): the Stop hook's runtime arm. Off until the
|
|
38
|
+
# owner's C32 ruling; read-config emits it as the string "false", which
|
|
39
|
+
# StopGate parses as a boolean itself rather than trusting.
|
|
40
|
+
"runner" => {
|
|
41
|
+
"stop_hook" => false
|
|
36
42
|
}
|
|
37
43
|
}.freeze
|
|
38
44
|
|
|
@@ -40,6 +46,7 @@ def parse_args(argv)
|
|
|
40
46
|
key = nil
|
|
41
47
|
default_val = nil
|
|
42
48
|
project_dir = nil
|
|
49
|
+
harness = nil
|
|
43
50
|
|
|
44
51
|
i = 0
|
|
45
52
|
while i < argv.length
|
|
@@ -52,13 +59,17 @@ def parse_args(argv)
|
|
|
52
59
|
abort "Error: --project requires a value" unless argv[i + 1]
|
|
53
60
|
project_dir = argv[i + 1]
|
|
54
61
|
i += 2
|
|
62
|
+
when "--harness"
|
|
63
|
+
abort "Error: --harness requires a value" unless argv[i + 1]
|
|
64
|
+
harness = argv[i + 1]
|
|
65
|
+
i += 2
|
|
55
66
|
else
|
|
56
67
|
key = argv[i]
|
|
57
68
|
i += 1
|
|
58
69
|
end
|
|
59
70
|
end
|
|
60
71
|
|
|
61
|
-
[key, default_val, project_dir]
|
|
72
|
+
[key, default_val, project_dir, harness]
|
|
62
73
|
end
|
|
63
74
|
|
|
64
75
|
def load_yaml(path)
|
|
@@ -113,10 +124,10 @@ if ARGV.include?("--migrate")
|
|
|
113
124
|
exit 0
|
|
114
125
|
end
|
|
115
126
|
|
|
116
|
-
key, explicit_default, project_dir = parse_args(ARGV)
|
|
127
|
+
key, explicit_default, project_dir, harness = parse_args(ARGV)
|
|
117
128
|
|
|
118
129
|
if key.nil? || key.empty?
|
|
119
|
-
$stderr.puts "Usage: read-config <key> [--default VALUE] [--project PATH]"
|
|
130
|
+
$stderr.puts "Usage: read-config <key> [--default VALUE] [--project PATH] [--harness claude|codex]"
|
|
120
131
|
exit 1
|
|
121
132
|
end
|
|
122
133
|
|
|
@@ -129,9 +140,42 @@ if project_dir
|
|
|
129
140
|
project_config = load_yaml(project_config_path)
|
|
130
141
|
end
|
|
131
142
|
|
|
132
|
-
|
|
133
|
-
|
|
143
|
+
def canonical_harness(value)
|
|
144
|
+
return nil if value.nil?
|
|
145
|
+
return "claude" if %w[claude claude-code].include?(value.to_s)
|
|
146
|
+
return "codex" if value.to_s == "codex"
|
|
147
|
+
|
|
148
|
+
abort "Error: --harness must be claude or codex"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def harness_agent_value(config, key, harness)
|
|
152
|
+
match = key.match(/\Aagents\.(models|efforts)\.([^.]+)\z/)
|
|
153
|
+
return nil unless match
|
|
154
|
+
|
|
155
|
+
section, agent = match.captures
|
|
156
|
+
if section == "models"
|
|
157
|
+
AgentModels.models_section(config, harness: harness)[agent]
|
|
158
|
+
else
|
|
159
|
+
AgentModels.efforts_section(config, harness)[agent]
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
harness = canonical_harness(harness)
|
|
164
|
+
if harness
|
|
165
|
+
value = harness_agent_value(project_config, key, harness)
|
|
166
|
+
value = harness_agent_value(global_config, key, harness) if value.nil?
|
|
167
|
+
else
|
|
168
|
+
value = dig_key(project_config, key)
|
|
169
|
+
value = dig_key(global_config, key) if value.nil?
|
|
170
|
+
end
|
|
134
171
|
value = explicit_default if value.nil? && explicit_default
|
|
172
|
+
if value.nil? && harness && (match = key.match(/\Aagents\.(models|efforts)\.([^.]+)\z/))
|
|
173
|
+
value = if match[1] == "models"
|
|
174
|
+
AgentModels.shipped_model_for(match[2], harness: harness)
|
|
175
|
+
else
|
|
176
|
+
AgentModels::DEFAULT_EFFORT
|
|
177
|
+
end
|
|
178
|
+
end
|
|
135
179
|
value = dig_key(DEFAULTS, key) if value.nil?
|
|
136
180
|
|
|
137
181
|
puts format_value(value)
|