continuous-improvement 3.15.0 → 3.16.0
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/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +14 -0
- package/README.md +4 -3
- package/bin/check-skill-count-prose.mjs +168 -0
- package/bin/install.mjs +46 -1
- package/bin/lint-transcript.mjs +15 -3
- package/bin/mcp-server.mjs +1 -0
- package/commands/roast.md +34 -0
- package/hooks/workflow-distill.mjs +145 -0
- package/lib/plugin-metadata.mjs +8 -3
- package/lib/version-check.mjs +115 -0
- package/llms.txt +1 -1
- package/package.json +4 -3
- package/plugins/beginner.json +1 -1
- package/plugins/continuous-improvement/.claude-plugin/marketplace.json +2 -2
- package/plugins/continuous-improvement/.claude-plugin/plugin.json +2 -2
- package/plugins/continuous-improvement/bin/mcp-server.mjs +1 -0
- package/plugins/continuous-improvement/commands/roast.md +34 -0
- package/plugins/continuous-improvement/hooks/hooks.json +6 -1
- package/plugins/continuous-improvement/hooks/workflow-distill.mjs +145 -0
- package/plugins/continuous-improvement/lib/plugin-metadata.mjs +8 -3
- package/plugins/continuous-improvement/skills/README.md +1 -0
- package/plugins/continuous-improvement/skills/roast/SKILL.md +108 -0
- package/plugins/continuous-improvement/skills/strategic-compact/SKILL.md +12 -32
- package/plugins/expert.json +1 -1
- package/skills/README.md +1 -1
- package/skills/roast.md +108 -0
- package/skills/strategic-compact.md +12 -32
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
"plugins": [
|
|
8
8
|
{
|
|
9
9
|
"name": "continuous-improvement",
|
|
10
|
-
"description": "The persistent-memory and runtime-discipline layer for Claude Code. It remembers the corrections you already gave, grounds every edit in real facts before it lands, and — through the Mulahazah engine — turns each fix into a reusable instinct, so a lesson learned once is applied automatically next time with no re-teaching. Built on the 7 Laws of AI Agent Discipline (research, plan, verify, reflect, learn) and shipped as
|
|
11
|
-
"version": "3.
|
|
10
|
+
"description": "The persistent-memory and runtime-discipline layer for Claude Code. It remembers the corrections you already gave, grounds every edit in real facts before it lands, and — through the Mulahazah engine — turns each fix into a reusable instinct, so a lesson learned once is applied automatically next time with no re-teaching. Built on the 7 Laws of AI Agent Discipline (research, plan, verify, reflect, learn) and shipped as 26 bundled skills, instinct-aware hooks, an MCP toolset for recall and reflection, and a GitHub Action transcript linter that feeds real work history back into sharper instincts.",
|
|
11
|
+
"version": "3.16.0",
|
|
12
12
|
"source": "./plugins/continuous-improvement",
|
|
13
13
|
"author": {
|
|
14
14
|
"name": "naimkatiman"
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,20 @@ All notable changes to this skill are documented here.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [3.16.0] — 2026-06-27
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`/roast` — a 5-persona adversarial idea council that pressure-tests a plan before any code is written** — routes a proposed idea or plan through five distinct critic personas (each attacking a different failure axis) and reconciles their objections into a go / revise / kill verdict. Catches misalignment at Law 1 (Research) before it lands in the diff. Brings the bundle to 26 skills. (#255, #256)
|
|
12
|
+
- **npm-install users get nudged when a newer version is published** — the installer compares the running version against the registry `latest` and surfaces an upgrade hint instead of silently running stale. (#252)
|
|
13
|
+
- **Hook nudge to distill a verified Workflow run into a reusable instinct** — a completed multi-agent Workflow's verified trajectory no longer evaporates with the session; the agent is prompted to capture it as a draft instinct. (#251)
|
|
14
|
+
- **`lint-transcript` GitHub Action IO + fleet docs** — the transcript linter ships defined Action inputs/outputs for CI wiring, with fleet-usage documentation. (#250)
|
|
15
|
+
- **Skill use-case decision guide** — a when-to-reach-for-each-skill map so the right skill is picked without scanning all of them. (#254)
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **`skill-count` prose is now gated against the actual bundle** — the verify suite fails if any doc claims a skill count that disagrees with the shipped skill set; synced all prose to 26 after `/roast`. (#256)
|
|
20
|
+
|
|
7
21
|
## [3.15.0] — 2026-06-21
|
|
8
22
|
|
|
9
23
|
### Added
|
package/README.md
CHANGED
|
@@ -82,7 +82,7 @@ You have used Claude Code (or any agentic coding tool) long enough to recognize
|
|
|
82
82
|
|
|
83
83
|
| # | Failure mode | What you see | Which Law fires | What enforces it |
|
|
84
84
|
|---|---|---|---|---|
|
|
85
|
-
| 1 | **Misalignment** | The agent doesn't do what you want — invents requirements, reinvents helpers that already exist | Law 1 (Research) | [`grill-me`](skills/grill-me.md), [`grill-with-docs`](skills/grill-with-docs.md), [`gateguard`](skills/gateguard.md), [`workspace-surface-audit`](skills/workspace-surface-audit.md) |
|
|
85
|
+
| 1 | **Misalignment** | The agent doesn't do what you want — invents requirements, reinvents helpers that already exist, or builds the wrong thing before anyone challenged the idea | Law 1 (Research) | [`roast`](skills/roast.md), [`grill-me`](skills/grill-me.md), [`grill-with-docs`](skills/grill-with-docs.md), [`gateguard`](skills/gateguard.md), [`workspace-surface-audit`](skills/workspace-surface-audit.md) |
|
|
86
86
|
| 2 | **No shared language** | The agent uses 20 words where 1 would do; jargon decoded fresh every session; variable names drift from domain terms | Law 2 (Plan), Law 7 (Learn) | [`grill-with-docs`](skills/grill-with-docs.md) (writes & maintains `CONTEXT.md`), [`token-budget-advisor`](skills/token-budget-advisor.md), [`strategic-compact`](skills/strategic-compact.md) |
|
|
87
87
|
| 3 | **No feedback loop** | The code doesn't work — agent claims "done" without running build, tests, or healthcheck | Law 4 (Verify) | [`tdd-workflow`](skills/tdd-workflow.md), [`verification-loop`](skills/verification-loop.md), [`deploy-receipt`](skills/deploy-receipt.md) |
|
|
88
88
|
| 4 | **Design rot** | Ball-of-mud accelerates — agent bundles three concerns into one PR, stacks untested changes, ignores prior architectural decisions | Law 2 (Plan), Law 3 (One Thing) | [`superpowers:writing-plans`](https://github.com/obra/superpowers/blob/main/skills/writing-plans/SKILL.md), [`safety-guard`](skills/safety-guard.md), [`worktree-safety`](skills/worktree-safety.md), [`wild-risa-balance`](skills/wild-risa-balance.md) |
|
|
@@ -149,7 +149,7 @@ npx continuous-improvement install --pack react # optional: react | python | g
|
|
|
149
149
|
```
|
|
150
150
|
|
|
151
151
|
Verify: run `/dashboard` in Claude Code — you should see instinct health and observation count.
|
|
152
|
-
Update later with `/plugin marketplace update continuous-improvement` or by re-running the npx command.
|
|
152
|
+
Update later with `/plugin marketplace update continuous-improvement` or by re-running the npx command. When you run the npx installer, it makes one throttled, fail-open read of the public npm registry and prints a one-line notice if a newer version is published (no telemetry — nothing about you is sent). Silence it with `CLAUDE_CI_UPDATE_CHECK=off`.
|
|
153
153
|
|
|
154
154
|
### Troubleshooting install
|
|
155
155
|
|
|
@@ -172,6 +172,7 @@ The framework has documented operator-level modes that change hook behavior with
|
|
|
172
172
|
| `CLAUDE_THREE_SECTION_CLOSE_DISABLED=1` | `three-section-close.mjs` short-circuits before any enforcement or telemetry. Use when end-of-turn reflection should run as internal thinking rather than visible "What has been done / What is next / Recommendation" sections. Public default unchanged — the rule still fires for everyone else. | bash/zsh: `export CLAUDE_THREE_SECTION_CLOSE_DISABLED=1` in `~/.bashrc` / `~/.zshrc`. PowerShell: `$env:CLAUDE_THREE_SECTION_CLOSE_DISABLED=1` (session) or `[Environment]::SetEnvironmentVariable('CLAUDE_THREE_SECTION_CLOSE_DISABLED','1','User')` (persistent). |
|
|
173
173
|
| `CLAUDE_GOAL_DRIFT_GATE` | `goal-drift-stop.mjs` (a `Stop` hook) scores each turn's activity against the `## Goal` in `task_plan.md` and acts on drift. `warn` (default) prints a one-line stderr notice and never blocks; `block` re-prompts a substantive wrap-up that has drifted off-goal so the goal gates the close; `off` disables it. Reads the same observation feed as Mulahazah; fails open on any error. | bash/zsh: `export CLAUDE_GOAL_DRIFT_GATE=block` in `~/.bashrc` / `~/.zshrc`. PowerShell: `$env:CLAUDE_GOAL_DRIFT_GATE='block'` (session) or `[Environment]::SetEnvironmentVariable('CLAUDE_GOAL_DRIFT_GATE','block','User')` (persistent). |
|
|
174
174
|
| `CLAUDE_RECALL_BRIEFING=1` | `hooks/recall-briefing.mjs` (a UserPromptSubmit hook) makes episodic memory proactive: on the first substantive prompt of a session it searches this project's past observations (BM25) and injects a one-time `<system-reminder>` with the most relevant prior activity, so the agent reuses a past fix instead of re-deriving it. Opt-in and default off; it is an amplifier, never a gate — it cannot block a prompt and fails open. The `ci_recall` MCP tool stays available for explicit, deeper searches. | bash/zsh: `export CLAUDE_RECALL_BRIEFING=1` in `~/.bashrc` / `~/.zshrc`. PowerShell: `$env:CLAUDE_RECALL_BRIEFING=1` (session) or `[Environment]::SetEnvironmentVariable('CLAUDE_RECALL_BRIEFING','1','User')` (persistent). |
|
|
175
|
+
| `CLAUDE_WORKFLOW_DISTILL_NUDGE=on` | `hooks/workflow-distill.mjs` (a `Stop` hook) closes the orchestration-to-memory loop: when a native Workflow run's output then passed a verify in the same session, it prints a one-line stderr nudge to run the `ci_distill_from_workflow` MCP tool, so an expensive multi-agent run leaves a durable Mulahazah draft instinct instead of evaporating. `on` enables it; default (unset or any other value) is off. Opt-in amplifier, never a gate — it cannot block the Stop, dedupes per run, and fails open. | bash/zsh: `export CLAUDE_WORKFLOW_DISTILL_NUDGE=on` in `~/.bashrc` / `~/.zshrc`. PowerShell: `$env:CLAUDE_WORKFLOW_DISTILL_NUDGE='on'` (session) or `[Environment]::SetEnvironmentVariable('CLAUDE_WORKFLOW_DISTILL_NUDGE','on','User')` (persistent). |
|
|
175
176
|
|
|
176
177
|
### Works with other agents
|
|
177
178
|
|
|
@@ -260,7 +261,7 @@ All 18 ship in the marketplace bundle. The Beginner install gets all of them —
|
|
|
260
261
|
|
|
261
262
|
## Skills
|
|
262
263
|
|
|
263
|
-
The plugin ships **
|
|
264
|
+
The plugin ships **26 skills** — 1 core + 1 featured + 6 tier-1 + 15 tier-2 + 3 always-bundled. Beginner install gets tier-1, featured, and the always-bundled companion; Expert adds tier-2, the MCP server, and observation hooks. Full catalog with per-skill descriptions, Law tagging, and drop-in single-file install: [docs/skills.md](docs/skills.md). Adding a 27th skill: [CONTRIBUTING.md § Evolution — adding a new skill](CONTRIBUTING.md#evolution--adding-a-new-skill).
|
|
264
265
|
|
|
265
266
|
---
|
|
266
267
|
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Skill Count Prose Check
|
|
4
|
+
*
|
|
5
|
+
* Sibling to `verify:skill-count`. That lint gates the literal phrase
|
|
6
|
+
* "N bundled skills" in the 5 generator-owned manifest surfaces. This lint
|
|
7
|
+
* gates the *human-facing* prose — the phrasings the marketing/docs copy
|
|
8
|
+
* actually uses, which no other invariant checks and which therefore rot
|
|
9
|
+
* silently every time a skill is added without a full prose cascade.
|
|
10
|
+
*
|
|
11
|
+
* Origin: PR #255 (`/roast`, the 26th skill) bumped the gated "N bundled
|
|
12
|
+
* skills" manifests but left README.md / docs/skills.md / docs/skill-use-cases.md
|
|
13
|
+
* / CONTRIBUTING.md / docs/using-this-plugin.md at "25 skills / 14 tier-2".
|
|
14
|
+
* verify:all stayed green because nothing checked those surfaces.
|
|
15
|
+
* `reports/daily-improvement.md` logs three prior rounds of the same drift.
|
|
16
|
+
*
|
|
17
|
+
* Single source of truth: the skill set on disk. The total and the
|
|
18
|
+
* core/featured/tier-1/tier-2/companion breakdown are *derived* from
|
|
19
|
+
* `skills/*.md` + the root `SKILL.md` frontmatter via the same primitives the
|
|
20
|
+
* tier lint uses (`discoverSkillSources`, `parseSkillFrontmatter`,
|
|
21
|
+
* `normalizeTier`). Nothing is hardcoded — add or retire a skill and this lint
|
|
22
|
+
* recomputes the expected numbers, then names every doc that disagrees.
|
|
23
|
+
*
|
|
24
|
+
* Checked surfaces (file → which derived claims it must contain):
|
|
25
|
+
* - README.md breakdown, total-skills, next-ordinal
|
|
26
|
+
* - docs/skills.md breakdown, total-skills, total-catalog, next-ordinal
|
|
27
|
+
* - docs/skill-use-cases.md breakdown, total-skills
|
|
28
|
+
* - CONTRIBUTING.md total-skills
|
|
29
|
+
* - docs/using-this-plugin.md total-catalog
|
|
30
|
+
*
|
|
31
|
+
* Usage:
|
|
32
|
+
* node bin/check-skill-count-prose.mjs # Check the current repo
|
|
33
|
+
* node bin/check-skill-count-prose.mjs <repo-root> # Check a specific repo root
|
|
34
|
+
*
|
|
35
|
+
* Exit codes:
|
|
36
|
+
* 0 — every checked surface states the bundle-derived count
|
|
37
|
+
* 1 — at least one surface states a stale count, or a surface file is missing
|
|
38
|
+
*/
|
|
39
|
+
import { readFileSync } from "node:fs";
|
|
40
|
+
import { join } from "node:path";
|
|
41
|
+
import { argv, cwd, exit } from "node:process";
|
|
42
|
+
import { normalizeTier, parseSkillFrontmatter } from "../lib/skill-tiers.mjs";
|
|
43
|
+
import { discoverSkillSources } from "./check-skill-tiers.mjs";
|
|
44
|
+
/** Derive the total + per-tier breakdown from `skills/` + root SKILL.md frontmatter. */
|
|
45
|
+
export function deriveCounts(repoRoot) {
|
|
46
|
+
const sources = discoverSkillSources(repoRoot);
|
|
47
|
+
const tally = { core: 0, featured: 0, tier1: 0, tier2: 0, companion: 0, unknown: 0 };
|
|
48
|
+
for (const src of sources) {
|
|
49
|
+
const front = parseSkillFrontmatter(readFileSync(src.path, "utf8"));
|
|
50
|
+
switch (normalizeTier(front.tier)) {
|
|
51
|
+
case "core":
|
|
52
|
+
tally.core++;
|
|
53
|
+
break;
|
|
54
|
+
case "featured":
|
|
55
|
+
tally.featured++;
|
|
56
|
+
break;
|
|
57
|
+
case "1":
|
|
58
|
+
tally.tier1++;
|
|
59
|
+
break;
|
|
60
|
+
case "2":
|
|
61
|
+
tally.tier2++;
|
|
62
|
+
break;
|
|
63
|
+
case "companion":
|
|
64
|
+
tally.companion++;
|
|
65
|
+
break;
|
|
66
|
+
default:
|
|
67
|
+
tally.unknown++;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const total = sources.length;
|
|
72
|
+
return { ...tally, total, next: total + 1 };
|
|
73
|
+
}
|
|
74
|
+
/** English ordinal: 1→1st, 2→2nd, 3→3rd, 11→11th, 21→21st, 27→27th. */
|
|
75
|
+
export function ordinal(n) {
|
|
76
|
+
const mod100 = n % 100;
|
|
77
|
+
if (mod100 >= 11 && mod100 <= 13)
|
|
78
|
+
return `${n}th`;
|
|
79
|
+
switch (n % 10) {
|
|
80
|
+
case 1:
|
|
81
|
+
return `${n}st`;
|
|
82
|
+
case 2:
|
|
83
|
+
return `${n}nd`;
|
|
84
|
+
case 3:
|
|
85
|
+
return `${n}rd`;
|
|
86
|
+
default:
|
|
87
|
+
return `${n}th`;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/** The exact substring a surface must contain for the given check kind. */
|
|
91
|
+
export function expectedFor(check, c) {
|
|
92
|
+
switch (check) {
|
|
93
|
+
case "breakdown":
|
|
94
|
+
return `${c.core} core + ${c.featured} featured + ${c.tier1} tier-1 + ${c.tier2} tier-2 + ${c.companion} always-bundled`;
|
|
95
|
+
case "total-skills":
|
|
96
|
+
return `${c.total} skills`;
|
|
97
|
+
case "total-catalog":
|
|
98
|
+
return `${c.total}-skill catalog`;
|
|
99
|
+
case "next-ordinal":
|
|
100
|
+
return `${ordinal(c.next)} skill`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export const PROSE_SURFACES = [
|
|
104
|
+
{ file: "README.md", checks: ["breakdown", "total-skills", "next-ordinal"] },
|
|
105
|
+
{
|
|
106
|
+
file: "docs/skills.md",
|
|
107
|
+
checks: ["breakdown", "total-skills", "total-catalog", "next-ordinal"],
|
|
108
|
+
},
|
|
109
|
+
{ file: "docs/skill-use-cases.md", checks: ["breakdown", "total-skills"] },
|
|
110
|
+
{ file: "CONTRIBUTING.md", checks: ["total-skills"] },
|
|
111
|
+
{ file: "docs/using-this-plugin.md", checks: ["total-catalog"] },
|
|
112
|
+
];
|
|
113
|
+
/**
|
|
114
|
+
* Assert every surface contains every derived claim it owns. `counts` defaults
|
|
115
|
+
* to the value derived from `repoRoot`; pass it explicitly to test the
|
|
116
|
+
* surface-matching logic in isolation.
|
|
117
|
+
*/
|
|
118
|
+
export function checkProseSurfaces(repoRoot, counts, surfaces = PROSE_SURFACES) {
|
|
119
|
+
const c = counts ?? deriveCounts(repoRoot);
|
|
120
|
+
const failures = [];
|
|
121
|
+
for (const surface of surfaces) {
|
|
122
|
+
let content;
|
|
123
|
+
try {
|
|
124
|
+
content = readFileSync(join(repoRoot, surface.file), "utf8");
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
content = null;
|
|
128
|
+
}
|
|
129
|
+
for (const check of surface.checks) {
|
|
130
|
+
const expected = expectedFor(check, c);
|
|
131
|
+
if (content === null) {
|
|
132
|
+
failures.push({ file: surface.file, check, expected, reason: "target-missing" });
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (!content.includes(expected)) {
|
|
136
|
+
failures.push({ file: surface.file, check, expected, reason: "stale" });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return failures;
|
|
141
|
+
}
|
|
142
|
+
function main() {
|
|
143
|
+
const repoRoot = argv[2] ?? cwd();
|
|
144
|
+
const counts = deriveCounts(repoRoot);
|
|
145
|
+
const failures = checkProseSurfaces(repoRoot, counts);
|
|
146
|
+
if (failures.length === 0) {
|
|
147
|
+
console.log(`OK skill-count-prose: all ${PROSE_SURFACES.length} doc surface(s) state the derived count (total=${counts.total}: ${expectedFor("breakdown", counts)}).`);
|
|
148
|
+
exit(0);
|
|
149
|
+
}
|
|
150
|
+
console.error(`FAIL skill-count-prose: ${failures.length} doc prose claim(s) do not match the bundle-derived count of ${counts.total}.`);
|
|
151
|
+
console.error(`Source of truth: skills/ frontmatter → ${expectedFor("breakdown", counts)} = ${counts.total} skills; next skill is the ${ordinal(counts.next)}.`);
|
|
152
|
+
console.error("");
|
|
153
|
+
for (const f of failures) {
|
|
154
|
+
if (f.reason === "target-missing") {
|
|
155
|
+
console.error(` ${f.file} — file not found (expected to contain "${f.expected}")`);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
console.error(` ${f.file} — missing "${f.expected}" [${f.check}]`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
console.error("");
|
|
162
|
+
console.error("Fix: bump the prose in the named file(s) to the derived count. When a skill is genuinely added or retired, this lint enumerates every human-facing surface that needs the same change.");
|
|
163
|
+
exit(1);
|
|
164
|
+
}
|
|
165
|
+
const invokedDirectly = argv[1] !== undefined && import.meta.url.endsWith(argv[1].replace(/\\/g, "/"));
|
|
166
|
+
if (invokedDirectly || argv[1]?.endsWith("check-skill-count-prose.mjs")) {
|
|
167
|
+
main();
|
|
168
|
+
}
|
package/bin/install.mjs
CHANGED
|
@@ -15,8 +15,9 @@ import { homedir } from "node:os";
|
|
|
15
15
|
import { dirname, join } from "node:path";
|
|
16
16
|
import { fileURLToPath } from "node:url";
|
|
17
17
|
import { createHash } from "node:crypto";
|
|
18
|
-
import { getToolNames } from "../lib/plugin-metadata.mjs";
|
|
18
|
+
import { PACKAGE_NAME, VERSION, getToolNames } from "../lib/plugin-metadata.mjs";
|
|
19
19
|
import { TARGET_IDS, planTargetWrites, resolveTargets } from "../lib/install-targets.mjs";
|
|
20
|
+
import { evaluateUpdateCheck, fetchLatestNpmVersion, isThrottled, pendingNotice, } from "../lib/version-check.mjs";
|
|
20
21
|
const __filename = fileURLToPath(import.meta.url);
|
|
21
22
|
const __dirname = dirname(__filename);
|
|
22
23
|
const SKILL_SOURCE = join(__dirname, "..", "SKILL.md");
|
|
@@ -694,3 +695,47 @@ Next steps:
|
|
|
694
695
|
${INSTALL_MODE === "expert" ? `\nMCP tools available (${getToolNames("expert").length}): ${getToolNames("expert").join(", ")}` : ""}
|
|
695
696
|
Available instinct packs: npx continuous-improvement install --pack react|python|go|meta
|
|
696
697
|
`);
|
|
698
|
+
// Update-available nudge for the npm/CLI install path (the marketplace path is
|
|
699
|
+
// covered by Claude Code's native plugin auto-update). Reads the public npm
|
|
700
|
+
// registry only — no telemetry. Throttled, default-on, off via
|
|
701
|
+
// CLAUDE_CI_UPDATE_CHECK=off. Fully fail-open: it can never change the
|
|
702
|
+
// installer's outcome. See docs/plans/2026-06-25-version-check-nudge.md.
|
|
703
|
+
async function maybeNotifyUpdate() {
|
|
704
|
+
if ((process.env.CLAUDE_CI_UPDATE_CHECK ?? "on").trim().toLowerCase() === "off")
|
|
705
|
+
return;
|
|
706
|
+
const cacheFile = join(getHomeDir(), ".claude", "instincts", "global", "update-check.json");
|
|
707
|
+
const now = Date.now();
|
|
708
|
+
let cache = null;
|
|
709
|
+
try {
|
|
710
|
+
if (existsSync(cacheFile))
|
|
711
|
+
cache = JSON.parse(readFileSync(cacheFile, "utf8"));
|
|
712
|
+
}
|
|
713
|
+
catch {
|
|
714
|
+
cache = null;
|
|
715
|
+
}
|
|
716
|
+
if (isThrottled(cache, now)) {
|
|
717
|
+
const notice = pendingNotice(cache, VERSION);
|
|
718
|
+
if (notice)
|
|
719
|
+
console.log(`\n${notice}`);
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
const remote = await fetchLatestNpmVersion(PACKAGE_NAME);
|
|
723
|
+
const decision = evaluateUpdateCheck({ local: VERSION, remote, now });
|
|
724
|
+
if (decision.notice)
|
|
725
|
+
console.log(`\n${decision.notice}`);
|
|
726
|
+
if (decision.nextCache) {
|
|
727
|
+
try {
|
|
728
|
+
mkdirSync(dirname(cacheFile), { recursive: true });
|
|
729
|
+
writeFileSync(cacheFile, JSON.stringify(decision.nextCache) + "\n", "utf8");
|
|
730
|
+
}
|
|
731
|
+
catch {
|
|
732
|
+
// cache persistence is best-effort; never throw
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
try {
|
|
737
|
+
await maybeNotifyUpdate();
|
|
738
|
+
}
|
|
739
|
+
catch {
|
|
740
|
+
// a version check must never change the installer's outcome
|
|
741
|
+
}
|
package/bin/lint-transcript.mjs
CHANGED
|
@@ -160,6 +160,12 @@ function formatReport(result) {
|
|
|
160
160
|
return lines.join("\n");
|
|
161
161
|
}
|
|
162
162
|
const args = process.argv.slice(2);
|
|
163
|
+
function actionInput(name) {
|
|
164
|
+
return (process.env[`INPUT_${name.toUpperCase()}`] ?? "").trim();
|
|
165
|
+
}
|
|
166
|
+
function actionInputBoolean(name) {
|
|
167
|
+
return /^(1|true|yes)$/i.test(actionInput(name));
|
|
168
|
+
}
|
|
163
169
|
if (args.includes("--help") || args.includes("-h")) {
|
|
164
170
|
console.log(`
|
|
165
171
|
Agent Transcript Linter — The 7 Laws of AI Agent Discipline
|
|
@@ -176,7 +182,7 @@ Options:
|
|
|
176
182
|
`);
|
|
177
183
|
process.exit(0);
|
|
178
184
|
}
|
|
179
|
-
const isStrict = args.includes("--strict");
|
|
185
|
+
const isStrict = args.includes("--strict") || actionInputBoolean("strict");
|
|
180
186
|
const isJson = args.includes("--json");
|
|
181
187
|
const isStdin = args.includes("--stdin");
|
|
182
188
|
async function main() {
|
|
@@ -190,7 +196,9 @@ async function main() {
|
|
|
190
196
|
}
|
|
191
197
|
}
|
|
192
198
|
else {
|
|
193
|
-
const filePath = args.find((arg) => !arg.startsWith("--"))
|
|
199
|
+
const filePath = args.find((arg) => !arg.startsWith("--"))
|
|
200
|
+
|| actionInput("transcript-path")
|
|
201
|
+
|| actionInput("observations-path");
|
|
194
202
|
if (!filePath) {
|
|
195
203
|
console.error("Error: provide a file path or use --stdin");
|
|
196
204
|
process.exit(1);
|
|
@@ -213,16 +221,20 @@ async function main() {
|
|
|
213
221
|
process.exit(0);
|
|
214
222
|
}
|
|
215
223
|
const result = analyzeTranscript(events, parseFailures);
|
|
224
|
+
const report = formatReport(result);
|
|
216
225
|
if (isJson) {
|
|
217
226
|
console.log(JSON.stringify(result, null, 2));
|
|
218
227
|
}
|
|
219
228
|
else {
|
|
220
|
-
console.log(
|
|
229
|
+
console.log(report);
|
|
221
230
|
}
|
|
222
231
|
if (process.env.GITHUB_OUTPUT) {
|
|
223
232
|
const outputLines = [
|
|
224
233
|
`violations=${result.violations.length}`,
|
|
225
234
|
`score=${result.score}`,
|
|
235
|
+
`report<<CI_REPORT_EOF`,
|
|
236
|
+
report,
|
|
237
|
+
"CI_REPORT_EOF",
|
|
226
238
|
];
|
|
227
239
|
const { appendFileSync } = await import("node:fs");
|
|
228
240
|
for (const line of outputLines) {
|
package/bin/mcp-server.mjs
CHANGED
|
@@ -219,6 +219,7 @@ function readDistillObservations(projectHash) {
|
|
|
219
219
|
tool: getString(observation.tool),
|
|
220
220
|
input_summary: getString(observation.input_summary),
|
|
221
221
|
output_summary: getString(observation.output_summary),
|
|
222
|
+
event: getString(observation.event),
|
|
222
223
|
}));
|
|
223
224
|
}
|
|
224
225
|
function detectLevel(projectHash) {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: roast
|
|
3
|
+
description: "Convene a 5-persona adversarial council (Contrarian, Expansionist, Logician, Researcher, Buyer) to pressure-test an idea, then deliver one GO / RESHAPE / KILL verdict plus the cheapest 48-hour test to de-risk it. Enforces Law 1 (Research Before Executing)."
|
|
4
|
+
argument-hint: "[the idea to roast]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /roast
|
|
8
|
+
|
|
9
|
+
Pressure-test an idea before you build it. Convene a council of five independent persona agents who attack the idea from every angle, then act as the Judge and return one decisive verdict — so Law 1 (Research Before Executing) is satisfied on the *idea* itself, not just the plan.
|
|
10
|
+
|
|
11
|
+
## Trigger phrases
|
|
12
|
+
|
|
13
|
+
- `/roast`
|
|
14
|
+
- `/roast <the idea>`
|
|
15
|
+
- "roast this idea"
|
|
16
|
+
- "convene the council"
|
|
17
|
+
- "pressure-test this" / "stress-test this idea"
|
|
18
|
+
- "validate this business idea"
|
|
19
|
+
- "give me a brutal second opinion before I build this"
|
|
20
|
+
|
|
21
|
+
## What happens
|
|
22
|
+
|
|
23
|
+
1. **Brief.** Read the idea from the argument (if given) and ask up to 3-4 clarifying questions in one batch — the idea, the buyer + money model, your edge, your constraints. Skip the questions if the user says "just run it."
|
|
24
|
+
2. **Council (parallel).** Spin up all five personas in parallel, each pasted the same brief: Contrarian (assume it fails), Expansionist (the 10x case), Logician (first-principles, no web), Researcher (web evidence + competitors), Buyer (role-play the target customer). Each returns a stance, 3-5 sharp points, the one thing you must hear, and a 1-10 score.
|
|
25
|
+
3. **Verdict.** Act as the Judge: resolve the council's tension, fold in the economics lens, and return one `GO / RESHAPE / KILL` call with the biggest risk, biggest upside, money read, and — most importantly — the cheapest 48-hour test to validate the riskiest assumption before building anything.
|
|
26
|
+
|
|
27
|
+
## Skill file
|
|
28
|
+
|
|
29
|
+
Full behavior is defined in [`skills/roast.md`](../skills/roast.md).
|
|
30
|
+
|
|
31
|
+
## Pairs with
|
|
32
|
+
|
|
33
|
+
- `/grill-me` — roast validates the idea; grill-me then hardens the plan.
|
|
34
|
+
- `/proceed-with-the-recommendation` — walk the verdict's next steps under the 7 Laws.
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// workflow-distill.mts — Stop hook (opt-in, default off) that nudges the operator
|
|
3
|
+
// to persist a verified native Workflow run as a Mulahazah draft instinct via the
|
|
4
|
+
// ci_distill_from_workflow MCP tool. This is the one integration a per-turn
|
|
5
|
+
// orchestration primitive cannot do for itself: make the lesson of an expensive
|
|
6
|
+
// multi-agent run survive the run instead of evaporating when the turn ends — the
|
|
7
|
+
// fast-follow deferred in docs/plans/2026-06-08-workflow-instinct-bridge.md.
|
|
8
|
+
//
|
|
9
|
+
// Mode via CLAUDE_WORKFLOW_DISTILL_NUDGE: "on" | "off" (default).
|
|
10
|
+
// - off (default): no-op. Operators who do not use native Workflows are never
|
|
11
|
+
// nagged.
|
|
12
|
+
// - on: if the session's observation feed shows a verified Workflow run that
|
|
13
|
+
// has not already been nudged, print ONE stderr line suggesting
|
|
14
|
+
// ci_distill_from_workflow. It is an amplifier, never a gate — it cannot
|
|
15
|
+
// block the Stop and writes nothing to stdout.
|
|
16
|
+
//
|
|
17
|
+
// Project-hash resolution is byte-identical to bin/observe.mts and
|
|
18
|
+
// hooks/goal-drift-stop.mts so this hook reads the SAME observations.jsonl the
|
|
19
|
+
// observer writes. The "is this run verified" decision is the pure, already-tested
|
|
20
|
+
// workflowRunFromObservations (src/lib/skill-distill.mts), which fails closed:
|
|
21
|
+
// no Workflow row, unparseable meta, or no following verify-exit-0 => null => no
|
|
22
|
+
// nudge. Per-run dedup state lives in
|
|
23
|
+
// ~/.claude/instincts/<project-hash>/workflow-distill-state.json keyed by run
|
|
24
|
+
// name + verify command, so a run is nudged at most once. Fail-open by
|
|
25
|
+
// construction: any error exits 0 and never blocks. No network. 5s hook budget.
|
|
26
|
+
import { execFileSync } from "node:child_process";
|
|
27
|
+
import { createHash } from "node:crypto";
|
|
28
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
29
|
+
import { join } from "node:path";
|
|
30
|
+
import { resolveHomeDir } from "../lib/resolve-home-dir.mjs";
|
|
31
|
+
import { workflowRunFromObservations } from "../lib/skill-distill.mjs";
|
|
32
|
+
function safeJsonParse(text) {
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(text);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function resolveMode() {
|
|
41
|
+
return (process.env.CLAUDE_WORKFLOW_DISTILL_NUDGE ?? "off").trim().toLowerCase() === "on" ? "on" : "off";
|
|
42
|
+
}
|
|
43
|
+
// Mirrors bin/observe.mts:resolveProjectRoot — the same basis the observer uses
|
|
44
|
+
// to bucket observations.jsonl, so the hash here resolves to the same file.
|
|
45
|
+
function resolveProjectRoot() {
|
|
46
|
+
const fromEnv = process.env.CLAUDE_PROJECT_DIR;
|
|
47
|
+
if (fromEnv)
|
|
48
|
+
return fromEnv;
|
|
49
|
+
try {
|
|
50
|
+
const root = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
51
|
+
encoding: "utf8",
|
|
52
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
53
|
+
}).trim();
|
|
54
|
+
if (root)
|
|
55
|
+
return root;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// not a git repo
|
|
59
|
+
}
|
|
60
|
+
return "global";
|
|
61
|
+
}
|
|
62
|
+
function projectHash(root) {
|
|
63
|
+
return createHash("sha256").update(root).digest("hex").slice(0, 12);
|
|
64
|
+
}
|
|
65
|
+
function readObservations(instinctsProjectDir) {
|
|
66
|
+
const file = join(instinctsProjectDir, "observations.jsonl");
|
|
67
|
+
try {
|
|
68
|
+
if (!existsSync(file))
|
|
69
|
+
return [];
|
|
70
|
+
const lines = readFileSync(file, "utf8")
|
|
71
|
+
.split("\n")
|
|
72
|
+
.filter((line) => line.trim().length > 0);
|
|
73
|
+
const out = [];
|
|
74
|
+
// A workflow row plus its following verify both sit near the tail; a bounded
|
|
75
|
+
// window keeps the 5s budget safe on a long-lived feed.
|
|
76
|
+
for (const line of lines.slice(-5000)) {
|
|
77
|
+
const row = safeJsonParse(line);
|
|
78
|
+
if (!row)
|
|
79
|
+
continue;
|
|
80
|
+
out.push({
|
|
81
|
+
ts: typeof row.ts === "string" ? row.ts : "",
|
|
82
|
+
session: typeof row.session === "string" ? row.session : "",
|
|
83
|
+
session_id: typeof row.session_id === "string" ? row.session_id : "",
|
|
84
|
+
tool: typeof row.tool === "string" ? row.tool : "",
|
|
85
|
+
input_summary: typeof row.input_summary === "string" ? row.input_summary : "",
|
|
86
|
+
output_summary: typeof row.output_summary === "string" ? row.output_summary : "",
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function readNudgedKeys(instinctsProjectDir) {
|
|
96
|
+
const file = join(instinctsProjectDir, "workflow-distill-state.json");
|
|
97
|
+
try {
|
|
98
|
+
if (!existsSync(file))
|
|
99
|
+
return new Set();
|
|
100
|
+
const parsed = safeJsonParse(readFileSync(file, "utf8"));
|
|
101
|
+
const arr = parsed && Array.isArray(parsed.nudged) ? parsed.nudged : [];
|
|
102
|
+
return new Set(arr.filter((x) => typeof x === "string"));
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return new Set();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function persistNudged(instinctsProjectDir, keys) {
|
|
109
|
+
try {
|
|
110
|
+
mkdirSync(instinctsProjectDir, { recursive: true });
|
|
111
|
+
writeFileSync(join(instinctsProjectDir, "workflow-distill-state.json"), JSON.stringify({ ts: new Date().toISOString(), nudged: [...keys] }) + "\n", "utf8");
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// dedup persistence is best-effort; never throw
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function main() {
|
|
118
|
+
if (resolveMode() === "off")
|
|
119
|
+
return;
|
|
120
|
+
const home = resolveHomeDir();
|
|
121
|
+
if (!home)
|
|
122
|
+
return;
|
|
123
|
+
const projectRoot = resolveProjectRoot();
|
|
124
|
+
const instinctsProjectDir = join(home, ".claude", "instincts", projectHash(projectRoot));
|
|
125
|
+
const observations = readObservations(instinctsProjectDir);
|
|
126
|
+
if (observations.length === 0)
|
|
127
|
+
return;
|
|
128
|
+
const run = workflowRunFromObservations(observations);
|
|
129
|
+
if (!run)
|
|
130
|
+
return; // fail closed: no verified run, nothing to nudge
|
|
131
|
+
const runKey = `${run.name}::${run.verifyCommand}`;
|
|
132
|
+
const nudged = readNudgedKeys(instinctsProjectDir);
|
|
133
|
+
if (nudged.has(runKey))
|
|
134
|
+
return; // already nudged this run — stay quiet
|
|
135
|
+
process.stderr.write(`[continuous-improvement] workflow-distill: verified workflow run "${run.name}" detected — ` +
|
|
136
|
+
"run the ci_distill_from_workflow MCP tool to persist it as a Mulahazah draft instinct.\n");
|
|
137
|
+
nudged.add(runKey);
|
|
138
|
+
persistNudged(instinctsProjectDir, nudged);
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
main();
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// fail open — never block the session due to a hook bug
|
|
145
|
+
}
|
package/lib/plugin-metadata.mjs
CHANGED
|
@@ -26,7 +26,7 @@ const KEYWORDS = [
|
|
|
26
26
|
"transcript-linter",
|
|
27
27
|
];
|
|
28
28
|
const CLAUDE_PLUGIN_CATEGORY = "productivity";
|
|
29
|
-
const SHARED_PLUGIN_DESCRIPTION = "The persistent-memory and runtime-discipline layer for Claude Code. It remembers the corrections you already gave, grounds every edit in real facts before it lands, and — through the Mulahazah engine — turns each fix into a reusable instinct, so a lesson learned once is applied automatically next time with no re-teaching. Built on the 7 Laws of AI Agent Discipline (research, plan, verify, reflect, learn) and shipped as
|
|
29
|
+
const SHARED_PLUGIN_DESCRIPTION = "The persistent-memory and runtime-discipline layer for Claude Code. It remembers the corrections you already gave, grounds every edit in real facts before it lands, and — through the Mulahazah engine — turns each fix into a reusable instinct, so a lesson learned once is applied automatically next time with no re-teaching. Built on the 7 Laws of AI Agent Discipline (research, plan, verify, reflect, learn) and shipped as 26 bundled skills, instinct-aware hooks, an MCP toolset for recall and reflection, and a GitHub Action transcript linter that feeds real work history back into sharper instincts.";
|
|
30
30
|
// Four vendored upstream companions registered alongside the CI plugin.
|
|
31
31
|
// Each entry points at a pinned-SHA snapshot under third-party/<name>/.
|
|
32
32
|
// See third-party/MANIFEST.md for refresh recipes and per-snapshot
|
|
@@ -492,6 +492,11 @@ export function getPluginHooksConfig() {
|
|
|
492
492
|
command: "node \"${CLAUDE_PLUGIN_ROOT}/hooks/goal-drift-stop.mjs\"",
|
|
493
493
|
timeout: 5,
|
|
494
494
|
};
|
|
495
|
+
const workflowDistillCommand = {
|
|
496
|
+
type: "command",
|
|
497
|
+
command: "node \"${CLAUDE_PLUGIN_ROOT}/hooks/workflow-distill.mjs\"",
|
|
498
|
+
timeout: 5,
|
|
499
|
+
};
|
|
495
500
|
const routePromptCommand = {
|
|
496
501
|
type: "command",
|
|
497
502
|
command: "node \"${CLAUDE_PLUGIN_ROOT}/hooks/route-prompt.mjs\"",
|
|
@@ -503,7 +508,7 @@ export function getPluginHooksConfig() {
|
|
|
503
508
|
timeout: 5,
|
|
504
509
|
};
|
|
505
510
|
return {
|
|
506
|
-
description: "Gateguard fact-forcing PreToolUse, companion-preference enforcement, observation, session lifecycle, 3-section-close discipline, goal-drift Stop gate, and UserPromptSubmit lazy-routing plus opt-in proactive recall-briefing hooks for continuous-improvement.",
|
|
511
|
+
description: "Gateguard fact-forcing PreToolUse, companion-preference enforcement, observation, session lifecycle, 3-section-close discipline, goal-drift Stop gate, opt-in workflow-distill Stop nudge, and UserPromptSubmit lazy-routing plus opt-in proactive recall-briefing hooks for continuous-improvement.",
|
|
507
512
|
hooks: {
|
|
508
513
|
// gateguard runs FIRST on PreToolUse so its block decision short-circuits
|
|
509
514
|
// before companion-preference sees the call. companion-preference runs
|
|
@@ -527,7 +532,7 @@ export function getPluginHooksConfig() {
|
|
|
527
532
|
UserPromptSubmit: [{ hooks: [routePromptCommand, recallBriefingCommand] }],
|
|
528
533
|
SessionStart: [{ hooks: [sessionCommand] }],
|
|
529
534
|
SessionEnd: [{ hooks: [sessionCommand] }],
|
|
530
|
-
Stop: [{ hooks: [threeSectionCloseCommand, goalDriftStopCommand] }],
|
|
535
|
+
Stop: [{ hooks: [threeSectionCloseCommand, goalDriftStopCommand, workflowDistillCommand] }],
|
|
531
536
|
},
|
|
532
537
|
};
|
|
533
538
|
}
|