@zalom/plastic 1.14.1 → 2.0.0-alpha.2
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 +9 -49
- package/hooks/record +4 -0
- package/hooks/savepoint +5 -5
- package/package.json +3 -2
- package/scripts/agent-report +8 -9
- package/scripts/append-ledger +208 -0
- package/scripts/codex-hook +78 -90
- package/scripts/dashboard.rb +4 -4
- package/scripts/day-summary +53 -0
- package/scripts/doctor.rb +106 -43
- package/scripts/end-intent +77 -76
- package/scripts/exec-worktree +11 -20
- package/scripts/file-session-intent +61 -0
- package/scripts/hook-capture +242 -0
- package/scripts/hook-close +36 -0
- package/scripts/hook-record +149 -0
- package/scripts/hook-savepoint +45 -0
- package/scripts/hook-session-start +106 -12
- package/scripts/install.rb +8 -6
- package/scripts/lib/agent_models.rb +1 -7
- package/scripts/lib/arm.rb +283 -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 +206 -0
- package/scripts/lib/doctor_core.rb +65 -88
- package/scripts/lib/doctor_session_ledger.rb +106 -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 +44 -107
- package/scripts/lib/insights.rb +3 -4
- package/scripts/lib/installer_core.rb +104 -40
- package/scripts/lib/lock.rb +6 -26
- package/scripts/lib/outcome_guard.rb +7 -5
- package/scripts/lib/project_validator.rb +30 -1
- 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 +320 -0
- package/scripts/lib/scaffold_intent.rb +13 -179
- package/scripts/lib/session_backfill.rb +184 -0
- package/scripts/lib/session_close.rb +104 -0
- package/scripts/lib/session_git.rb +469 -0
- package/scripts/lib/session_ledger.rb +497 -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 +94 -41
- package/scripts/promote-session-item +150 -0
- package/scripts/read-config +3 -0
- package/scripts/rebuild-graph +1 -1
- package/scripts/roadmap-next +2 -2
- package/scripts/rollback.rb +48 -3
- package/scripts/scaffold-intent +37 -63
- package/scripts/session-commit +116 -0
- package/scripts/spawn-preamble +5 -5
- 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 +218 -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 +8 -8
- 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 +47 -55
- 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 +132 -89
- package/skills/intent-continuing/references/boarding-matrix.md +34 -0
- package/skills/intent-continuing/references/context-management.md +2 -2
- package/skills/intent-creating/SKILL.md +2 -2
- package/skills/intent-creating/references/lifecycle.md +1 -1
- package/skills/intent-ending/SKILL.md +28 -37
- package/skills/intent-ending/evals/evals.json +4 -4
- package/skills/intent-executing/SKILL.md +12 -49
- 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/project.yml +7 -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
|
@@ -9,6 +9,7 @@ require "time"
|
|
|
9
9
|
require_relative "hook_registry"
|
|
10
10
|
require_relative "agent_models"
|
|
11
11
|
require_relative "harness_text"
|
|
12
|
+
require_relative "compact_instructions"
|
|
12
13
|
|
|
13
14
|
# Shared installer machinery, instantiable with injected package root / store / agent
|
|
14
15
|
# map so the verb scripts (install/update/uninstall/rollback) and their tests can run
|
|
@@ -33,6 +34,15 @@ class InstallerCore
|
|
|
33
34
|
# Regex matching exactly one managed section (BEGIN line .. END line), non-greedy.
|
|
34
35
|
CODEX_SECTION_RE = /^<!-- BEGIN PLASTIC INTEGRATION.*?-->\n.*?\n<!-- END PLASTIC INTEGRATION -->\n?/m
|
|
35
36
|
|
|
37
|
+
# Claude CLAUDE.md marked-section markers (intent 312). A pair of its own, not the
|
|
38
|
+
# Codex literals: the two managed files can be one file (a user who symlinks
|
|
39
|
+
# ~/.claude/CLAUDE.md at ~/.codex/AGENTS.md, or the reverse), and a shared literal
|
|
40
|
+
# would let one body silently replace the other and an uninstall of one strip both.
|
|
41
|
+
# doctor_core.rb matches these literals structurally, so keep the two in sync by hand.
|
|
42
|
+
CLAUDE_SECTION_BEGIN_PREFIX = "<!-- BEGIN PLASTIC COMPACT"
|
|
43
|
+
CLAUDE_SECTION_END = "<!-- END PLASTIC COMPACT -->"
|
|
44
|
+
CLAUDE_SECTION_RE = /^<!-- BEGIN PLASTIC COMPACT.*?-->\n.*?\n<!-- END PLASTIC COMPACT -->\n?/m
|
|
45
|
+
|
|
36
46
|
# Curated essentials plus a pointer to ~/.plastic/PLASTIC.md and the plastic-conventions
|
|
37
47
|
# skill, injected into ~/.codex/AGENTS.md. Not a slice of PLASTIC.md itself: AGENTS.md is
|
|
38
48
|
# a shared file Codex merges from multiple sources, so this block stays a small,
|
|
@@ -174,13 +184,6 @@ class InstallerCore
|
|
|
174
184
|
keys.uniq
|
|
175
185
|
end
|
|
176
186
|
|
|
177
|
-
def channel_from(argv, default: "latest")
|
|
178
|
-
return "alpha" if argv.include?("--alpha")
|
|
179
|
-
return "beta" if argv.include?("--beta")
|
|
180
|
-
return "latest" if argv.include?("--latest")
|
|
181
|
-
default
|
|
182
|
-
end
|
|
183
|
-
|
|
184
187
|
def prompt_agents(input: $stdin)
|
|
185
188
|
return ["claude"] unless input.tty?
|
|
186
189
|
|
|
@@ -329,25 +332,21 @@ class InstallerCore
|
|
|
329
332
|
"scripts/write-config" => "scripts/write-config",
|
|
330
333
|
"scripts/select-update-target" => "scripts/select-update-target",
|
|
331
334
|
"scripts/hook-session-start" => "scripts/hook-session-start",
|
|
332
|
-
"scripts/hook-
|
|
333
|
-
"scripts/hook-
|
|
334
|
-
"scripts/hook-
|
|
335
|
-
"scripts/hook-savepoint-pre" => "scripts/hook-savepoint-pre",
|
|
336
|
-
"scripts/hook-power-tools" => "scripts/hook-power-tools",
|
|
337
|
-
"scripts/lib/qmd_hook.rb" => "scripts/lib/qmd_hook.rb",
|
|
335
|
+
"scripts/hook-capture" => "scripts/hook-capture",
|
|
336
|
+
"scripts/hook-record" => "scripts/hook-record",
|
|
337
|
+
"scripts/hook-close" => "scripts/hook-close",
|
|
338
338
|
"scripts/lib/power_tools.rb" => "scripts/lib/power_tools.rb",
|
|
339
339
|
"scripts/lib/ruby_probe.rb" => "scripts/lib/ruby_probe.rb",
|
|
340
340
|
"scripts/lib/agent_models.rb" => "scripts/lib/agent_models.rb",
|
|
341
341
|
"scripts/lib/config_asks.rb" => "scripts/lib/config_asks.rb",
|
|
342
342
|
"scripts/lib/release_guard.rb" => "scripts/lib/release_guard.rb",
|
|
343
|
-
"scripts/hook-code-gate" => "scripts/hook-code-gate",
|
|
344
|
-
"scripts/hook-lock-gate" => "scripts/hook-lock-gate",
|
|
345
|
-
"scripts/hook-bash-gate" => "scripts/hook-bash-gate",
|
|
346
|
-
"scripts/hook-auto-arm" => "scripts/hook-auto-arm",
|
|
347
343
|
"scripts/lib/bridge.rb" => "scripts/lib/bridge.rb",
|
|
344
|
+
"scripts/lib/savepoint.rb" => "scripts/lib/savepoint.rb",
|
|
345
|
+
"scripts/lib/arm.rb" => "scripts/lib/arm.rb",
|
|
348
346
|
"scripts/lib/lock.rb" => "scripts/lib/lock.rb",
|
|
349
347
|
"scripts/plastic-lock" => "scripts/plastic-lock",
|
|
350
348
|
"scripts/lib/hook_registry.rb" => "scripts/lib/hook_registry.rb",
|
|
349
|
+
"scripts/lib/compact_instructions.rb" => "scripts/lib/compact_instructions.rb",
|
|
351
350
|
"scripts/agent-report" => "scripts/agent-report",
|
|
352
351
|
"scripts/lib/insights.rb" => "scripts/lib/insights.rb",
|
|
353
352
|
"scripts/insight-append" => "scripts/insight-append",
|
|
@@ -380,13 +379,7 @@ class InstallerCore
|
|
|
380
379
|
"scripts/validate-intent" => "scripts/validate-intent",
|
|
381
380
|
"scripts/new-intent" => "scripts/new-intent",
|
|
382
381
|
"scripts/end-intent" => "scripts/end-intent",
|
|
383
|
-
"scripts/hook-create-gate" => "scripts/hook-create-gate",
|
|
384
|
-
"scripts/hook-links-gate" => "scripts/hook-links-gate",
|
|
385
|
-
"scripts/lib/links_gate.rb" => "scripts/lib/links_gate.rb",
|
|
386
|
-
"scripts/lib/edit_gates.rb" => "scripts/lib/edit_gates.rb",
|
|
387
|
-
"scripts/hook-edit-gates" => "scripts/hook-edit-gates",
|
|
388
382
|
"scripts/lib/apply_patch_envelope.rb" => "scripts/lib/apply_patch_envelope.rb",
|
|
389
|
-
"scripts/lib/codex_edit_gates.rb" => "scripts/lib/codex_edit_gates.rb",
|
|
390
383
|
"scripts/lib/harness_text.rb" => "scripts/lib/harness_text.rb",
|
|
391
384
|
"scripts/codex-hook" => "scripts/codex-hook",
|
|
392
385
|
"scripts/spawn-preamble" => "scripts/spawn-preamble",
|
|
@@ -401,24 +394,36 @@ class InstallerCore
|
|
|
401
394
|
"scripts/uninstall.rb" => "scripts/uninstall.rb",
|
|
402
395
|
"scripts/rollback.rb" => "scripts/rollback.rb",
|
|
403
396
|
"scripts/lib/outcome_guard.rb" => "scripts/lib/outcome_guard.rb",
|
|
404
|
-
"scripts/lib/spec_header.rb" => "scripts/lib/spec_header.rb",
|
|
405
397
|
"scripts/lib/scaffold_intent.rb" => "scripts/lib/scaffold_intent.rb",
|
|
398
|
+
"scripts/lib/backfill_intent.rb" => "scripts/lib/backfill_intent.rb",
|
|
406
399
|
"scripts/scaffold-intent" => "scripts/scaffold-intent",
|
|
407
400
|
"scripts/lib/verify_intent.rb" => "scripts/lib/verify_intent.rb",
|
|
408
401
|
"scripts/verify-intent" => "scripts/verify-intent",
|
|
409
|
-
"scripts/lib/start_intent.rb" => "scripts/lib/start_intent.rb",
|
|
410
|
-
"scripts/start-intent" => "scripts/start-intent",
|
|
411
402
|
"scripts/lib/exec_worktree.rb" => "scripts/lib/exec_worktree.rb",
|
|
412
403
|
"scripts/exec-worktree" => "scripts/exec-worktree",
|
|
413
404
|
"scripts/doctor.rb" => "scripts/doctor.rb",
|
|
414
405
|
"scripts/lib/doctor_core.rb" => "scripts/lib/doctor_core.rb",
|
|
415
406
|
"scripts/lib/rule_catalog.rb" => "scripts/lib/rule_catalog.rb",
|
|
416
407
|
"scripts/lib/doctor_exclusions.rb" => "scripts/lib/doctor_exclusions.rb",
|
|
408
|
+
"scripts/lib/doctor_session_ledger.rb" => "scripts/lib/doctor_session_ledger.rb",
|
|
417
409
|
"scripts/dashboard.rb" => "scripts/dashboard.rb",
|
|
418
410
|
"scripts/skill-lint" => "scripts/skill-lint",
|
|
419
411
|
"scripts/lib/skill_lint.rb" => "scripts/lib/skill_lint.rb",
|
|
420
412
|
"scripts/feedback-report" => "scripts/feedback-report",
|
|
421
413
|
"scripts/lib/feedback_report.rb" => "scripts/lib/feedback_report.rb",
|
|
414
|
+
"scripts/append-ledger" => "scripts/append-ledger",
|
|
415
|
+
"scripts/lib/session_ledger.rb" => "scripts/lib/session_ledger.rb",
|
|
416
|
+
"scripts/session-commit" => "scripts/session-commit",
|
|
417
|
+
"scripts/file-session-intent" => "scripts/file-session-intent",
|
|
418
|
+
"scripts/promote-session-item" => "scripts/promote-session-item",
|
|
419
|
+
"scripts/lib/session_backfill.rb" => "scripts/lib/session_backfill.rb",
|
|
420
|
+
"scripts/lib/session_close.rb" => "scripts/lib/session_close.rb",
|
|
421
|
+
"scripts/lib/session_git.rb" => "scripts/lib/session_git.rb",
|
|
422
|
+
"scripts/lib/handoff.rb" => "scripts/lib/handoff.rb",
|
|
423
|
+
"scripts/lib/day_summary.rb" => "scripts/lib/day_summary.rb",
|
|
424
|
+
"scripts/write-handoff" => "scripts/write-handoff",
|
|
425
|
+
"scripts/day-summary" => "scripts/day-summary",
|
|
426
|
+
"scripts/hook-savepoint" => "scripts/hook-savepoint",
|
|
422
427
|
}
|
|
423
428
|
end
|
|
424
429
|
|
|
@@ -432,6 +437,8 @@ class InstallerCore
|
|
|
432
437
|
version: 3
|
|
433
438
|
execution_mode: subagent-driven
|
|
434
439
|
stale_threshold_days: 3
|
|
440
|
+
context_offer_tokens: 350000
|
|
441
|
+
context_insist_tokens: 500000
|
|
435
442
|
hash_length: 6
|
|
436
443
|
hash_algorithm: sha256-base36
|
|
437
444
|
max_slug_words: 5
|
|
@@ -718,7 +725,7 @@ class InstallerCore
|
|
|
718
725
|
end
|
|
719
726
|
|
|
720
727
|
# `install_skills_flat` relocates any top-level underscore-prefixed markdown
|
|
721
|
-
# fragment (today: `
|
|
728
|
+
# fragment (today: `_decision-tables.md`) out of the
|
|
722
729
|
# per-agent skills tree and into `plastic_home` directly, then lists them in
|
|
723
730
|
# `installed`, so they are manifest-tracked and eligible for prune. But
|
|
724
731
|
# `install_for_agent`'s prune roots are `[config[:dir], config[:home_dir]]`,
|
|
@@ -790,6 +797,11 @@ class InstallerCore
|
|
|
790
797
|
choice = statusline_choice(settings_path, argv: argv, input: input, reinstall: reinstall)
|
|
791
798
|
merge_claude_hooks(settings_path, choice: choice)
|
|
792
799
|
|
|
800
|
+
# Instruction injection (intent 312): the compact-instructions block into
|
|
801
|
+
# ~/.claude/CLAUDE.md. A partial-ownership user file, so it is NOT manifest-tracked
|
|
802
|
+
# (stripped surgically on uninstall), the same treatment ~/.codex/AGENTS.md gets.
|
|
803
|
+
inject_claude_compact_md(File.join(config[:dir], "CLAUDE.md"))
|
|
804
|
+
|
|
793
805
|
# Write manifest
|
|
794
806
|
manifest_path = File.join(plastic_dir, "manifest.json")
|
|
795
807
|
write_manifest(installed, manifest_path)
|
|
@@ -1012,7 +1024,7 @@ class InstallerCore
|
|
|
1012
1024
|
|
|
1013
1025
|
# Copy each skills/<name>/ to <skills_root>/plastic-<name>/ (flat, namespaced by
|
|
1014
1026
|
# directory name -- the only personal-skill namespacing Claude Code supports).
|
|
1015
|
-
# Any top-level underscore-prefixed markdown fragment (e.g. `
|
|
1027
|
+
# Any top-level underscore-prefixed markdown fragment (e.g. `_decision-tables.md`,
|
|
1016
1028
|
# `_decision-tables.md`) is a shared non-skill fragment and relocates to ~/.plastic/
|
|
1017
1029
|
# instead, so every skill can read it from one shared location. `exclude` skips
|
|
1018
1030
|
# named top-level skill directories entirely (intent 185: the agent-advisor skill
|
|
@@ -1273,8 +1285,8 @@ class InstallerCore
|
|
|
1273
1285
|
plastic_hooks.each do |event, group|
|
|
1274
1286
|
hooks[event] ||= []
|
|
1275
1287
|
|
|
1276
|
-
# An event may map to a LIST of plastic groups (
|
|
1277
|
-
#
|
|
1288
|
+
# An event may map to a LIST of plastic groups (none does since the edit-path
|
|
1289
|
+
# gates left in 2.0, intent 302; the shape stays). The purge pass above already removed all
|
|
1278
1290
|
# prior plastic groups, so appending each desired group fresh is idempotent
|
|
1279
1291
|
# across re-runs and never collapses two matchers into one group.
|
|
1280
1292
|
groups = group.is_a?(Array) ? group : [group]
|
|
@@ -1355,14 +1367,28 @@ class InstallerCore
|
|
|
1355
1367
|
raise e
|
|
1356
1368
|
end
|
|
1357
1369
|
|
|
1358
|
-
|
|
1370
|
+
# A managed instruction file may be a symlink into a dotfiles repo. write_text_atomic
|
|
1371
|
+
# renames a temp file over its argument, which would replace the link with a regular
|
|
1372
|
+
# file and silently detach it, so every read and write resolves the link first and the
|
|
1373
|
+
# change lands on its target (intent 312).
|
|
1374
|
+
def resolve_managed_path(path)
|
|
1375
|
+
File.symlink?(path) ? File.realpath(path) : path
|
|
1376
|
+
rescue Errno::ENOENT
|
|
1377
|
+
path
|
|
1378
|
+
end
|
|
1379
|
+
|
|
1380
|
+
def marked_section(body: CODEX_AGENTS_MD_BODY, begin_prefix: CODEX_SECTION_BEGIN_PREFIX,
|
|
1381
|
+
end_marker: CODEX_SECTION_END)
|
|
1359
1382
|
hash = Digest::SHA256.hexdigest(body)[0, 12]
|
|
1360
|
-
"#{
|
|
1383
|
+
"#{begin_prefix} hash:#{hash} -->\n#{body.strip}\n#{end_marker}\n"
|
|
1361
1384
|
end
|
|
1362
1385
|
|
|
1363
1386
|
# Returns :created / :appended / :replaced / :refused. Never raises on a normal user file.
|
|
1364
|
-
def
|
|
1365
|
-
|
|
1387
|
+
def inject_marked_section(path, body: CODEX_AGENTS_MD_BODY,
|
|
1388
|
+
begin_prefix: CODEX_SECTION_BEGIN_PREFIX,
|
|
1389
|
+
end_marker: CODEX_SECTION_END, section_re: CODEX_SECTION_RE)
|
|
1390
|
+
section = marked_section(body: body, begin_prefix: begin_prefix, end_marker: end_marker)
|
|
1391
|
+
path = resolve_managed_path(path)
|
|
1366
1392
|
|
|
1367
1393
|
unless File.exist?(path)
|
|
1368
1394
|
FileUtils.mkdir_p(File.dirname(path))
|
|
@@ -1371,14 +1397,14 @@ class InstallerCore
|
|
|
1371
1397
|
end
|
|
1372
1398
|
|
|
1373
1399
|
content = File.read(path)
|
|
1374
|
-
has_begin = content.include?(
|
|
1375
|
-
has_end = content.include?(
|
|
1400
|
+
has_begin = content.include?(begin_prefix)
|
|
1401
|
+
has_end = content.include?(end_marker)
|
|
1376
1402
|
|
|
1377
1403
|
# 22a safety rule: never write if the existing section cannot be parsed.
|
|
1378
1404
|
return :refused if has_begin && !has_end
|
|
1379
1405
|
|
|
1380
1406
|
if has_begin
|
|
1381
|
-
write_text_atomic(path, content.sub(
|
|
1407
|
+
write_text_atomic(path, content.sub(section_re, section))
|
|
1382
1408
|
:replaced
|
|
1383
1409
|
else
|
|
1384
1410
|
base = content.end_with?("\n") ? content : content + "\n"
|
|
@@ -1387,18 +1413,41 @@ class InstallerCore
|
|
|
1387
1413
|
end
|
|
1388
1414
|
end
|
|
1389
1415
|
|
|
1416
|
+
# --- The two blocks Plastic ships, each with its own marker pair ---
|
|
1417
|
+
|
|
1418
|
+
def codex_section(body: CODEX_AGENTS_MD_BODY)
|
|
1419
|
+
marked_section(body: body)
|
|
1420
|
+
end
|
|
1421
|
+
|
|
1422
|
+
def inject_codex_agents_md(path, body: CODEX_AGENTS_MD_BODY)
|
|
1423
|
+
inject_marked_section(path, body: body)
|
|
1424
|
+
end
|
|
1425
|
+
|
|
1426
|
+
# The compact-instructions block for ~/.claude/CLAUDE.md (intent 312).
|
|
1427
|
+
def claude_compact_section(body: CompactInstructions::BODY)
|
|
1428
|
+
marked_section(body: body, begin_prefix: CLAUDE_SECTION_BEGIN_PREFIX,
|
|
1429
|
+
end_marker: CLAUDE_SECTION_END)
|
|
1430
|
+
end
|
|
1431
|
+
|
|
1432
|
+
def inject_claude_compact_md(path, body: CompactInstructions::BODY)
|
|
1433
|
+
inject_marked_section(path, body: body, begin_prefix: CLAUDE_SECTION_BEGIN_PREFIX,
|
|
1434
|
+
end_marker: CLAUDE_SECTION_END, section_re: CLAUDE_SECTION_RE)
|
|
1435
|
+
end
|
|
1436
|
+
|
|
1390
1437
|
# Remove exactly Plastic's managed section from a user-owned AGENTS.md. Preserve all other
|
|
1391
1438
|
# content. Delete the file only if Plastic created it and nothing else remains. Returns the
|
|
1392
1439
|
# path when it acted, nil on no-op. Mirrors remove_claude_hooks: dedicated surgical strip,
|
|
1393
1440
|
# never the manifest whole-file-delete path.
|
|
1394
|
-
def
|
|
1441
|
+
def strip_marked_section(path, begin_prefix: CODEX_SECTION_BEGIN_PREFIX,
|
|
1442
|
+
section_re: CODEX_SECTION_RE)
|
|
1443
|
+
path = resolve_managed_path(path)
|
|
1395
1444
|
return nil unless File.exist?(path)
|
|
1396
1445
|
content = File.read(path)
|
|
1397
|
-
return nil unless content.include?(
|
|
1446
|
+
return nil unless content.include?(begin_prefix)
|
|
1398
1447
|
|
|
1399
1448
|
# Remove the section plus the single separator newline the append introduced, so a
|
|
1400
1449
|
# standard user file round-trips byte-identical.
|
|
1401
|
-
stripped = content.sub(/\n?#{
|
|
1450
|
+
stripped = content.sub(/\n?#{section_re}/, "")
|
|
1402
1451
|
|
|
1403
1452
|
if stripped.strip.empty?
|
|
1404
1453
|
File.delete(path) # Plastic-created file: nothing else left
|
|
@@ -1409,6 +1458,15 @@ class InstallerCore
|
|
|
1409
1458
|
path
|
|
1410
1459
|
end
|
|
1411
1460
|
|
|
1461
|
+
def strip_codex_section(path)
|
|
1462
|
+
strip_marked_section(path)
|
|
1463
|
+
end
|
|
1464
|
+
|
|
1465
|
+
def strip_claude_compact_section(path)
|
|
1466
|
+
strip_marked_section(path, begin_prefix: CLAUDE_SECTION_BEGIN_PREFIX,
|
|
1467
|
+
section_re: CLAUDE_SECTION_RE)
|
|
1468
|
+
end
|
|
1469
|
+
|
|
1412
1470
|
# --- Uninstall ---
|
|
1413
1471
|
|
|
1414
1472
|
def handle_uninstall(uninstall_agents)
|
|
@@ -1435,6 +1493,7 @@ class InstallerCore
|
|
|
1435
1493
|
puts " ls ~/.claude/skills | grep '^plastic-' # → no output"
|
|
1436
1494
|
puts " ls ~/.claude/hooks | grep '^plastic-' # → no output"
|
|
1437
1495
|
puts " grep -c plastic ~/.claude/settings.json # → only hook refs gone"
|
|
1496
|
+
puts " grep 'PLASTIC COMPACT' ~/.claude/CLAUDE.md # → no output"
|
|
1438
1497
|
puts "\n To also delete your intent store: rm -rf #{tilde(plastic_home)}\n\n"
|
|
1439
1498
|
end
|
|
1440
1499
|
|
|
@@ -1481,6 +1540,11 @@ class InstallerCore
|
|
|
1481
1540
|
settings_path = File.join(config[:dir], "settings.json")
|
|
1482
1541
|
remove_claude_hooks(settings_path) if File.exist?(settings_path)
|
|
1483
1542
|
removed.concat(migrate_legacy_plugin(config[:dir]))
|
|
1543
|
+
|
|
1544
|
+
# The compact-instructions block in the user-owned CLAUDE.md (intent 312): a
|
|
1545
|
+
# surgical strip, never the manifest whole-file-delete path above.
|
|
1546
|
+
stripped = strip_claude_compact_section(File.join(config[:dir], "CLAUDE.md"))
|
|
1547
|
+
removed << stripped if stripped
|
|
1484
1548
|
end
|
|
1485
1549
|
|
|
1486
1550
|
# Codex: surgically strip Plastic's marked section from the user-owned AGENTS.md
|
package/scripts/lib/lock.rb
CHANGED
|
@@ -49,11 +49,11 @@ module Lock
|
|
|
49
49
|
DELEGATE_ACTIVITY_LIMIT = 20
|
|
50
50
|
DELEGATE_STATUSES = %w[active finished failed].freeze
|
|
51
51
|
|
|
52
|
-
# Lease TTL. Heartbeats fire from the
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
52
|
+
# Lease TTL. Heartbeats fire from the PostToolUse record hook on every write
|
|
53
|
+
# inside the intent dir (intent 302), so a delivering session refreshes
|
|
54
|
+
# constantly; 30 minutes tolerates long read-only stretches without opening
|
|
55
|
+
# a takeover window mid-delivery. Reclaim is explicit either way (takeover),
|
|
56
|
+
# so the TTL only bounds WHEN takeover is allowed.
|
|
57
57
|
TTL_SECONDS = 1800
|
|
58
58
|
|
|
59
59
|
# The write guard is a mutex, not a lock in the Plastic sense: it carries no
|
|
@@ -122,7 +122,7 @@ module Lock
|
|
|
122
122
|
Array(data["delegates"]).map(&:to_s).include?(session.to_s)
|
|
123
123
|
end
|
|
124
124
|
|
|
125
|
-
# The one question
|
|
125
|
+
# The one question the write path asks: does session hold this intent's lock?
|
|
126
126
|
# Owner/delegate on an EXISTING lock counts even when stale (a stale lock is
|
|
127
127
|
# still theirs until an explicit takeover replaces it); freshness only
|
|
128
128
|
# guards AGAINST other sessions.
|
|
@@ -676,24 +676,4 @@ module Claim
|
|
|
676
676
|
end
|
|
677
677
|
end
|
|
678
678
|
|
|
679
|
-
# Second, independent write gate at the artifact grain (intent 111 D7). Returns a
|
|
680
|
-
# deny reason String to BLOCK, or nil to ALLOW. Composes UNDER the delivery-lock
|
|
681
|
-
# gate: only reached after the session already holds the intent's delivery lock.
|
|
682
|
-
# ENGAGES only when a claim file exists (dormant otherwise, so single-owner flows
|
|
683
|
-
# and the existing suite stay green, AC7). Fails open on stale/corrupt via
|
|
684
|
-
# fail_open?, the named contract.
|
|
685
|
-
def claim_gate_reason(intent_dir, artifact, session:, ttl: Lock::TTL_SECONDS, now: Time.now,
|
|
686
|
-
harness: :claude)
|
|
687
|
-
return nil if Lock.blank?(artifact)
|
|
688
|
-
return nil unless File.exist?(path(intent_dir, artifact)) # dormant: no claim
|
|
689
|
-
return nil if holds_claim?(intent_dir, artifact, session: session) # you hold it
|
|
690
|
-
return nil if fail_open?(intent_dir, artifact, ttl: ttl, now: now) # stale/corrupt: yield
|
|
691
|
-
data = read(intent_dir, artifact)
|
|
692
|
-
holder = data && data["owner_session"]
|
|
693
|
-
since = data && data["acquired_at"]
|
|
694
|
-
"artifact #{artifact} is claimed by #{holder} since #{since}; another writer holds " \
|
|
695
|
-
"it. Back off or run #{Lock.skill_ref('plastic-doctor', harness: harness)} check the " \
|
|
696
|
-
"lock status. If you are a distinct delegate, the owner must register you: " \
|
|
697
|
-
"plastic-lock delegate --intent-dir #{intent_dir} --session <your-session-id>"
|
|
698
|
-
end
|
|
699
679
|
end
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
# encoding: UTF-8
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require_relative "
|
|
4
|
+
require_relative "savepoint"
|
|
5
5
|
require_relative "intent_validator"
|
|
6
6
|
|
|
7
7
|
# OutcomeGuard - the single definition of "is outcome.md real and disposition-matched for
|
|
8
8
|
# THIS close" (intent 222). Extracted verbatim from scripts/end-intent's own
|
|
9
|
-
# outcome_guard_reason (intent 161/188) so
|
|
10
|
-
#
|
|
11
|
-
# as a valid outcome.md.
|
|
9
|
+
# outcome_guard_reason (intent 161/188) so its two callers, doctor's per-intent check
|
|
10
|
+
# (intent_lifecycle_artifacts) and end-intent's post-backfill self-check, can never drift on
|
|
11
|
+
# what counts as a valid outcome.md. Since intent 308 nothing blocks on it: end-intent
|
|
12
|
+
# backfills a missing or placeholder outcome.md first, prints a non-nil reason, and proceeds.
|
|
13
|
+
# Pure, read-only: authors nothing regardless of outcome.
|
|
12
14
|
module OutcomeGuard
|
|
13
15
|
module_function
|
|
14
16
|
|
|
@@ -23,7 +25,7 @@ module OutcomeGuard
|
|
|
23
25
|
|
|
24
26
|
content = File.read(path)
|
|
25
27
|
first_line = content.each_line.first.to_s.chomp
|
|
26
|
-
if first_line ==
|
|
28
|
+
if first_line == Savepoint::PLACEHOLDER_SENTINEL
|
|
27
29
|
return "outcome.md is still the scaffold placeholder (first line is the sentinel)"
|
|
28
30
|
end
|
|
29
31
|
|
|
@@ -24,6 +24,15 @@ require "yaml"
|
|
|
24
24
|
module ProjectValidator
|
|
25
25
|
module_function
|
|
26
26
|
|
|
27
|
+
# The two enumerated flow: knobs (intent 300, spec D2). Kept as a plain
|
|
28
|
+
# literal here rather than a require on SessionGit: this validator only
|
|
29
|
+
# reports a bad value in `errors` (project.yml is still "accepted", spec
|
|
30
|
+
# D2's matrix row), it never blocks spawn completeness, since a bad value
|
|
31
|
+
# degrades gracefully at commit time (SessionGit falls back with a Note)
|
|
32
|
+
# rather than blocking the spawn.
|
|
33
|
+
FLOW_MODES = %w[direct pull_request].freeze
|
|
34
|
+
FLOW_WORKSPACES = %w[checkout worktree].freeze
|
|
35
|
+
|
|
27
36
|
def validate(slug, plastic_home: File.join(Dir.home, ".plastic"))
|
|
28
37
|
missing = []
|
|
29
38
|
errors = []
|
|
@@ -53,7 +62,9 @@ module ProjectValidator
|
|
|
53
62
|
rescue StandardError
|
|
54
63
|
nil
|
|
55
64
|
end
|
|
56
|
-
|
|
65
|
+
if parsed.is_a?(Hash)
|
|
66
|
+
validate_flow_block(parsed, errors)
|
|
67
|
+
else
|
|
57
68
|
missing << "project.yml (valid YAML)"
|
|
58
69
|
errors << "project.yml exists at #{project_yml_path} but does not parse as YAML"
|
|
59
70
|
end
|
|
@@ -88,6 +99,24 @@ module ProjectValidator
|
|
|
88
99
|
{ ok: missing.empty?, missing: missing, errors: errors }
|
|
89
100
|
end
|
|
90
101
|
|
|
102
|
+
# Non-blocking: an unknown `mode` or `workspace` value in project.yml's
|
|
103
|
+
# `flow:` block, if present, is reported in `errors` but never added to
|
|
104
|
+
# `missing`, so it never flips `ok`.
|
|
105
|
+
def validate_flow_block(parsed, errors)
|
|
106
|
+
flow = parsed["flow"]
|
|
107
|
+
return unless flow.is_a?(Hash)
|
|
108
|
+
|
|
109
|
+
validate_flow_knob(flow, "mode", FLOW_MODES, errors)
|
|
110
|
+
validate_flow_knob(flow, "workspace", FLOW_WORKSPACES, errors)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def validate_flow_knob(flow, key, allowed, errors)
|
|
114
|
+
value = flow[key]
|
|
115
|
+
return if value.nil? || allowed.include?(value.to_s)
|
|
116
|
+
|
|
117
|
+
errors << "project.yml flow.#{key} is #{value.inspect}, must be one of #{allowed.join(", ")}"
|
|
118
|
+
end
|
|
119
|
+
|
|
91
120
|
# Invariant 1: registered in projects.yml with a 'path'. Returns the
|
|
92
121
|
# project's entry Hash, or nil when unregistered or the entry has no path.
|
|
93
122
|
def registration_for(slug, plastic_home)
|
|
@@ -20,10 +20,10 @@ class FileOrderRanker
|
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
# RoadmapQueue - the one deterministic reader the auto loop and plastic-
|
|
23
|
+
# RoadmapQueue - the one deterministic reader the auto loop and plastic-intent-continuing
|
|
24
24
|
# both call (intent 148). Constructor-DI, hermetic: clock and paths injected, no eval, no ENV
|
|
25
25
|
# or global config seam. It does two things: liveness-ranks a tier's roadmaps/*.md files
|
|
26
|
-
# (porting plastic-
|
|
26
|
+
# (porting plastic-intent-continuing's read-time algorithm), and, within the winning
|
|
27
27
|
# roadmap, selects the frontier wave plus its dispatchable set (D-b), value-ordered by the
|
|
28
28
|
# injected ranker (default FileOrderRanker, the intent-173 swap seam). Every frontier token is
|
|
29
29
|
# reconciled against INDEX.md first, INDEX wins. Reads through the 134 ledger via the public
|
|
@@ -181,7 +181,7 @@ class RoadmapQueue
|
|
|
181
181
|
File.join(File.dirname(@roadmaps_dir), "INDEX.md")
|
|
182
182
|
end
|
|
183
183
|
|
|
184
|
-
# --- liveness ranking (ports plastic-
|
|
184
|
+
# --- liveness ranking (ports plastic-intent-continuing's read-time algorithm) -
|
|
185
185
|
|
|
186
186
|
def rank_candidates(parsed_list)
|
|
187
187
|
parsed_list.map do |c|
|
|
@@ -102,7 +102,7 @@ module RoadmapSavepoint
|
|
|
102
102
|
# entry has a `merged` line. Every timestamp comes from an on-disk source (the Log, or INDEX
|
|
103
103
|
# `## Completed`); an entry with no recoverable timestamp is not emitted (D4, never invented).
|
|
104
104
|
# Overwrites the ledger (the one operation allowed to rewrite it, matching
|
|
105
|
-
# `
|
|
105
|
+
# `Savepoint.rebuild_savepoint`). Returns the number of lines written.
|
|
106
106
|
def rebuild(roadmap_path)
|
|
107
107
|
text = File.read(roadmap_path)
|
|
108
108
|
log_lines = classify_log(section_body(text, "Log"))
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
# different axes so there is exactly one place to look up or register a rule name:
|
|
6
6
|
#
|
|
7
7
|
# EXCLUDABLE_CHECKS - a doctor check `name` a per-store doctor-exclusions file may name. A
|
|
8
|
-
# check name says WHICH DIAGNOSTIC FIRED.
|
|
9
|
-
# (
|
|
10
|
-
#
|
|
8
|
+
# check name says WHICH DIAGNOSTIC FIRED. Two keys: savepoint_operational (274, spec D3) and
|
|
9
|
+
# backfilled_complete (308). Most doctor checks have no exclusion mechanism at all; these two
|
|
10
|
+
# are the ones whose gap can be a known, accepted fact on an old or migrated intent.
|
|
11
11
|
#
|
|
12
12
|
# REVISION_RULES - the `[rule: <tag>]` vocabulary every revisions.md entry must carry
|
|
13
13
|
# (scripts/lib/revisions_writer.rb). A tag says WHY an intent's files were structurally
|
|
@@ -33,6 +33,9 @@ module RuleCatalog
|
|
|
33
33
|
"delivered|abandoned echo, on a terminal intent. Usually " \
|
|
34
34
|
"repairable via maintenance-run --tool rebuild-savepoint; " \
|
|
35
35
|
"excludable for the gaps 219 D6 forbids ever repairing.",
|
|
36
|
+
"backfilled_complete" => "spec.md or plan.md missing or still the placeholder, or no real " \
|
|
37
|
+
"action file, on a terminal intent. Repairable via scaffold-intent " \
|
|
38
|
+
"backfill; excludable for an old intent whose record was never kept.",
|
|
36
39
|
}.freeze
|
|
37
40
|
|
|
38
41
|
# Measured from live store data across all eight stores, 2026-08-24 (spec D1).
|