@rmrdeveloper/sideroom-pi 8.9.0 → 8.11.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 +66 -45
- package/extensions/ask/index.ts +3 -2
- package/extensions/explain/guard.ts +14 -12
- package/extensions/explain/model.ts +4 -3
- package/extensions/guidelines/review.ts +63 -18
- package/extensions/jev/client.ts +43 -8
- package/extensions/jev/context.ts +327 -0
- package/extensions/jev/guard.ts +92 -25
- package/extensions/jev/index.ts +2 -2
- package/extensions/jev/model.ts +97 -16
- package/extensions/jev/ui.ts +1 -0
- package/extensions/persona/catalog.ts +2 -2
- package/extensions/persona/prompt.ts +2 -2
- package/extensions/shared/file-path.ts +26 -1
- package/extensions/shared/review-note.ts +14 -0
- package/extensions/shared/settle.ts +50 -0
- package/package.json +3 -3
- package/skills/sideroom-architecture/SKILL.md +12 -5
- package/skills/sideroom-grill/SKILL.md +10 -2
- package/skills/sideroom-persona/SKILL.md +34 -24
package/extensions/jev/model.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { languageForPath } from '../rules/catalog.ts';
|
|
1
|
+
import { LANGUAGE, languageForPath } from '../rules/catalog.ts';
|
|
2
2
|
import { type AddedLine, addedLines } from '../rules/model.ts';
|
|
3
3
|
|
|
4
4
|
export type { AddedLine } from '../rules/model.ts';
|
|
@@ -11,11 +11,11 @@ export const JEV_MODEL = 'jev-latest';
|
|
|
11
11
|
// noise on every edit.
|
|
12
12
|
export const VIOLATION_PROBABILITY = 0.8;
|
|
13
13
|
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
export const
|
|
14
|
+
// TypeSafe documents 32k tokens for state plus the longest question and 64k
|
|
15
|
+
// for the full request. JSON UTF-8 bytes are a deliberately conservative
|
|
16
|
+
// upper bound; no third-party tokenizer can promise the model's exact count.
|
|
17
|
+
export const MAX_STATE_BYTES = 32_000;
|
|
18
|
+
export const MAX_REQUEST_BYTES = 64_000;
|
|
19
19
|
|
|
20
20
|
const NOUL_TYPE = 'noul';
|
|
21
21
|
|
|
@@ -30,8 +30,7 @@ export interface JevRule {
|
|
|
30
30
|
readonly question: JevNoulQuestion;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
//
|
|
34
|
-
// check covers. Rules 11, 12, 15, 16, 17 and 18 need the module's neighbours.
|
|
33
|
+
// Questions must only use evidence in the file and the selected direct neighbours.
|
|
35
34
|
export const JEV_RULES: readonly JevRule[] = [
|
|
36
35
|
{
|
|
37
36
|
id: 'guard-clauses',
|
|
@@ -64,10 +63,11 @@ export const JEV_RULES: readonly JevRule[] = [
|
|
|
64
63
|
question: {
|
|
65
64
|
type: NOUL_TYPE,
|
|
66
65
|
instructions:
|
|
67
|
-
'In `change.addedLines`, does a function
|
|
66
|
+
'In `change.addedLines`, does a function return a value while changing a parameter, shared state or an external resource, or does a getter/query cause such a side effect? Ignore changes to local collections created inside the function to build its result.',
|
|
68
67
|
criteria: {
|
|
69
|
-
true: 'A function
|
|
70
|
-
false:
|
|
68
|
+
true: 'A function returns data while changing caller-visible state, or a getter/query mutates that state',
|
|
69
|
+
false:
|
|
70
|
+
'Local accumulation and read-only file access do not change caller-visible state',
|
|
71
71
|
},
|
|
72
72
|
},
|
|
73
73
|
},
|
|
@@ -101,15 +101,60 @@ export const JEV_RULES: readonly JevRule[] = [
|
|
|
101
101
|
question: {
|
|
102
102
|
type: NOUL_TYPE,
|
|
103
103
|
instructions:
|
|
104
|
-
'
|
|
104
|
+
'Does `change.addedLines` repeat a required, type or range check that is visibly performed at the input boundary in `file.body` or `context.files`? Do not infer unseen validation.',
|
|
105
|
+
criteria: {
|
|
106
|
+
true: 'The same check is visible both at the boundary and in the added lines',
|
|
107
|
+
false:
|
|
108
|
+
'The boundary check is not visible, or the added lines do not repeat it',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: 'single-responsibility',
|
|
114
|
+
question: {
|
|
115
|
+
type: NOUL_TYPE,
|
|
116
|
+
instructions:
|
|
117
|
+
'Do the added lines give the changed function or class two unrelated reasons to change, visible in its body? Do not flag a function merely for coordinating focused collaborators.',
|
|
105
118
|
criteria: {
|
|
106
|
-
true: '
|
|
107
|
-
false:
|
|
119
|
+
true: 'The changed unit itself performs two unrelated responsibilities with visible evidence',
|
|
120
|
+
false:
|
|
121
|
+
'The unit has one responsibility or only coordinates separate focused units',
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: 'dependency-direction',
|
|
127
|
+
question: {
|
|
128
|
+
type: NOUL_TYPE,
|
|
129
|
+
instructions:
|
|
130
|
+
'Do the added lines introduce an import from business/domain code to infrastructure, HTTP, or a framework? Use paths and bodies in `context.files` as evidence; never infer a layer from an unavailable file.',
|
|
131
|
+
criteria: {
|
|
132
|
+
true: 'A new inward-to-outward dependency is visible in the import and its related file',
|
|
133
|
+
false:
|
|
134
|
+
'No such dependency is visible, or the roles of the files are unclear',
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: 'comments',
|
|
140
|
+
question: {
|
|
141
|
+
type: NOUL_TYPE,
|
|
142
|
+
instructions:
|
|
143
|
+
'Does a comment in `change.addedLines` merely narrate obvious code instead of explaining non-obvious intent, a trade-off or a hazard?',
|
|
144
|
+
criteria: {
|
|
145
|
+
true: 'A newly added comment repeats what the adjacent code already says',
|
|
146
|
+
false: 'No comment was added, or it explains non-obvious intent',
|
|
108
147
|
},
|
|
109
148
|
},
|
|
110
149
|
},
|
|
111
150
|
];
|
|
112
151
|
|
|
152
|
+
export interface JevRelatedFile {
|
|
153
|
+
readonly path: string;
|
|
154
|
+
readonly body: string;
|
|
155
|
+
readonly relation: 'imported' | 'consumer';
|
|
156
|
+
}
|
|
157
|
+
|
|
113
158
|
export interface JevFileState {
|
|
114
159
|
readonly file: {
|
|
115
160
|
readonly path: string;
|
|
@@ -120,6 +165,10 @@ export interface JevFileState {
|
|
|
120
165
|
readonly kind: string;
|
|
121
166
|
readonly addedLines: readonly string[];
|
|
122
167
|
};
|
|
168
|
+
readonly context: {
|
|
169
|
+
readonly files: readonly JevRelatedFile[];
|
|
170
|
+
readonly partial: boolean;
|
|
171
|
+
};
|
|
123
172
|
}
|
|
124
173
|
|
|
125
174
|
export interface JevRequestBody {
|
|
@@ -147,10 +196,13 @@ export function buildFileState(
|
|
|
147
196
|
body: string,
|
|
148
197
|
kind: string,
|
|
149
198
|
lines: readonly AddedLine[],
|
|
199
|
+
relatedFiles: readonly JevRelatedFile[] = [],
|
|
200
|
+
partial = false,
|
|
150
201
|
): JevFileState {
|
|
151
202
|
return {
|
|
152
203
|
file: { path, language: languageForPath(path), body },
|
|
153
204
|
change: { kind, addedLines: lines.map((line) => line.text) },
|
|
205
|
+
context: { files: relatedFiles, partial },
|
|
154
206
|
};
|
|
155
207
|
}
|
|
156
208
|
|
|
@@ -167,13 +219,38 @@ export function addedLinesForEdit(
|
|
|
167
219
|
return edits.flatMap((edit) => addedLines(edit.oldText, edit.newText));
|
|
168
220
|
}
|
|
169
221
|
|
|
170
|
-
|
|
171
|
-
|
|
222
|
+
// Questions are about code structure; a README or a lockfile has no useful state.
|
|
223
|
+
export function isReviewablePath(path: string): boolean {
|
|
224
|
+
return languageForPath(path) !== LANGUAGE.generic;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function stateFitsBudget(state: JevFileState): boolean {
|
|
228
|
+
const request = buildRequestBody(state);
|
|
229
|
+
const longestQuestion = Object.values(request.questions).reduce(
|
|
230
|
+
(longest, question) =>
|
|
231
|
+
Math.max(longest, Buffer.byteLength(JSON.stringify(question))),
|
|
232
|
+
0,
|
|
233
|
+
);
|
|
234
|
+
return (
|
|
235
|
+
Buffer.byteLength(JSON.stringify(state)) + longestQuestion <=
|
|
236
|
+
MAX_STATE_BYTES &&
|
|
237
|
+
Buffer.byteLength(JSON.stringify(request)) <= MAX_REQUEST_BYTES
|
|
238
|
+
);
|
|
172
239
|
}
|
|
173
240
|
|
|
174
241
|
export function buildRequestBody(file: JevFileState): JevRequestBody {
|
|
175
242
|
const questions: Record<string, JevNoulQuestion> = {};
|
|
243
|
+
const hasImportedFile = file.context.files.some(
|
|
244
|
+
(related) => related.relation === 'imported',
|
|
245
|
+
);
|
|
246
|
+
const addedText = file.change.addedLines.join('\n');
|
|
176
247
|
for (const rule of JEV_RULES) {
|
|
248
|
+
if (rule.id === 'dependency-direction' && !hasImportedFile) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (rule.id === 'comments' && !/(?:\/\/|\/\*|#)/u.test(addedText)) {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
177
254
|
questions[rule.id] = rule.question;
|
|
178
255
|
}
|
|
179
256
|
return { model: JEV_MODEL, state: file, questions };
|
|
@@ -190,6 +267,7 @@ export function parseEnvelope(text: string): JevResponseEnvelope | undefined {
|
|
|
190
267
|
|
|
191
268
|
export function readFindings(
|
|
192
269
|
envelope: JevResponseEnvelope,
|
|
270
|
+
askedRuleIds: ReadonlySet<string> = new Set(JEV_RULES.map((rule) => rule.id)),
|
|
193
271
|
): readonly JevFinding[] {
|
|
194
272
|
const answers = envelope.answers;
|
|
195
273
|
if (answers === undefined) {
|
|
@@ -197,6 +275,9 @@ export function readFindings(
|
|
|
197
275
|
}
|
|
198
276
|
const findings: JevFinding[] = [];
|
|
199
277
|
for (const rule of JEV_RULES) {
|
|
278
|
+
if (!askedRuleIds.has(rule.id)) {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
200
281
|
const answer = answers[rule.id];
|
|
201
282
|
if (answer === undefined || answer.noul < VIOLATION_PROBABILITY) {
|
|
202
283
|
continue;
|
package/extensions/jev/ui.ts
CHANGED
|
@@ -30,6 +30,7 @@ const STATUS_LABEL: Readonly<Record<JevStatus, string>> = {
|
|
|
30
30
|
[JEV_STATUS.noKey]: 'jev: no key (F10)',
|
|
31
31
|
[JEV_STATUS.quota]: 'jev: out of quota (F10)',
|
|
32
32
|
[JEV_STATUS.auth]: 'jev: key rejected (F10)',
|
|
33
|
+
[JEV_STATUS.rateLimited]: 'jev: rate limited',
|
|
33
34
|
[JEV_STATUS.unavailable]: 'jev: unreachable',
|
|
34
35
|
};
|
|
35
36
|
|
|
@@ -35,7 +35,7 @@ export const VOICE_RULES: readonly VoiceRule[] = [
|
|
|
35
35
|
{
|
|
36
36
|
id: 'plain-language',
|
|
37
37
|
instruction:
|
|
38
|
-
'Explain in plain words:
|
|
38
|
+
'Explain in plain words: say what the user needs to know or do before details. Use everyday words even if the user uses technical terms. Keep a technical term only when needed to understand or act; explain it at first use in one short sentence. Do not repeat yourself or chain technical labels.',
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
id: 'direct',
|
|
@@ -49,7 +49,7 @@ export const VOICE_RULES: readonly VoiceRule[] = [
|
|
|
49
49
|
{
|
|
50
50
|
id: 'no-invented-terms',
|
|
51
51
|
instruction:
|
|
52
|
-
|
|
52
|
+
"Do not coin intermediate terms, abbreviations, or codenames. Use the user's terms only when needed to answer; otherwise explain in everyday words.",
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
id: 'short-prose',
|
|
@@ -18,8 +18,8 @@ const ENFORCEMENT_SUMMARY =
|
|
|
18
18
|
// Static Do/Don't pairs. Literals only: no per-turn data, so the reminder
|
|
19
19
|
// stays byte-identical across turns and the provider prompt cache holds.
|
|
20
20
|
export const PERSONA_EXAMPLES = [
|
|
21
|
-
"Do:
|
|
22
|
-
"Do:
|
|
21
|
+
"Do: Your work list stays with this conversation, not in project files. Don't: Session-scoped board state persists outside the repository.",
|
|
22
|
+
"Do: To get a fresh answer, turn off the cache (stored results). Don't: Disable the cache invalidation pipeline.",
|
|
23
23
|
] as const;
|
|
24
24
|
|
|
25
25
|
export const PERSONA_REMINDER = `${PERSONA_REMINDER_HEADING}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { realpathSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
|
-
import { isAbsolute, join, resolve } from 'node:path';
|
|
3
|
+
import { isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
|
|
6
6
|
const FILE_URL_PREFIX = 'file://';
|
|
@@ -29,6 +29,31 @@ export function resolveFileToolPath(
|
|
|
29
29
|
return realpathOr(absolutePath);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
// A file that does not exist yet keeps its unresolved path, so it is matched
|
|
33
|
+
// against the literal working directory as well as the canonical one.
|
|
34
|
+
export function projectRelativePath(
|
|
35
|
+
cwd: string,
|
|
36
|
+
absolutePath: string,
|
|
37
|
+
): string | undefined {
|
|
38
|
+
const literalRoot = resolve(cwd);
|
|
39
|
+
for (const root of new Set([realpathOr(literalRoot), literalRoot])) {
|
|
40
|
+
const relativePath = relative(root, absolutePath);
|
|
41
|
+
if (isInsideRoot(relativePath)) {
|
|
42
|
+
return relativePath;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isInsideRoot(relativePath: string): boolean {
|
|
49
|
+
return (
|
|
50
|
+
relativePath.length > 0 &&
|
|
51
|
+
relativePath !== '..' &&
|
|
52
|
+
!relativePath.startsWith(`..${sep}`) &&
|
|
53
|
+
!isAbsolute(relativePath)
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
32
57
|
function normalizeFileToolPath(path: string): string {
|
|
33
58
|
const normalizedSpaces = path.replace(UNICODE_SPACES, ' ');
|
|
34
59
|
const withoutAtPrefix = normalizedSpaces.startsWith('@')
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const REVIEW_NOTE_EVENT = 'sideroom:review-note';
|
|
2
|
+
|
|
3
|
+
export interface ReviewNote {
|
|
4
|
+
readonly text: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function isReviewNote(value: unknown): value is ReviewNote {
|
|
8
|
+
return (
|
|
9
|
+
typeof value === 'object' &&
|
|
10
|
+
value !== null &&
|
|
11
|
+
'text' in value &&
|
|
12
|
+
typeof value.text === 'string'
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentBeforeSettleEvent,
|
|
3
|
+
AgentBeforeSettleEventResult,
|
|
4
|
+
InputEvent,
|
|
5
|
+
InputSource,
|
|
6
|
+
} from '@earendil-works/pi-coding-agent';
|
|
7
|
+
|
|
8
|
+
const COMPLETED_OUTCOME = 'completed';
|
|
9
|
+
const USER_ROLE = 'user';
|
|
10
|
+
const CUSTOM_MESSAGE_ENTRY = 'custom_message';
|
|
11
|
+
const USER_INPUT_SOURCES: ReadonlySet<InputSource> = new Set([
|
|
12
|
+
'interactive',
|
|
13
|
+
'rpc',
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
// A message typed while the agent runs joins the current run as a steer or
|
|
17
|
+
// follow-up; only input that arrives idle opens a new turn.
|
|
18
|
+
export function startsUserTurn(event: InputEvent): boolean {
|
|
19
|
+
return (
|
|
20
|
+
USER_INPUT_SOURCES.has(event.source) &&
|
|
21
|
+
event.streamingBehavior === undefined
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// An aborted run is the user stopping the agent, an errored run has nothing
|
|
26
|
+
// worth reviewing, and a pending user message already decides what comes next.
|
|
27
|
+
export function acceptsSettleSteer(event: AgentBeforeSettleEvent): boolean {
|
|
28
|
+
if (event.outcome !== COMPLETED_OUTCOME) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return !event.context.pendingMessages.some(
|
|
32
|
+
(message) => message.role === USER_ROLE,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Handlers chain on agent_before_settle, so keep the entries earlier handlers
|
|
37
|
+
// proposed; replacing them would drop another extension's steer.
|
|
38
|
+
export function withHiddenSteer(
|
|
39
|
+
event: AgentBeforeSettleEvent,
|
|
40
|
+
customType: string,
|
|
41
|
+
content: string,
|
|
42
|
+
): AgentBeforeSettleEventResult {
|
|
43
|
+
return {
|
|
44
|
+
entries: [
|
|
45
|
+
...event.entries,
|
|
46
|
+
{ type: CUSTOM_MESSAGE_ENTRY, customType, content, display: false },
|
|
47
|
+
],
|
|
48
|
+
continue: true,
|
|
49
|
+
};
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmrdeveloper/sideroom-pi",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.11.0",
|
|
4
4
|
"description": "Pi coding-agent package that asks clarifying questions, shows a live todo board, enforces coding guidelines, and will not call it done while your checks are red.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -89,8 +89,8 @@
|
|
|
89
89
|
"@changesets/cli": "^3.0.2",
|
|
90
90
|
"@commitlint/cli": "^21.2.2",
|
|
91
91
|
"@commitlint/config-conventional": "^21.2.2",
|
|
92
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
93
|
-
"@earendil-works/pi-tui": "0.
|
|
92
|
+
"@earendil-works/pi-coding-agent": "0.87.1",
|
|
93
|
+
"@earendil-works/pi-tui": "0.87.1",
|
|
94
94
|
"@types/node": "^22.20.1",
|
|
95
95
|
"typebox": "1.3.7",
|
|
96
96
|
"typescript": "^5.6.0"
|
|
@@ -50,7 +50,14 @@ impact), or for a word conflict — that is `sideroom-domain-modeling`.
|
|
|
50
50
|
technology label.
|
|
51
51
|
- Present the decision as the current situation, the alternatives by their
|
|
52
52
|
practical consequences, and one recommendation with a one-line justification.
|
|
53
|
-
|
|
53
|
+
Prefer the most maintainable option that can meet known growth needs over the
|
|
54
|
+
cheapest initial build; name its upfront cost and the cost of later changes.
|
|
55
|
+
Do not recommend maximum capacity merely for hypothetical growth. Never reduce
|
|
56
|
+
the alternatives to technology names.
|
|
57
|
+
- If a missing figure could change which option is best, ask for it before
|
|
58
|
+
recommending an architecture. The preliminary question still needs a
|
|
59
|
+
justified recommended option (such as obtaining the figure); never invent
|
|
60
|
+
a target or recommend a solution without evidence.
|
|
54
61
|
- One open architectural dimension blocks implementation. `Out of scope` closes
|
|
55
62
|
a thread, is recorded, and is not re-asked.
|
|
56
63
|
|
|
@@ -77,10 +84,10 @@ an explicit `Out of scope` on each one you pick.
|
|
|
77
84
|
codebase, and note any inconsistent pattern to name in a prompt.
|
|
78
85
|
2. List the architectural decisions the change forces. Drop the ones the code
|
|
79
86
|
already settles.
|
|
80
|
-
3. For each remaining decision, pick the dimensions it touches
|
|
81
|
-
`sideroom_ask` round: situation,
|
|
82
|
-
|
|
83
|
-
explicit `Out of scope`.
|
|
87
|
+
3. For each remaining decision, pick the dimensions it touches. Ask for any
|
|
88
|
+
decisive missing figures first, then run a `sideroom_ask` round: situation,
|
|
89
|
+
alternatives by consequence, one evidence-backed recommendation, each option
|
|
90
|
+
with a `description`. Force a number or an explicit `Out of scope`.
|
|
84
91
|
4. Repeat rounds until no architectural dimension is open. Do not implement
|
|
85
92
|
with one open.
|
|
86
93
|
5. Record an accepted decision as an ADR under `docs/adr/` only when it clears
|
|
@@ -34,6 +34,14 @@ if the effort is too big to hold in one session, say so and stop.
|
|
|
34
34
|
by their practical consequences, and one recommendation with a one-line
|
|
35
35
|
justification. Never reduce it to technology names. Name a repository
|
|
36
36
|
inconsistency in the prompt instead of picking one.
|
|
37
|
+
- Recommend for sustainable quality: prefer maintainability and room to grow
|
|
38
|
+
for known needs over the cheapest initial build. Explain the upfront cost and
|
|
39
|
+
future change cost; do not mistake speculative maximum capacity for quality.
|
|
40
|
+
Apply this to minor decisions too, without asking the user about them.
|
|
41
|
+
- If a missing fact or figure could change the recommendation, ask for it in
|
|
42
|
+
a preliminary round before recommending a solution. That round still needs
|
|
43
|
+
a defensible recommended option, such as gathering the missing evidence;
|
|
44
|
+
never invent a number or mark an unsupported solution as recommended.
|
|
37
45
|
- An *Out of scope* answer closes that thread. Do not re-ask it.
|
|
38
46
|
- Everything a round settles is recorded through `sideroom-domain-modeling`:
|
|
39
47
|
inline glossary entries, the ADR three-gate offer, lazy file creation.
|
|
@@ -47,8 +55,8 @@ if the effort is too big to hold in one session, say so and stop.
|
|
|
47
55
|
1. Read the code around the change. Settle from the codebase everything the
|
|
48
56
|
codebase settles.
|
|
49
57
|
2. Run one `sideroom_ask` round on what remains genuinely open: terms first,
|
|
50
|
-
then
|
|
51
|
-
round on them.
|
|
58
|
+
then missing facts that could change a recommendation, then the architectural
|
|
59
|
+
decisions. Minor decisions are yours; do not spend a round on them.
|
|
52
60
|
3. After each round, write resolved terms to `CONTEXT.md` and qualifying
|
|
53
61
|
decisions to `docs/adr/`. Offer the ADR for an accepted architectural
|
|
54
62
|
decision only when all three gates pass.
|
|
@@ -22,37 +22,39 @@ needs the full persona instead of the injected reminder.
|
|
|
22
22
|
|
|
23
23
|
| # | Rule | Do | Don't |
|
|
24
24
|
| --- | --- | --- | --- |
|
|
25
|
-
| 1 | Plain, never in doubt | `
|
|
26
|
-
| 2 | Direct and dry | `The
|
|
25
|
+
| 1 | Plain, never in doubt | `Your work list stays with this conversation, not in project files.` | `Session-scoped board state persists outside the repository.` |
|
|
26
|
+
| 2 | Direct and dry | `The file was not saved. Remove the symbol and try again.` | `I'd be happy to help with that! Let's take a look together.` |
|
|
27
27
|
| 3 | No filler | Start with the answer. | `Before we dive in…`, `As I mentioned earlier…`, `To summarize what I just said…` |
|
|
28
|
-
| 4 | No invented terms | `the file
|
|
28
|
+
| 4 | No invented terms | `the file you changed` | `the artifact delta` (when the user never asked for that name) |
|
|
29
29
|
| 5 | Short prose | Prose for one or two points. | A three-bullet list for a single sentence. |
|
|
30
30
|
| 6 | User's language | Answer in the language and variant the user wrote in. | A Spanish question answered in English. |
|
|
31
31
|
|
|
32
32
|
### Plain means everyday words, not more words
|
|
33
33
|
|
|
34
34
|
Rule 1 outranks brevity for clarity, and rule 3 outranks rule 1 for length.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
- Do: `
|
|
45
|
-
|
|
46
|
-
-
|
|
47
|
-
|
|
48
|
-
|
|
35
|
+
Say what the user needs to know or do before explaining how. Choose words a
|
|
36
|
+
student can understand without knowing the code. Even if the user uses a
|
|
37
|
+
technical term, do not repeat it unless they need it to understand the answer
|
|
38
|
+
or take the next step. If they do, explain it at first use in one short
|
|
39
|
+
sentence. Keep exact commands, file names, and code names when needed to act;
|
|
40
|
+
explain their purpose in everyday words. Plain words must stay accurate.
|
|
41
|
+
Do not repeat the question or explain the same point twice.
|
|
42
|
+
|
|
43
|
+
- User: `Where is the session-scoped work board?`
|
|
44
|
+
- Do: `Your work list stays with this conversation, not in project files.`
|
|
45
|
+
- Don't: `Session-scoped board state persists outside the repository.`
|
|
46
|
+
- Do: `To get a fresh answer, turn off the cache (stored results).`
|
|
47
|
+
- Don't: `Disable the cache invalidation pipeline.`
|
|
48
|
+
- Do: `The answer is saved with this conversation. You can read it again later.`
|
|
49
|
+
- Don't: `The response is persisted to the session, so the session stores the
|
|
50
|
+
response for future access.`
|
|
49
51
|
|
|
50
52
|
### Names the user did not ask for
|
|
51
53
|
|
|
52
|
-
Rule 4 bans coining vocabulary mid-conversation. Do not
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
Rule 4 bans coining vocabulary mid-conversation. Do not invent abbreviations
|
|
55
|
+
(`the PRF layer`) or use an internal field name as if it were the domain word.
|
|
56
|
+
Replace a user's unnecessary technical term with everyday words, not another
|
|
57
|
+
new label. Keep exact names only when needed to act.
|
|
56
58
|
|
|
57
59
|
## Hard prohibitions
|
|
58
60
|
|
|
@@ -77,9 +79,12 @@ dead-locked.
|
|
|
77
79
|
### What steering means
|
|
78
80
|
|
|
79
81
|
The extension sends a corrective steer after a degraded artifact block or a
|
|
80
|
-
prohibited assistant message.
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
prohibited assistant message. Both moments happen while the agent is working, so
|
|
83
|
+
the steer travels through `pi.sendMessage` with `{ triggerTurn: true, deliverAs:
|
|
84
|
+
'steer' }` and joins the current run. A steer sent after the run ended belongs
|
|
85
|
+
to the guidelines review and the walkthrough offer, which append a
|
|
86
|
+
`custom_message` entry on `agent_before_settle` instead.
|
|
87
|
+
At most three steers are sent per agent run.
|
|
83
88
|
|
|
84
89
|
## Boundaries
|
|
85
90
|
|
|
@@ -87,3 +92,8 @@ agent run.
|
|
|
87
92
|
- Nothing is written to the target repository.
|
|
88
93
|
- The persona governs wording. It never relaxes the coding guidelines, the
|
|
89
94
|
added-line rules, or the check-before-finish gate.
|
|
95
|
+
|
|
96
|
+
## References
|
|
97
|
+
|
|
98
|
+
- [Digital.gov: Avoid jargon](https://digital.gov/guides/plain-language/principles/avoid-jargon): keep only necessary specialist words and explain them.
|
|
99
|
+
- [GOV.UK: Use clear language](https://guidance.publishing.service.gov.uk/writing-to-gov-uk-standards/writing-guidelines/clear-language/): write plainly even for specialists.
|