@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
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: plastic-spec-specialist
|
|
3
|
-
description: |
|
|
4
|
-
Use at the Why-to-How boundary in auto mode: consolidate the enriched Why into
|
|
5
|
-
spec.md from the spec template.
|
|
6
|
-
model: sonnet
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
You are the Plastic Spec Specialist. You own the Why-to-How boundary in the What->Why->How->Exec cycle.
|
|
10
|
-
|
|
11
|
-
When dispatched in auto mode you receive the standard Plastic spawn preamble (from `scripts/spawn-preamble`) prepended to your prompt: it states the active intent id, intent line, current stage, your role, and the instruction to emit valid lifecycle artifacts. Honor it as your live state; do not re-derive or contradict it.
|
|
12
|
-
|
|
13
|
-
## Your Responsibilities
|
|
14
|
-
|
|
15
|
-
1. **Consolidate the Why**: turn the enriched `## Context` and `### Decisions` into one spec
|
|
16
|
-
2. **Follow the template**: produce `spec.md` with all 8 sections in template order: Problem, Goals,
|
|
17
|
-
Non-Goals, Approach, Alternatives Considered, Decisions, Acceptance Criteria, Open Questions.
|
|
18
|
-
The shared fill method for each section is `skills/intent-speccing/references/per-section-fill-rules.md`;
|
|
19
|
-
read it when filling the template, it is the single owner of the method, do not fork it here.
|
|
20
|
-
3. **Stamp the tier**: write a `Tier: S|M|L` line as the TOP line of spec.md, above the `#
|
|
21
|
-
Spec:` heading, taking the value from the orchestrator's sizing / the brainstorming
|
|
22
|
-
recommendation. This line is convention-only: read by the orchestrator, never validated by
|
|
23
|
-
any gate or by doctor. It is an extra top line, not a template field, so no template changes.
|
|
24
|
-
4. **Accept collapsed sections at S/M**: a spec.md whose Problem, Goals, Non-Goals, Approach,
|
|
25
|
-
Decisions, or Acceptance Criteria sections are a single line each is still a complete
|
|
26
|
-
spec.md under the same-structure invariant; do not pad it. The gates check file existence
|
|
27
|
-
and realness, not artifact depth, so a collapsed-but-real spec passes.
|
|
28
|
-
5. **Make it the contract**: the spec is what the planner and executor build against
|
|
29
|
-
6. **Hand off**: leave a complete `spec.md` ready for the planner
|
|
30
|
-
|
|
31
|
-
## How You Work
|
|
32
|
-
|
|
33
|
-
1. Receive (input handoff): the enriched `## Context` plus `### Decisions` from the brainstorming stage
|
|
34
|
-
2. Read the spec template (`templates/spec.md`) and match its FORM exactly
|
|
35
|
-
3. Write `spec.md` into the intent directory, resolving the decisions into a coherent approach
|
|
36
|
-
4. Produce (output handoff): a complete `spec.md`
|
|
37
|
-
5. Report for gating (see `## Completion Report`); the enforcer checks the spec against its exit criteria before How begins
|
|
38
|
-
|
|
39
|
-
## Completion Report
|
|
40
|
-
|
|
41
|
-
END your turn with a structured completion report as your final message, per the spawn preamble's `REPORT_CONTRACT` and `skills/auto/references/agent-report-contract.md`. Do not finish silently. Carry the common envelope (role, intent id, stage, status, artifacts written, verification, checklist deltas, deviations, blockers, insights) plus the spec payload:
|
|
42
|
-
|
|
43
|
-
- The spec sections produced (Problem, Goals, Non-Goals, Approach, Decisions, Acceptance Criteria)
|
|
44
|
-
- How the recorded decisions resolved into the chosen approach
|
|
45
|
-
- The acceptance-criteria count, so the planner knows the surface to cover
|
|
46
|
-
- The stamped `Tier:` value
|
|
47
|
-
|
|
48
|
-
## Constraints
|
|
49
|
-
|
|
50
|
-
- You are dispatched by the plastic-enforcer and your deliverable is gated before How begins
|
|
51
|
-
- You write `spec.md` only; you do not write `plan.md`, `actions/`, or project code
|
|
52
|
-
- You write only intent-store files, never project code
|
|
53
|
-
- You do not re-open exploration; if decisions are missing, flag the gap rather than inventing scope
|
package/hooks/auto-arm
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
INPUT=$(cat)
|
|
3
|
-
MESSAGE=$(echo "$INPUT" | env -u RUBYOPT ruby -rjson -e 'puts JSON.parse(STDIN.read)["user_prompt"].to_s' 2>/dev/null || echo "")
|
|
4
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
5
|
-
env -u RUBYOPT ruby "$SCRIPT_DIR/../scripts/hook-auto-arm" "$MESSAGE"
|
package/hooks/bash-gate
DELETED
package/hooks/continue
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
INPUT=$(cat)
|
|
3
|
-
MESSAGE=$(echo "$INPUT" | env -u RUBYOPT ruby -rjson -e 'puts JSON.parse(STDIN.read)["user_prompt"]' 2>/dev/null || echo "")
|
|
4
|
-
|
|
5
|
-
if echo "$MESSAGE" | grep -iq '\bcontinue\b'; then
|
|
6
|
-
GLOBAL_INDEX="$HOME/.plastic/INDEX.md"
|
|
7
|
-
|
|
8
|
-
if [ ! -f "$GLOBAL_INDEX" ]; then
|
|
9
|
-
exit 0
|
|
10
|
-
fi
|
|
11
|
-
|
|
12
|
-
INDEX="$GLOBAL_INDEX"
|
|
13
|
-
STORE_ROOT="$HOME/.plastic"
|
|
14
|
-
MODE="global"
|
|
15
|
-
|
|
16
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
17
|
-
OUTPUT=$(env -u RUBYOPT ruby "$SCRIPT_DIR/../scripts/hook-continue" "$INDEX" "$STORE_ROOT" "$MODE" 2>/dev/null)
|
|
18
|
-
|
|
19
|
-
if [ -n "$OUTPUT" ]; then
|
|
20
|
-
echo "$OUTPUT"
|
|
21
|
-
else
|
|
22
|
-
cat <<'HOOKJSON'
|
|
23
|
-
{
|
|
24
|
-
"hookSpecificOutput": {
|
|
25
|
-
"hookEventName": "UserPromptSubmit",
|
|
26
|
-
"additionalContext": "PLASTIC CONTINUE: The user wants to resume previous work.\n\nRead ~/.plastic/INDEX.md and follow the plastic-continuing skill workflow."
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
HOOKJSON
|
|
30
|
-
fi
|
|
31
|
-
fi
|
package/hooks/edit-gates
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
INPUT=$(cat)
|
|
3
|
-
MESSAGE=$(echo "$INPUT" | env -u RUBYOPT ruby -rjson -e 'puts JSON.parse(STDIN.read)["user_prompt"].downcase' 2>/dev/null || echo "")
|
|
4
|
-
|
|
5
|
-
if [ ${#MESSAGE} -lt 10 ]; then
|
|
6
|
-
exit 0
|
|
7
|
-
fi
|
|
8
|
-
|
|
9
|
-
if echo "$MESSAGE" | grep -qx '\s*continue\s*'; then
|
|
10
|
-
exit 0
|
|
11
|
-
fi
|
|
12
|
-
|
|
13
|
-
GLOBAL_INDEX="$HOME/.plastic/INDEX.md"
|
|
14
|
-
LOCAL_INDEX=".plastic/INDEX.md"
|
|
15
|
-
|
|
16
|
-
if [ -f "$GLOBAL_INDEX" ]; then
|
|
17
|
-
STORE_ROOT="$HOME/.plastic"
|
|
18
|
-
elif [ -f "$LOCAL_INDEX" ]; then
|
|
19
|
-
STORE_ROOT=".plastic"
|
|
20
|
-
else
|
|
21
|
-
exit 0
|
|
22
|
-
fi
|
|
23
|
-
|
|
24
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
25
|
-
env -u RUBYOPT ruby "$SCRIPT_DIR/../scripts/hook-future-intent-check" "$STORE_ROOT" "$MESSAGE"
|
package/hooks/gate-check
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
INPUT=$(cat)
|
|
3
|
-
FILE_PATH=$(echo "$INPUT" | env -u RUBYOPT ruby -rjson -e 'data = JSON.parse(STDIN.read); puts data.dig("tool_params", "file_path") || data.dig("tool_input", "file_path") || ""' 2>/dev/null)
|
|
4
|
-
|
|
5
|
-
if [ -z "$FILE_PATH" ]; then
|
|
6
|
-
exit 0
|
|
7
|
-
fi
|
|
8
|
-
|
|
9
|
-
SESSION_ID=$(echo "$INPUT" | env -u RUBYOPT ruby -rjson -e 'data = JSON.parse(STDIN.read); puts data.dig("session_id") || ""' 2>/dev/null)
|
|
10
|
-
|
|
11
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
12
|
-
env -u RUBYOPT ruby "$SCRIPT_DIR/../scripts/hook-gate-check" "$FILE_PATH" "$SESSION_ID"
|
package/hooks/power-tools
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# power-tools mandate hook launcher (intents 66, 66b, 187, 246). No-op when there is no global store.
|
|
3
|
-
GLOBAL_INDEX="$HOME/.plastic/INDEX.md"
|
|
4
|
-
if [ ! -f "$GLOBAL_INDEX" ]; then
|
|
5
|
-
exit 0
|
|
6
|
-
fi
|
|
7
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
-
exec env -u RUBYOPT ruby "$SCRIPT_DIR/../scripts/hook-power-tools"
|
package/scripts/hook-auto-arm
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# encoding: UTF-8
|
|
3
|
-
# frozen_string_literal: true
|
|
4
|
-
|
|
5
|
-
# Usage: hook-auto-arm <message>
|
|
6
|
-
# UserPromptSubmit arming (intent 27). Two jobs, both additive context — never blocks:
|
|
7
|
-
# 1. Detect auto-trigger phrases -> steer the agent to invoke plastic-auto.
|
|
8
|
-
# 2. If auto mode is armed and the active intent hasn't reached How -> remind the agent
|
|
9
|
-
# that project-code edits are gate-blocked until plan.md + checklist.md exist.
|
|
10
|
-
# Outputs UserPromptSubmit hookSpecificOutput JSON, or exits silently.
|
|
11
|
-
|
|
12
|
-
require "json"
|
|
13
|
-
require_relative "lib/bridge"
|
|
14
|
-
|
|
15
|
-
message = (ARGV[0] || "").downcase
|
|
16
|
-
parts = []
|
|
17
|
-
|
|
18
|
-
# --- 1. Auto-trigger steering ---
|
|
19
|
-
AUTO_TRIGGERS = ["take it from here", "deliver this", "/plastic-auto", "auto mode"].freeze
|
|
20
|
-
# bare "auto" only as a standalone word to avoid matching "automatic", "automation", etc.
|
|
21
|
-
triggered = AUTO_TRIGGERS.any? { |p| message.include?(p) } || message.match?(/\bauto\b/)
|
|
22
|
-
if triggered
|
|
23
|
-
parts << "Auto-mode request detected. Invoke the plastic-auto skill to deliver the " \
|
|
24
|
-
"active intent autonomously — it arms the lifecycle gate so project code " \
|
|
25
|
-
"cannot be edited before plan.md + checklist.md exist."
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# --- 2. Standing gate reminder when armed and pre-How ---
|
|
29
|
-
bridge_data = Bridge.discover_bridge(session: ENV["CLAUDE_CODE_SESSION_ID"], cwd: Dir.pwd)
|
|
30
|
-
if bridge_data && bridge_data.dig("build", "auto") == true
|
|
31
|
-
intent = bridge_data["intent"] || {}
|
|
32
|
-
store = intent["store"]; dir = intent["dir"]
|
|
33
|
-
if store && dir
|
|
34
|
-
intent_dir = File.expand_path("#{store}/#{dir}")
|
|
35
|
-
reached_how = File.exist?("#{intent_dir}/plan.md") && File.exist?("#{intent_dir}/checklist.md")
|
|
36
|
-
unless reached_how
|
|
37
|
-
parts << "Auto mode armed on intent #{intent["id"]} — produce spec -> plan -> " \
|
|
38
|
-
"checklist before editing project code. Code edits are gate-blocked until then."
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
exit 0 if parts.empty?
|
|
44
|
-
|
|
45
|
-
payload = {
|
|
46
|
-
"hookSpecificOutput" => {
|
|
47
|
-
"hookEventName" => "UserPromptSubmit",
|
|
48
|
-
"additionalContext" => parts.join("\n")
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
puts JSON.generate(payload)
|
package/scripts/hook-bash-gate
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# encoding: UTF-8
|
|
3
|
-
# frozen_string_literal: true
|
|
4
|
-
|
|
5
|
-
# PreToolUse gate for Bash (intent 27a): close the Bash-edit bypass. When auto
|
|
6
|
-
# mode is armed and the active intent has not reached How (plan.md + checklist.md),
|
|
7
|
-
# block Bash commands that WRITE project code outside the store.
|
|
8
|
-
#
|
|
9
|
-
# Reads the Claude Code PreToolUse payload as JSON on STDIN:
|
|
10
|
-
# { "tool_input": { "command": "..." }, "cwd": "..." }
|
|
11
|
-
# Empty / unparseable / no command => exit 0 (allow). Conservatism is the prime
|
|
12
|
-
# directive: when a command's write intent is ambiguous, ALLOW.
|
|
13
|
-
#
|
|
14
|
-
# Exit 0 = allow. Exit 2 = block (reason on stderr, shown to the agent).
|
|
15
|
-
|
|
16
|
-
require "json"
|
|
17
|
-
require_relative "lib/bridge"
|
|
18
|
-
|
|
19
|
-
raw = $stdin.read rescue nil
|
|
20
|
-
exit 0 if raw.nil? || raw.strip.empty?
|
|
21
|
-
|
|
22
|
-
payload = JSON.parse(raw) rescue nil
|
|
23
|
-
exit 0 unless payload.is_a?(Hash)
|
|
24
|
-
|
|
25
|
-
command = payload.dig("tool_input", "command") || payload.dig("tool_params", "command")
|
|
26
|
-
exit 0 if command.nil? || command.to_s.strip.empty?
|
|
27
|
-
|
|
28
|
-
cwd = payload["cwd"]
|
|
29
|
-
cwd = Dir.pwd if cwd.nil? || cwd.to_s.empty?
|
|
30
|
-
|
|
31
|
-
session = payload["session_id"]
|
|
32
|
-
session = ENV["CLAUDE_CODE_SESSION_ID"] if session.nil? || session.to_s.empty?
|
|
33
|
-
|
|
34
|
-
# Auditable escape (intent 108, D7): a trailing `# plastic-ok` allows the
|
|
35
|
-
# command and logs it, so sanctioned writes are visible, not silent.
|
|
36
|
-
if Bridge.bash_escape?(command)
|
|
37
|
-
begin
|
|
38
|
-
require "fileutils"
|
|
39
|
-
log = File.join(Dir.home, ".plastic", ".cache", "gate-escapes.log")
|
|
40
|
-
FileUtils.mkdir_p(File.dirname(log))
|
|
41
|
-
File.open(log, "a") do |io|
|
|
42
|
-
io.puts("#{Time.now.utc.iso8601}\t#{session}\t#{command.gsub(/\s+/, ' ').strip}")
|
|
43
|
-
end
|
|
44
|
-
rescue StandardError
|
|
45
|
-
# the escape still applies; logging is best-effort
|
|
46
|
-
end
|
|
47
|
-
exit 0
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# --- Load bridge (shared resolution; stdin session_id -> CLAUDE_CODE_SESSION_ID -> /tmp scan) ---
|
|
51
|
-
# A nil bridge does NOT short-circuit (intent 108): the lock gate decides from
|
|
52
|
-
# the durable delivery.lock file, so it must run even without a bridge cache.
|
|
53
|
-
bridge_data = Bridge.discover_bridge(session: session, cwd: cwd)
|
|
54
|
-
|
|
55
|
-
reason = Bridge.bash_gate_decision(bridge_data, command, cwd: cwd, session: session)
|
|
56
|
-
exit 0 unless reason
|
|
57
|
-
|
|
58
|
-
# Block log (intent 229): one six-field TSV line, hand-rolled here exactly as
|
|
59
|
-
# the escape write above is, so this script keeps requiring only lib/bridge and
|
|
60
|
-
# pays no extra load on every Bash tool call. Best-effort: the block applies
|
|
61
|
-
# regardless.
|
|
62
|
-
begin
|
|
63
|
-
require "fileutils"
|
|
64
|
-
require "time"
|
|
65
|
-
blocklog = File.join(Dir.home, ".plastic", ".cache", "gate-blocks.log")
|
|
66
|
-
FileUtils.mkdir_p(File.dirname(blocklog))
|
|
67
|
-
intent_id = bridge_data.is_a?(Hash) ? bridge_data.dig("intent", "id").to_s : ""
|
|
68
|
-
File.open(blocklog, "a") do |io|
|
|
69
|
-
io.puts([Time.now.utc.iso8601, "bash-gate", session.to_s, intent_id,
|
|
70
|
-
command.to_s.gsub(/\s+/, " ").strip,
|
|
71
|
-
reason.to_s.gsub(/\s+/, " ").strip].join("\t"))
|
|
72
|
-
end
|
|
73
|
-
rescue StandardError
|
|
74
|
-
# the block still applies; logging is best-effort
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
$stderr.puts "PLASTIC GATE — #{reason}"
|
|
78
|
-
exit 2
|
package/scripts/hook-code-gate
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# encoding: UTF-8
|
|
3
|
-
# frozen_string_literal: true
|
|
4
|
-
|
|
5
|
-
# Usage: hook-code-gate <file_path> [session_id] [content]
|
|
6
|
-
# PreToolUse gate. Composes two independent block rules; EITHER blocks the edit:
|
|
7
|
-
# - Stage rule (intent 27): when auto mode is armed and the active intent has not
|
|
8
|
-
# reached How (plan.md + checklist.md), block edits to project code outside the store.
|
|
9
|
-
# - Worktree isolation rule (intent 73c2): when the intent has a provisioned code
|
|
10
|
-
# worktree, block project-code edits outside it; and block edits to another
|
|
11
|
-
# intent's store dir whose delivery lock is held by a live non-owner session.
|
|
12
|
-
#
|
|
13
|
-
# Exit 0 = allow. Exit 2 = block (reason on stderr, shown to the agent).
|
|
14
|
-
# No bridge = allow. Each rule fails open on its own conditions (see bridge.rb).
|
|
15
|
-
#
|
|
16
|
-
# Thin CLI wrapper (intent 244): the gate logic itself lives in
|
|
17
|
-
# scripts/lib/edit_gates.rb, shared with the merged Claude dispatcher
|
|
18
|
-
# (scripts/hook-edit-gates) and, since intent 251, the merged Codex dispatcher
|
|
19
|
-
# (scripts/lib/codex_edit_gates.rb). This wrapper has no production caller on
|
|
20
|
-
# either harness anymore; it remains as the per-gate isolation surface for the
|
|
21
|
-
# hook test suite.
|
|
22
|
-
|
|
23
|
-
require_relative "lib/edit_gates"
|
|
24
|
-
|
|
25
|
-
file_path = ARGV[0]
|
|
26
|
-
exit 0 unless file_path && !file_path.empty?
|
|
27
|
-
|
|
28
|
-
session = (ARGV[1] unless ARGV[1].to_s.empty?) || ENV["CLAUDE_CODE_SESSION_ID"]
|
|
29
|
-
|
|
30
|
-
ctx = EditGates::Context.new(
|
|
31
|
-
tool_name: "Write", session: session, gate_path: file_path, file_path: file_path,
|
|
32
|
-
content: ARGV[2], harness: "claude",
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
outcome = EditGates.code_gate(ctx)
|
|
36
|
-
exit 0 unless outcome
|
|
37
|
-
outcome.lines.each { |line| $stderr.puts line }
|
|
38
|
-
exit 2
|
package/scripts/hook-continue
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# encoding: UTF-8
|
|
3
|
-
# Usage: hook-continue <index_path> <store_root> <mode>
|
|
4
|
-
# Renders the dashboard `continue` cockpit as UserPromptSubmit additionalContext.
|
|
5
|
-
# Exits silently (no output) if the dashboard produces nothing — the bash caller
|
|
6
|
-
# then falls back to a minimal notice.
|
|
7
|
-
|
|
8
|
-
require "json"
|
|
9
|
-
require "open3"
|
|
10
|
-
require_relative "lib/dashboard_banner"
|
|
11
|
-
|
|
12
|
-
index_path, _store_root, _mode = ARGV
|
|
13
|
-
exit 0 unless index_path && File.exist?(index_path)
|
|
14
|
-
|
|
15
|
-
dashboard = File.expand_path("dashboard.rb", __dir__)
|
|
16
|
-
exit 0 unless File.exist?(dashboard)
|
|
17
|
-
|
|
18
|
-
cockpit, _err, status = Open3.capture3({"RUBYOPT" => nil}, "ruby", dashboard, "continue")
|
|
19
|
-
exit 0 unless status.success? && !cockpit.strip.empty?
|
|
20
|
-
|
|
21
|
-
context = cockpit.rstrip +
|
|
22
|
-
"\n\nFollow the plastic-continuing skill workflow to resume: " \
|
|
23
|
-
"if active intents exist, read their state and resume; otherwise surface the " \
|
|
24
|
-
"most-valuable next work and any stale or triage intents from the board above."
|
|
25
|
-
|
|
26
|
-
payload = {
|
|
27
|
-
"hookSpecificOutput" => {
|
|
28
|
-
"hookEventName" => "UserPromptSubmit",
|
|
29
|
-
"additionalContext" => context
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
# Hook-owned systemMessage floor (intent 125, Task 6): a hard fallback summary
|
|
34
|
-
# independent of the agent's reply, mirroring hook-session-start's BootBanner
|
|
35
|
-
# pattern. Best-effort only — any failure here (subprocess, JSON, renderer)
|
|
36
|
-
# degrades silently, omitting systemMessage; this hook must never crash
|
|
37
|
-
# UserPromptSubmit over a summary line.
|
|
38
|
-
begin
|
|
39
|
-
data_json, _data_err, data_status = Open3.capture3({"RUBYOPT" => nil}, "ruby", dashboard, "continue", "--data")
|
|
40
|
-
if data_status.success?
|
|
41
|
-
banner = DashboardBanner.render(JSON.parse(data_json))
|
|
42
|
-
payload["systemMessage"] = banner if banner
|
|
43
|
-
end
|
|
44
|
-
rescue StandardError
|
|
45
|
-
nil
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
puts JSON.generate(payload)
|
package/scripts/hook-create-gate
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# encoding: UTF-8
|
|
3
|
-
# frozen_string_literal: true
|
|
4
|
-
|
|
5
|
-
# PreToolUse create gate (intent 60b): validate the PROPOSED content of a Write
|
|
6
|
-
# to an intent file before it lands. Fires when the target path is an intent file
|
|
7
|
-
# inside its own equally-named directory (store/<id>--<slug>/<id>--<slug>.md). It
|
|
8
|
-
# validates the proposed content from the hook payload (tool_input.content), NOT
|
|
9
|
-
# the on-disk file (which does not exist yet at PreToolUse), using IntentValidator
|
|
10
|
-
# for born-complete frontmatter plus the sanctioned section structure.
|
|
11
|
-
#
|
|
12
|
-
# It depends ONLY on the stdin path + content, never on the auto-bridge or any
|
|
13
|
-
# session id, so it runs unconditionally (headless / background sessions).
|
|
14
|
-
# It validates ONLY the intent file, never sentinel placeholder lifecycle files
|
|
15
|
-
# (spec.md/plan.md/etc.); the path matcher excludes them.
|
|
16
|
-
#
|
|
17
|
-
# Exit 0 = allow. Exit 2 = block (reason on stderr, shown to the agent).
|
|
18
|
-
#
|
|
19
|
-
# Reads the Claude Code PreToolUse payload as JSON on STDIN:
|
|
20
|
-
# { "tool_input": { "file_path": "...", "content": "..." } }
|
|
21
|
-
# Empty / unparseable / non-matching path => exit 0 (cannot judge, allow).
|
|
22
|
-
# Three payload shapes on a matching path (intent 108, D7):
|
|
23
|
-
# Write (content): validate the proposed content.
|
|
24
|
-
# Edit (old_string): simulate the replacement on the on-disk file and
|
|
25
|
-
# validate the RESULT; a missing file blocks.
|
|
26
|
-
# Pathless MCP mutation: validate the CURRENT on-disk file (the PostToolUse
|
|
27
|
-
# gate-check backstop validates the result); a
|
|
28
|
-
# missing file blocks (fail-safe).
|
|
29
|
-
#
|
|
30
|
-
# Thin CLI wrapper (intent 244): the gate logic itself lives in
|
|
31
|
-
# scripts/lib/edit_gates.rb, shared with the merged Claude dispatcher
|
|
32
|
-
# (scripts/hook-edit-gates) and, since intent 251, the merged Codex dispatcher
|
|
33
|
-
# (scripts/lib/codex_edit_gates.rb). This wrapper has no production caller on
|
|
34
|
-
# either harness anymore; it remains as the per-gate isolation surface for the
|
|
35
|
-
# hook test suite. Deliberately WITHOUT a top-level rescue, matching this
|
|
36
|
-
# script's behavior before the extraction: an internal exception here has
|
|
37
|
-
# never been caught.
|
|
38
|
-
|
|
39
|
-
require "json"
|
|
40
|
-
require_relative "lib/edit_gates"
|
|
41
|
-
|
|
42
|
-
raw = $stdin.read rescue nil
|
|
43
|
-
exit 0 if raw.nil? || raw.strip.empty?
|
|
44
|
-
|
|
45
|
-
payload = JSON.parse(raw) rescue nil
|
|
46
|
-
exit 0 unless payload.is_a?(Hash)
|
|
47
|
-
|
|
48
|
-
outcome = EditGates.create_gate(EditGates.context_from(payload))
|
|
49
|
-
exit 0 unless outcome
|
|
50
|
-
outcome.lines.each { |line| $stderr.puts line }
|
|
51
|
-
exit 2
|
package/scripts/hook-edit-gates
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# encoding: UTF-8
|
|
3
|
-
# frozen_string_literal: true
|
|
4
|
-
|
|
5
|
-
# The merged Claude PreToolUse edit-path dispatcher (intent 244). One registered
|
|
6
|
-
# hook, one Ruby process, one stdin parse, five gates in-process, in the fixed
|
|
7
|
-
# order savepoint-pre, lock-gate, code-gate, links-gate, create-gate (spec D-a).
|
|
8
|
-
# The first deny ends evaluation and its own shape is reproduced exactly: stderr
|
|
9
|
-
# plus exit 2 for code-gate/links-gate/create-gate, stdout permissionDecision
|
|
10
|
-
# JSON plus exit 0 for lock-gate (spec D-b). savepoint-pre runs first so its
|
|
11
|
-
# `started` ledger append stays unconditional, including on a call another gate
|
|
12
|
-
# then blocks (spec D-c).
|
|
13
|
-
#
|
|
14
|
-
# Fail-open everywhere (spec D-i): each gate runs inside its own rescue in
|
|
15
|
-
# EditGates.dispatch, and this file's top level rescues to exit 0, so a gate bug
|
|
16
|
-
# never denies and never silences a sibling gate.
|
|
17
|
-
#
|
|
18
|
-
# LOAD-BEARING SHAPE: doctor's claude_hooks_implemented_check reads THIS FILE as
|
|
19
|
-
# plain text and extracts the `when "<gate>"` labels of the `case gate` statement
|
|
20
|
-
# below, because a script with top-level side effects can never be required to
|
|
21
|
-
# introspect it (the same technique intent 200 used for scripts/codex-hook). If
|
|
22
|
-
# the case statement is reshaped, update that check in scripts/doctor.rb in the
|
|
23
|
-
# same change, or it will fail loudly by design.
|
|
24
|
-
|
|
25
|
-
require "json"
|
|
26
|
-
require_relative "lib/edit_gates"
|
|
27
|
-
|
|
28
|
-
def route(gate, ctx)
|
|
29
|
-
case gate
|
|
30
|
-
when "savepoint-pre" then EditGates.savepoint_pre(ctx)
|
|
31
|
-
when "lock-gate" then EditGates.lock_gate(ctx)
|
|
32
|
-
when "code-gate" then EditGates.code_gate(ctx)
|
|
33
|
-
when "links-gate" then EditGates.links_gate(ctx)
|
|
34
|
-
when "create-gate" then EditGates.create_gate(ctx)
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
begin
|
|
39
|
-
raw = begin
|
|
40
|
-
$stdin.read
|
|
41
|
-
rescue StandardError
|
|
42
|
-
nil
|
|
43
|
-
end
|
|
44
|
-
exit 0 if raw.nil? || raw.strip.empty?
|
|
45
|
-
|
|
46
|
-
payload = begin
|
|
47
|
-
JSON.parse(raw)
|
|
48
|
-
rescue StandardError
|
|
49
|
-
nil
|
|
50
|
-
end
|
|
51
|
-
exit 0 unless payload.is_a?(Hash)
|
|
52
|
-
|
|
53
|
-
exit EditGates.dispatch(ctx: EditGates.context_from(payload), route: method(:route))
|
|
54
|
-
rescue StandardError => e
|
|
55
|
-
# Dispatcher-internal failure: never impersonate a deny (spec D-i).
|
|
56
|
-
$stderr.puts "plastic edit-gates error: #{e.message}"
|
|
57
|
-
exit 0
|
|
58
|
-
end
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# encoding: UTF-8
|
|
3
|
-
# Usage: hook-future-intent-check <store_root> <message>
|
|
4
|
-
# Outputs hook JSON when the user's message matches future intent keywords.
|
|
5
|
-
# Exits silently if no matches.
|
|
6
|
-
|
|
7
|
-
require "json"
|
|
8
|
-
require "yaml"
|
|
9
|
-
|
|
10
|
-
store_root, message = ARGV
|
|
11
|
-
exit 0 unless store_root && message
|
|
12
|
-
|
|
13
|
-
index_path = "#{store_root}/INDEX.md"
|
|
14
|
-
exit 0 unless File.exist?(index_path)
|
|
15
|
-
|
|
16
|
-
# --- Find future intent dirs ---
|
|
17
|
-
|
|
18
|
-
lines = File.readlines(index_path)
|
|
19
|
-
future_dirs = []
|
|
20
|
-
section = nil
|
|
21
|
-
|
|
22
|
-
lines.each do |line|
|
|
23
|
-
section = :future if line.start_with?("## Future")
|
|
24
|
-
section = nil if line.start_with?("## ") && !line.start_with?("## Future")
|
|
25
|
-
next unless section == :future && line.strip.start_with?("- [")
|
|
26
|
-
|
|
27
|
-
if line =~ /store\/([\w-]+)\//
|
|
28
|
-
future_dirs << $1
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
exit 0 if future_dirs.empty?
|
|
33
|
-
|
|
34
|
-
# --- Match keywords against message ---
|
|
35
|
-
|
|
36
|
-
matches = []
|
|
37
|
-
message_words = message.split(/\W+/).reject { |w| w.length < 4 }
|
|
38
|
-
|
|
39
|
-
future_dirs.each do |dir|
|
|
40
|
-
intent_path = "#{store_root}/store/#{dir}/#{dir}.md"
|
|
41
|
-
next unless File.exist?(intent_path)
|
|
42
|
-
|
|
43
|
-
content = File.read(intent_path)
|
|
44
|
-
|
|
45
|
-
tags = []
|
|
46
|
-
if content =~ /^tags:\s*\[([^\]]+)\]/
|
|
47
|
-
tags = $1.split(",").map(&:strip).map(&:downcase)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
intent_name = ""
|
|
51
|
-
if content =~ /^intent:\s*["']?(.+?)["']?\s*$/
|
|
52
|
-
intent_name = $1.downcase
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
intent_id = ""
|
|
56
|
-
if content =~ /^id:\s*["']?([a-z0-9]+)["']?\s*$/
|
|
57
|
-
intent_id = $1
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
keywords = tags + intent_name.split(/\W+/).reject { |w| w.length < 4 }
|
|
61
|
-
keywords = keywords.map(&:downcase).uniq
|
|
62
|
-
|
|
63
|
-
matched_keywords = keywords.select { |kw| message.include?(kw) }
|
|
64
|
-
name_matches = message_words.select { |w| intent_name.include?(w) }
|
|
65
|
-
matched_keywords = (matched_keywords + name_matches).uniq
|
|
66
|
-
|
|
67
|
-
if matched_keywords.length >= 1
|
|
68
|
-
index_line = lines.find { |l| l.include?(dir) }&.strip || "#{intent_id} — #{intent_name}"
|
|
69
|
-
matches << { "index_line" => index_line, "keywords" => matched_keywords }
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
exit 0 if matches.empty?
|
|
74
|
-
|
|
75
|
-
# --- Build context ---
|
|
76
|
-
|
|
77
|
-
parts = []
|
|
78
|
-
parts << "PLASTIC — Future intents related to this message:\n"
|
|
79
|
-
matches.each do |m|
|
|
80
|
-
parts << "#{m["index_line"]} (matched: #{m["keywords"].join(", ")})"
|
|
81
|
-
end
|
|
82
|
-
parts << "\nConsider asking the user if they want to activate any of these, or note the connection."
|
|
83
|
-
|
|
84
|
-
payload = {
|
|
85
|
-
"hookSpecificOutput" => {
|
|
86
|
-
"hookEventName" => "UserPromptSubmit",
|
|
87
|
-
"additionalContext" => parts.join("\n")
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
puts JSON.generate(payload)
|