Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Changed

- **The shared view registry records whether each lane's plugin ships a `medium` option.** `lib/view-registry.mjs` gains `hasMediumOption` on every lane registration, and the retired `~/.claude/rendered-views.md` notice names the plugin's `medium` option only for a lane where it is set; this plugin's notices are unchanged.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Fixed

- **The retired `~/.claude/rendered-views.md` notice no longer points at a `medium` option this plugin does not ship.** The shared medium resolver adds "; per user, the <plugin> plugin's medium option also works" only for a lane whose plugin ships a `medium` userConfig option, which each lane registration in `lib/view-registry.mjs` now records as `hasMediumOption`. `generate` now prints every notice as the resolver gives it, in place of rewriting that notice by hand.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Fixed

- **The retired `~/.claude/rendered-views.md` notice no longer points at a `medium` option this plugin does not ship.** The shared medium resolver adds "; per user, the <plugin> plugin's medium option also works" only for a lane whose plugin ships a `medium` userConfig option, which each lane registration in `lib/view-registry.mjs` now records as `hasMediumOption`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Changed

- **The shared view registry records whether each lane's plugin ships a `medium` option.** `lib/view-registry.mjs` gains `hasMediumOption` on every lane registration, and the retired `~/.claude/rendered-views.md` notice names the plugin's `medium` option only for a lane where it is set; this plugin's notices are unchanged.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Changed

- **The shared view registry records whether each lane's plugin ships a `medium` option.** `lib/view-registry.mjs` gains `hasMediumOption` on every lane registration, and the retired `~/.claude/rendered-views.md` notice names the plugin's `medium` option only for a lane where it is set; this plugin's notices are unchanged.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Changed

- **The shared view registry records whether each lane's plugin ships a `medium` option.** `lib/view-registry.mjs` gains `hasMediumOption` on every lane registration, and the retired `~/.claude/rendered-views.md` notice names the plugin's `medium` option only for a lane where it is set; this plugin's notices are unchanged.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Changed

- **The shared view registry records whether each lane's plugin ships a `medium` option.** `lib/view-registry.mjs` gains `hasMediumOption` on every lane registration, and the retired `~/.claude/rendered-views.md` notice names the plugin's `medium` option only for a lane where it is set; this plugin's notices are unchanged.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Changed

- **The shared view registry records whether each lane's plugin ships a `medium` option.** `lib/view-registry.mjs` gains `hasMediumOption` on every lane registration, and the retired `~/.claude/rendered-views.md` notice names the plugin's `medium` option only for a lane where it is set; this plugin's notices are unchanged.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Changed

- **The shared view registry records whether each lane's plugin ships a `medium` option.** `lib/view-registry.mjs` gains `hasMediumOption` on every lane registration, and the retired `~/.claude/rendered-views.md` notice names the plugin's `medium` option only for a lane where it is set; this plugin's notices are unchanged.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Fixed

- **The retired `~/.claude/rendered-views.md` notice no longer points at a `medium` option this plugin does not ship.** The shared medium resolver adds "; per user, the <plugin> plugin's medium option also works" only for a lane whose plugin ships a `medium` userConfig option, which each lane registration in `lib/view-registry.mjs` now records as `hasMediumOption`. The audit's retired-file eval no longer expects the clause.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Changed

- **The shared view registry records whether each lane's plugin ships a `medium` option.** `lib/view-registry.mjs` gains `hasMediumOption` on every lane registration, and the retired `~/.claude/rendered-views.md` notice names the plugin's `medium` option only for a lane where it is set; this plugin's notices are unchanged.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Fixed

- **The retired `~/.claude/rendered-views.md` notice no longer points at a `medium` option this plugin does not ship.** The shared medium resolver adds "; per user, the <plugin> plugin's medium option also works" only for a lane whose plugin ships a `medium` userConfig option, which each lane registration in `lib/view-registry.mjs` now records as `hasMediumOption`. `explore-directions` and `pressure-test` now print every notice as it stands, with no clause to drop by hand.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Changed

- **The shared view registry records whether each lane's plugin ships a `medium` option.** `lib/view-registry.mjs` gains `hasMediumOption` on every lane registration, and the retired `~/.claude/rendered-views.md` notice names the plugin's `medium` option only for a lane where it is set; this plugin's notices are unchanged.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Changed

- **The shared view registry records whether each lane's plugin ships a `medium` option.** `lib/view-registry.mjs` gains `hasMediumOption` on every lane registration, and the retired `~/.claude/rendered-views.md` notice names the plugin's `medium` option only for a lane where it is set; this plugin's notices are unchanged.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Changed

- **The shared view registry records whether each lane's plugin ships a `medium` option.** `lib/view-registry.mjs` gains `hasMediumOption` on every lane registration, and the retired `~/.claude/rendered-views.md` notice names the plugin's `medium` option only for a lane where it is set; this plugin's notices are unchanged.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Changed

- **The shared view registry records whether each lane's plugin ships a `medium` option.** `lib/view-registry.mjs` gains `hasMediumOption` on every lane registration, and the retired `~/.claude/rendered-views.md` notice names the plugin's `medium` option only for a lane where it is set; this plugin's notices are unchanged.
41 changes: 21 additions & 20 deletions lib/view-registry.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
// lib/view-settings.mjs. Adding a medium or a lane is an addition here, never a skill edit.
//
// MediumEntry { id, publishes, reachable(context) -> {ok, reason}, deliver(view, context) -> {location}, reportName }
// Lane { laneId, kinds, hostedCapable, default }
// Lane { laneId, kinds, hostedCapable, default, hasMediumOption }
//
// `context.interactive` is false in a CI or other non-interactive run, where only the terminal is
// reachable (docs/contracts/rendered-views/README.md, "Reachability and preference"). A lane's
// `default` is its shipped medium, applied when no scope decides; null leaves the choice to the
// skill's own content rule.
// skill's own content rule. `hasMediumOption` is true when the lane's plugin ships a `medium`
// userConfig option, so a notice may point the user at it.

const interactiveOnly = (context = {}) => (context.interactive === false ? { ok: false, reason: "non-interactive run" } : { ok: true });

Expand Down Expand Up @@ -40,28 +41,28 @@ export const SCHEMA = Object.freeze({
properties: { medium: { type: "string", enum: [NO_PREFERENCE, ...Object.keys(MEDIUMS)] } },
});

const lane = (laneId, kinds, hostedCapable, medium) => Object.freeze({ laneId, kinds: Object.freeze(kinds), hostedCapable, default: medium });
const lane = (laneId, kinds, hostedCapable, medium, hasMediumOption) => Object.freeze({ laneId, kinds: Object.freeze(kinds), hostedCapable, default: medium, hasMediumOption });

export const LANES = Object.freeze(
Object.fromEntries(
[
lane("review:explain-change", ["digest"], true, "artifact"),
lane("visualization:present", ["deck"], false, "artifact"),
lane("visualization:visualize", [], false, null),
lane("planning", ["plan", "brainstorm", "design"], false, "artifact"),
lane("architecture", ["landscape", "containers", "components", "dependencies", "data", "events", "flow", "context", "deployment"], false, "file"),
lane("discovery:blindspot", ["blindspot"], false, "file"),
lane("debugging:debug", ["post-mortem"], false, "file"),
lane("education:illustrate", [], false, "file"),
lane("work-items:morning-brief", ["morning-brief"], false, "file"),
lane("work-items:triage", ["board"], false, "file"),
lane("adhd:clarify", [], false, null),
lane("codebase-health:audit", [], false, null),
lane("overengineering:audit", [], false, null),
lane("education:teach", [], false, null),
lane("prototype:explore-directions", [], false, null),
lane("prototype:pressure-test", [], false, null),
lane("ai-briefing:generate", [], false, null),
lane("review:explain-change", ["digest"], true, "artifact", true),
lane("visualization:present", ["deck"], false, "artifact", true),
lane("visualization:visualize", [], false, null, true),
lane("planning", ["plan", "brainstorm", "design"], false, "artifact", true),
lane("architecture", ["landscape", "containers", "components", "dependencies", "data", "events", "flow", "context", "deployment"], false, "file", false),
lane("discovery:blindspot", ["blindspot"], false, "file", true),
lane("debugging:debug", ["post-mortem"], false, "file", true),
lane("education:illustrate", [], false, "file", true),
lane("work-items:morning-brief", ["morning-brief"], false, "file", true),
lane("work-items:triage", ["board"], false, "file", true),
lane("adhd:clarify", [], false, null, true),
lane("codebase-health:audit", [], false, null, true),
lane("overengineering:audit", [], false, null, false),
lane("education:teach", [], false, null, true),
lane("prototype:explore-directions", [], false, null, false),
lane("prototype:pressure-test", [], false, null, false),
lane("ai-briefing:generate", [], false, null, false),
].map((l) => [l.laneId, l]),
),
);
Expand Down
6 changes: 3 additions & 3 deletions lib/view-settings.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ function retiredLocal(legacy) {
}

/** One notice per retired file, unless this plugin already showed it for this project with these bytes. */
function retiredNotices(legacy, { plugin, root, userHome, dataDir }) {
function retiredNotices(legacy, { plugin, hasMediumOption, root, userHome, dataDir }) {
const where = (path) => retiredWhere(path, root, userHome);
const destination = { "claude-user": PERSONAL_FILE, "claude-project": TEAM_FILE, "claude-local": PERSONAL_FILE, "fenced-block": TEAM_FILE };
const key = dataDir ? stateKey(root) : null;
Expand Down Expand Up @@ -213,7 +213,7 @@ function retiredNotices(legacy, { plugin, root, userHome, dataDir }) {
: ""
: "";
const status = rank({ path, kind }) >= 0 && isLocal(value) ? "is retired but still keeps the view local" : "is no longer read";
const perUser = kind === "claude-user" ? `; per user, the ${plugin} plugin's medium option also works` : "";
const perUser = kind === "claude-user" && hasMediumOption ? `; per user, the ${plugin} plugin's medium option also works` : "";
notices.push({ kind: "retired", path: shown, value, destination: to, text: `${shown} ${status}${value ? ` (it sets medium: ${value})` : ""}; set it in ${to} instead${caveat}${perUser}` });
}
if (state && changed) {
Expand Down Expand Up @@ -243,7 +243,7 @@ export function resolveMedium({ lane, argument = null, userConfig = null, projec
const root = repoScopes(projectRoot, userHome) ? projectRoot : null;
const team = root && typeof teamRef === "string" ? teamAt(root, teamRef, notices) : undefined;
const { layers, legacy } = readLayers({ plugin: CONCERN, projectRoot: root, schema: SCHEMA, userConfig: { medium: userConfig ?? "" }, team, userHome });
notices.unshift(...retiredNotices(legacy, { plugin: lane.split(":")[0], root, userHome, dataDir }));
notices.unshift(...retiredNotices(legacy, { plugin: lane.split(":")[0], hasMediumOption: laneEntry.hasMediumOption, root, userHome, dataDir }));
const finish = (preferred, decidedBy) => {
if (!preferred) return { preferred: null, medium: null, decidedBy, notices };
const { medium, fell } = fallBack(preferred, laneEntry, { interactive });
Expand Down
14 changes: 13 additions & 1 deletion lib/view-settings.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { tempDir } from "./test-tmp.mjs";
const HERE = dirname(fileURLToPath(import.meta.url));
const CLI = join(HERE, "view-settings.mjs");
const { resolveMedium, gateExplicit } = await import("./view-settings.mjs");
const { reportLines } = await import("./view-registry.mjs");
const { LANES, reportLines } = await import("./view-registry.mjs");

const TEAM = "docs/conventions/rendered-views.yaml";
const PERSONAL = "docs/conventions/rendered-views.local.yaml";
Expand Down Expand Up @@ -224,6 +224,18 @@ describe("retired rendered-views files", () => {
assert.equal(run(tempDir("view-settings-other-plugin-")).length, 3);
assert.ok(readdirSync(join(dataDir, "view-settings")).length > 0, "state is keyed under the plugin data directory");
});
test("the retired user file's notice names the plugin's medium option only when the plugin ships one", () => {
const home = userHome({ ".claude/rendered-views.md": "medium: file\n" });
const userNotice = (lane) => resolve({ lane }, { userHome: home }).notices.find((n) => n.path === "~/.claude/rendered-views.md").text;
assert.ok(userNotice("planning").endsWith("; per user, the planning plugin's medium option also works"));
assert.doesNotMatch(userNotice("overengineering:audit"), /per user/);
});
test("each lane's hasMediumOption matches whether its plugin ships a medium option", () => {
for (const lane of Object.values(LANES)) {
const manifest = JSON.parse(readFileSync(join(HERE, "..", "plugins", lane.laneId.split(":")[0], ".claude-plugin", "plugin.json"), "utf8"));
assert.equal(lane.hasMediumOption, Object.hasOwn(manifest.userConfig ?? {}, "medium"), lane.laneId);
}
});
});

describe("a retired file's terminal or file value keeps the view local", () => {
Expand Down
41 changes: 21 additions & 20 deletions plugins/adhd/lib/view-registry.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
// lib/view-settings.mjs. Adding a medium or a lane is an addition here, never a skill edit.
//
// MediumEntry { id, publishes, reachable(context) -> {ok, reason}, deliver(view, context) -> {location}, reportName }
// Lane { laneId, kinds, hostedCapable, default }
// Lane { laneId, kinds, hostedCapable, default, hasMediumOption }
//
// `context.interactive` is false in a CI or other non-interactive run, where only the terminal is
// reachable (docs/contracts/rendered-views/README.md, "Reachability and preference"). A lane's
// `default` is its shipped medium, applied when no scope decides; null leaves the choice to the
// skill's own content rule.
// skill's own content rule. `hasMediumOption` is true when the lane's plugin ships a `medium`
// userConfig option, so a notice may point the user at it.

const interactiveOnly = (context = {}) => (context.interactive === false ? { ok: false, reason: "non-interactive run" } : { ok: true });

Expand Down Expand Up @@ -42,28 +43,28 @@ export const SCHEMA = Object.freeze({
properties: { medium: { type: "string", enum: [NO_PREFERENCE, ...Object.keys(MEDIUMS)] } },
});

const lane = (laneId, kinds, hostedCapable, medium) => Object.freeze({ laneId, kinds: Object.freeze(kinds), hostedCapable, default: medium });
const lane = (laneId, kinds, hostedCapable, medium, hasMediumOption) => Object.freeze({ laneId, kinds: Object.freeze(kinds), hostedCapable, default: medium, hasMediumOption });

export const LANES = Object.freeze(
Object.fromEntries(
[
lane("review:explain-change", ["digest"], true, "artifact"),
lane("visualization:present", ["deck"], false, "artifact"),
lane("visualization:visualize", [], false, null),
lane("planning", ["plan", "brainstorm", "design"], false, "artifact"),
lane("architecture", ["landscape", "containers", "components", "dependencies", "data", "events", "flow", "context", "deployment"], false, "file"),
lane("discovery:blindspot", ["blindspot"], false, "file"),
lane("debugging:debug", ["post-mortem"], false, "file"),
lane("education:illustrate", [], false, "file"),
lane("work-items:morning-brief", ["morning-brief"], false, "file"),
lane("work-items:triage", ["board"], false, "file"),
lane("adhd:clarify", [], false, null),
lane("codebase-health:audit", [], false, null),
lane("overengineering:audit", [], false, null),
lane("education:teach", [], false, null),
lane("prototype:explore-directions", [], false, null),
lane("prototype:pressure-test", [], false, null),
lane("ai-briefing:generate", [], false, null),
lane("review:explain-change", ["digest"], true, "artifact", true),
lane("visualization:present", ["deck"], false, "artifact", true),
lane("visualization:visualize", [], false, null, true),
lane("planning", ["plan", "brainstorm", "design"], false, "artifact", true),
lane("architecture", ["landscape", "containers", "components", "dependencies", "data", "events", "flow", "context", "deployment"], false, "file", false),
lane("discovery:blindspot", ["blindspot"], false, "file", true),
lane("debugging:debug", ["post-mortem"], false, "file", true),
lane("education:illustrate", [], false, "file", true),
lane("work-items:morning-brief", ["morning-brief"], false, "file", true),
lane("work-items:triage", ["board"], false, "file", true),
lane("adhd:clarify", [], false, null, true),
lane("codebase-health:audit", [], false, null, true),
lane("overengineering:audit", [], false, null, false),
lane("education:teach", [], false, null, true),
lane("prototype:explore-directions", [], false, null, false),
lane("prototype:pressure-test", [], false, null, false),
lane("ai-briefing:generate", [], false, null, false),
].map((l) => [l.laneId, l]),
),
);
Expand Down
Loading
Loading