@zalom/plastic 1.0.0-beta.2 → 1.0.0-beta.21
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 +131 -7
- package/agents/plastic-brainstorming.md +9 -1
- package/agents/plastic-enforcer.md +1 -1
- package/agents/plastic-executor.md +11 -1
- package/agents/plastic-intent-curator.md +7 -5
- package/agents/plastic-planner.md +11 -1
- package/agents/plastic-spec-specialist.md +9 -1
- package/hooks/hooks.json +20 -0
- package/hooks/retrieval-gate +10 -0
- package/hooks/savepoint-pre +10 -0
- package/hooks/statusline +150 -41
- package/package.json +1 -1
- package/scripts/agent-report +163 -0
- package/scripts/doctor.rb +172 -0
- package/scripts/hook-auto-arm +1 -1
- package/scripts/hook-bash-gate +2 -2
- package/scripts/hook-code-gate +11 -6
- package/scripts/hook-create-gate +2 -2
- package/scripts/hook-gate-check +14 -23
- package/scripts/hook-retrieval-gate +136 -0
- package/scripts/hook-savepoint-pre +32 -0
- package/scripts/hook-session-start +1 -1
- package/scripts/insight-append +51 -0
- package/scripts/lib/bridge.rb +374 -34
- package/scripts/lib/frontmatter_writer.rb +130 -0
- package/scripts/lib/graph_rebuild.rb +328 -0
- package/scripts/lib/insights.rb +86 -0
- package/scripts/lib/installer_core.rb +23 -0
- package/scripts/lib/link_suggestions.rb +322 -0
- package/scripts/lib/links_projection.rb +160 -0
- package/scripts/lib/links_section.rb +207 -0
- package/scripts/lib/power_tools.rb +76 -0
- package/scripts/lib/qmd_hook.rb +38 -25
- package/scripts/lib/qmd_sync.rb +36 -0
- package/scripts/lib/retrieval_gate.rb +211 -0
- package/scripts/lib/worktree.rb +409 -0
- package/scripts/link-suggest +211 -0
- package/scripts/new-intent +138 -29
- package/scripts/project-links +287 -0
- package/scripts/qmd-sync +50 -3
- package/scripts/rebuild-graph +244 -0
- package/scripts/spawn-preamble +26 -1
- package/skills/auto/SKILL.md +58 -11
- package/skills/auto/evals/evals.json +48 -0
- package/skills/auto/references/agent-architecture.md +27 -4
- package/skills/auto/references/agent-report-contract.md +121 -0
- package/skills/brainstorming/SKILL.md +1 -0
- package/skills/brainstorming/evals/evals.json +22 -0
- package/skills/continuing/SKILL.md +30 -8
- package/skills/continuing/evals/evals.json +9 -0
- package/skills/creating-intent/SKILL.md +16 -2
- package/skills/creating-intent/evals/evals.json +16 -0
- package/skills/creating-intent/references/lifecycle.md +9 -4
- package/skills/creating-skills/SKILL.md +65 -0
- package/skills/creating-skills/evals/evals.json +108 -0
- package/skills/creating-skills/references/agents.md +168 -0
- package/skills/creating-skills/references/evals.md +41 -0
- package/skills/creating-skills/references/hooks.md +248 -0
- package/skills/creating-skills/references/progressive-disclosure.md +176 -0
- package/skills/creating-skills/references/scripts.md +166 -0
- package/skills/creating-skills/references/skills.md +165 -0
- package/skills/creating-skills/scripts/scaffold.rb +313 -0
- package/skills/dashboard/SKILL.md +5 -0
- package/skills/dashboard/evals/evals.json +22 -0
- package/skills/executing-plan/SKILL.md +2 -2
- package/skills/humanizer/SKILL.md +39 -0
- package/skills/humanizer/evals/evals.json +70 -0
- package/skills/humanizer/references/always-on-snippet.md +9 -0
- package/skills/humanizer/references/examples.md +48 -0
- package/skills/intent-curator/SKILL.md +6 -1
- package/skills/intent-curator/evals/evals.json +22 -0
- package/skills/linking-intents/SKILL.md +54 -12
- package/skills/linking-intents/evals/evals.json +22 -0
- package/skills/linking-intents/references/zettelkasten.md +7 -0
- package/skills/managing-index/SKILL.md +8 -0
- package/skills/managing-index/evals/evals.json +22 -0
- package/skills/managing-index/references/zettelkasten-linking.md +6 -1
- package/skills/releasing/SKILL.md +32 -0
- package/skills/research/SKILL.md +8 -0
- package/skills/research/evals/evals.json +22 -0
- package/skills/writing-instructions/SKILL.md +0 -159
- package/skills/writing-instructions/references/agentskills-spec.md +0 -135
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Authoring an Agent Skill
|
|
2
|
+
|
|
3
|
+
Open this when writing or restructuring a single Agent Skill: the frontmatter, the
|
|
4
|
+
description that triggers it, the body voice, and the on-disk layout. Rule ids in brackets
|
|
5
|
+
(A1 through B10) point at the synthesis standard the rules come from.
|
|
6
|
+
|
|
7
|
+
For the three load levels (metadata, body, resources), the thin-router pattern, and how to
|
|
8
|
+
split a body into `references/`, read `progressive-disclosure.md`. This file does not repeat
|
|
9
|
+
that model.
|
|
10
|
+
|
|
11
|
+
## Contents
|
|
12
|
+
|
|
13
|
+
- Skill file layout
|
|
14
|
+
- Frontmatter fields
|
|
15
|
+
- The `name` field
|
|
16
|
+
- The `description` field (triggering)
|
|
17
|
+
- Optional frontmatter fields
|
|
18
|
+
- Body voice
|
|
19
|
+
- Body content
|
|
20
|
+
- Self-check
|
|
21
|
+
|
|
22
|
+
## Skill file layout
|
|
23
|
+
|
|
24
|
+
A skill is a directory whose name matches the `name` field. The directory holds `SKILL.md`
|
|
25
|
+
plus optional buckets. Pick a bucket by how the file touches context.
|
|
26
|
+
|
|
27
|
+
| Path | Loads when | Holds |
|
|
28
|
+
|------|-----------|-------|
|
|
29
|
+
| `SKILL.md` | The skill triggers | Frontmatter plus the body |
|
|
30
|
+
| `references/` | The body points at it, on demand | Deep how-to, specs, variant material |
|
|
31
|
+
| `scripts/` | Executed, never read into context | Repeated deterministic code |
|
|
32
|
+
| `assets/` | Copied into output, never read | Templates, boilerplate the output needs |
|
|
33
|
+
| `evals/` | Run by the eval harness | Eval cases for the skill |
|
|
34
|
+
|
|
35
|
+
When picking a bucket, follow `progressive-disclosure.md` for the cross-cutting layout rules:
|
|
36
|
+
references one level deep (C5), no orphan or auxiliary files in the skill (C9), and each fact
|
|
37
|
+
stored once (C7).
|
|
38
|
+
|
|
39
|
+
## Frontmatter fields
|
|
40
|
+
|
|
41
|
+
`SKILL.md` opens with YAML frontmatter. Two fields are required.
|
|
42
|
+
|
|
43
|
+
| Field | Required | Constraints |
|
|
44
|
+
|-------|----------|-------------|
|
|
45
|
+
| `name` | Yes | 1 to 64 chars. Lowercase alphanumeric plus hyphens. No leading, trailing, or consecutive hyphens. Matches the directory name. |
|
|
46
|
+
| `description` | Yes | 1 to 1024 chars. Non-empty. States what and when. |
|
|
47
|
+
|
|
48
|
+
Invent no other top-level fields. Unknown fields are ignored or rejected and add noise [A6].
|
|
49
|
+
|
|
50
|
+
## The `name` field
|
|
51
|
+
|
|
52
|
+
Rules [A5]:
|
|
53
|
+
|
|
54
|
+
1. 64 chars or fewer.
|
|
55
|
+
2. Lowercase letters, numbers, and hyphens only.
|
|
56
|
+
3. No leading, trailing, or consecutive hyphens.
|
|
57
|
+
4. Must match the parent directory name exactly.
|
|
58
|
+
5. Must not contain `anthropic` or `claude`.
|
|
59
|
+
6. Prefer the gerund form, which reads as a capability (`processing-pdfs`, `creating-skills`,
|
|
60
|
+
not `pdf-tool`).
|
|
61
|
+
|
|
62
|
+
## The `description` field (triggering)
|
|
63
|
+
|
|
64
|
+
The description is the only text loaded at discovery time. The body is not loaded when the
|
|
65
|
+
agent decides whether to trigger, so the description alone has to win the match.
|
|
66
|
+
|
|
67
|
+
Rules:
|
|
68
|
+
|
|
69
|
+
1. Write the description as triggering conditions only ("Use when ..."), never as a summary of
|
|
70
|
+
the workflow [A1]. A workflow summary makes the agent act on the summary and skip the body,
|
|
71
|
+
dropping steps. Documented failure: a description that summarized "code review between
|
|
72
|
+
tasks" produced one review instead of two.
|
|
73
|
+
2. Write in the third person [A2]. The description is injected into the system prompt, where
|
|
74
|
+
mixed point of view degrades discovery.
|
|
75
|
+
3. State both what the skill does and when to use it, and front-load concrete trigger terms
|
|
76
|
+
[A3]. The model picks from many skills on this text and the listing is budget-truncated, so
|
|
77
|
+
terms placed late may never be read.
|
|
78
|
+
4. Include at least one indirect trigger where the user does not name the domain [A4]. Real
|
|
79
|
+
prompts rarely name the skill, so a keyword-only description misses oblique requests.
|
|
80
|
+
|
|
81
|
+
Shape:
|
|
82
|
+
|
|
83
|
+
```yaml
|
|
84
|
+
description: >
|
|
85
|
+
[One clause: what it does]. Use when [primary trigger], [secondary trigger],
|
|
86
|
+
or when [indirect trigger where the user does not name the domain].
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
For side-effecting workflows (deploy, release, destructive ops), set
|
|
90
|
+
`disable-model-invocation: true` so only the user fires the skill [A7]. Auto-triggering on
|
|
91
|
+
irreversible work is a known failure mode.
|
|
92
|
+
|
|
93
|
+
## Optional frontmatter fields
|
|
94
|
+
|
|
95
|
+
Use only the documented optionals below. Add nothing beyond them [A6].
|
|
96
|
+
|
|
97
|
+
| Field | Holds |
|
|
98
|
+
|-------|-------|
|
|
99
|
+
| `license` | Short name or filename reference |
|
|
100
|
+
| `compatibility` | Environment requirements, 1 to 500 chars, only when needed |
|
|
101
|
+
| `metadata` | String-to-string map with unique keys |
|
|
102
|
+
| `allowed-tools` | Space-separated tool list (experimental) |
|
|
103
|
+
| `model` | Claude Code extension: pin the model for this skill |
|
|
104
|
+
| `disable-model-invocation` | Claude Code extension: only the user may fire the skill |
|
|
105
|
+
| `context: fork` | Claude Code extension: run the body as a forked task |
|
|
106
|
+
| `paths` | Claude Code extension: scope the skill to matching paths |
|
|
107
|
+
|
|
108
|
+
## Body voice
|
|
109
|
+
|
|
110
|
+
Rules:
|
|
111
|
+
|
|
112
|
+
1. Write in imperative or infinitive voice ("Run the validator", "Extract the text"), never
|
|
113
|
+
second person ("you should", "you can") [B1]. Command voice is shorter and binds tighter;
|
|
114
|
+
hedged second-person language is a known slop pattern.
|
|
115
|
+
2. Use one consistent term per concept ("extract", never also "pull", "get", "retrieve")
|
|
116
|
+
[B6]. Synonym drift makes the agent unsure whether two terms name the same operation.
|
|
117
|
+
|
|
118
|
+
## Body content
|
|
119
|
+
|
|
120
|
+
The body persists in context every turn the skill is active, so each line is a recurring
|
|
121
|
+
cost. Spend lines only where the agent would otherwise go wrong.
|
|
122
|
+
|
|
123
|
+
Rules:
|
|
124
|
+
|
|
125
|
+
1. State what to do, not how or why, and cut anything the model already knows [B2]. Claude is
|
|
126
|
+
already capable; the body is for the project-specific and the non-obvious.
|
|
127
|
+
2. Apply the test "would the agent get this wrong without it?" to every instruction. If no,
|
|
128
|
+
delete it [B7]. Instructions that restate default competence are pure token cost.
|
|
129
|
+
3. Challenge every paragraph with "does this justify its token cost?" and prefer one excellent
|
|
130
|
+
example over many mediocre ones [B3].
|
|
131
|
+
4. Lead each section with one bold maxim and close it with one concrete self-check [B4]. The
|
|
132
|
+
maxim is what survives in context; the check lets the agent apply the rule mid-task.
|
|
133
|
+
5. Put gotchas as concrete corrections, placed early [B5]. A correction only helps if the
|
|
134
|
+
agent reads it before it makes the mistake. Write the specific fact, not general advice:
|
|
135
|
+
|
|
136
|
+
```markdown
|
|
137
|
+
## Gotchas
|
|
138
|
+
- The `users` table uses soft deletes. Queries must include `WHERE deleted_at IS NULL`.
|
|
139
|
+
- User id is `user_id` in the DB, `uid` in auth, `accountId` in billing. Same value.
|
|
140
|
+
- The `/health` endpoint returns 200 even when the DB is down. Use `/ready`.
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
6. Carry reference and decision material in tables and numbered lists. Use a flowchart only
|
|
144
|
+
for a genuinely non-obvious decision or an early-stop loop [B10]. Tables are denser per
|
|
145
|
+
token; flowcharts waste tokens on reference, code, or linear steps.
|
|
146
|
+
7. State the tradeoff and ship an escape hatch instead of policing edge cases with long
|
|
147
|
+
rationalization tables [B8]. Add an Excuse-and-Reality table only for a discipline skill
|
|
148
|
+
where an eval has actually shown the agent rationalizing a violation.
|
|
149
|
+
8. Avoid time-sensitive content. Move deprecated material into a collapsed "Old patterns"
|
|
150
|
+
section [B9]. Dated instructions rot and mislead.
|
|
151
|
+
|
|
152
|
+
## Self-check
|
|
153
|
+
|
|
154
|
+
Before shipping the skill, confirm each line:
|
|
155
|
+
|
|
156
|
+
- [ ] `name` matches the directory, is lowercase-hyphen, and carries no `claude`/`anthropic`.
|
|
157
|
+
- [ ] `description` reads as "Use when ...", third person, with front-loaded triggers and at
|
|
158
|
+
least one indirect trigger.
|
|
159
|
+
- [ ] No invented frontmatter fields; optionals are drawn only from the table above.
|
|
160
|
+
- [ ] Side-effecting skill sets `disable-model-invocation`.
|
|
161
|
+
- [ ] Body is imperative, with one term per concept.
|
|
162
|
+
- [ ] Every instruction passes "would the agent get this wrong without it?".
|
|
163
|
+
- [ ] Gotchas are concrete corrections, placed early.
|
|
164
|
+
- [ ] Reference and decision material is in tables or lists, not prose.
|
|
165
|
+
- [ ] `references/` files are one level deep; no orphan or auxiliary files in the skill.
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# scaffold.rb - born-slim starting files for a new skill, agent, or hook.
|
|
5
|
+
#
|
|
6
|
+
# Plastic skills, agents, and hooks start small and grow by progressive
|
|
7
|
+
# disclosure. This scaffolder emits the minimum valid skeleton so the body
|
|
8
|
+
# stays slim and detail moves into references/ as the work earns it.
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# ruby scaffold.rb skill <name> [--out <dir>] [dest]
|
|
12
|
+
# ruby scaffold.rb agent <name> [--out <dir>] [dest]
|
|
13
|
+
# ruby scaffold.rb hook [<Event>] <name> [--out <dir>] [dest]
|
|
14
|
+
# ruby scaffold.rb --help
|
|
15
|
+
#
|
|
16
|
+
# All input is positional plus flags. There are no interactive prompts, so an
|
|
17
|
+
# agent never hangs waiting on input. The destination defaults to the current
|
|
18
|
+
# directory; pass --out or a trailing positional to choose another directory.
|
|
19
|
+
#
|
|
20
|
+
# Exit codes:
|
|
21
|
+
# 0 success, or --help
|
|
22
|
+
# 1 usage error (unknown subcommand, missing argument)
|
|
23
|
+
# 2 validation error (bad name or event)
|
|
24
|
+
# 3 refused to overwrite an existing target, or a filesystem error
|
|
25
|
+
#
|
|
26
|
+
# This script and every file it emits contain no em-dashes; the emitted files
|
|
27
|
+
# are user-facing.
|
|
28
|
+
|
|
29
|
+
require "json"
|
|
30
|
+
require "fileutils"
|
|
31
|
+
|
|
32
|
+
EXIT_OK = 0
|
|
33
|
+
EXIT_USAGE = 1
|
|
34
|
+
EXIT_VALIDATION = 2
|
|
35
|
+
EXIT_CONFLICT = 3
|
|
36
|
+
|
|
37
|
+
NAME_PATTERN = /\A[a-z0-9]+(-[a-z0-9]+)*\z/.freeze
|
|
38
|
+
EVENT_PATTERN = /\A[A-Z][A-Za-z]+\z/.freeze
|
|
39
|
+
DEFAULT_EVENT = "PostToolUse"
|
|
40
|
+
|
|
41
|
+
USAGE = <<~TEXT
|
|
42
|
+
scaffold.rb - born-slim starting files for a new skill, agent, or hook.
|
|
43
|
+
|
|
44
|
+
Usage:
|
|
45
|
+
ruby scaffold.rb skill <name> [--out <dir>] [dest]
|
|
46
|
+
ruby scaffold.rb agent <name> [--out <dir>] [dest]
|
|
47
|
+
ruby scaffold.rb hook [<Event>] <name> [--out <dir>] [dest]
|
|
48
|
+
ruby scaffold.rb --help
|
|
49
|
+
|
|
50
|
+
Subcommands:
|
|
51
|
+
skill Emit <dest>/<name>/SKILL.md, references/.gitkeep, evals/evals.json.
|
|
52
|
+
agent Emit <dest>/<name>.md agent role file.
|
|
53
|
+
hook Emit <dest>/<name> no-op hook handler (Ruby) wired for <Event>.
|
|
54
|
+
|
|
55
|
+
Names must be lowercase alphanumeric and hyphens, with no leading,
|
|
56
|
+
trailing, or repeated hyphens, 1 to 64 characters.
|
|
57
|
+
The hook <Event> is optional and defaults to #{DEFAULT_EVENT}.
|
|
58
|
+
The destination defaults to the current directory.
|
|
59
|
+
|
|
60
|
+
Exit codes:
|
|
61
|
+
0 success, or --help
|
|
62
|
+
1 usage error (unknown subcommand, missing argument)
|
|
63
|
+
2 validation error (bad name or event)
|
|
64
|
+
3 refused to overwrite an existing target, or a filesystem error
|
|
65
|
+
TEXT
|
|
66
|
+
|
|
67
|
+
# A small error that carries the exit code to use when it reaches the top.
|
|
68
|
+
class ScaffoldError < StandardError
|
|
69
|
+
attr_reader :code
|
|
70
|
+
|
|
71
|
+
def initialize(message, code)
|
|
72
|
+
super(message)
|
|
73
|
+
@code = code
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def fail_with(message, code)
|
|
78
|
+
raise ScaffoldError.new(message, code)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def validate_name!(name)
|
|
82
|
+
if name.nil? || name.empty?
|
|
83
|
+
fail_with("missing <name>. See --help for usage.", EXIT_USAGE)
|
|
84
|
+
end
|
|
85
|
+
unless name.length <= 64 && name =~ NAME_PATTERN
|
|
86
|
+
fail_with(
|
|
87
|
+
"invalid name #{name.inspect}: use lowercase letters, digits, and " \
|
|
88
|
+
"hyphens, no leading, trailing, or repeated hyphens, 1 to 64 chars.",
|
|
89
|
+
EXIT_VALIDATION
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
name
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def validate_event!(event)
|
|
96
|
+
unless event =~ EVENT_PATTERN
|
|
97
|
+
fail_with(
|
|
98
|
+
"invalid event #{event.inspect}: use a CamelCase hook event name, " \
|
|
99
|
+
"for example PostToolUse or SessionStart.",
|
|
100
|
+
EXIT_VALIDATION
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
event
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Separate --out and any trailing positional dest from the bare positionals.
|
|
107
|
+
# Returns [positionals, out_dir]. out_dir is nil when not given.
|
|
108
|
+
def parse_args(args)
|
|
109
|
+
positionals = []
|
|
110
|
+
out_dir = nil
|
|
111
|
+
i = 0
|
|
112
|
+
while i < args.length
|
|
113
|
+
arg = args[i]
|
|
114
|
+
case arg
|
|
115
|
+
when "--out"
|
|
116
|
+
out_dir = args[i + 1]
|
|
117
|
+
if out_dir.nil?
|
|
118
|
+
fail_with("--out needs a directory argument.", EXIT_USAGE)
|
|
119
|
+
end
|
|
120
|
+
i += 2
|
|
121
|
+
else
|
|
122
|
+
if arg.start_with?("--")
|
|
123
|
+
fail_with("unknown option #{arg.inspect}. See --help for usage.", EXIT_USAGE)
|
|
124
|
+
end
|
|
125
|
+
positionals << arg
|
|
126
|
+
i += 1
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
[positionals, out_dir]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def refuse_if_exists!(path)
|
|
133
|
+
if File.exist?(path)
|
|
134
|
+
fail_with("refusing to overwrite existing #{path}. Remove it or pick another destination.", EXIT_CONFLICT)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def write_file(path, content)
|
|
139
|
+
refuse_if_exists!(path)
|
|
140
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
141
|
+
File.write(path, content)
|
|
142
|
+
puts "created #{path}"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# -- generators ----------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
def skill_body(name)
|
|
148
|
+
<<~MD
|
|
149
|
+
---
|
|
150
|
+
name: #{name}
|
|
151
|
+
description: >
|
|
152
|
+
Use when the user needs #{name}. State here WHEN this skill should
|
|
153
|
+
trigger, in the third person, so the agent can match it. Replace this
|
|
154
|
+
placeholder with one or two concrete trigger conditions before shipping.
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
# #{name}
|
|
158
|
+
|
|
159
|
+
One line on what this skill does and why it exists.
|
|
160
|
+
|
|
161
|
+
## Gotchas
|
|
162
|
+
|
|
163
|
+
- List the non-obvious failure modes here, one per line.
|
|
164
|
+
|
|
165
|
+
## Tasks
|
|
166
|
+
|
|
167
|
+
Keep the body slim. Route detail to references as the work earns it.
|
|
168
|
+
|
|
169
|
+
| Task | Reference |
|
|
170
|
+
| ---- | --------- |
|
|
171
|
+
| Replace this row with a real task | references/REPLACE-ME.md |
|
|
172
|
+
MD
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def eval_stub(name)
|
|
176
|
+
data = {
|
|
177
|
+
"skill_name" => name,
|
|
178
|
+
"evals" => [
|
|
179
|
+
{
|
|
180
|
+
"id" => 1,
|
|
181
|
+
"prompt" => "",
|
|
182
|
+
"expected_output" => "",
|
|
183
|
+
"files" => [],
|
|
184
|
+
"assertions" => []
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
JSON.pretty_generate(data) + "\n"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def generate_skill(name, out_dir)
|
|
192
|
+
base = File.join(out_dir, name)
|
|
193
|
+
refuse_if_exists!(base)
|
|
194
|
+
write_file(File.join(base, "SKILL.md"), skill_body(name))
|
|
195
|
+
write_file(File.join(base, "references", ".gitkeep"), "")
|
|
196
|
+
write_file(File.join(base, "evals", "evals.json"), eval_stub(name))
|
|
197
|
+
puts "skill scaffold ready at #{base}"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def agent_body(name)
|
|
201
|
+
<<~MD
|
|
202
|
+
---
|
|
203
|
+
name: #{name}
|
|
204
|
+
description: >
|
|
205
|
+
Use this agent when the user needs #{name}. State here WHEN to delegate
|
|
206
|
+
to this agent, in the third person, so the orchestrator can route to it.
|
|
207
|
+
Replace this placeholder with concrete delegation conditions.
|
|
208
|
+
tools: Read, Edit, Bash
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
You are the #{name} agent.
|
|
212
|
+
|
|
213
|
+
## Responsibilities
|
|
214
|
+
|
|
215
|
+
- One line per durable responsibility this agent owns.
|
|
216
|
+
|
|
217
|
+
## How you work
|
|
218
|
+
|
|
219
|
+
Keep this body slim. State the contract, the inputs, and the outputs.
|
|
220
|
+
Move long procedures into a references file as the work earns it.
|
|
221
|
+
|
|
222
|
+
## Completion
|
|
223
|
+
|
|
224
|
+
End your turn with a short report of what you did and how you verified it.
|
|
225
|
+
MD
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def generate_agent(name, out_dir)
|
|
229
|
+
write_file(File.join(out_dir, "#{name}.md"), agent_body(name))
|
|
230
|
+
puts "agent scaffold ready at #{File.join(out_dir, "#{name}.md")}"
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def hook_body(name, event)
|
|
234
|
+
<<~RUBY
|
|
235
|
+
#!/usr/bin/env ruby
|
|
236
|
+
# frozen_string_literal: true
|
|
237
|
+
|
|
238
|
+
# #{name} - #{event} hook handler (no-op by default).
|
|
239
|
+
#
|
|
240
|
+
# This skeleton does nothing until you opt in. Set the environment flag
|
|
241
|
+
# below to a non-empty value to activate the real behavior. Until then it
|
|
242
|
+
# exits 0 so it never blocks the session.
|
|
243
|
+
#
|
|
244
|
+
# Wire it in settings.json under hooks.#{event}:
|
|
245
|
+
# { "type": "command", "command": "ruby /absolute/path/to/#{name}" }
|
|
246
|
+
#
|
|
247
|
+
# Exit codes:
|
|
248
|
+
# 0 no-op, or success
|
|
249
|
+
# 2 block the action (only when you add real logic that should block)
|
|
250
|
+
|
|
251
|
+
OPT_IN = "#{name.tr("-", "_").upcase}_ENABLED"
|
|
252
|
+
|
|
253
|
+
if ENV[OPT_IN].nil? || ENV[OPT_IN].empty?
|
|
254
|
+
exit 0
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Opt-in is set. Add the real handler here. The hook receives event JSON on
|
|
258
|
+
# stdin; parse it only when you need it. Exit 0 to allow, exit 2 to block.
|
|
259
|
+
exit 0
|
|
260
|
+
RUBY
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def generate_hook(name, event, out_dir)
|
|
264
|
+
path = File.join(out_dir, name)
|
|
265
|
+
write_file(path, hook_body(name, event))
|
|
266
|
+
FileUtils.chmod("+x", path)
|
|
267
|
+
puts "hook scaffold ready at #{path} (executable, #{event}, no-op until opt-in)"
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# -- dispatch ------------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
def run(argv)
|
|
273
|
+
if argv.empty? || argv.include?("--help") || argv.include?("-h")
|
|
274
|
+
puts USAGE
|
|
275
|
+
return EXIT_OK
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
subcommand = argv.shift
|
|
279
|
+
positionals, out_flag = parse_args(argv)
|
|
280
|
+
|
|
281
|
+
case subcommand
|
|
282
|
+
when "skill", "agent"
|
|
283
|
+
name = validate_name!(positionals[0])
|
|
284
|
+
out_dir = out_flag || positionals[1] || "."
|
|
285
|
+
subcommand == "skill" ? generate_skill(name, out_dir) : generate_agent(name, out_dir)
|
|
286
|
+
when "hook"
|
|
287
|
+
# Accept "hook <name>" or "hook <Event> <name>", with an optional trailing dest.
|
|
288
|
+
if positionals.length >= 2 && positionals[0] =~ EVENT_PATTERN
|
|
289
|
+
event = validate_event!(positionals[0])
|
|
290
|
+
name = validate_name!(positionals[1])
|
|
291
|
+
out_dir = out_flag || positionals[2] || "."
|
|
292
|
+
else
|
|
293
|
+
event = DEFAULT_EVENT
|
|
294
|
+
name = validate_name!(positionals[0])
|
|
295
|
+
out_dir = out_flag || positionals[1] || "."
|
|
296
|
+
end
|
|
297
|
+
generate_hook(name, event, out_dir)
|
|
298
|
+
else
|
|
299
|
+
fail_with("unknown subcommand #{subcommand.inspect}. See --help for usage.", EXIT_USAGE)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
EXIT_OK
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
begin
|
|
306
|
+
exit run(ARGV.dup)
|
|
307
|
+
rescue ScaffoldError => e
|
|
308
|
+
warn "scaffold.rb: #{e.message}"
|
|
309
|
+
exit e.code
|
|
310
|
+
rescue Errno::EACCES, Errno::ENOENT, Errno::EEXIST => e
|
|
311
|
+
warn "scaffold.rb: filesystem error: #{e.message}"
|
|
312
|
+
exit EXIT_CONFLICT
|
|
313
|
+
end
|
|
@@ -67,6 +67,11 @@ instead of showing it.
|
|
|
67
67
|
|
|
68
68
|
### Step 4 — Entry flow (the board is the menu)
|
|
69
69
|
|
|
70
|
+
QMD-first (when available): when the user navigates by describing an intent rather than giving its
|
|
71
|
+
id, before scanning the store with grep/Read run `ruby ~/.plastic/scripts/qmd-sync search "<terms>"`
|
|
72
|
+
to surface the candidate intent, then open the authoritative intent file for the hit. The command is
|
|
73
|
+
a no-op when QMD is absent, so fall back to the existing INDEX.md / file scan.
|
|
74
|
+
|
|
70
75
|
The board lists everything; the user navigates by free prose (no capped picker):
|
|
71
76
|
- On the **global** board, the user replies with an **intent id** (work it), a **project
|
|
72
77
|
name** (re-run `project <slug> --data` and present that board), or **"new"** (start a new
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skill_name": "plastic-dashboard",
|
|
3
|
+
"notes": "Intent 66a. Spec for the QMD-first step in the entry flow (resolve a free-prose intent reference before grep/Read). Runner is intent 76; spec only.",
|
|
4
|
+
"evals": [
|
|
5
|
+
{
|
|
6
|
+
"id": 1,
|
|
7
|
+
"scope": "behavior",
|
|
8
|
+
"set": "validation",
|
|
9
|
+
"prompt": "QMD is present. On the board, the user navigates by describing an intent (the search verb work) instead of giving its id.",
|
|
10
|
+
"expected_output": "Before scanning the store with grep/Read to resolve the free-prose reference, runs `ruby ~/.plastic/scripts/qmd-sync search \"search verb\"` to surface the candidate intent, then opens the authoritative intent file for the hit. No-op fallback to INDEX.md / file scan when QMD is absent.",
|
|
11
|
+
"files": [],
|
|
12
|
+
"assertions": [
|
|
13
|
+
{
|
|
14
|
+
"type": "human",
|
|
15
|
+
"check": "qmd-sync search is run before grep/Read when the user navigates by description; authoritative file opened for the hit",
|
|
16
|
+
"observed": "SKILL.md (or agent file) carries the QMD-first step: run qmd-sync search before grep/Read, then open the authoritative file; no-op fallback when QMD is absent",
|
|
17
|
+
"result": "pass"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -75,7 +75,7 @@ Capture observations in `## Insights`. When ALL checklist items are checked:
|
|
|
75
75
|
3. Move intent from `## Active` to `## Completed` in INDEX.md (with today's date)
|
|
76
76
|
4. Update cluster entries to show `_(completed)_`
|
|
77
77
|
5. Auto-commit: `cd <store-root> && git add . && git commit -m "feat: complete intent <ID> — <name>"`
|
|
78
|
-
6.
|
|
78
|
+
6. On completion, ALWAYS refresh the QMD search index for this store (no-op when QMD is absent), running in the background so it never blocks the turn: `ruby ~/.plastic/scripts/qmd-sync reindex --store <store-root> --async`. Completion is the lifecycle event that keeps the search index fresh.
|
|
79
79
|
|
|
80
80
|
**This is NOT optional.** An intent with all checklist items done but no Outcome is a broken state. Complete the intent immediately — do not leave it for later.
|
|
81
81
|
|
|
@@ -101,7 +101,7 @@ Capture observations in `## Insights`. When ALL checklist items are checked:
|
|
|
101
101
|
3. Move intent from `## Active` to `## Completed` in INDEX.md (with today's date)
|
|
102
102
|
4. Update cluster entries to show `_(completed)_`
|
|
103
103
|
5. Auto-commit: `cd <store-root> && git add . && git commit -m "feat: complete intent <ID> — <name>"`
|
|
104
|
-
6.
|
|
104
|
+
6. On completion, ALWAYS refresh the QMD search index for this store (no-op when QMD is absent), running in the background so it never blocks the turn: `ruby ~/.plastic/scripts/qmd-sync reindex --store <store-root> --async`. Completion is the lifecycle event that keeps the search index fresh.
|
|
105
105
|
|
|
106
106
|
**This is NOT optional.** Complete the intent immediately when work is done.
|
|
107
107
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: plastic-humanizer
|
|
3
|
+
description: Use to clean authored prose so it reads human and clutter-free. Removes AI tells and slop from a document, spec, outcome, README, report, or release note. Use when the user says "humanize", "de-slop", "remove the AI tells", or "clean up the wording". For documents, not for every chat reply, and not for code.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Humanizer
|
|
7
|
+
|
|
8
|
+
Make writing read like a person wrote it: plain, direct, low-clutter. Run this as a pass over authored prose (specs, outcomes, README, reports). Do not run it on code or on every chat turn.
|
|
9
|
+
|
|
10
|
+
## Lead (house style)
|
|
11
|
+
Reframe your answers and any questions you have for me like you are speaking to a well-educated non-English native person. Reduce slang, and rarely used words and terms in the general communication. And cut all the fancy words from explanations. Technical terms and concepts are fine and need no gloss.
|
|
12
|
+
|
|
13
|
+
## Voice
|
|
14
|
+
Answer like a busy bartender or a senior support engineer who has seen almost every ticket. Help fast, give the fix first, earn the tip.
|
|
15
|
+
|
|
16
|
+
## Job 1 - remove these surface tells
|
|
17
|
+
1. Em-dashes and en-dashes - use a comma or a full stop.
|
|
18
|
+
2. "Not X but Y" (and "it's not just X, it's Y").
|
|
19
|
+
3. Rule of three - three items only for rhythm.
|
|
20
|
+
4. Hype / AI words - delve, robust, comprehensive, seamless, leverage, crucial, unlock, landscape.
|
|
21
|
+
5. Filler openers / signposting - "It's worth noting", "It's important to", "Let's dive in".
|
|
22
|
+
6. Hedging pile-up - might, could, perhaps, generally, when not needed.
|
|
23
|
+
7. Sycophancy - "Great question", "You're absolutely right".
|
|
24
|
+
8. Over-bolding - bold only what carries weight.
|
|
25
|
+
|
|
26
|
+
## Job 2 - fix the structure
|
|
27
|
+
- Lead with the one main point.
|
|
28
|
+
- Cut sentences that only restate.
|
|
29
|
+
- Pick concrete words over abstract ones.
|
|
30
|
+
- Match the user's voice when samples of their writing exist.
|
|
31
|
+
|
|
32
|
+
## Process
|
|
33
|
+
Write, check once against the rules above, then send. On documents, run this pass last.
|
|
34
|
+
|
|
35
|
+
## Length
|
|
36
|
+
Keep it short. If a sentence does not help the reader, cut it.
|
|
37
|
+
|
|
38
|
+
## More
|
|
39
|
+
For before/after examples, read `references/examples.md`. To make the house style always-on in chat, see `references/always-on-snippet.md`. The full 33-pattern catalog and the research behind this skill live in intent 92's `resources/`. They are background and are not loaded here.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skill_name": "plastic-humanizer",
|
|
3
|
+
"notes": "Intent 92. Cleans authored prose: removes 8 surface tells and applies structural levers (one point, cut restatement, concrete words, match voice). Triggers on document-cleanup asks, not on code. Runner is intent 76; assertions are human for now.",
|
|
4
|
+
"evals": [
|
|
5
|
+
{
|
|
6
|
+
"id": 1,
|
|
7
|
+
"scope": "trigger",
|
|
8
|
+
"set": "validation",
|
|
9
|
+
"prompt": "Humanize this README - it reads like AI wrote it.",
|
|
10
|
+
"expected_output": "Selects plastic-humanizer and runs the cleanup pass over the document.",
|
|
11
|
+
"files": [],
|
|
12
|
+
"assertions": [
|
|
13
|
+
{
|
|
14
|
+
"type": "human",
|
|
15
|
+
"check": "plastic-humanizer is selected for a document-cleanup request",
|
|
16
|
+
"observed": "description triggers on 'humanize' / 'reads like AI' for a document",
|
|
17
|
+
"result": "pass"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": 2,
|
|
23
|
+
"scope": "trigger",
|
|
24
|
+
"set": "validation",
|
|
25
|
+
"prompt": "Clean up the wording in this spec.",
|
|
26
|
+
"expected_output": "Selects plastic-humanizer and cleans the spec prose.",
|
|
27
|
+
"files": [],
|
|
28
|
+
"assertions": [
|
|
29
|
+
{
|
|
30
|
+
"type": "human",
|
|
31
|
+
"check": "plastic-humanizer is selected for 'clean up the wording'",
|
|
32
|
+
"observed": "description triggers on 'clean up the wording'",
|
|
33
|
+
"result": "pass"
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": 3,
|
|
39
|
+
"scope": "non-trigger",
|
|
40
|
+
"set": "validation",
|
|
41
|
+
"prompt": "Fix the failing test in foo.rb.",
|
|
42
|
+
"expected_output": "Does NOT select plastic-humanizer; this is a code task, not prose cleanup.",
|
|
43
|
+
"files": [],
|
|
44
|
+
"assertions": [
|
|
45
|
+
{
|
|
46
|
+
"type": "human",
|
|
47
|
+
"check": "plastic-humanizer is not selected for a code task",
|
|
48
|
+
"observed": "description scopes the skill to documents, not code",
|
|
49
|
+
"result": "pass"
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": 4,
|
|
55
|
+
"scope": "output",
|
|
56
|
+
"set": "validation",
|
|
57
|
+
"prompt": "Given a slop sample: 'It's worth noting that this is not just a fix, but a comprehensive, robust, and seamless improvement that will unlock real value.' Humanize it.",
|
|
58
|
+
"expected_output": "Cleaned text has no em-dashes and none of the 8 tells, leads with one point, and drops restatement. For example: 'This fix makes X faster.'",
|
|
59
|
+
"files": [],
|
|
60
|
+
"assertions": [
|
|
61
|
+
{
|
|
62
|
+
"type": "human",
|
|
63
|
+
"check": "output drops the 8 tells, leads with one point, cuts restatement",
|
|
64
|
+
"observed": "no em-dashes; no 'not X but Y'; no rule-of-three; no hype words; no filler opener; one load-bearing point",
|
|
65
|
+
"result": "pass"
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Always-on chat style (copy-paste)
|
|
2
|
+
|
|
3
|
+
Paste this into your global `CLAUDE.md` (applies to all projects) or a project `AGENTS.md` (one project). It keeps the chat style on at near-zero cost. The full cleanup for documents is the `plastic-humanizer` skill.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
Reframe your answers and any questions like you are speaking to a well-educated person who is not a native English speaker. Reduce slang and rarely used words. Cut fancy words from explanations. Technical terms and concepts are fine.
|
|
7
|
+
|
|
8
|
+
Answer like a busy bartender or a senior support engineer who has seen almost every ticket: help fast, give the fix first, earn the tip.
|
|
9
|
+
```
|