@cratis/pi 3.3.0 → 3.4.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/README.md CHANGED
@@ -14,6 +14,12 @@ Cratis quality hooks. When no `.cratis/ai.json` exists, it exposes the complete
14
14
  packaged skill set. It therefore gives Pi the complete single-harness Cratis
15
15
  experience without requiring the Cratis CLI.
16
16
 
17
+ The `subagent` tool stands down for the session when another extension already
18
+ provides a delegation tool named `Agent` (such as pi-subagents, which lists the
19
+ same agents). It removes itself from the active tools at session start and
20
+ shows one notice when Pi has a UI, so the model is not offered two delegation
21
+ tools.
22
+
17
23
  The managed CLI path remains the choice when one repository must configure and
18
24
  synchronize several harnesses. It writes the resolved corpus to `.cratis/ai`,
19
25
  creates every harness integration, and records hashes for safe update and
@@ -5,7 +5,6 @@ description: >
5
5
  Creates the single slice file containing all backend artifacts:
6
6
  commands, events, validators, constraints, read models, projections,
7
7
  and reactors — all in strict compliance with the vertical slice architecture.
8
- model: claude-sonnet-4-5
9
8
  tools:
10
9
  - Read
11
10
  - Grep
@@ -4,7 +4,6 @@ description: >
4
4
  Quality gate agent for Cratis-based projects. Reviews code against all
5
5
  project instruction files, checking architecture conformance, C# and
6
6
  TypeScript conventions, and vertical slice correctness before merge.
7
- model: claude-sonnet-4-5
8
7
  tools:
9
8
  - Read
10
9
  - Grep
@@ -8,7 +8,6 @@ description: >
8
8
  Use this agent when a request spans multiple concerns (backend + frontend,
9
9
  multiple slices, mixed C#/TypeScript work, or requires both implementation
10
10
  and review).
11
- model: claude-sonnet-4-5
12
11
  tools:
13
12
  - Read
14
13
  - Grep
@@ -4,7 +4,6 @@ description: >
4
4
  Specialist for TypeScript/React frontend code within a vertical slice.
5
5
  Implements React components that consume auto-generated command and query
6
6
  proxies, following the project's component and styling conventions.
7
- model: claude-sonnet-4-5
8
7
  tools:
9
8
  - Read
10
9
  - Grep
@@ -8,7 +8,6 @@ description: >
8
8
  Use this agent as the entry point whenever multiple agents need to work together
9
9
  as a team: mixed implementation + documentation + review, multi-feature work,
10
10
  large refactors, or any goal that spans more than one concern.
11
- model: claude-sonnet-4-5
12
11
  tools:
13
12
  - Read
14
13
  - Grep
@@ -4,7 +4,6 @@ description: >
4
4
  Performance-focused review agent for Cratis-based projects. Analyzes changed
5
5
  files for projection efficiency, query patterns, unnecessary allocations,
6
6
  React render overhead, and Chronicle anti-patterns before merge.
7
- model: claude-sonnet-4-5
8
7
  tools:
9
8
  - Read
10
9
  - Grep
@@ -5,7 +5,6 @@ description: >
5
5
  Breaks the work into ordered, parallelisable tasks, delegates each task
6
6
  to the right specialist agent, and ensures quality gates are met before
7
7
  the work is considered done.
8
- model: claude-sonnet-4-5
9
8
  tools:
10
9
  - Read
11
10
  - Grep
@@ -4,7 +4,6 @@ description: >
4
4
  Independent, read-only reviewer for typed Cratis repository investigations.
5
5
  Reviews evidence and repository-mode reasoning without applying application
6
6
  conventions to framework or client-library repositories.
7
- model: claude-opus-5
8
7
  tools:
9
8
  - Read
10
9
  - Grep
@@ -4,7 +4,6 @@ description: >
4
4
  Read-only investigator for Cratis application and framework repositories.
5
5
  Produces typed, evidence-backed findings without changing source, invoking
6
6
  mutating Chronicle operations, or assuming an application architecture.
7
- model: claude-opus-5
8
7
  tools:
9
8
  - Read
10
9
  - Grep
@@ -5,7 +5,6 @@ description: >
5
5
  security review of all changed files before merge, covering input validation,
6
6
  auth/authz, data exposure, secrets, event sourcing specifics, and frontend
7
7
  attack surface.
8
- model: claude-sonnet-4-5
9
8
  tools:
10
9
  - Read
11
10
  - Grep
@@ -4,7 +4,6 @@ description: >
4
4
  Implements a Cratis vertical slice end-to-end — all backend artifacts in one slice file, BDD specs
5
5
  in when_*/ folders, and the React surface (page and/or command dialog). Use for new slices and for
6
6
  non-trivial slice changes spanning backend and frontend.
7
- model: claude-opus-4-8
8
7
  tools:
9
8
  - Read
10
9
  - Grep
@@ -4,7 +4,6 @@ description: >
4
4
  Specialist for writing C# specs (the in-process scenario family) and
5
5
  TypeScript/React specs for vertical slices. Ensures every slice has
6
6
  comprehensive behavior coverage following the project's BDD conventions.
7
- model: claude-sonnet-4-5
8
7
  tools:
9
8
  - Read
10
9
  - Grep
@@ -4,7 +4,8 @@
4
4
  * The agent definitions are the SINGLE-SOURCE corpus files under `.cratis/ai/agents/*.md`,
5
5
  * surfaced to Pi through symlink adapters in `.pi/agents/*.md`. Those files are written
6
6
  * in the Claude/Copilot shape (Title-Case `name`, a YAML-list `tools:` using Claude tool
7
- * names such as `Read`/`Glob`/`Bash`, and a `model:` id). Pi's built-in tools are the
7
+ * names such as `Read`/`Glob`/`Bash`). Agents without a `model:` inherit the dispatching
8
+ * session's model. Pi's built-in tools are the
8
9
  * lowercase set `read, write, edit, bash, grep, find, ls`, so this module NORMALIZES the
9
10
  * shared shape to Pi semantics — the adapter layer absorbs the tool difference, exactly
10
11
  * like every other adapter in this corpus, so `.pi/agents/*.md` can stay pure symlinks.
@@ -0,0 +1,40 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ /**
5
+ * Deciding whether the Cratis `subagent` tool stands down for a session.
6
+ *
7
+ * Widely used delegation extensions already read `.pi/agents/*.md` — pi-subagents registers a tool named
8
+ * `Agent` that lists every Cratis corpus agent. Offering that tool and `subagent` side by side makes the
9
+ * model guess which to call and pay for both descriptions, so the Cratis tool yields to the one the user
10
+ * installed deliberately.
11
+ *
12
+ * The match is on the exact tool name, not a substring: `agent` is a common word in unrelated tool names,
13
+ * and silently withdrawing delegation against one of those would be a bug nobody could see.
14
+ *
15
+ * Kept free of host calls so the policy is testable from the tool list alone; the extension owns
16
+ * `getAllTools`, `setActiveTools` and the notice.
17
+ */
18
+
19
+ import type { ToolInfo } from "@earendil-works/pi-coding-agent";
20
+
21
+ /** The name the Cratis delegation tool registers under. */
22
+ export const SUBAGENT_TOOL_NAME = "subagent";
23
+
24
+ /** Tool names that mean "another extension already delegates to agents". */
25
+ export const FOREIGN_DELEGATION_TOOL_NAMES: ReadonlySet<string> = new Set(["Agent"]);
26
+
27
+ /** The registered delegation tool the Cratis tool yields to, or `undefined` when there is none. */
28
+ export function foreignDelegationTool<T extends Pick<ToolInfo, "name">>(tools: readonly T[]): T | undefined {
29
+ return tools.find((tool) => FOREIGN_DELEGATION_TOOL_NAMES.has(tool.name));
30
+ }
31
+
32
+ /** The one informational notice shown when the Cratis tool stands down. */
33
+ export function standDownNotice(tool: Pick<ToolInfo, "name"> & { sourceInfo?: Partial<ToolInfo["sourceInfo"]> }): string {
34
+ const source = tool.sourceInfo?.source ?? "another extension";
35
+ return (
36
+ `The "${tool.name}" tool from ${source} already delegates to agents, so the Cratis ` +
37
+ `"${SUBAGENT_TOOL_NAME}" tool is disabled for this session to avoid offering two delegation tools. ` +
38
+ `Remove that extension to use "${SUBAGENT_TOOL_NAME}" instead.`
39
+ );
40
+ }
@@ -12,6 +12,10 @@
12
12
  * - single: { agent, task }
13
13
  * - parallel: { tasks: [{ agent, task }, ...] } (max 8, 4 concurrent)
14
14
  * - chain: { chain: [{ agent, task }, ...] } (sequential, {previous} placeholder)
15
+ *
16
+ * Stand-down: when another extension already provides a delegation tool named `Agent` (pi-subagents,
17
+ * which reads the same `.pi/agents/*.md`), this tool removes itself from the session's active tools at
18
+ * session start and says so once — see `./delegation.ts`. Without such a tool it behaves unchanged.
15
19
  */
16
20
 
17
21
  import { spawn } from "node:child_process";
@@ -23,6 +27,7 @@ import { CONFIG_DIR_NAME, type ExtensionAPI, getAgentDir } from "@earendil-works
23
27
  import { StringEnum } from "@earendil-works/pi-ai";
24
28
  import { Type } from "typebox";
25
29
  import { type AgentConfig, type AgentScope, discoverAgents } from "./agents.ts";
30
+ import { foreignDelegationTool, standDownNotice, SUBAGENT_TOOL_NAME } from "./delegation.ts";
26
31
 
27
32
  const extensionPath = fileURLToPath(import.meta.url);
28
33
  const isPackagedExtension = extensionPath.includes(`${path.sep}package${path.sep}corpus${path.sep}`);
@@ -257,8 +262,28 @@ export default function (pi: ExtensionAPI) {
257
262
  } catch {
258
263
  knownAgents = "";
259
264
  }
265
+ // Yield to another extension's delegation tool. This can only happen at session start: getAllTools
266
+ // cannot be called while extensions load, and an extension loaded after this one is not visible yet.
267
+ // setActiveTools rebuilds the system prompt before the first turn, so the model never sees this tool.
268
+ let standDownNoticeShown = false;
269
+ pi.on("session_start", (_event, ctx) => {
270
+ try {
271
+ const foreign = foreignDelegationTool(pi.getAllTools());
272
+ if (!foreign) return;
273
+ const active = pi.getActiveTools();
274
+ if (!active.includes(SUBAGENT_TOOL_NAME)) return;
275
+ pi.setActiveTools(active.filter((name) => name !== SUBAGENT_TOOL_NAME));
276
+ if (ctx.hasUI && !standDownNoticeShown) {
277
+ standDownNoticeShown = true;
278
+ ctx.ui.notify(standDownNotice(foreign), "info");
279
+ }
280
+ } catch {
281
+ // Some hosts cannot list or switch tools; being unable to check is no reason to fail the session.
282
+ }
283
+ });
284
+
260
285
  pi.registerTool({
261
- name: "subagent",
286
+ name: SUBAGENT_TOOL_NAME,
262
287
  label: "Subagent",
263
288
  description: [
264
289
  "Delegate a task to a specialized Cratis agent in an isolated context (separate pi process).",
@@ -42,6 +42,38 @@ a trade rather than a free improvement.
42
42
  Check the opening by reading only the first three lines and asking whether they are worth
43
43
  expanding. If the answer depends on the fourth line, the opening is wrong.
44
44
 
45
+ ## Sound like a person talking
46
+
47
+ A post that reads as generated gets scrolled past as generated, however accurate it is. The
48
+ fix is not a smoother sentence. It is writing the way someone who built the thing would
49
+ explain it to a colleague over coffee.
50
+
51
+ - **Open with something that happened.** What shipped, a real number, what you found this
52
+ week. A concrete, checkable event reads as a person reporting. An aphorism about the domain
53
+ ("A skill is just text…") reads as a slogan, however clever it is.
54
+ - **Talk, don't announce.** Contractions, a sentence that starts with "So" or "And", a
55
+ parenthetical aside, a question answered straight away ("Why bother? Because…"). Use them
56
+ the way speech does, now and then, not as a new pattern on every line.
57
+ - **Show the default, then the better way.** Name what the reader, or their coding agent,
58
+ writes today in concrete terms, then the shape you are offering, with real identifiers. A
59
+ before-and-after persuades; adjectives do not.
60
+ - **Sell with specifics.** Enthusiasm is welcome and a list of real capabilities is
61
+ persuasive. What persuades is the command, the count, and the thing the reader can do
62
+ tomorrow, not "powerful", "seamless" or "game-changing".
63
+ - **Say why in the words you would use out loud.** A reason with a stake ("we don't want the
64
+ good version of that to be a paid add-on") beats a principle stated in the abstract.
65
+ - **Put a limit where it applies.** "On macOS and Linux, the installer…" carries the limit
66
+ inside the claim. A closing paragraph labeled "One honest limit:" reads as a disclaimer a
67
+ reviewer bolted on.
68
+ - **Keep the review out of the copy.** Evidence, verification and approval language belongs
69
+ in the review record. A qualifier that protects the writer instead of informing the reader
70
+ makes the post read as noncommittal. Keep every qualification that is true and would
71
+ change what the reader does; drop the rest.
72
+ - **Stop when you are done.** A plain next step ("Links in the comments") ends a post better
73
+ than a tidy closing maxim.
74
+
75
+ The tells to remove in review are listed in the **cratis-writing-voice-and-cadence** skill.
76
+
45
77
  ## Never use Unicode pseudo-formatting
46
78
 
47
79
  Third-party tools offer pseudo-bold and pseudo-italic built from Mathematical Alphanumeric
@@ -66,7 +98,9 @@ said first. A point that needs visual weight belongs in an image with its own al
66
98
 
67
99
  ## Structure that survives a phone
68
100
 
69
- - One idea per post. A feed reader will not assemble two.
101
+ - One thread per post. A feed reader will not assemble two arguments. An announcement may
102
+ still list what shipped as a plain list of short fragments inside that thread, with no
103
+ bolded labels and no slogan per item.
70
104
  - Short paragraphs, one to three sentences, and visibly different from each other in length.
71
105
  A column of identically sized blocks reads as a generated template before it reads as an
72
106
  argument.
@@ -9,6 +9,10 @@
9
9
  { "kind": "skill-contains", "value": "A brief opening sentence is a hook doing its job" },
10
10
  { "kind": "skill-contains", "value": "No external link in the body" },
11
11
  { "kind": "skill-contains", "value": "No engagement bait" },
12
- { "kind": "skill-contains", "value": "unofficial approximations" }
12
+ { "kind": "skill-contains", "value": "unofficial approximations" },
13
+ { "kind": "skill-contains", "value": "Sound like a person talking" },
14
+ { "kind": "skill-contains", "value": "Open with something that happened" },
15
+ { "kind": "skill-contains", "value": "Put a limit where it applies" },
16
+ { "kind": "skill-contains", "value": "Keep the review out of the copy" }
13
17
  ]
14
18
  }
@@ -27,6 +27,11 @@ recurrence.
27
27
  | **"rather than"** | The same correction carried by a different connective |
28
28
  | **Em-dash pivot** | The dash used to swing into a closing clause |
29
29
  | **"That is what X is for"** | A pointer sentence standing in for an explanation |
30
+ | **Punchline fragment** | A two- or three-word sentence landing a slogan: "They run." |
31
+ | **Signpost pair** | "That's the X. Here's the Y." Announcing the structure instead of having one |
32
+ | **Labeled caveat** | "One honest limit:" A limitation parked in its own closing paragraph |
33
+ | **Aphoristic close** | A tidy maxim as the last line, restating what the piece already said |
34
+ | **Reflexive triplet** | Three parallel items where the thought had one or two |
30
35
 
31
36
  To measure rather than guess, count them. Searching for `, not a`, `; ` between two clauses,
32
37
  `rather than`, and an em-dash followed by a short closing clause takes a few minutes across a
@@ -48,9 +53,23 @@ Look at ten pieces together, not one at a time:
48
53
  assertion — "A screen that needs…", "A registration test can…" — is one move. Alternate it
49
54
  with a question, a direct address, a concrete moment, or a number.
50
55
 
56
+ ## Hedging that protects the writer
57
+
58
+ The other common complaint is "wishy-washy": copy so qualified that it never commits to
59
+ anything. It usually comes from review language leaking into the material: evidence,
60
+ verification and approval wording that belongs in the review record, and qualifiers that
61
+ protect the writer without informing the reader.
62
+
63
+ The rule below protects every hedge that is true and would change what the reader does. It
64
+ does not protect review residue. Move the evidence to the review record, state the capability
65
+ plainly, and put a real limit inside the sentence it limits ("on macOS and Linux, …") instead
66
+ of in a separate disclaimer.
67
+
51
68
  ## Address somebody
52
69
 
53
70
  - **Say "you".** Material that tells a reader what to do should address that reader.
71
+ - **A company account is a team talking.** Write "we", the way the people who built it would
72
+ say it, not the company name in the third person.
54
73
  - **Attributed writing should sound attributed.** Prose published under a person's name and
55
74
  written entirely in the impersonal third person reads as documentation wearing a byline.
56
75
  Opinion, judgement and preference belong to the named author and are theirs to give.
@@ -8,6 +8,10 @@
8
8
  { "kind": "skill-contains", "value": "every hedging word" },
9
9
  { "kind": "skill-contains", "value": "First person is not a licence to invent" },
10
10
  { "kind": "skill-contains", "value": "Structural sameness is the more damaging half" },
11
- { "kind": "skill-contains", "value": "Rewrite a tranche, read it, and only then continue" }
11
+ { "kind": "skill-contains", "value": "Rewrite a tranche, read it, and only then continue" },
12
+ { "kind": "skill-contains", "value": "Punchline fragment" },
13
+ { "kind": "skill-contains", "value": "Labeled caveat" },
14
+ { "kind": "skill-contains", "value": "Hedging that protects the writer" },
15
+ { "kind": "skill-contains", "value": "A company account is a team talking" }
12
16
  ]
13
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/pi",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "description": "Configuration-aware Cratis AI integration for Pi",
5
5
  "type": "module",
6
6
  "files": [