@cratis/pi 2.21.3 → 2.23.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/package/corpus/agents/backend-developer.md +6 -5
- package/package/corpus/agents/code-reviewer.md +7 -7
- package/package/corpus/agents/coordinator.md +5 -4
- package/package/corpus/agents/frontend-developer.md +34 -40
- package/package/corpus/agents/orchestrator.md +5 -4
- package/package/corpus/agents/performance-reviewer.md +5 -4
- package/package/corpus/agents/planner.md +5 -4
- package/package/corpus/agents/repository-investigation-reviewer.md +2 -1
- package/package/corpus/agents/repository-investigator.md +2 -1
- package/package/corpus/agents/security-reviewer.md +5 -4
- package/package/corpus/agents/slice-implementer.md +8 -2
- package/package/corpus/agents/spec-writer.md +6 -4
- package/package/corpus/harnesses/pi/extensions/subagent/agents.ts +52 -7
- package/package/corpus/harnesses/pi/extensions/subagent/index.ts +14 -0
- package/package/corpus/hooks/README.md +29 -27
- package/package/corpus/hooks/agent-stop.md +1 -5
- package/package/corpus/hooks/scripts/cratis-patterns.json +1 -1
- package/package/corpus/hooks/scripts/validate-package-subpaths.sh +1 -2
- package/package/corpus/prompts/verify-ai-setup.prompt.md +17 -7
- package/package/corpus/rules/ai-distribution.md +58 -0
- package/package/corpus/rules/components.md +77 -44
- package/package/corpus/rules/dialogs.md +24 -20
- package/package/corpus/rules/efcore.md +35 -187
- package/package/corpus/rules/framework.md +1 -1
- package/package/corpus/rules/frontend-quality.md +1 -1
- package/package/corpus/rules/frontend-testing.md +4 -4
- package/package/corpus/rules/general.md +29 -105
- package/package/corpus/rules/new-repository-intake.md +34 -0
- package/package/corpus/rules/orleans.md +13 -27
- package/package/corpus/rules/react.md +12 -12
- package/package/corpus/rules/reactors.md +28 -5
- package/package/corpus/rules/specs.scenarios.csharp.md +18 -3
- package/package/corpus/rules/specs.typescript.md +1 -1
- package/package/corpus/rules/storybook.md +2 -2
- package/package/corpus/rules/typescript.md +1 -0
- package/package/corpus/rules/vertical-slices.md +20 -15
- package/package/corpus/rules/writing-correct-examples.md +1 -0
- package/package/corpus/skills/cratis-application-react-specifications/SKILL.md +4 -4
- package/package/corpus/skills/cratis-application-slice-specifications/SKILL.md +16 -8
- package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/SKILL.md +6 -4
- package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/references/authentication.md +1 -1
- package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/references/authorization.md +1 -1
- package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/references/frontend.md +1 -1
- package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/references/local-development.md +1 -1
- package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/references/tenancy.md +7 -4
- package/package/corpus/skills/cratis-arc-command/SKILL.md +9 -7
- package/package/corpus/skills/cratis-arc-command/references/command-result.md +1 -1
- package/package/corpus/skills/cratis-arc-command/references/handler-shapes.md +2 -2
- package/package/corpus/skills/cratis-arc-command/references/proxy-generation.md +6 -3
- package/package/corpus/skills/cratis-arc-command/references/read-model-injection.md +1 -1
- package/package/corpus/skills/cratis-arc-command-execution/SKILL.md +12 -2
- package/package/corpus/skills/cratis-arc-command-validation/SKILL.md +5 -3
- package/package/corpus/skills/cratis-arc-ef-core-migration/SKILL.md +7 -4
- package/package/corpus/skills/cratis-arc-observable-query-http/SKILL.md +10 -3
- package/package/corpus/skills/cratis-arc-query-paging/SKILL.md +13 -8
- package/package/corpus/skills/cratis-arc-react-page/SKILL.md +27 -24
- package/package/corpus/skills/cratis-arc-react-page/references/data-page.md +15 -11
- package/package/corpus/skills/cratis-arc-react-page/references/data-tables.md +9 -2
- package/package/corpus/skills/cratis-arc-react-page/references/dialogs.md +11 -9
- package/package/corpus/skills/cratis-arc-react-page/references/queries-and-commands.md +26 -2
- package/package/corpus/skills/cratis-chronicle-cli-operations/SKILL.md +19 -13
- package/package/corpus/skills/cratis-chronicle-client-dotnet/SKILL.md +17 -15
- package/package/corpus/skills/cratis-chronicle-client-elixir/SKILL.md +24 -20
- package/package/corpus/skills/cratis-chronicle-client-kotlin/SKILL.md +26 -15
- package/package/corpus/skills/cratis-chronicle-client-typescript/SKILL.md +15 -11
- package/package/corpus/skills/cratis-chronicle-compliance/SKILL.md +14 -3
- package/package/corpus/skills/cratis-chronicle-event-constraints/SKILL.md +13 -6
- package/package/corpus/skills/cratis-chronicle-event-modeling/SKILL.md +2 -2
- package/package/corpus/skills/cratis-chronicle-event-specifications/SKILL.md +11 -5
- package/package/corpus/skills/cratis-chronicle-event-type-migration/SKILL.md +2 -2
- package/package/corpus/skills/cratis-chronicle-multi-tenancy/SKILL.md +2 -2
- package/package/corpus/skills/cratis-chronicle-projection/SKILL.md +23 -4
- package/package/corpus/skills/cratis-chronicle-projection/references/fluent-builder.md +1 -1
- package/package/corpus/skills/cratis-chronicle-projection/references/model-bound-attributes.md +2 -2
- package/package/corpus/skills/cratis-chronicle-reactor/SKILL.md +22 -8
- package/package/corpus/skills/cratis-chronicle-read-model/SKILL.md +1 -1
- package/package/corpus/skills/cratis-chronicle-read-model/references/queries.md +1 -1
- package/package/corpus/skills/cratis-chronicle-read-model-specifications/SKILL.md +3 -1
- package/package/corpus/skills/cratis-chronicle-reducer/SKILL.md +1 -1
- package/package/corpus/skills/cratis-chronicle-web-workbench/SKILL.md +19 -11
- package/package/corpus/skills/cratis-cli-terminal-workbench/SKILL.md +11 -6
- package/package/corpus/skills/cratis-code-review/SKILL.md +8 -6
- package/package/corpus/skills/cratis-components-accessibility/SKILL.md +112 -82
- package/package/corpus/skills/cratis-components-schema-editor/SKILL.md +12 -10
- package/package/corpus/skills/cratis-components-stepper-command-dialog/SKILL.md +11 -12
- package/package/corpus/skills/cratis-components-styling/SKILL.md +204 -170
- package/package/corpus/skills/cratis-components-toolbar/SKILL.md +32 -26
- package/package/corpus/skills/cratis-event-model-diagram/SKILL.md +1 -1
- package/package/corpus/skills/cratis-fundamentals-concept/SKILL.md +2 -2
- package/package/corpus/skills/cratis-fundamentals-type-discovery/SKILL.md +2 -2
- package/package/corpus/skills/cratis-lens-browser-extension/SKILL.md +4 -2
- package/package/corpus/skills/cratis-performance-review/SKILL.md +4 -2
- package/package/corpus/skills/cratis-screenplay-model-authoring/SKILL.md +15 -5
- package/package/corpus/skills/cratis-security-review/SKILL.md +20 -3
- package/package/corpus/skills/cratis-specifications-csharp/SKILL.md +3 -3
- package/package/corpus/skills/cratis-specifications-csharp/references/application-scenarios.md +12 -8
- package/package/corpus/skills/cratis-specifications-csharp/references/csharp-patterns.md +1 -1
- package/package/corpus/skills/cratis-specifications-csharp/references/integration-specs.md +7 -3
- package/package/corpus/skills/cratis-specifications-typescript/SKILL.md +6 -3
- package/package/corpus/skills/cratis-specifications-typescript/references/typescript-patterns.md +21 -4
- package/package/corpus/skills/cratis-stage-rendering-and-sandbox/SKILL.md +29 -17
- package/package.json +1 -1
|
@@ -7,11 +7,12 @@ description: >
|
|
|
7
7
|
and reactors — all in strict compliance with the vertical slice architecture.
|
|
8
8
|
model: claude-sonnet-4-5
|
|
9
9
|
tools:
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
10
|
+
- Read
|
|
11
|
+
- Grep
|
|
12
|
+
- Glob
|
|
13
|
+
- Bash
|
|
14
|
+
- Edit
|
|
15
|
+
- Write
|
|
15
16
|
---
|
|
16
17
|
|
|
17
18
|
# Backend Developer
|
|
@@ -6,11 +6,11 @@ description: >
|
|
|
6
6
|
TypeScript conventions, and vertical slice correctness before merge.
|
|
7
7
|
model: claude-sonnet-4-5
|
|
8
8
|
tools:
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
|
|
9
|
+
- Read
|
|
10
|
+
- Grep
|
|
11
|
+
- Glob
|
|
12
|
+
- Bash
|
|
13
|
+
readonly: true
|
|
14
14
|
---
|
|
15
15
|
|
|
16
16
|
# Code Reviewer
|
|
@@ -110,7 +110,7 @@ When checking unused code, references, or naming, use semantic navigation if the
|
|
|
110
110
|
|
|
111
111
|
## TypeScript Styling checklist
|
|
112
112
|
|
|
113
|
-
- [ ] No hard-coded hex/rgb values —
|
|
113
|
+
- [ ] No hard-coded hex/rgb values — `--cratis-*` tokens used
|
|
114
114
|
- [ ] CSS co-located with component (`.css` file in same folder)
|
|
115
115
|
- [ ] No `!important` unless absolutely required and justified with a comment
|
|
116
116
|
|
|
@@ -128,7 +128,7 @@ When checking unused code, references, or naming, use semantic navigation if the
|
|
|
128
128
|
- [ ] README.md exists for complex component folders
|
|
129
129
|
- [ ] `CommandDialog` from `@cratis/components/CommandDialog` used for command-based dialogs
|
|
130
130
|
- [ ] `Dialog` from `@cratis/components/Dialogs` used for data-only dialogs
|
|
131
|
-
- [ ] Never
|
|
131
|
+
- [ ] Never uses a vendor or hand-rolled modal — `CommandDialog` / `Dialog` from Cratis Components
|
|
132
132
|
- [ ] No monolithic components — decomposed into smaller, focused sub-components
|
|
133
133
|
|
|
134
134
|
---
|
|
@@ -6,11 +6,12 @@ description: >
|
|
|
6
6
|
proxies, following the project's component and styling conventions.
|
|
7
7
|
model: claude-sonnet-4-5
|
|
8
8
|
tools:
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
9
|
+
- Read
|
|
10
|
+
- Grep
|
|
11
|
+
- Glob
|
|
12
|
+
- Bash
|
|
13
|
+
- Edit
|
|
14
|
+
- Write
|
|
14
15
|
---
|
|
15
16
|
|
|
16
17
|
# Frontend Developer
|
|
@@ -64,7 +65,7 @@ Confirm that the TypeScript proxies exist in the slice folder before writing any
|
|
|
64
65
|
- Place `.tsx` files in the **same folder** as the corresponding `.cs` file.
|
|
65
66
|
- Do NOT prefix the file name with the feature or slice name (folder provides context).
|
|
66
67
|
- Each component has its own `.css` file for static styles.
|
|
67
|
-
- Use
|
|
68
|
+
- Use the `--cratis-*` design tokens for all colors, backgrounds, and borders — never hard-code hex values. The default stack is Cratis Components 4 (Components-owned markup and tokens; no PrimeReact) — not Tailwind.
|
|
68
69
|
- Use `const` over `let`.
|
|
69
70
|
- Use full descriptive names (never abbreviations like `e`, `idx`, `prev`).
|
|
70
71
|
- **Move non-trivial state out of the render function** into a `withViewModel` view model (or a tested state module) — see `react.md`. Extract as soon as a component has 3+ `useState`, a state-syncing `useEffect`, or derived values. A view model is a plain class with no React hooks, constructible in a spec.
|
|
@@ -90,28 +91,22 @@ const handleSubmit = async () => {
|
|
|
90
91
|
## Query usage pattern (with paging)
|
|
91
92
|
|
|
92
93
|
```tsx
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
totalRecords={allProjectsResult.paging.totalItems}
|
|
104
|
-
alwaysShowPaginator={false}
|
|
105
|
-
first={allProjectsResult.paging.page * pageSize}
|
|
106
|
-
onPage={event => setPage(event.page ?? 0)}
|
|
107
|
-
scrollable scrollHeight="flex"
|
|
108
|
-
emptyMessage="No items found.">
|
|
109
|
-
<Column field="name" header="Name" />
|
|
110
|
-
</DataTable>
|
|
111
|
-
);
|
|
112
|
-
};
|
|
94
|
+
import { DataTableForQuery, Column } from '@cratis/components/DataTables';
|
|
95
|
+
import { AllProjects } from './AllProjects';
|
|
96
|
+
|
|
97
|
+
// The table subscribes to the query itself and pages server-side (20 rows a page);
|
|
98
|
+
// do not fetch rows and pass an items array.
|
|
99
|
+
export const Listing = () => (
|
|
100
|
+
<DataTableForQuery query={AllProjects} emptyMessage="No items found.">
|
|
101
|
+
<Column field="name" header="Name" />
|
|
102
|
+
</DataTableForQuery>
|
|
103
|
+
);
|
|
113
104
|
```
|
|
114
105
|
|
|
106
|
+
For caller-controlled paging outside a table, use the proxy hook directly —
|
|
107
|
+
`const [result, , , setPage] = AllProjects.useWithPaging(pageSize)`; `result.data` and
|
|
108
|
+
`result.paging` (`page`, `size`, `totalItems`, `totalPages`) drive your own layout.
|
|
109
|
+
|
|
115
110
|
---
|
|
116
111
|
|
|
117
112
|
## Dialog patterns
|
|
@@ -133,7 +128,7 @@ export const AddProject = ({ closeDialog }: DialogProps) => {
|
|
|
133
128
|
title="Add Project"
|
|
134
129
|
okLabel="Add"
|
|
135
130
|
cancelLabel="Cancel"
|
|
136
|
-
|
|
131
|
+
onSuccess={() => closeDialog(DialogResult.Ok)}
|
|
137
132
|
onCancel={() => closeDialog(DialogResult.Cancelled)}
|
|
138
133
|
>
|
|
139
134
|
<InputTextField<RegisterProject>
|
|
@@ -157,7 +152,7 @@ Use this for dialogs that collect data and return it without executing a command
|
|
|
157
152
|
import { useState } from 'react';
|
|
158
153
|
import { DialogProps, DialogResult } from '@cratis/arc.react/dialogs';
|
|
159
154
|
import { Dialog } from '@cratis/components/Dialogs';
|
|
160
|
-
import {
|
|
155
|
+
import { TextInput } from '@cratis/components/Common';
|
|
161
156
|
|
|
162
157
|
export const AddProject = ({ closeDialog }: DialogProps<{ name: string }>) => {
|
|
163
158
|
const [name, setName] = useState('');
|
|
@@ -173,18 +168,18 @@ export const AddProject = ({ closeDialog }: DialogProps<{ name: string }>) => {
|
|
|
173
168
|
onConfirm={() => closeDialog(DialogResult.Ok, { name })}
|
|
174
169
|
onCancel={() => closeDialog(DialogResult.Cancelled)}
|
|
175
170
|
>
|
|
176
|
-
<
|
|
171
|
+
<TextInput
|
|
177
172
|
value={name}
|
|
178
|
-
onChange={
|
|
173
|
+
onChange={value => setName(value)}
|
|
179
174
|
placeholder="Enter a name"
|
|
180
|
-
|
|
175
|
+
aria-label="Project name"
|
|
181
176
|
/>
|
|
182
177
|
</Dialog>
|
|
183
178
|
);
|
|
184
179
|
};
|
|
185
180
|
```
|
|
186
181
|
|
|
187
|
-
> **Never**
|
|
182
|
+
> **Never** use a vendor or hand-rolled modal — dialogs are Components-owned.
|
|
188
183
|
|
|
189
184
|
---
|
|
190
185
|
|
|
@@ -195,20 +190,19 @@ import { Page } from '@cratis/components/Common';
|
|
|
195
190
|
import { AddProject } from './Registration/AddProject';
|
|
196
191
|
import { Listing } from './Listing/Listing';
|
|
197
192
|
import { DialogResult, useDialog } from '@cratis/arc.react/dialogs';
|
|
198
|
-
import { Button } from '
|
|
199
|
-
import
|
|
193
|
+
import { Button } from '@cratis/components/Common';
|
|
194
|
+
import { MdAdd } from 'react-icons/md';
|
|
200
195
|
|
|
201
196
|
export const Projects = () => {
|
|
202
197
|
const [AddProjectDialog, showAddProjectDialog] = useDialog(AddProject);
|
|
203
198
|
|
|
204
199
|
// For a query-backed list page, prefer `DataPage` with `<DataPage.MenuItems>`
|
|
205
|
-
// (it owns the action bar).
|
|
206
|
-
//
|
|
200
|
+
// (it owns the action bar). For a custom action row, compose Components `Button`s
|
|
201
|
+
// (`variant`: solid | outline | ghost | link; `tone`: neutral | accent | positive | caution | critical).
|
|
207
202
|
return (
|
|
208
203
|
<Page title="Projects">
|
|
209
|
-
<Button variant="
|
|
210
|
-
|
|
211
|
-
<span>Add Project</span>
|
|
204
|
+
<Button variant="ghost" icon={<MdAdd aria-hidden="true" />} onClick={() => showAddProjectDialog()}>
|
|
205
|
+
Add Project
|
|
212
206
|
</Button>
|
|
213
207
|
<Listing />
|
|
214
208
|
<AddProjectDialog />
|
|
@@ -238,7 +232,7 @@ Before handing back:
|
|
|
238
232
|
- [ ] `npx tsc -b` passes with zero errors
|
|
239
233
|
- [ ] Components are in the correct slice folder
|
|
240
234
|
- [ ] If the app has a localization convention, user-visible text is routed through it (product policy — not a Cratis rule)
|
|
241
|
-
- [ ] No hard-coded hex/rgb color values —
|
|
235
|
+
- [ ] No hard-coded hex/rgb color values — `--cratis-*` tokens used throughout
|
|
242
236
|
- [ ] All variable/parameter names are fully descriptive (no abbreviations)
|
|
243
237
|
- [ ] No `any` types — `unknown` with type guards where needed
|
|
244
238
|
- [ ] Composition page updated to include the new component
|
|
@@ -10,10 +10,11 @@ description: >
|
|
|
10
10
|
large refactors, or any goal that spans more than one concern.
|
|
11
11
|
model: claude-sonnet-4-5
|
|
12
12
|
tools:
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
13
|
+
- Read
|
|
14
|
+
- Grep
|
|
15
|
+
- Glob
|
|
16
|
+
- Bash
|
|
17
|
+
- Agent
|
|
17
18
|
---
|
|
18
19
|
|
|
19
20
|
# Orchestrator
|
|
@@ -6,10 +6,11 @@ description: >
|
|
|
6
6
|
React render overhead, and Chronicle anti-patterns before merge.
|
|
7
7
|
model: claude-sonnet-4-5
|
|
8
8
|
tools:
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
9
|
+
- Read
|
|
10
|
+
- Grep
|
|
11
|
+
- Glob
|
|
12
|
+
- Bash
|
|
13
|
+
readonly: true
|
|
13
14
|
---
|
|
14
15
|
|
|
15
16
|
# Performance Reviewer
|
|
@@ -5,7 +5,13 @@ description: >
|
|
|
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
7
|
model: claude-opus-4-8
|
|
8
|
-
tools:
|
|
8
|
+
tools:
|
|
9
|
+
- Read
|
|
10
|
+
- Grep
|
|
11
|
+
- Glob
|
|
12
|
+
- Bash
|
|
13
|
+
- Edit
|
|
14
|
+
- Write
|
|
9
15
|
---
|
|
10
16
|
|
|
11
17
|
# Slice Implementer
|
|
@@ -52,7 +58,7 @@ Proxies now exist. Build React components from the generated proxies (`react.md`
|
|
|
52
58
|
- Events: no arguments on `[EventType]`, non-nullable, past tense, `<summary>`, never carry the event-source id.
|
|
53
59
|
- `[OnceOnly]` on non-idempotent reactor side effects; reactors return side-effect events or use `ICommandPipeline` (never `IEventLog`).
|
|
54
60
|
- Specs `#if DEBUG`, command aliased, per-test unique values.
|
|
55
|
-
- Frontend via `withViewModel` + Arc proxy hooks + Cratis Components; never edit generated proxies; never
|
|
61
|
+
- Frontend via `withViewModel` + Arc proxy hooks + Cratis Components; never edit generated proxies; never use a vendor or hand-rolled modal (dialogs come from `@cratis/components/CommandDialog` and `/Dialogs`).
|
|
56
62
|
|
|
57
63
|
## Output
|
|
58
64
|
|
|
@@ -6,10 +6,12 @@ description: >
|
|
|
6
6
|
comprehensive behavior coverage following the project's BDD conventions.
|
|
7
7
|
model: claude-sonnet-4-5
|
|
8
8
|
tools:
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
9
|
+
- Read
|
|
10
|
+
- Grep
|
|
11
|
+
- Glob
|
|
12
|
+
- Bash
|
|
13
|
+
- Edit
|
|
14
|
+
- Write
|
|
13
15
|
---
|
|
14
16
|
|
|
15
17
|
# Spec Writer
|
|
@@ -23,6 +23,13 @@ export interface AgentConfig {
|
|
|
23
23
|
name: string;
|
|
24
24
|
description: string;
|
|
25
25
|
tools?: string[];
|
|
26
|
+
/**
|
|
27
|
+
* Set when the agent declared a non-empty `tools` list of which nothing maps to a Pi tool. Such an
|
|
28
|
+
* agent must not launch: dropping the whole list would hand a deliberately restricted agent (a
|
|
29
|
+
* read-only reviewer) Pi's full default toolset. Mirrors Claude Code, which refuses to launch a
|
|
30
|
+
* subagent whose `tools` list resolves to no tool.
|
|
31
|
+
*/
|
|
32
|
+
toolsError?: string;
|
|
26
33
|
model?: string;
|
|
27
34
|
systemPrompt: string;
|
|
28
35
|
source: "package" | "user" | "project";
|
|
@@ -70,22 +77,59 @@ const TOOL_NAME_MAP: Record<string, string | null> = {
|
|
|
70
77
|
websearch: null,
|
|
71
78
|
};
|
|
72
79
|
|
|
80
|
+
/**
|
|
81
|
+
* The outcome of normalizing a frontmatter `tools` value.
|
|
82
|
+
*
|
|
83
|
+
* `tools` is the de-duplicated Pi allowlist, or `undefined` when the agent declared no tools at
|
|
84
|
+
* all (it then inherits Pi's full default toolset, exactly like an omitted `tools:` in Claude Code).
|
|
85
|
+
* `unresolved` lists every declared name that is neither a Pi built-in nor a known orchestration
|
|
86
|
+
* tool, so a caller can tell "nothing declared" apart from "declared, but nothing resolved".
|
|
87
|
+
*/
|
|
88
|
+
export interface NormalizedTools {
|
|
89
|
+
tools?: string[];
|
|
90
|
+
unresolved: string[];
|
|
91
|
+
}
|
|
92
|
+
|
|
73
93
|
/**
|
|
74
94
|
* Normalize a frontmatter `tools` value to a de-duplicated list of Pi tool names.
|
|
75
95
|
* Accepts both YAML spellings in use (`tools: [Read, Bash]` and `tools: Read, Bash`).
|
|
76
|
-
* Returns `undefined` when nothing maps, so the subagent inherits Pi's full default
|
|
77
|
-
* toolset rather than being launched with an empty allowlist.
|
|
78
96
|
*/
|
|
79
|
-
export function
|
|
97
|
+
export function normalizeToolsDetailed(value: unknown): NormalizedTools {
|
|
80
98
|
const raw = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : [];
|
|
81
|
-
const
|
|
99
|
+
const declared = raw
|
|
82
100
|
.filter((t): t is string => typeof t === "string")
|
|
83
|
-
.map((t) => t.trim()
|
|
84
|
-
.filter(Boolean)
|
|
101
|
+
.map((t) => t.trim())
|
|
102
|
+
.filter(Boolean);
|
|
103
|
+
const mapped = declared
|
|
104
|
+
.map((t) => t.toLowerCase())
|
|
85
105
|
.map((t) => (t in TOOL_NAME_MAP ? TOOL_NAME_MAP[t] : null))
|
|
86
106
|
.filter((t): t is string => typeof t === "string");
|
|
107
|
+
const unresolved = declared.filter((t) => !(t.toLowerCase() in TOOL_NAME_MAP));
|
|
87
108
|
const deduped = Array.from(new Set(mapped));
|
|
88
|
-
return deduped.length > 0 ? deduped : undefined;
|
|
109
|
+
return { tools: deduped.length > 0 ? deduped : undefined, unresolved };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Normalize a frontmatter `tools` value to a de-duplicated list of Pi tool names, or `undefined`
|
|
114
|
+
* when nothing maps. Prefer {@link normalizeToolsDetailed} where the difference between an omitted
|
|
115
|
+
* list and an unresolvable one matters — it always does when the result gates a launch.
|
|
116
|
+
*/
|
|
117
|
+
export function normalizeTools(value: unknown): string[] | undefined {
|
|
118
|
+
return normalizeToolsDetailed(value).tools;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The launch error for an agent whose declared tools resolve to nothing, or `undefined` when the
|
|
123
|
+
* agent either declared no tools or at least one of them resolved.
|
|
124
|
+
*/
|
|
125
|
+
export function toolsErrorFor(name: string, value: unknown): string | undefined {
|
|
126
|
+
const { tools, unresolved } = normalizeToolsDetailed(value);
|
|
127
|
+
if (tools !== undefined || unresolved.length === 0) return undefined;
|
|
128
|
+
return (
|
|
129
|
+
`Agent "${name}" declares tools that resolve to no Pi tool: ${unresolved.join(", ")}. ` +
|
|
130
|
+
`It would launch with every tool instead of the restriction it declares, so it is not launched. ` +
|
|
131
|
+
`Declare Pi-resolvable names (Read, Write, Edit, Bash, Grep, Glob, ls) in its frontmatter.`
|
|
132
|
+
);
|
|
89
133
|
}
|
|
90
134
|
|
|
91
135
|
function loadAgentsFromDir(dir: string, source: "package" | "user" | "project"): AgentConfig[] {
|
|
@@ -119,6 +163,7 @@ function loadAgentsFromDir(dir: string, source: "package" | "user" | "project"):
|
|
|
119
163
|
name: frontmatter.name,
|
|
120
164
|
description: frontmatter.description,
|
|
121
165
|
tools: normalizeTools(frontmatter.tools),
|
|
166
|
+
toolsError: toolsErrorFor(frontmatter.name, frontmatter.tools),
|
|
122
167
|
model: typeof frontmatter.model === "string" ? frontmatter.model.trim() : undefined,
|
|
123
168
|
systemPrompt: body,
|
|
124
169
|
source,
|
|
@@ -96,6 +96,20 @@ async function runSingleAgent(
|
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
// A restricted agent whose restriction cannot be expressed in Pi is not launched at all: launching it
|
|
100
|
+
// with the full toolset would be the opposite of what its author declared (see AgentConfig.toolsError).
|
|
101
|
+
if (agent.toolsError) {
|
|
102
|
+
return {
|
|
103
|
+
agent: agentName,
|
|
104
|
+
agentSource: agent.source,
|
|
105
|
+
task,
|
|
106
|
+
exitCode: 1,
|
|
107
|
+
finalText: "",
|
|
108
|
+
stderr: agent.toolsError,
|
|
109
|
+
step,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
99
113
|
// An agent that pins no model inherits the dispatching session's model + thinking level.
|
|
100
114
|
const model = agent.model ?? dispatch.model;
|
|
101
115
|
const args: string[] = ["--mode", "json", "-p", "--no-session"];
|
|
@@ -13,24 +13,27 @@ Three layers:
|
|
|
13
13
|
| Quality gate | `Stop` | `scripts/cratis-quality-gate.sh` | one build/test run, only when relevant files changed | exits **2** — the turn does not end |
|
|
14
14
|
|
|
15
15
|
The Claude Code wiring that fires them is tracked here, in
|
|
16
|
-
[`settings.template.json`](./settings.template.json). Claude reads `.claude/settings.json
|
|
17
|
-
|
|
16
|
+
[`settings.template.json`](./settings.template.json). Claude reads `.claude/settings.json`. In a
|
|
17
|
+
repository set up with `cratis ai install`, that file is a **symlink** to this template and follows
|
|
18
|
+
every `cratis ai update`; there is nothing to copy. Where the corpus is present without the CLI,
|
|
19
|
+
activate the hooks by copying the template once:
|
|
18
20
|
|
|
19
21
|
```bash
|
|
20
22
|
cp .cratis/ai/hooks/settings.template.json .claude/settings.json
|
|
21
23
|
```
|
|
22
24
|
|
|
23
|
-
If you already have a `.claude/settings.json
|
|
24
|
-
than overwriting — the rest of that file is yours
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
`scripts/validate-ai-setup.sh` checks the template against the script names this page documents.
|
|
25
|
+
If you already have a `.claude/settings.json` of your own, merge the template's `hooks` block into it
|
|
26
|
+
rather than overwriting — the rest of that file is yours — and re-copy after the template changes.
|
|
27
|
+
**Edit the template, never the copy or the managed file**: `.cratis/ai/` is the source of truth (see the
|
|
28
|
+
[corpus README](../README.md)), and `cratis ai status` reports a hand-edited managed file as drift.
|
|
28
29
|
|
|
29
30
|
The markdown files in this folder (`agent-stop.md`, `pre-commit.md`) remain *lifecycle guidance* —
|
|
30
31
|
they describe what a hook should do for tools that have no wiring yet.
|
|
31
32
|
|
|
32
|
-
> Hooks are the one surface with no folder adapter: Claude reads `.claude/settings.json
|
|
33
|
-
>
|
|
33
|
+
> Hooks are the one surface with no folder adapter: Claude Code reads `.claude/settings.json`;
|
|
34
|
+
> the Pi harness bridges the same three scripts to its own events through the `cratis-hooks`
|
|
35
|
+
> extension under `../harnesses/pi/extensions/`; Copilot would read `.github/hooks/*.json`, and no
|
|
36
|
+
> Copilot wiring ships yet.
|
|
34
37
|
|
|
35
38
|
## What is enforced
|
|
36
39
|
|
|
@@ -83,13 +86,16 @@ the managed gates running unchanged.
|
|
|
83
86
|
`general.md` and the steps in [`agent-stop.md`](./agent-stop.md) — Debug build, specs, Release
|
|
84
87
|
build (with `-p:CratisProxiesOutputPath=` per `general.md`, so the proxy generator does not
|
|
85
88
|
re-run and touch already-correct generated files), frontend lint / compile / compile-specs /
|
|
86
|
-
test
|
|
89
|
+
test.
|
|
87
90
|
|
|
88
|
-
## The
|
|
91
|
+
## The package drift guards
|
|
89
92
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
Three scripts sit outside the three layers and are not bound to a hook event: run
|
|
94
|
+
`scripts/validate-package-subpaths.sh` directly and it chains `validate-type-references.sh` and
|
|
95
|
+
`validate-package-imports.sh` over the same roots. They check the corpus text against the packages a
|
|
96
|
+
repository actually has installed, so they are meaningful only where `node_modules` or a NuGet cache
|
|
97
|
+
exists. The corpus's own structure, catalog and adapters are verified in the `Cratis/AI` repository
|
|
98
|
+
before anything is published; a consuming repository checks its installed copy with `cratis ai status`.
|
|
93
99
|
|
|
94
100
|
### Package subpath existence — `scripts/validate-package-subpaths.sh` (warn)
|
|
95
101
|
|
|
@@ -113,11 +119,9 @@ and the `ai-corpus` CI job checks out the tree and installs nothing — so faili
|
|
|
113
119
|
permanent no-op in CI while turning repos red locally for their own dependency pin. The warning
|
|
114
120
|
names the file, the line and the installed version, and leaves the judgement to a human.
|
|
115
121
|
|
|
116
|
-
>
|
|
117
|
-
>
|
|
118
|
-
>
|
|
119
|
-
> hooks do in a **consuming** repository. Here they are silent, which is the designed
|
|
120
|
-
> behavior, not a broken setup.
|
|
122
|
+
> Every `.cs` / `.ts` / `Source/**` reference below describes what the hooks do in a repository
|
|
123
|
+
> that has such a project. Where there is none — a documentation or corpus-only repository — the
|
|
124
|
+
> hooks are silent, which is the designed behavior, not a broken setup.
|
|
121
125
|
|
|
122
126
|
**Silent when it cannot judge.** No `jq`, no `node_modules`, a package this repository does not
|
|
123
127
|
depend on, or a package published without an `exports` map: skipped without a word. "Not installed"
|
|
@@ -137,8 +141,7 @@ specifiers, nothing else.
|
|
|
137
141
|
|
|
138
142
|
Run it standalone, optionally over other roots, and add `CRATIS_HOOKS_SUBPATH_REPORT=1` to see every
|
|
139
143
|
reference and how it resolved rather than only the failures. It invokes Tier 3 before its own gates
|
|
140
|
-
and Tier 2 after its own work, over the same roots, so
|
|
141
|
-
`validate-ai-setup.sh` gets all three.
|
|
144
|
+
and Tier 2 after its own work, over the same roots, so one call gets all three.
|
|
142
145
|
|
|
143
146
|
### Named import existence — `scripts/validate-package-imports.sh` (warn)
|
|
144
147
|
|
|
@@ -247,7 +250,7 @@ phrases and the guard stays quiet.
|
|
|
247
250
|
**Silent when it cannot judge.** No `Directory.Packages.props`, no local NuGet cache, or a cache
|
|
248
251
|
holding none of the pinned versions: skipped without a word. It needs no `jq` and no `node_modules`,
|
|
249
252
|
which is why Tier 1 invokes it *above* its own gates rather than beside the Tier 2 call — a backend-
|
|
250
|
-
only repository must still get this check. It adds about 1.4 s to
|
|
253
|
+
only repository must still get this check. It adds about 1.4 s to the chained run.
|
|
251
254
|
|
|
252
255
|
**The allowlist — `scripts/type-references-allowlist.txt`.** Thirteen entries, each with a written
|
|
253
256
|
justification: ASP.NET Core and BCL attributes that live in ref packs (which ship no XML docs at
|
|
@@ -288,15 +291,15 @@ gate script find it — `workingDirectoryFrom: ["*.slnx", "*.sln", "**/*.slnx",
|
|
|
288
291
|
`dotnet build` in whichever directory holds the repository's own solution, preferring one at the
|
|
289
292
|
root because the globs are tried in order. The frontend gates discover `package.json` the same way.
|
|
290
293
|
The same shipped file therefore activates in an application repository, activates in a framework
|
|
291
|
-
repository, and stays quiet in a
|
|
294
|
+
repository, and stays quiet in a repository that has no project at all.
|
|
292
295
|
|
|
293
296
|
**Overriding it, in order of increasing force.** Set `workingDirectory` on a gate to pin one of
|
|
294
297
|
several candidate projects; drop a `quality-gates.json` of your own in place of the shipped one; or
|
|
295
298
|
point `CRATIS_HOOKS_GATES` at a file anywhere. None of them requires forking the script.
|
|
296
299
|
|
|
297
300
|
**Profile note.** The C# patterns are application-profile and scoped to `Source/**/*.cs`, which is
|
|
298
|
-
the application source root [`../rules/general.md`](../rules/general.md) documents
|
|
299
|
-
|
|
301
|
+
the application source root [`../rules/general.md`](../rules/general.md) documents. A
|
|
302
|
+
framework-profile repository (Arc, Chronicle,
|
|
300
303
|
Fundamentals, Components — see [`../rules/framework.md`](../rules/framework.md)) has no vertical
|
|
301
304
|
slices and should disable them in its `cratis-patterns.local.json`; a repository whose application
|
|
302
305
|
source root is not `Source/` re-scopes the `paths` globs there too.
|
|
@@ -350,8 +353,7 @@ Each is an explicit, auditable opt-out — none of them is a default.
|
|
|
350
353
|
The scripts read hook JSON on stdin, so they are directly testable:
|
|
351
354
|
|
|
352
355
|
The pattern pass and the gate both read the repository they are pointed at, so testing them means
|
|
353
|
-
pointing them at a repository that *has* the thing under test
|
|
354
|
-
project, so run those two against a consuming checkout (or a scratch tree), and expect silence here.
|
|
356
|
+
pointing them at a repository that *has* the thing under test; where it is absent, expect silence.
|
|
355
357
|
|
|
356
358
|
```bash
|
|
357
359
|
# Pattern pass — expect exit 0, and JSON on stdout only when something matched.
|
|
@@ -10,11 +10,7 @@ When the agent finishes a session, verify the work against **fresh signals** bef
|
|
|
10
10
|
|
|
11
11
|
## Pick the path for this repository
|
|
12
12
|
|
|
13
|
-
- **
|
|
14
|
-
```
|
|
15
|
-
.cratis/ai/hooks/scripts/validate-ai-setup.sh
|
|
16
|
-
```
|
|
17
|
-
Stop only when it passes (symlinks/adapters healthy, frontmatter present, no broken cross-links). Skip the application gates below.
|
|
13
|
+
- **No .NET solution or frontend** — a documentation or corpus-only repository. Run that repository's own documentation and corpus checks instead of a code build, and `cratis ai status` when the change touched the installed AI corpus or its adapters (a hand-edited managed file is reported as drift). Skip the application gates below.
|
|
18
14
|
|
|
19
15
|
- **Application repo** — there is a .NET solution and/or a frontend. Run the application gates below.
|
|
20
16
|
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"pattern": "from[[:space:]]+['\"]primereact/dialog['\"]",
|
|
79
79
|
"paths": ["**/*.ts", "**/*.tsx"],
|
|
80
80
|
"exclude_paths": ["**/node_modules/**", "**/dist/**", "**/wwwroot/**", "**/obj/**", "**/bin/**"],
|
|
81
|
-
"message": "
|
|
81
|
+
"message": "Cratis Components 4 has no PrimeReact underneath — a `primereact/dialog` import is a leftover from Components 3. Use `CommandDialog` from `@cratis/components/CommandDialog` or `Dialog` from `@cratis/components/Dialogs`."
|
|
82
82
|
}
|
|
83
83
|
]
|
|
84
84
|
}
|
|
@@ -113,8 +113,7 @@ done
|
|
|
113
113
|
|
|
114
114
|
# Tier 2 over the same roots: a subpath that resolves says nothing about the *names* imported
|
|
115
115
|
# through it. Kept in its own script — a different question, a different corpus extraction and a
|
|
116
|
-
# different report variable — and invoked from here so
|
|
117
|
-
# gets both. Tested with -f, not -x, and run through `bash`: a checkout that lost the exec bit must
|
|
116
|
+
# different report variable — and invoked from here so one call to this script gets both. Tested with -f, not -x, and run through `bash`: a checkout that lost the exec bit must
|
|
118
117
|
# not silently drop the guard.
|
|
119
118
|
imports="$(dirname "${BASH_SOURCE[0]}")/validate-package-imports.sh"
|
|
120
119
|
if [[ -f "$imports" ]]; then bash "$imports" "$@" || true; fi
|