@crouton-kit/crouter 0.3.42 → 0.3.44
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/bin/crouter +12 -1
- package/bin/crtr +12 -1
- package/bin/crtrd +12 -1
- package/dist/builtin-memory/05-kinds/advisor/00-base.md +5 -0
- package/dist/builtin-memory/05-kinds/design/00-base.md +8 -1
- package/dist/builtin-memory/05-kinds/developer/00-base.md +7 -0
- package/dist/builtin-memory/05-kinds/explore/00-base.md +7 -0
- package/dist/builtin-memory/05-kinds/general/00-base.md +5 -1
- package/dist/builtin-memory/05-kinds/plan/00-base.md +8 -1
- package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +8 -4
- package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +5 -1
- package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +6 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +5 -1
- package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +6 -1
- package/dist/builtin-memory/05-kinds/product/00-base.md +4 -0
- package/dist/builtin-memory/05-kinds/review/00-base.md +6 -0
- package/dist/builtin-memory/05-kinds/spec/00-base.md +3 -0
- package/dist/builtin-memory/wedged-child-on-runaway-bash.md +34 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/memory-slash-commands.test.ts +184 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +166 -0
- package/dist/clients/attach/attach-cmd.js +306 -306
- package/dist/commands/memory/delete.d.ts +1 -0
- package/dist/commands/memory/delete.js +77 -0
- package/dist/commands/memory/lint.js +10 -0
- package/dist/commands/memory/shared.js +1 -0
- package/dist/commands/memory/write.js +7 -1
- package/dist/commands/memory.js +3 -2
- package/dist/commands/node.js +35 -9
- package/dist/core/__tests__/pid-zombie.test.d.ts +1 -0
- package/dist/core/__tests__/pid-zombie.test.js +42 -0
- package/dist/core/canvas/pid.d.ts +14 -3
- package/dist/core/canvas/pid.js +41 -4
- package/dist/core/memory-resolver.d.ts +6 -0
- package/dist/core/memory-resolver.js +1 -1
- package/dist/core/runtime/broker.js +13 -5
- package/dist/core/runtime/launch.d.ts +1 -0
- package/dist/core/runtime/launch.js +4 -3
- package/dist/core/runtime/spawn.js +18 -3
- package/dist/core/runtime/stop-guard.js +19 -0
- package/dist/core/runtime/structured-output.d.ts +12 -0
- package/dist/core/runtime/structured-output.js +90 -0
- package/dist/core/substrate/schema.d.ts +15 -0
- package/dist/core/substrate/schema.js +9 -0
- package/dist/pi-extensions/canvas-structured-output.d.ts +9 -0
- package/dist/pi-extensions/canvas-structured-output.js +150 -0
- package/dist/prompts/review.js +14 -0
- package/package.json +1 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import { parse as parseYaml } from "yaml";
|
|
6
|
+
|
|
7
|
+
const exec = promisify(execFile);
|
|
8
|
+
const CRTR = "crtr";
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// memory-slash-commands: any memory doc frontmatter-flagged `slash: true`
|
|
12
|
+
// becomes an invocable slash command (`/<name>`). This mirrors Hermes Agent's
|
|
13
|
+
// "every skill is automatically a slash command" — /research-alpha injects
|
|
14
|
+
// the doc body (plus the user's free-text args) as a normal turn instead of
|
|
15
|
+
// requiring a separate skill-invocation mechanism.
|
|
16
|
+
//
|
|
17
|
+
// Discovery shells out to `crtr memory list --json`, which already resolves
|
|
18
|
+
// the substrate's scope precedence (project stack > profile > user >
|
|
19
|
+
// builtin) — this file does not walk memory/ directories or reimplement scope
|
|
20
|
+
// resolution. `list`'s JSON items are already scope-precedence sorted (see
|
|
21
|
+
// `scopeRank` in crouter's memory/shared.ts), so first-occurrence-wins per doc
|
|
22
|
+
// name reproduces the resolver's identity precedence for free.
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
const FRONTMATTER_RE = /^---\s*\r?\n([\s\S]*?)\r?\n---\s*\r?\n?/;
|
|
26
|
+
|
|
27
|
+
interface MemoryListItem {
|
|
28
|
+
name: string;
|
|
29
|
+
path: string;
|
|
30
|
+
is_dir: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface SlashDoc {
|
|
34
|
+
/** Path-derived doc name, e.g. "research-alpha" or "taste/foo" — what
|
|
35
|
+
* `crtr memory read` resolves. */
|
|
36
|
+
name: string;
|
|
37
|
+
/** `name` with `/` -> `:` — the registered command name (a flat name is
|
|
38
|
+
* unchanged). */
|
|
39
|
+
commandName: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function defaultListMemoryDocs(): Promise<MemoryListItem[]> {
|
|
43
|
+
const { stdout } = await exec(CRTR, ["memory", "list", "--json"], { maxBuffer: 8 * 1024 * 1024 });
|
|
44
|
+
const parsed = JSON.parse(stdout) as { items: MemoryListItem[] };
|
|
45
|
+
return parsed.items;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function defaultReadMemoryDoc(name: string): Promise<string> {
|
|
49
|
+
const { stdout } = await exec(CRTR, ["memory", "read", name, "--json"], { maxBuffer: 8 * 1024 * 1024 });
|
|
50
|
+
const parsed = JSON.parse(stdout) as { content: string };
|
|
51
|
+
return parsed.content;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let listMemoryDocsImpl = defaultListMemoryDocs;
|
|
55
|
+
let readMemoryDocImpl = defaultReadMemoryDoc;
|
|
56
|
+
|
|
57
|
+
/** Test seam: override doc discovery so tests never shell out to `crtr`. */
|
|
58
|
+
export function __setListMemoryDocsForTest(fn?: typeof listMemoryDocsImpl): void {
|
|
59
|
+
listMemoryDocsImpl = fn ?? defaultListMemoryDocs;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Test seam: override doc-body reads so tests never shell out to `crtr`. */
|
|
63
|
+
export function __setReadMemoryDocForTest(fn?: typeof readMemoryDocImpl): void {
|
|
64
|
+
readMemoryDocImpl = fn ?? defaultReadMemoryDoc;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Read a memory doc's raw frontmatter directly off its already-resolved
|
|
68
|
+
* `path` (from `crtr memory list`) and report whether `slash` is `true`.
|
|
69
|
+
* No directory walk, no scope reasoning — the path is already resolved. */
|
|
70
|
+
function isSlashFlagged(path: string): boolean {
|
|
71
|
+
let raw: string;
|
|
72
|
+
try {
|
|
73
|
+
raw = readFileSync(path, "utf8");
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
const match = raw.match(FRONTMATTER_RE);
|
|
78
|
+
if (!match) return false;
|
|
79
|
+
let data: unknown;
|
|
80
|
+
try {
|
|
81
|
+
data = parseYaml(match[1]);
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) return false;
|
|
86
|
+
return (data as Record<string, unknown>).slash === true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** `/`-joined path-derived doc name -> the registered slash-command name. */
|
|
90
|
+
export function slashCommandName(docName: string): string {
|
|
91
|
+
return docName.split("/").join(":");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Every `slash: true` memory doc, scope-precedence-deduped by name (see
|
|
95
|
+
* module header). Directory INDEX entries never register as commands. A doc
|
|
96
|
+
* without `slash` (or `slash: false`) is silently excluded. */
|
|
97
|
+
export async function discoverSlashDocs(): Promise<SlashDoc[]> {
|
|
98
|
+
let items: MemoryListItem[];
|
|
99
|
+
try {
|
|
100
|
+
items = await listMemoryDocsImpl();
|
|
101
|
+
} catch {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
const seen = new Set<string>();
|
|
105
|
+
const out: SlashDoc[] = [];
|
|
106
|
+
for (const item of items) {
|
|
107
|
+
if (item.is_dir) continue;
|
|
108
|
+
if (seen.has(item.name)) continue;
|
|
109
|
+
seen.add(item.name);
|
|
110
|
+
if (isSlashFlagged(item.path)) {
|
|
111
|
+
out.push({ name: item.name, commandName: slashCommandName(item.name) });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Compose the invocation prompt from the freshly-read doc body plus the
|
|
118
|
+
* user's raw invocation args, following Claude Code's `$ARGUMENTS`
|
|
119
|
+
* convention. When the body contains `$ARGUMENTS`, every occurrence is
|
|
120
|
+
* replaced with the trimmed args (empty string when none were given) and the
|
|
121
|
+
* substituted body is injected AS-IS — the doc placed its own args, so no
|
|
122
|
+
* extra header is added. Otherwise: a short invocation header naming the
|
|
123
|
+
* command and the raw args (both the given-args and no-args cases are
|
|
124
|
+
* load-bearing, per the Hermes /learn prompt pattern this mirrors), followed
|
|
125
|
+
* by the full doc body as the instructions to follow. */
|
|
126
|
+
export function composeSlashPrompt(commandName: string, rawArgs: string, body: string): string {
|
|
127
|
+
const args = rawArgs.trim();
|
|
128
|
+
if (body.includes("$ARGUMENTS")) {
|
|
129
|
+
return body.split("$ARGUMENTS").join(args);
|
|
130
|
+
}
|
|
131
|
+
const header =
|
|
132
|
+
args === ""
|
|
133
|
+
? `You invoked /${commandName} with no arguments.`
|
|
134
|
+
: `You invoked /${commandName} with: ${args}`;
|
|
135
|
+
return `${header}\n\nFollow the instructions below exactly as written — every part, including the presence or absence of arguments above, is a load-bearing requirement.\n\n${body}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export default async function (pi: ExtensionAPI) {
|
|
139
|
+
const docs = await discoverSlashDocs();
|
|
140
|
+
for (const doc of docs) {
|
|
141
|
+
pi.registerCommand(doc.commandName, {
|
|
142
|
+
description: `/${doc.commandName} — memory doc slash command (${doc.name})`,
|
|
143
|
+
handler: async (args, ctx) => {
|
|
144
|
+
let body: string;
|
|
145
|
+
try {
|
|
146
|
+
body = await readMemoryDocImpl(doc.name);
|
|
147
|
+
} catch (err) {
|
|
148
|
+
ctx.ui.notify(
|
|
149
|
+
`/${doc.commandName}: failed to read memory doc "${doc.name}": ${err instanceof Error ? err.message : String(err)}`,
|
|
150
|
+
"error",
|
|
151
|
+
);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const prompt = composeSlashPrompt(doc.commandName, args ?? "", body);
|
|
155
|
+
pi.sendMessage(
|
|
156
|
+
{
|
|
157
|
+
customType: "memory-slash-command",
|
|
158
|
+
content: prompt,
|
|
159
|
+
display: true,
|
|
160
|
+
},
|
|
161
|
+
{ triggerTurn: true },
|
|
162
|
+
);
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|