@zalom/plastic 2.0.0-alpha.9 → 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 +11 -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 +55 -2
- 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 +153 -30
- 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 +7 -0
- 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 +23 -14
- 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 +531 -71
- package/scripts/lib/intent_screen.rb +4 -4
- 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 +350 -54
- 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 +1371 -47
- 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 +365 -28
- 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 +177 -16
- 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 +8 -3
- 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 -296
- 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 -176
- 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
package/bin/plastic
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: UTF-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# Plastic skill usage census (intent 324). Counts typed /plastic-* commands
|
|
6
|
+
# from the prompt history against agent-invoked Skill calls, loads, and
|
|
7
|
+
# attribution from the transcript tree, so D19 (which Plastic verbs stay
|
|
8
|
+
# visible as slash commands) is decided on measured use.
|
|
9
|
+
#
|
|
10
|
+
# Maintainer tool: it reads the paths given to it and is never installed
|
|
11
|
+
# into ~/.plastic. This file never resolves the running process's home
|
|
12
|
+
# directory or its home environment variable: `bin/` ships in the npm
|
|
13
|
+
# tarball, so a default rooted there would make a published executable
|
|
14
|
+
# read any installer's transcripts. `--history` and `--transcripts` are
|
|
15
|
+
# required, with no default.
|
|
16
|
+
#
|
|
17
|
+
# Usage: bin/plastic-skill-census --history PATH --transcripts DIR
|
|
18
|
+
# [--skills-dir DIR] [--format md|json] [--out PATH]
|
|
19
|
+
# [--cutoff YYYY-MM-DD]
|
|
20
|
+
#
|
|
21
|
+
# --history PATH ~/.claude/history.jsonl, or a fixture (required)
|
|
22
|
+
# --transcripts DIR ~/.claude/projects, or a fixture tree (required)
|
|
23
|
+
# --skills-dir DIR a skill roster directory (default: this repo's skills/)
|
|
24
|
+
# --format md|json report format (default: md)
|
|
25
|
+
# --out PATH write the report here instead of stdout
|
|
26
|
+
# --cutoff YYYY-MM-DD exclude records on or after this date (default: 2026-09-02)
|
|
27
|
+
#
|
|
28
|
+
# Exit codes: 0 a report was produced, 1 a named path does not exist,
|
|
29
|
+
# 2 bad usage or a missing required flag.
|
|
30
|
+
|
|
31
|
+
require_relative "lib/skill_census"
|
|
32
|
+
|
|
33
|
+
USAGE = <<~TEXT
|
|
34
|
+
usage: plastic-skill-census --history PATH --transcripts DIR [--skills-dir DIR] [--format md|json] [--out PATH] [--cutoff YYYY-MM-DD]
|
|
35
|
+
|
|
36
|
+
--history PATH ~/.claude/history.jsonl, or a fixture (required)
|
|
37
|
+
--transcripts DIR ~/.claude/projects, or a fixture tree (required)
|
|
38
|
+
--skills-dir DIR a skill roster directory (default: this repo's skills/)
|
|
39
|
+
--format md|json report format (default: md)
|
|
40
|
+
--out PATH write the report here instead of stdout
|
|
41
|
+
--cutoff YYYY-MM-DD exclude records on or after this date (default: #{SkillCensus::DEFAULT_CUTOFF})
|
|
42
|
+
--help this message
|
|
43
|
+
|
|
44
|
+
Exits 0 on a report, 1 when a named path is missing, 2 on bad usage.
|
|
45
|
+
TEXT
|
|
46
|
+
|
|
47
|
+
def fail_usage(message)
|
|
48
|
+
warn "plastic-skill-census: #{message}"
|
|
49
|
+
warn USAGE
|
|
50
|
+
exit 2
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
history = nil
|
|
54
|
+
transcripts = nil
|
|
55
|
+
skills_dir = File.expand_path("../skills", __dir__)
|
|
56
|
+
format = "md"
|
|
57
|
+
out_path = nil
|
|
58
|
+
cutoff = SkillCensus::DEFAULT_CUTOFF
|
|
59
|
+
|
|
60
|
+
argv = ARGV.dup
|
|
61
|
+
until argv.empty?
|
|
62
|
+
case (flag = argv.shift)
|
|
63
|
+
when "--help", "-h"
|
|
64
|
+
puts USAGE
|
|
65
|
+
exit 0
|
|
66
|
+
when "--history"
|
|
67
|
+
history = argv.shift
|
|
68
|
+
fail_usage("--history needs a path") if history.to_s.empty?
|
|
69
|
+
when "--transcripts"
|
|
70
|
+
transcripts = argv.shift
|
|
71
|
+
fail_usage("--transcripts needs a path") if transcripts.to_s.empty?
|
|
72
|
+
when "--skills-dir"
|
|
73
|
+
skills_dir = argv.shift
|
|
74
|
+
fail_usage("--skills-dir needs a path") if skills_dir.to_s.empty?
|
|
75
|
+
when "--format"
|
|
76
|
+
format = argv.shift
|
|
77
|
+
fail_usage("--format must be md or json") unless %w[md json].include?(format)
|
|
78
|
+
when "--out"
|
|
79
|
+
out_path = argv.shift
|
|
80
|
+
fail_usage("--out needs a path") if out_path.to_s.empty?
|
|
81
|
+
when "--cutoff"
|
|
82
|
+
cutoff = argv.shift
|
|
83
|
+
fail_usage("--cutoff needs a YYYY-MM-DD date") if cutoff.to_s.empty?
|
|
84
|
+
else
|
|
85
|
+
fail_usage("unknown argument #{flag}")
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
fail_usage("--history is required") if history.nil?
|
|
90
|
+
fail_usage("--transcripts is required") if transcripts.nil?
|
|
91
|
+
|
|
92
|
+
begin
|
|
93
|
+
roster = SkillCensus::Roster.load(skills_dir)
|
|
94
|
+
history_scan = SkillCensus::HistoryScanner.new(history, cutoff: cutoff).scan
|
|
95
|
+
transcript_scan = SkillCensus::TranscriptScanner.new(transcripts, cutoff: cutoff).scan
|
|
96
|
+
built = SkillCensus::Tally.new(history_scan, transcript_scan, roster).build
|
|
97
|
+
|
|
98
|
+
report = if format == "json"
|
|
99
|
+
SkillCensus::Report.json(built, history_path: history, transcripts_path: transcripts, cutoff: cutoff)
|
|
100
|
+
else
|
|
101
|
+
SkillCensus::Report.markdown(built, history_path: history, transcripts_path: transcripts, cutoff: cutoff)
|
|
102
|
+
end
|
|
103
|
+
rescue RuntimeError => error
|
|
104
|
+
warn "plastic-skill-census: #{error.message}"
|
|
105
|
+
exit 1
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
if out_path
|
|
109
|
+
File.write(out_path, report)
|
|
110
|
+
else
|
|
111
|
+
puts report
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
exit 0
|
package/bin/test
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
# encoding: UTF-8
|
|
3
3
|
# frozen_string_literal: true
|
|
4
4
|
|
|
5
|
-
# Runs the
|
|
5
|
+
# Runs the Minitest suite in a single process, either the whole suite or a
|
|
6
|
+
# named subset via --only (intent 355, n4, D5). Every run, named or full,
|
|
7
|
+
# goes through FailuresReporter, so a green run costs one line and a red run
|
|
8
|
+
# prints only the failures plus that line, never a dot per test.
|
|
6
9
|
#
|
|
7
10
|
# `release.verify` used to be `ruby -Itest test/*_test.rb`, but Ruby runs only
|
|
8
11
|
# the first glob-expanded file as the program — the rest land in ARGV and are
|
|
@@ -12,17 +15,34 @@
|
|
|
12
15
|
# loaded.
|
|
13
16
|
|
|
14
17
|
root = File.expand_path("..", __dir__)
|
|
15
|
-
|
|
16
|
-
files = Dir.glob(File.join(root, "test", "**", "*_test.rb")).sort
|
|
17
|
-
abort "No test files found under #{File.join(root, "test")}" if files.empty?
|
|
18
|
+
discover = -> { Dir.glob(File.join(root, "test", "**", "*_test.rb")).sort }
|
|
18
19
|
|
|
19
20
|
# `bin/test --list` prints the discovered files without running them, so the
|
|
20
21
|
# discovery logic can be tested cheaply (no full-suite execution).
|
|
21
22
|
if ARGV.delete("--list")
|
|
23
|
+
files = discover.call
|
|
24
|
+
abort "No test files found under #{File.join(root, "test")}" if files.empty?
|
|
22
25
|
puts files.map { |f| f.sub("#{root}/", "") }
|
|
23
26
|
exit 0
|
|
24
27
|
end
|
|
25
28
|
|
|
29
|
+
only_index = ARGV.index("--only")
|
|
30
|
+
if only_index
|
|
31
|
+
named = ARGV.slice!(only_index..-1)
|
|
32
|
+
named.shift
|
|
33
|
+
abort "--only requires at least one test file" if named.empty?
|
|
34
|
+
files = named.map { |f| File.expand_path(f, root) }
|
|
35
|
+
missing = files.reject { |f| File.exist?(f) }
|
|
36
|
+
unless missing.empty?
|
|
37
|
+
warn "no such test file: #{missing.map { |f| f.sub("#{root}/", "") }.join(', ')}"
|
|
38
|
+
exit 2
|
|
39
|
+
end
|
|
40
|
+
else
|
|
41
|
+
files = discover.call
|
|
42
|
+
abort "No test files found under #{File.join(root, "test")}" if files.empty?
|
|
43
|
+
end
|
|
44
|
+
|
|
26
45
|
$LOAD_PATH.unshift File.join(root, "test")
|
|
46
|
+
require_relative "../test/lib/failures_reporter"
|
|
27
47
|
require "minitest/autorun"
|
|
28
48
|
files.each { |f| require f }
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
require "fileutils"
|
|
3
|
+
require "open3"
|
|
4
|
+
require "tmpdir"
|
|
5
|
+
|
|
6
|
+
class VerifyChange
|
|
7
|
+
USAGE = <<~TEXT
|
|
8
|
+
Usage: bin/verify-change [BASE] [--test FILE]...
|
|
9
|
+
|
|
10
|
+
Checks only the code changed since the merge base of BASE and HEAD
|
|
11
|
+
(BASE defaults to origin/main, then main, then master):
|
|
12
|
+
|
|
13
|
+
1. lint on the changed Ruby files, in the project's style guide
|
|
14
|
+
2. the tests for the changed files, plus every --test FILE, with coverage on
|
|
15
|
+
3. patch coverage of the changed lines (simplecov patch)
|
|
16
|
+
4. mutation testing of the changed lines (mutineer, operators from .mutineer.yml)
|
|
17
|
+
5. CRAP scores of the changed methods (bin/crap)
|
|
18
|
+
|
|
19
|
+
Runs RSpec specs in a project with spec/ and .rspec, spec/spec_helper.rb,
|
|
20
|
+
or spec/rails_helper.rb, or with rspec, rspec-core, or rspec-rails under
|
|
21
|
+
DEPENDENCIES in Gemfile.lock, and Minitest tests otherwise. Warns when
|
|
22
|
+
both test/ and spec/ exist. Pass --test for the integration or system
|
|
23
|
+
tests of a feature the change touches.
|
|
24
|
+
|
|
25
|
+
A lint failure is reported but does not stop the other checks. Lint is
|
|
26
|
+
skipped, not failed, with no style guide, two style guides, no changed
|
|
27
|
+
Ruby files, or the linter gem missing from Gemfile.lock.
|
|
28
|
+
|
|
29
|
+
Every child runs under a throwaway HOME and PLASTIC_TMP, so a test or a
|
|
30
|
+
mutant that spawns an installer cannot reach the real home directory.
|
|
31
|
+
|
|
32
|
+
Exit codes: 0 verified, 1 a check failed, 2 usage or git error.
|
|
33
|
+
TEXT
|
|
34
|
+
|
|
35
|
+
PATCH_COVERAGE_MINIMUM = 100
|
|
36
|
+
MUTATION_THRESHOLD = 75
|
|
37
|
+
CRAP_THRESHOLD = 30
|
|
38
|
+
TESTS_STEP = "Tests with coverage"
|
|
39
|
+
LINT_STEP = "Lint"
|
|
40
|
+
DEFAULT_BASES = %w[origin/main main master].freeze
|
|
41
|
+
ALL_OPERATORS = %w[arithmetic comparison boolean_connector boolean_literal statement_removal return_nil
|
|
42
|
+
literal_mutation condition_negation string_literal regex collection_method].freeze
|
|
43
|
+
SOURCE_PATH = %r{\A(app|lib|tools|scripts|bin/lib)/.+\.rb\z}
|
|
44
|
+
RSPEC_DEPENDENCY = /^ rspec(-core|-rails)?(\s|!|$)/
|
|
45
|
+
|
|
46
|
+
Suite = Data.define(:name, :folder, :test_path, :locations, :suffix)
|
|
47
|
+
MINITEST = Suite.new(name: "Minitest", folder: "test/", test_path: %r{\Atest/.+_test\.rb\z}, suffix: "_test.rb",
|
|
48
|
+
locations: { %r{\Aapp/} => ["test/"], %r{\Alib/} => ["test/", "test/lib/"], %r{\Atools/} => ["test/tools/"],
|
|
49
|
+
%r{\Ascripts/lib/} => ["test/", "test/lib/"], %r{\Abin/lib/} => ["test/"], %r{\Ascripts/} => ["test/"] })
|
|
50
|
+
RSPEC = Suite.new(name: "RSpec", folder: "spec/", test_path: %r{\Aspec/.+_spec\.rb\z}, suffix: "_spec.rb",
|
|
51
|
+
locations: { %r{\Aapp/} => ["spec/"], %r{\Alib/} => ["spec/", "spec/lib/"], %r{\Atools/} => ["spec/tools/"] })
|
|
52
|
+
|
|
53
|
+
Step = Struct.new(:title, :env, :command)
|
|
54
|
+
LintDecision = Struct.new(:step, :skip_reason)
|
|
55
|
+
Plan = Struct.new(:sources, :tests, :untested, :lint_files, :lint_decision, :steps)
|
|
56
|
+
|
|
57
|
+
def self.test_candidates(source, suite = MINITEST)
|
|
58
|
+
suite.locations.flat_map do |prefix, targets|
|
|
59
|
+
source.match?(prefix) ? targets.map { |target| source.sub(prefix, target).sub(/\.rb\z/, suite.suffix) } : []
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def initialize(argv, root: Dir.pwd, out: $stdout, err: $stderr, git: nil, run: nil, sandbox: nil)
|
|
64
|
+
@argv = argv.dup
|
|
65
|
+
@root = root
|
|
66
|
+
@out = out
|
|
67
|
+
@err = err
|
|
68
|
+
@git = git || ->(*args) { Open3.capture2e("git", "-C", root, *args) }
|
|
69
|
+
@run = run || ->(env, command) { system(env, *command, chdir: root) }
|
|
70
|
+
@sandbox = sandbox
|
|
71
|
+
@owned_sandbox = sandbox.nil?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# A throwaway home for every child this script spawns. A gate step runs the
|
|
75
|
+
# project's own tests, and a test (or a mutant that defeats an injected
|
|
76
|
+
# runner) can spawn an installer, which resolves ~/.plastic and ~/.claude
|
|
77
|
+
# from HOME. Pointing HOME at a fresh directory is what keeps the real home
|
|
78
|
+
# out of reach; PLASTIC_TMP does the same for the session bridge files.
|
|
79
|
+
def sandbox
|
|
80
|
+
@sandbox ||= { "HOME" => Dir.mktmpdir("verify-change-home"),
|
|
81
|
+
"PLASTIC_TMP" => Dir.mktmpdir("verify-change-tmp") }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def call
|
|
85
|
+
return usage(0) if @argv.include?("--help") || @argv.include?("-h")
|
|
86
|
+
|
|
87
|
+
requested_base, extra_tests = parse_arguments
|
|
88
|
+
return usage(2) unless requested_base
|
|
89
|
+
|
|
90
|
+
base = merge_base(requested_base)
|
|
91
|
+
return 2 unless base
|
|
92
|
+
|
|
93
|
+
report_untracked
|
|
94
|
+
chosen = suite
|
|
95
|
+
report_both_suites(chosen)
|
|
96
|
+
plan = plan(changed: changed_files(base), extra_tests: extra_tests, base: base, rails: rails?, suite: chosen)
|
|
97
|
+
execute(plan, requested_base)
|
|
98
|
+
ensure
|
|
99
|
+
discard_sandbox
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def discard_sandbox
|
|
103
|
+
return unless @owned_sandbox && @sandbox
|
|
104
|
+
|
|
105
|
+
@sandbox.each_value { |dir| FileUtils.remove_entry(dir, true) }
|
|
106
|
+
@sandbox = nil
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def plan(changed:, extra_tests:, base:, rails:, suite: MINITEST)
|
|
110
|
+
sources = changed.grep(SOURCE_PATH).select { |path| exist?(path) }
|
|
111
|
+
lint_files = changed.select { |path| File.extname(path) == ".rb" && exist?(path) }
|
|
112
|
+
mapped = sources.to_h { |source| [source, self.class.test_candidates(source, suite).select { |path| exist?(path) }] }
|
|
113
|
+
changed_tests = changed.grep(suite.test_path)
|
|
114
|
+
tests = (changed_tests + mapped.values.flatten + extra_tests).uniq.select { |path| exist?(path) }
|
|
115
|
+
covered_elsewhere = !extra_tests.empty? || changed_tests.any?
|
|
116
|
+
untested = covered_elsewhere ? [] : mapped.select { |_source, found| found.empty? }.keys
|
|
117
|
+
Plan.new(sources, tests, untested, lint_files, lint_decision(lint_files), steps(sources, tests, base, rails, suite))
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
def parse_arguments
|
|
123
|
+
base = nil
|
|
124
|
+
extra_tests = []
|
|
125
|
+
arguments = @argv.dup
|
|
126
|
+
until arguments.empty?
|
|
127
|
+
argument = arguments.shift
|
|
128
|
+
if argument == "--test"
|
|
129
|
+
file = arguments.shift
|
|
130
|
+
return [nil, []] unless file
|
|
131
|
+
|
|
132
|
+
extra_tests << file
|
|
133
|
+
elsif argument.start_with?("-") || base
|
|
134
|
+
return [nil, []]
|
|
135
|
+
else
|
|
136
|
+
base = argument
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
[base || default_base, extra_tests]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def default_base
|
|
143
|
+
DEFAULT_BASES.find { |ref| @git.call("rev-parse", "--verify", "--quiet", ref).last.success? } || DEFAULT_BASES.last
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def merge_base(ref)
|
|
147
|
+
output, status = @git.call("merge-base", ref, "HEAD")
|
|
148
|
+
return output.strip if status.success?
|
|
149
|
+
|
|
150
|
+
@err.puts "Cannot find the merge base of #{ref} and HEAD: #{output.strip}"
|
|
151
|
+
nil
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def changed_files(base)
|
|
155
|
+
@git.call("diff", "--name-only", base, "--", "*.rb").first.split("\n")
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def report_untracked
|
|
159
|
+
untracked = @git.call("ls-files", "--others", "--exclude-standard", "--", "*.rb").first.split("\n")
|
|
160
|
+
return if untracked.empty?
|
|
161
|
+
|
|
162
|
+
@err.puts "Not checked, because git does not track them yet (run git add first): #{untracked.join(", ")}"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def report_both_suites(chosen)
|
|
166
|
+
return unless File.directory?(File.join(@root, "test")) && File.directory?(File.join(@root, "spec"))
|
|
167
|
+
|
|
168
|
+
skipped = chosen == RSPEC ? MINITEST : RSPEC
|
|
169
|
+
@err.puts "Both test/ and spec/ exist. Checking with #{chosen.name} only, so changed files under #{skipped.folder} do not run."
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def steps(sources, tests, base, rails, suite)
|
|
173
|
+
steps = []
|
|
174
|
+
steps << step(TESTS_STEP, { "COVERAGE" => "1" }, test_command(tests, rails, suite)) unless tests.empty?
|
|
175
|
+
return steps if sources.empty?
|
|
176
|
+
|
|
177
|
+
steps << step("Patch coverage", {}, %W[bundle exec simplecov patch --base #{base} --minimum #{PATCH_COVERAGE_MINIMUM}])
|
|
178
|
+
steps << step("Mutation testing", {}, mutation_command(sources, tests, base, rails, suite))
|
|
179
|
+
steps << step("CRAP scores", {}, %W[bundle exec bin/crap --threshold #{CRAP_THRESHOLD} --since #{base}] + sources)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def step(title, env, command)
|
|
183
|
+
Step.new(title, sandbox.merge(env), command)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def test_command(tests, rails, suite)
|
|
187
|
+
return %w[bundle exec rspec] + tests if suite == RSPEC
|
|
188
|
+
return %w[bin/rails test] + tests if rails
|
|
189
|
+
|
|
190
|
+
%w[bundle exec ruby -Ilib -Itools -Itest -e] + [%(require "test_helper"; ARGV.each { |file| require File.expand_path(file) })] + tests
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def lint_decision(lint_files)
|
|
194
|
+
return LintDecision.new(nil, "no changed Ruby files to lint") if lint_files.empty?
|
|
195
|
+
return LintDecision.new(nil, "two style guides found: .standard.yml and .rubocop.yml") if ambiguous_style_guide?
|
|
196
|
+
|
|
197
|
+
if rubocop_file?
|
|
198
|
+
return lint_gem_available?(:rubocop) ? LintDecision.new(lint_step(:rubocop, lint_files), nil) : LintDecision.new(nil, "the rubocop gem is not in Gemfile.lock")
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
if standard_file? || dependencies_section.match?(/^ standard(\s|!|$)/)
|
|
202
|
+
return lint_gem_available?(:standard) ? LintDecision.new(lint_step(:standard, lint_files), nil) : LintDecision.new(nil, "the standard gem is not in Gemfile.lock")
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
LintDecision.new(nil, "no style guide found")
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Local adaptation (intent 363): RuboCop reads .rubocop_with_todo.yml here, which
|
|
209
|
+
# is .rubocop.yml plus the generated todo. The todo holds this repository's 16,116
|
|
210
|
+
# pre-existing offences, so a lint run reports what the change did and nothing
|
|
211
|
+
# else. .rubocop.yml, the todo-free config, is what the repository is measured
|
|
212
|
+
# against as the todo shrinks.
|
|
213
|
+
def lint_step(guide, lint_files)
|
|
214
|
+
command = guide == :standard ? %w[bundle exec standardrb --force-exclusion] : rubocop_command
|
|
215
|
+
step(LINT_STEP, {}, command + lint_files)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def rubocop_command
|
|
219
|
+
base = %w[bundle exec rubocop --force-exclusion]
|
|
220
|
+
exist?(".rubocop_with_todo.yml") ? base + %w[--config .rubocop_with_todo.yml] : base
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def lint_gem_available?(command_gem)
|
|
224
|
+
return true if command_gem == :rubocop && (dependencies_section.match?(/^ rubocop(\s|!|$)/) ||
|
|
225
|
+
dependencies_section.match?(/^ standard(\s|!|$)/) || dependencies_section.match?(/^ rubocop-rails-omakase(\s|!|$)/))
|
|
226
|
+
return true if command_gem == :standard && dependencies_section.match?(/^ standard(\s|!|$)/)
|
|
227
|
+
|
|
228
|
+
false
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def ambiguous_style_guide?
|
|
232
|
+
standard_file? && rubocop_file? && !rubocop_config_uses_standard?
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def rubocop_config_uses_standard?
|
|
236
|
+
config = rubocop_config
|
|
237
|
+
return false if config.empty?
|
|
238
|
+
|
|
239
|
+
config.match?(/inherit_gem:\s*\{?\s*standard:/m) ||
|
|
240
|
+
config.match?(/^(plugins|require):\s*\n(?:[ \t]*-[ \t]*\S+[ \t]*\n)*?[ \t]*-[ \t]*standard[ \t]*$/m) ||
|
|
241
|
+
config.match?(/^(plugins|require):\s*\[[^\]]*\bstandard\b[^\]]*\]/m)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def rubocop_config
|
|
245
|
+
exist?(".rubocop.yml") ? File.read(File.join(@root, ".rubocop.yml")) : ""
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def standard_file?
|
|
249
|
+
exist?(".standard.yml")
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def rubocop_file?
|
|
253
|
+
exist?(".rubocop.yml")
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def dependencies_section
|
|
257
|
+
return @dependencies_section if defined?(@dependencies_section)
|
|
258
|
+
|
|
259
|
+
lockfile = exist?("Gemfile.lock") ? File.read(File.join(@root, "Gemfile.lock")) : nil
|
|
260
|
+
unless lockfile
|
|
261
|
+
return @dependencies_section = ""
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
lines = lockfile.lines
|
|
265
|
+
start = lines.index { |line| line.strip == "DEPENDENCIES" }
|
|
266
|
+
unless start
|
|
267
|
+
return @dependencies_section = ""
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
section = []
|
|
271
|
+
lines[(start + 1)..].each do |line|
|
|
272
|
+
break if line.strip.empty?
|
|
273
|
+
|
|
274
|
+
section << line
|
|
275
|
+
end
|
|
276
|
+
@dependencies_section = section.join
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def mutation_command(sources, tests, base, rails, suite)
|
|
280
|
+
%w[bundle exec mutineer run] + sources + tests.flat_map { |test| ["--test", test] } +
|
|
281
|
+
%W[--since #{base} --threshold #{MUTATION_THRESHOLD}] + (suite == RSPEC ? %w[--framework rspec] : []) +
|
|
282
|
+
(rails ? %w[--rails] : %w[--strategy redefine]) +
|
|
283
|
+
(exist?(".mutineer.yml") ? [] : ["--operators", ALL_OPERATORS.join(",")])
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def execute(plan, requested_base)
|
|
287
|
+
if plan.sources.empty? && plan.tests.empty? && plan.lint_files.empty?
|
|
288
|
+
@out.puts "No changed Ruby files since #{requested_base}."
|
|
289
|
+
return 0
|
|
290
|
+
end
|
|
291
|
+
unless plan.untested.empty?
|
|
292
|
+
@err.puts "No test file found for: #{plan.untested.join(", ")}. Write one, or pass --test FILE for the tests that cover it."
|
|
293
|
+
return 1
|
|
294
|
+
end
|
|
295
|
+
unless exist?(".mutineer.yml") || plan.sources.empty?
|
|
296
|
+
@err.puts "Missing .mutineer.yml: passing all 11 operators on the command line instead."
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
@out.puts "Base: #{requested_base}"
|
|
300
|
+
@out.puts "Sources: #{plan.sources.empty? ? "none" : plan.sources.join(", ")}"
|
|
301
|
+
@out.puts "Tests: #{plan.tests.join(", ")}"
|
|
302
|
+
@out.puts "Home: #{sandbox["HOME"]}"
|
|
303
|
+
FileUtils.rm_rf(File.join(@root, "coverage"))
|
|
304
|
+
failed = []
|
|
305
|
+
if plan.lint_decision.step
|
|
306
|
+
@out.puts "\n== #{LINT_STEP}"
|
|
307
|
+
failed << plan.lint_decision.step unless @run.call(plan.lint_decision.step.env, plan.lint_decision.step.command)
|
|
308
|
+
elsif plan.lint_decision.skip_reason
|
|
309
|
+
@out.puts "\nLint skipped: #{plan.lint_decision.skip_reason}"
|
|
310
|
+
end
|
|
311
|
+
plan.steps.each do |step|
|
|
312
|
+
@out.puts "\n== #{step.title}"
|
|
313
|
+
next if @run.call(step.env, step.command)
|
|
314
|
+
|
|
315
|
+
failed << step
|
|
316
|
+
next unless step.title == TESTS_STEP
|
|
317
|
+
|
|
318
|
+
@out.puts "\nThe tests are red, so the other checks did not run: a failing test kills every mutant."
|
|
319
|
+
break
|
|
320
|
+
end
|
|
321
|
+
@out.puts failed.empty? ? "\nChange verified" : "\nFailed: #{failed.map(&:title).join(", ")}"
|
|
322
|
+
failed.empty? ? 0 : 1
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def rails?
|
|
326
|
+
exist?("config/application.rb")
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def suite
|
|
330
|
+
listed = dependencies_section.match?(RSPEC_DEPENDENCY)
|
|
331
|
+
spec = File.directory?(File.join(@root, "spec")) && (exist?(".rspec") || exist?("spec/spec_helper.rb") || exist?("spec/rails_helper.rb"))
|
|
332
|
+
spec || listed ? RSPEC : MINITEST
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def exist?(path)
|
|
336
|
+
File.exist?(File.join(@root, path))
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def usage(code)
|
|
340
|
+
(code.zero? ? @out : @err).puts USAGE
|
|
341
|
+
code
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
exit VerifyChange.new(ARGV).call if $PROGRAM_NAME == __FILE__
|
package/config_asks.yml
CHANGED
|
@@ -32,7 +32,7 @@ config_asks:
|
|
|
32
32
|
agents: ["claude"]
|
|
33
33
|
question: "Which advisor should be the default?"
|
|
34
34
|
options:
|
|
35
|
-
- label: "
|
|
36
|
-
value: "plastic-
|
|
37
|
-
- label: "
|
|
38
|
-
value: "plastic-advisor"
|
|
35
|
+
- label: "Primary Advisor (recommended): Fable at medium effort for normal consultation."
|
|
36
|
+
value: "plastic-primary-advisor"
|
|
37
|
+
- label: "Secondary Advisor: Fable at high effort for explicit escalation."
|
|
38
|
+
value: "plastic-secondary-advisor"
|
package/deprecations.yml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Plastic deprecation notices
|
|
2
2
|
# Each entry is shown to the user during SessionStart.
|
|
3
|
-
# See
|
|
3
|
+
# See CONTRIBUTING.md's "Cut a release" section for the full convention.
|
|
4
4
|
#
|
|
5
5
|
# Schema:
|
|
6
6
|
# id: unique-slug (used for dismissal tracking)
|
|
@@ -2,16 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
*Companion to the Operating Manual. How to use Fable as your advisor and planner.*
|
|
4
4
|
|
|
5
|
-
*Adapted for Plastic (intent 185): this is the shipped reference copy
|
|
6
|
-
`plastic
|
|
7
|
-
`plastic-advisor` (
|
|
8
|
-
|
|
9
|
-
answer shape below
|
|
10
|
-
dispatch, never which file to pick.
|
|
11
|
-
|
|
12
|
-
(`advisor.claude.default`, falling back to `secondary`, then to
|
|
13
|
-
`plastic-faux-advisor`) to route your consultation automatically; name a
|
|
14
|
-
specific advisor in your request to override it. A frontier model rewards a
|
|
5
|
+
*Adapted for Plastic (intent 185): this is the shipped reference copy, reached with
|
|
6
|
+
`plastic help advisor-protocol`. Two named agents carry it,
|
|
7
|
+
`plastic-primary-advisor` (Primary Advisor) and `plastic-secondary-advisor`
|
|
8
|
+
(Secondary Advisor, the explicit escalation); the
|
|
9
|
+
answer shape below shapes the brief and the answer on whichever one you
|
|
10
|
+
dispatch, never which file to pick. There is no automatic routing between them: the user or
|
|
11
|
+
the main session dispatches one deliberately, by naming it. A frontier model rewards a
|
|
15
12
|
single, well-formed one-shot brief more than a back-and-forth, so front-load
|
|
16
13
|
everything section 3 below asks for before you send. The rest of this
|
|
17
14
|
document is the owner's protocol, unchanged.*
|
|
@@ -53,17 +50,18 @@ You own the work and the outcome. Fable owns the hardest thinking, on demand.
|
|
|
53
50
|
|
|
54
51
|
---
|
|
55
52
|
|
|
56
|
-
## 2.
|
|
53
|
+
## 2. Answer shape
|
|
57
54
|
|
|
58
55
|
Classify every consultation before sending it. Default to the smallest shape and
|
|
59
|
-
prove your way up.
|
|
60
|
-
|
|
56
|
+
prove your way up. Primary Advisor ships at medium reasoning effort. Secondary Advisor ships
|
|
57
|
+
at high reasoning effort on Claude Code and Codex. An explicit harness-scoped config override
|
|
58
|
+
may change one agent's effort.
|
|
61
59
|
|
|
62
60
|
| Shape | Coding | Business | Research | Effort | Brief size | Expected return |
|
|
63
61
|
|------|--------|----------|----------|--------|------------|-----------------|
|
|
64
|
-
| **Verdict** | Verdict on one step; choose between two named implementations | Pick between two options you already compared (vendor, pricing point) | Judge whether one source or result is trustworthy enough to build on | `
|
|
65
|
-
| **Plan** | Plan a feature inside one system; review a full plan for holes; design one interface; rank root causes | Positioning or pricing decision from a compiled evidence pack; review a proposal before sending it | Design a research plan for a bounded question; rank competing explanations of the data you gathered | `medium
|
|
66
|
-
| **Architecture** | Cross-system architecture; migration with one-way doors; deadlock after two failed attempts; security-critical design | Build-vs-buy, market entry, or any commitment measured in months; strategy where reversal is expensive | Synthesis across many sources where the conclusion drives a large bet; contested questions with conflicting evidence | `
|
|
62
|
+
| **Verdict** | Verdict on one step; choose between two named implementations | Pick between two options you already compared (vendor, pricing point) | Judge whether one source or result is trustworthy enough to build on | `medium` | Under 300 words | Verdict, one paragraph of reasoning, the single biggest risk |
|
|
63
|
+
| **Plan** | Plan a feature inside one system; review a full plan for holes; design one interface; rank root causes | Positioning or pricing decision from a compiled evidence pack; review a proposal before sending it | Design a research plan for a bounded question; rank competing explanations of the data you gathered | `medium` | Up to one page | Decision, numbered plan with per-step checks, risk map |
|
|
64
|
+
| **Architecture** | Cross-system architecture; migration with one-way doors; deadlock after two failed attempts; security-critical design | Build-vs-buy, market entry, or any commitment measured in months; strategy where reversal is expensive | Synthesis across many sources where the conclusion drives a large bet; contested questions with conflicting evidence | `medium` | Full evidence brief | Decision, plan, risk register, kill criteria, list of what could not be verified |
|
|
67
65
|
|
|
68
66
|
**Escalate one shape when any of these holds:**
|
|
69
67
|
|
|
@@ -124,11 +122,8 @@ What actually raises the quality of my answer, in order of impact:
|
|
|
124
122
|
under the Operating Manual." Then I write steps you can run at your best:
|
|
125
123
|
each step with its own check, its own trap named, and its own
|
|
126
124
|
stop-and-return trigger. A plan without that is half a plan.
|
|
127
|
-
6. **
|
|
128
|
-
|
|
129
|
-
`xhigh`/`max` I build the strongest case for every rival and then try to
|
|
130
|
-
break my own winner before you ever see it. Buy the depth the failure cost
|
|
131
|
-
justifies, nothing more.
|
|
125
|
+
6. **Use the medium default well.** Ask for the smallest answer shape that can decide the
|
|
126
|
+
question, and spend the available reasoning on the load-bearing risk.
|
|
132
127
|
7. **Come back on the same thread.** My context is cached inside a session.
|
|
133
128
|
The second question in a thread costs a fraction of the first. A new session
|
|
134
129
|
pays for your whole brief again.
|
|
@@ -142,7 +137,7 @@ briefing described above; use the block as your completeness check before
|
|
|
142
137
|
sending. Fable must never need to explore.
|
|
143
138
|
|
|
144
139
|
```
|
|
145
|
-
SHAPE: verdict | plan | architecture
|
|
140
|
+
SHAPE: verdict | plan | architecture
|
|
146
141
|
DOMAIN: coding | business | research
|
|
147
142
|
GOAL: <target state in one sentence, and the decision this answer feeds>
|
|
148
143
|
QUESTIONS:
|
|
@@ -229,8 +224,8 @@ reformat, then work with what you have.
|
|
|
229
224
|
hear yes.
|
|
230
225
|
- **Drip-feeding.** Splitting one decision across many small calls, paying
|
|
231
226
|
session overhead each time.
|
|
232
|
-
- **Prestige escalation.**
|
|
233
|
-
|
|
227
|
+
- **Prestige escalation.** Changing the medium default because the task feels important,
|
|
228
|
+
without evidence that a harness-scoped override will improve the decision.
|
|
234
229
|
- **Unbounded questions.** "Any thoughts on this approach?" invites an essay.
|
|
235
230
|
Ask for a decision with named options.
|
|
236
231
|
- **Silent adoption.** Pasting Fable's plan straight into execution without
|