@rmrdeveloper/sideroom-pi 9.0.0 → 9.1.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 +23 -28
- package/assets/visual-context/browser/background.js +2 -2
- package/assets/visual-context/browser/popup.js +1 -2
- package/extensions/ask/index.ts +0 -1
- package/extensions/ask/model.ts +1 -1
- package/extensions/ask/selection.ts +0 -4
- package/extensions/done/guard.ts +20 -0
- package/extensions/done/index.ts +0 -1
- package/extensions/done/model.ts +1 -3
- package/extensions/explain/guard.ts +11 -21
- package/extensions/explain/index.ts +0 -1
- package/extensions/explain/model.ts +0 -5
- package/extensions/guidelines/execute.ts +16 -2
- package/extensions/guidelines/guard.ts +1 -2
- package/extensions/guidelines/index.ts +7 -7
- package/extensions/guidelines/prompt.ts +0 -2
- package/extensions/guidelines/review.ts +9 -10
- package/extensions/jev/client.ts +0 -7
- package/extensions/jev/context.ts +0 -2
- package/extensions/jev/guard.ts +78 -44
- package/extensions/jev/index.ts +0 -3
- package/extensions/jev/key.ts +0 -3
- package/extensions/jev/model.ts +0 -8
- package/extensions/jev/queue.ts +125 -0
- package/extensions/jev/ui.ts +0 -14
- package/extensions/modified-files/index.ts +0 -1
- package/extensions/monorepo-skills/index.ts +0 -1
- package/extensions/persona/checks.ts +0 -4
- package/extensions/persona/guard.ts +45 -6
- package/extensions/persona/index.ts +4 -2
- package/extensions/persona/model.ts +0 -2
- package/extensions/persona/prompt.ts +0 -2
- package/extensions/rules/checks.ts +0 -5
- package/extensions/rules/index.ts +0 -1
- package/extensions/rules/lexer.ts +1 -1
- package/extensions/shared/check-status.ts +35 -0
- package/extensions/shared/file-path.ts +0 -3
- package/extensions/shared/read-file.ts +0 -2
- package/extensions/shared/settle.ts +78 -13
- package/extensions/todo/guards.ts +40 -84
- package/extensions/todo/index.ts +1 -2
- package/extensions/todo/model.ts +6 -48
- package/extensions/todo/session.ts +1 -4
- package/extensions/visual-context/broker.ts +10 -10
- package/extensions/visual-context/configure.ts +4 -4
- package/extensions/visual-context/files.ts +0 -3
- package/extensions/visual-context/index.ts +8 -9
- package/extensions/visual-context/literal.ts +0 -1
- package/extensions/visual-context/markup.ts +3 -5
- package/extensions/visual-context/model.ts +2 -2
- package/extensions/visual-context/pairing.ts +1 -2
- package/extensions/visual-context/parse-astro.ts +9 -12
- package/extensions/visual-context/parse-jsx.ts +2 -2
- package/extensions/visual-context/parse-vue.ts +1 -1
- package/extensions/visual-context/setup.ts +8 -8
- package/extensions/visual-context/source.ts +0 -3
- package/package.json +15 -9
- package/skills/sideroom-guidelines/SKILL.md +1 -1
- package/skills/sideroom-persona/SKILL.md +8 -6
- package/skills/sideroom-user-stories/SKILL.md +78 -0
- package/skills/sideroom-user-stories/references/review.md +84 -0
- package/skills/sideroom-user-stories/references/templates.md +131 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Sideroom Pi
|
|
1
|
+
# Sideroom Pi: make "done" mean done
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<img src="https://raw.githubusercontent.com/RMRdeveloper/sideroom-pi/main/media/logo.png" alt="The Sideroom Pi mark" width="640">
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
[](https://www.npmjs.com/package/@rmrdeveloper/sideroom-pi)
|
|
9
9
|
[](https://www.npmjs.com/package/@rmrdeveloper/sideroom-pi)
|
|
10
10
|
|
|
11
|
-
**
|
|
12
|
-
|
|
11
|
+
**The open-source quality layer for Pi.** Your agent asks before it guesses,
|
|
12
|
+
follows your code rules on the lines it writes, and cannot call a task
|
|
13
|
+
finished while your project's own checks are still red.
|
|
13
14
|
|
|
14
15
|
```bash
|
|
15
16
|
pi install npm:@rmrdeveloper/sideroom-pi
|
|
@@ -17,18 +18,16 @@ pi install npm:@rmrdeveloper/sideroom-pi
|
|
|
17
18
|
|
|
18
19
|

|
|
19
20
|
|
|
20
|
-
>
|
|
21
|
-
> bury progress in chat scroll, they litter your repo with `TODO.md` files,
|
|
22
|
-
> every session writes code in a slightly different style, and they declare
|
|
23
|
-
> victory while the project checks are still red.
|
|
21
|
+
> "Done, everything works." You open the project. The tests are red.
|
|
24
22
|
>
|
|
25
|
-
>
|
|
23
|
+
> Sideroom is for people who use Pi on real work and are tired of reviewing
|
|
24
|
+
> and redoing what the agent delivered. Same freedom and price as Pi; the
|
|
25
|
+
> discipline you expect from a senior review.
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
`.pi` config, no task graph, no Sideroom files left behind.
|
|
27
|
+
For developers who chose Pi for control or cost and need the result to be
|
|
28
|
+
actually ready: Sideroom is the quality layer that makes Pi follow your
|
|
29
|
+
rules, ask when there are two reasonable paths, and not say "done" until
|
|
30
|
+
your project passes its own checks.
|
|
32
31
|
|
|
33
32
|
## Why Sideroom exists
|
|
34
33
|
|
|
@@ -285,16 +284,13 @@ nothing is detectable, the gate does nothing.
|
|
|
285
284
|
|
|
286
285
|
### Explain — the walkthrough offer
|
|
287
286
|
|
|
288
|
-
When
|
|
289
|
-
|
|
290
|
-
only, both, or nothing.
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
while a message you typed is waiting. Once per turn, only when the turn touched at least five distinct
|
|
296
|
-
files, and only in the TUI, because `sideroom_ask` cannot run anywhere else. No
|
|
297
|
-
tool, no widget, no persisted state.
|
|
287
|
+
When a review, style correction, or check already requires a closing
|
|
288
|
+
continuation, `explain` can add a walkthrough offer through `sideroom_ask`:
|
|
289
|
+
changes only, test steps only, both, or nothing. The instruction asks the agent
|
|
290
|
+
to finish the work, review and checks before offering. It never opens a turn
|
|
291
|
+
solely for an offer. Once per user turn, only after at least five distinct
|
|
292
|
+
files changed, and only in the TUI. Aborts, errors and queued messages postpone
|
|
293
|
+
it. No tool, widget or persisted state.
|
|
298
294
|
|
|
299
295
|
## Quick path
|
|
300
296
|
|
|
@@ -467,14 +463,13 @@ itself.
|
|
|
467
463
|
since the last idle user prompt, in TUI mode, on a completed run with no user
|
|
468
464
|
message pending. File identity follows Pi's path aliases and canonicalizes
|
|
469
465
|
existing paths.
|
|
470
|
-
-
|
|
471
|
-
|
|
472
|
-
boundary.
|
|
466
|
+
- Delivery: joins an existing necessary settle continuation through the shared
|
|
467
|
+
supplement event, in any extension load order. If none is needed, stays silent.
|
|
473
468
|
- The steer names the intent; the agent writes the question in the user's
|
|
474
469
|
language with four options, one of them a decline, and one recommendation.
|
|
475
470
|
`sideroom_ask` still adds *Out of scope* and the custom answer.
|
|
476
|
-
- At most one offer per turn. The flag is set
|
|
477
|
-
offer triggers
|
|
471
|
+
- At most one offer per turn. The flag is set only when the supplement is
|
|
472
|
+
appended; the offer never triggers its own continuation.
|
|
478
473
|
- No green-check requirement: red checks still produce the offer.
|
|
479
474
|
|
|
480
475
|
### Grill contract
|
|
@@ -210,7 +210,7 @@ function scheduleReconnect(entry) {
|
|
|
210
210
|
entry.retry = setTimeout(() => {
|
|
211
211
|
void reconnect(entry);
|
|
212
212
|
}, RETRY_MS);
|
|
213
|
-
|
|
213
|
+
|
|
214
214
|
void chrome.alarms.create(RETRY_ALARM, { periodInMinutes: 1 });
|
|
215
215
|
}
|
|
216
216
|
|
|
@@ -284,7 +284,7 @@ async function openConnection(entry) {
|
|
|
284
284
|
});
|
|
285
285
|
return;
|
|
286
286
|
}
|
|
287
|
-
|
|
287
|
+
|
|
288
288
|
for (const selectionId of [...receipts.keys()]) {
|
|
289
289
|
await notifyReceipt({
|
|
290
290
|
endpoint: entry.endpoint,
|
|
@@ -6,8 +6,7 @@ const disconnect = document.querySelector('#disconnect');
|
|
|
6
6
|
const forget = document.querySelector('#forget');
|
|
7
7
|
const destination = document.querySelector('#destination');
|
|
8
8
|
const connect = document.querySelector('#connect');
|
|
9
|
-
|
|
10
|
-
// Chromium browser the companion supports answers on chrome://.
|
|
9
|
+
|
|
11
10
|
const SHORTCUT_PAGE = navigator.userAgent.includes('Edg/')
|
|
12
11
|
? 'edge://extensions/shortcuts'
|
|
13
12
|
: 'chrome://extensions/shortcuts';
|
package/extensions/ask/index.ts
CHANGED
|
@@ -23,7 +23,6 @@ export const ASK_PROMPT_GUIDELINES = [
|
|
|
23
23
|
'sideroom_ask only works in the interactive TUI; it errors in print, JSON, and RPC modes.',
|
|
24
24
|
];
|
|
25
25
|
|
|
26
|
-
// Pi loads extensions/*/index.ts through export default.
|
|
27
26
|
export default function registerAsk(pi: ExtensionAPI): void {
|
|
28
27
|
pi.registerTool({
|
|
29
28
|
name: TOOL_NAME,
|
package/extensions/ask/model.ts
CHANGED
|
@@ -423,7 +423,7 @@ function schemaErrorMessage(value: unknown): string {
|
|
|
423
423
|
return `Error: Option ${String(optionIndex + 1)} for question '${id}' may contain at most ${String(MAX_OPTION_LABEL_LENGTH)} characters`;
|
|
424
424
|
}
|
|
425
425
|
|
|
426
|
-
return
|
|
426
|
+
return 'Error: Invalid sideroom_ask parameters';
|
|
427
427
|
}
|
|
428
428
|
|
|
429
429
|
function questionId(
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import type { AskAnswer, AskQuestion, AskSelection } from './model.ts';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Sorted ascending so the answer and its rendering stay stable no matter the
|
|
5
|
-
* order the user toggled the options in.
|
|
6
|
-
*/
|
|
7
3
|
export function toggleSelection(
|
|
8
4
|
selected: readonly number[],
|
|
9
5
|
optionIndex: number,
|
package/extensions/done/guard.ts
CHANGED
|
@@ -6,8 +6,10 @@ import {
|
|
|
6
6
|
isToolCallEventType,
|
|
7
7
|
isWriteToolResult,
|
|
8
8
|
} from '@earendil-works/pi-coding-agent';
|
|
9
|
+
import { registerCheckStatus } from '../shared/check-status.ts';
|
|
9
10
|
import {
|
|
10
11
|
registerSettleGuard,
|
|
12
|
+
registerSettleSupplement,
|
|
11
13
|
SETTLE_PRIORITY,
|
|
12
14
|
withHiddenSteer,
|
|
13
15
|
} from '../shared/settle.ts';
|
|
@@ -46,6 +48,13 @@ export function registerDoneGuard({
|
|
|
46
48
|
const checkRuns = new Set<string>();
|
|
47
49
|
const mutationPaths = new Map<string, string>();
|
|
48
50
|
|
|
51
|
+
registerCheckStatus(pi, (ctx) => {
|
|
52
|
+
const check = resolveCommand(ctx);
|
|
53
|
+
return check === undefined
|
|
54
|
+
? undefined
|
|
55
|
+
: { command: check.display, green: state.checksGreen };
|
|
56
|
+
});
|
|
57
|
+
|
|
49
58
|
pi.on('tool_call', (event, ctx) => {
|
|
50
59
|
if (
|
|
51
60
|
isToolCallEventType('write', event) ||
|
|
@@ -86,6 +95,17 @@ export function registerDoneGuard({
|
|
|
86
95
|
}
|
|
87
96
|
});
|
|
88
97
|
|
|
98
|
+
registerSettleSupplement(pi, (ctx) => {
|
|
99
|
+
if (!shouldNoticeMissingTest(state, resolveTestSetup(ctx))) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
state.noticedMissingTest = true;
|
|
103
|
+
return {
|
|
104
|
+
customType: MISSING_TEST_NOTICE_TYPE,
|
|
105
|
+
content: formatMissingTestNotice(),
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
|
|
89
109
|
registerSettleGuard(pi, {
|
|
90
110
|
priority: SETTLE_PRIORITY.done,
|
|
91
111
|
pending: (ctx) =>
|
package/extensions/done/index.ts
CHANGED
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
} from './guard.ts';
|
|
16
16
|
import { createDoneState, resetDoneRun } from './model.ts';
|
|
17
17
|
|
|
18
|
-
// Pi loads extensions/*/index.ts through export default.
|
|
19
18
|
export default function registerDone(pi: ExtensionAPI): void {
|
|
20
19
|
const state = createDoneState();
|
|
21
20
|
const cache = new Map<string, CheckCommand | undefined>();
|
package/extensions/done/model.ts
CHANGED
|
@@ -15,8 +15,6 @@ export const MISSING_TEST_NOTICE_TYPE = 'sideroom-missing-test-notice';
|
|
|
15
15
|
|
|
16
16
|
const TEST_DIRECTORY = /(?:^|\/)(?:__tests__|tests?|specs?)(?:\/|$)/;
|
|
17
17
|
|
|
18
|
-
// Test paths are recognised by name only: the notice never claims the right
|
|
19
|
-
// test exists, so a missed convention only costs a nudge, never a false pass.
|
|
20
18
|
const TEST_FILE =
|
|
21
19
|
/(?:^|\/)(?:test_[\w.-]*\.py|[\w.-]*[._](?:test|spec)\.[\w.-]+|[\w.-]*(?:Test|Tests)\.java)$/;
|
|
22
20
|
|
|
@@ -82,5 +80,5 @@ export function formatDoneSteer(display: string): string {
|
|
|
82
80
|
}
|
|
83
81
|
|
|
84
82
|
export function formatMissingTestNotice(): string {
|
|
85
|
-
return 'This run changed code files and no test file. Check whether existing tests cover the changed behavior; add or update a test if coverage is missing,
|
|
83
|
+
return 'This run changed code files and no test file. Check whether existing tests cover the changed behavior; add or update a test if coverage is missing, reuse relevant checks that already passed after the latest mutation, and run missing, failed, or stale checks before finishing. Any code or test edit makes earlier checks stale.';
|
|
86
84
|
}
|
|
@@ -3,12 +3,7 @@ import {
|
|
|
3
3
|
isEditToolResult,
|
|
4
4
|
isWriteToolResult,
|
|
5
5
|
} from '@earendil-works/pi-coding-agent';
|
|
6
|
-
import {
|
|
7
|
-
registerSettleGuard,
|
|
8
|
-
SETTLE_PRIORITY,
|
|
9
|
-
startsUserTurn,
|
|
10
|
-
withHiddenSteer,
|
|
11
|
-
} from '../shared/settle.ts';
|
|
6
|
+
import { registerSettleSupplement, startsUserTurn } from '../shared/settle.ts';
|
|
12
7
|
import {
|
|
13
8
|
applyOfferDecision,
|
|
14
9
|
decideOffer,
|
|
@@ -43,7 +38,7 @@ export function registerExplainGuard(
|
|
|
43
38
|
if (!isEditToolResult(event) && !isWriteToolResult(event)) {
|
|
44
39
|
return;
|
|
45
40
|
}
|
|
46
|
-
|
|
41
|
+
|
|
47
42
|
const input = event.input as unknown as FileToolInput;
|
|
48
43
|
const pathKey = mutationPathKey(ctx.cwd, input.path);
|
|
49
44
|
if (pathKey === undefined) {
|
|
@@ -52,19 +47,14 @@ export function registerExplainGuard(
|
|
|
52
47
|
recordMutation(state, pathKey);
|
|
53
48
|
});
|
|
54
49
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
event,
|
|
65
|
-
customType: EXPLAIN_OFFER_TYPE,
|
|
66
|
-
content: EXPLAIN_OFFER,
|
|
67
|
-
});
|
|
68
|
-
},
|
|
50
|
+
registerSettleSupplement(pi, (ctx) => {
|
|
51
|
+
if (decideOffer(state, ctx.mode) !== EXPLAIN_DECISION.send) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
applyOfferDecision(state, EXPLAIN_DECISION.send);
|
|
55
|
+
return {
|
|
56
|
+
customType: EXPLAIN_OFFER_TYPE,
|
|
57
|
+
content: `After finishing the requested work, review, and checks: ${EXPLAIN_OFFER}`,
|
|
58
|
+
};
|
|
69
59
|
});
|
|
70
60
|
}
|
|
@@ -2,7 +2,6 @@ import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
|
2
2
|
import { registerExplainGuard } from './guard.ts';
|
|
3
3
|
import { createExplainState, resetExplainTurn } from './model.ts';
|
|
4
4
|
|
|
5
|
-
// Pi loads extensions/*/index.ts through export default.
|
|
6
5
|
export default function registerExplain(pi: ExtensionAPI): void {
|
|
7
6
|
const state = createExplainState();
|
|
8
7
|
registerExplainGuard(pi, state);
|
|
@@ -2,8 +2,6 @@ import { resolveFileToolPath } from '../shared/file-path.ts';
|
|
|
2
2
|
|
|
3
3
|
export const EXPLAIN_OFFER_TYPE = 'sideroom-explain-offer';
|
|
4
4
|
|
|
5
|
-
// Five is the point where a turn stopped being an adjustment and became work
|
|
6
|
-
// worth walking the user through.
|
|
7
5
|
export const EXPLAIN_FILE_THRESHOLD = 5;
|
|
8
6
|
|
|
9
7
|
const TUI_MODE = 'tui';
|
|
@@ -36,8 +34,6 @@ export function resetExplainTurn(state: ExplainState): void {
|
|
|
36
34
|
state.offeredThisTurn = false;
|
|
37
35
|
}
|
|
38
36
|
|
|
39
|
-
// Pi reports the path as the model wrote it, so use the built-in file-tool
|
|
40
|
-
// semantics and canonicalize existing aliases before counting the file.
|
|
41
37
|
export function mutationPathKey(cwd: string, path: string): string | undefined {
|
|
42
38
|
return resolveFileToolPath(cwd, path);
|
|
43
39
|
}
|
|
@@ -46,7 +42,6 @@ export function recordMutation(state: ExplainState, pathKey: string): void {
|
|
|
46
42
|
state.mutatedFilesThisTurn.add(pathKey);
|
|
47
43
|
}
|
|
48
44
|
|
|
49
|
-
// The shared settle guard gives review and checks priority over the offer.
|
|
50
45
|
export function decideOffer(
|
|
51
46
|
state: ExplainState,
|
|
52
47
|
mode: string,
|
|
@@ -6,15 +6,29 @@ export interface GuidelinesToolResult {
|
|
|
6
6
|
readonly details: { readonly paths: readonly string[] };
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
interface GuidelinesLoadOptions {
|
|
10
|
+
readonly loadedPaths?: ReadonlySet<string>;
|
|
11
|
+
readonly readDocument?: (path: string) => string;
|
|
12
|
+
}
|
|
13
|
+
|
|
9
14
|
export function executeGuidelines(
|
|
10
15
|
path: string,
|
|
11
|
-
|
|
16
|
+
{
|
|
17
|
+
loadedPaths = new Set(),
|
|
18
|
+
readDocument = (path) => readFileSync(path, 'utf8'),
|
|
19
|
+
}: GuidelinesLoadOptions = {},
|
|
12
20
|
): GuidelinesToolResult {
|
|
13
21
|
const guidePath = languageGuideFor(path);
|
|
14
22
|
const paths = [
|
|
15
23
|
GUIDELINE_SKILL_PATH,
|
|
16
24
|
...(guidePath === undefined ? [] : [guidePath]),
|
|
17
|
-
];
|
|
25
|
+
].filter((documentPath) => !loadedPaths.has(documentPath));
|
|
26
|
+
if (paths.length === 0) {
|
|
27
|
+
return {
|
|
28
|
+
content: [{ type: 'text', text: 'The required complete guidelines are already loaded in this run.' }],
|
|
29
|
+
details: { paths },
|
|
30
|
+
};
|
|
31
|
+
}
|
|
18
32
|
const content = paths.map((documentPath) => {
|
|
19
33
|
const text = readDocument(documentPath);
|
|
20
34
|
if (text.trim().length === 0) {
|
|
@@ -46,7 +46,7 @@ export function registerGuidelineGuard(
|
|
|
46
46
|
pi: ExtensionAPI,
|
|
47
47
|
state: GuidelineReadState,
|
|
48
48
|
): void {
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
pi.on('tool_execution_end', (event, ctx) => {
|
|
51
51
|
if (event.toolName !== GUIDELINES_TOOL_NAME) {
|
|
52
52
|
return;
|
|
@@ -78,7 +78,6 @@ export function registerGuidelineGuard(
|
|
|
78
78
|
return;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
// SAFETY: Pi validates built-in read arguments before emitting tool_result.
|
|
82
81
|
const input = event.input as unknown as ReadInput;
|
|
83
82
|
if (
|
|
84
83
|
input.offset !== undefined ||
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
2
2
|
import { Type } from 'typebox';
|
|
3
|
-
import { GUIDELINES_TOOL_NAME } from './catalog.ts';
|
|
3
|
+
import { GUIDELINE_SKILL_PATH, GUIDELINES_TOOL_NAME } from './catalog.ts';
|
|
4
4
|
import { executeGuidelines } from './execute.ts';
|
|
5
5
|
import {
|
|
6
6
|
createGuidelineReadState,
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
resetReviewTurn,
|
|
15
15
|
} from './review.ts';
|
|
16
16
|
|
|
17
|
-
// Pi loads extensions/*/index.ts through export default.
|
|
18
17
|
export default function registerGuidelines(pi: ExtensionAPI): void {
|
|
19
18
|
const readState = createGuidelineReadState();
|
|
20
19
|
registerGuidelineGuard(pi, readState);
|
|
@@ -35,10 +34,13 @@ export default function registerGuidelines(pi: ExtensionAPI): void {
|
|
|
35
34
|
executionMode: 'sequential',
|
|
36
35
|
async execute(toolCallId, { path }, signal) {
|
|
37
36
|
signal?.throwIfAborted();
|
|
38
|
-
const
|
|
37
|
+
const loadedPaths = new Set(readState.languageGuidesRead);
|
|
38
|
+
if (readState.skillRead) {
|
|
39
|
+
loadedPaths.add(GUIDELINE_SKILL_PATH);
|
|
40
|
+
}
|
|
41
|
+
const loaded = executeGuidelines(path, { loadedPaths });
|
|
39
42
|
signal?.throwIfAborted();
|
|
40
|
-
|
|
41
|
-
// authorize mutations, and resets discard receipts still awaiting delivery.
|
|
43
|
+
|
|
42
44
|
readState.pendingLoads.set(toolCallId, {
|
|
43
45
|
content: loaded.content.map((part) => ({ ...part })),
|
|
44
46
|
details: { paths: [...loaded.details.paths] },
|
|
@@ -63,8 +65,6 @@ export default function registerGuidelines(pi: ExtensionAPI): void {
|
|
|
63
65
|
return { systemPrompt };
|
|
64
66
|
});
|
|
65
67
|
|
|
66
|
-
// Compaction may summarize the loaded guides out of context mid-run; force
|
|
67
|
-
// a fresh read instead of trusting a now-stale in-memory flag.
|
|
68
68
|
pi.on('session_compact', () => {
|
|
69
69
|
resetGuidelineReadState(readState);
|
|
70
70
|
});
|
|
@@ -5,8 +5,6 @@ export { LANGUAGE_GUIDE_FILES } from './catalog.ts';
|
|
|
5
5
|
export const GUIDELINES_REMINDER_HEADING =
|
|
6
6
|
'Sideroom coding guidelines (always-on reminder):';
|
|
7
7
|
|
|
8
|
-
// One dense index instead of one sentence per language: the same paths and the
|
|
9
|
-
// same requirement, a fraction of the tokens every turn.
|
|
10
8
|
const LANGUAGE_GUIDE_INDEX = LANGUAGE_GUIDES.map(
|
|
11
9
|
({ extensions, path }) => `${extensions.join('/')} ${path}`,
|
|
12
10
|
).join('; ');
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
isEditToolResult,
|
|
4
4
|
isWriteToolResult,
|
|
5
5
|
} from '@earendil-works/pi-coding-agent';
|
|
6
|
+
import { readCheckStatus } from '../shared/check-status.ts';
|
|
6
7
|
import { isReviewNote, REVIEW_NOTE_EVENT } from '../shared/review-note.ts';
|
|
7
8
|
import {
|
|
8
9
|
registerSettleGuard,
|
|
@@ -14,13 +15,13 @@ import {
|
|
|
14
15
|
export const REVIEW_STEER_TYPE = 'sideroom-guidelines-review';
|
|
15
16
|
|
|
16
17
|
export const REVIEW_STEER =
|
|
17
|
-
'Finish any remaining requested implementation before reviewing. Review the changes this run introduced against the loaded Sideroom language guide: fix only demonstrated violations caused by those changes, preserving all requested requirements and existing behavior. KISS and YAGNI do not justify removing required error handling, compatibility, or tests. Include rule 20, Decomposition by intent: names and contracts should separate orchestration from coherent implementation details, even with one caller. Do not split solely for line count or separate tightly coupled steps into shared mutable state, excessive parameters, or functions that must be read together. Do not refactor unrelated code.
|
|
18
|
+
'Finish any remaining requested implementation before reviewing. Review the changes this run introduced against the loaded Sideroom language guide: fix only demonstrated violations caused by those changes, preserving all requested requirements and existing behavior. KISS and YAGNI do not justify removing required error handling, compatibility, or tests. Include rule 20, Decomposition by intent: names and contracts should separate orchestration from coherent implementation details, even with one caller. Do not split solely for line count or separate tightly coupled steps into shared mutable state, excessive parameters, or functions that must be read together. Do not refactor unrelated code. Reuse relevant checks that already passed in this user turn after the latest file mutation. Run only missing, failed, or stale formatter, linter, type checks, and tests; any further file mutation makes earlier checks stale. If the guide is no longer in context, re-read it in full first.';
|
|
18
19
|
|
|
19
20
|
export const WORKFLOW_REVIEW_STEER =
|
|
20
21
|
'Verify the semantic notes below against the visible evidence and the questionnaire or task contract. Correct only demonstrated ambiguity or contradictions; respect user answers and ask again only when clarification is necessary. Missing evidence is not proof of unfinished work. Do not create unrelated implementation work. Run project checks only if code changes.';
|
|
21
22
|
|
|
22
23
|
export const REVIEW_NOTES_STEER =
|
|
23
|
-
'Additional semantic notes arrived after the review. Verify each against the loaded Sideroom language guide, fix only demonstrated violations introduced by this run without removing required behavior or refactoring unrelated code,
|
|
24
|
+
'Additional semantic notes arrived after the review. Verify each against the loaded Sideroom language guide, fix only demonstrated violations introduced by this run without removing required behavior or refactoring unrelated code, reuse checks that remain valid, and run only missing, failed, or stale checks after any corrective edit.';
|
|
24
25
|
|
|
25
26
|
export interface ReviewState {
|
|
26
27
|
mutated: boolean;
|
|
@@ -49,8 +50,6 @@ export function shouldReview(state: ReviewState): boolean {
|
|
|
49
50
|
return (state.mutated || state.notes.length > 0) && !state.steeredThisTurn;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
// Notes that arrive after the review fired get one follow-up per turn, so a
|
|
53
|
-
// fix that draws new notes cannot keep the run going.
|
|
54
53
|
export function pendingReviewSteer(state: ReviewState): string | undefined {
|
|
55
54
|
if (shouldReview(state)) {
|
|
56
55
|
return withNotes(
|
|
@@ -110,23 +109,23 @@ export function registerReviewGuard(
|
|
|
110
109
|
}
|
|
111
110
|
});
|
|
112
111
|
|
|
113
|
-
// agent_before_settle runs after retries and compaction recovery, and its
|
|
114
|
-
// continuation stays inside the same run. agent_settled is notification-only:
|
|
115
|
-
// a steer sent there starts a second run after the user already saw the
|
|
116
|
-
// agent stop, and it fires even when the user pressed Escape.
|
|
117
112
|
registerSettleGuard(pi, {
|
|
118
113
|
priority: SETTLE_PRIORITY.review,
|
|
119
114
|
pending: () => pendingReviewSteer(state) !== undefined,
|
|
120
|
-
handle: (event) => {
|
|
115
|
+
handle: (event, ctx) => {
|
|
121
116
|
const steer = pendingReviewSteer(state);
|
|
122
117
|
if (steer === undefined) {
|
|
123
118
|
return;
|
|
124
119
|
}
|
|
125
120
|
markReviewSteered(state);
|
|
121
|
+
const check = readCheckStatus(pi, ctx);
|
|
122
|
+
const checkNote = check?.green
|
|
123
|
+
? `\n\nThe detected project check \`${check.command}\` passed after the latest successful file mutation in this user turn. Reuse that result if this review changes no files; rerun it after any further mutation.`
|
|
124
|
+
: '';
|
|
126
125
|
return withHiddenSteer({
|
|
127
126
|
event,
|
|
128
127
|
customType: REVIEW_STEER_TYPE,
|
|
129
|
-
content: steer,
|
|
128
|
+
content: steer + checkNote,
|
|
130
129
|
});
|
|
131
130
|
},
|
|
132
131
|
});
|
package/extensions/jev/client.ts
CHANGED
|
@@ -49,8 +49,6 @@ export interface JevAttempt {
|
|
|
49
49
|
readonly failure: JevFailure | undefined;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
// The endpoint is a package constant, never a value from the environment or the
|
|
53
|
-
// session, so the outbound URL cannot be steered by the repository being edited.
|
|
54
52
|
export function createFetchTransport(
|
|
55
53
|
timeoutMs: number = JEV_REQUEST_TIMEOUT_MS,
|
|
56
54
|
): JevTransport {
|
|
@@ -68,8 +66,6 @@ export function createFetchTransport(
|
|
|
68
66
|
};
|
|
69
67
|
}
|
|
70
68
|
|
|
71
|
-
// Pi waits for every tool_result handler before the result reaches the model,
|
|
72
|
-
// so the user's Escape has to cut the request, not only the timeout.
|
|
73
69
|
function requestSignal(
|
|
74
70
|
timeoutMs: number,
|
|
75
71
|
runSignal: AbortSignal | undefined,
|
|
@@ -97,9 +93,6 @@ export function classifyStatus(status: number): JevFailure | undefined {
|
|
|
97
93
|
return undefined;
|
|
98
94
|
}
|
|
99
95
|
|
|
100
|
-
// A Jev failure is a missing note, never an error: the mutation already
|
|
101
|
-
// succeeded and the mechanical rules already ran. So this returns a value
|
|
102
|
-
// instead of throwing, and the caller only decides whether to keep calling.
|
|
103
96
|
export async function askJev(
|
|
104
97
|
transport: JevTransport,
|
|
105
98
|
apiKey: string,
|
|
@@ -29,7 +29,6 @@ interface ScanResult {
|
|
|
29
29
|
readonly partial: boolean;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
// Keep the edited file complete. Other files are included whole or not at all.
|
|
33
32
|
export function buildContextualState(
|
|
34
33
|
cwd: string,
|
|
35
34
|
path: string,
|
|
@@ -339,7 +338,6 @@ function matchesReference(names: ReadonlySet<string>, path: string): boolean {
|
|
|
339
338
|
return false;
|
|
340
339
|
}
|
|
341
340
|
|
|
342
|
-
// Naming is evidence only for a sibling test, never for a repository-wide basename match.
|
|
343
341
|
function isCompanionTest(source: string, candidate: string): boolean {
|
|
344
342
|
if (
|
|
345
343
|
isTestPath(source) ||
|