discoclaw 1.1.0 → 1.1.1
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/.context/pa.md +12 -84
- package/dist/canvas/canvas-action.js +5 -53
- package/dist/canvas/canvas-action.test.js +11 -23
- package/dist/instructions/system-defaults.js +51 -1
- package/dist/instructions/system-defaults.test.js +38 -1
- package/dist/instructions/tracked-tools.js +5 -1
- package/dist/instructions/tracked-tools.test.js +8 -4
- package/dist/runtime/gemini-rest.js +3 -0
- package/dist/runtime/gemini-rest.test.js +21 -0
- package/package.json +1 -1
- package/templates/instructions/SYSTEM_DEFAULTS.md +15 -121
- package/templates/instructions/TOOLS.md +6 -22
package/.context/pa.md
CHANGED
|
@@ -4,25 +4,7 @@ Generic PA rules. Personal customizations go in `workspace/AGENTS.md`.
|
|
|
4
4
|
|
|
5
5
|
## Self-Awareness
|
|
6
6
|
|
|
7
|
-
You are a DiscoClaw bot — a personal AI orchestrator
|
|
8
|
-
For architecture details, see `.context/architecture.md`.
|
|
9
|
-
|
|
10
|
-
## Workspace Files
|
|
11
|
-
|
|
12
|
-
| File | Purpose | Owner | Loaded |
|
|
13
|
-
|------|---------|-------|--------|
|
|
14
|
-
| `SOUL.md` | Core personality and values | User | Every prompt |
|
|
15
|
-
| `IDENTITY.md` | Name and vibe | User | Every prompt |
|
|
16
|
-
| `USER.md` | Who you're helping | User | Every prompt |
|
|
17
|
-
| `templates/instructions/SYSTEM_DEFAULTS.md` | Tracked default instructions (runtime-injected) | Discoclaw repo (tracked) | Every prompt |
|
|
18
|
-
| `AGENTS.md` | Personal rules and preferences | User (never overwritten) | Every prompt |
|
|
19
|
-
| `TOOLS.md` | Available tools and integrations | Discoclaw | Every prompt |
|
|
20
|
-
| `MEMORY.md` | Curated long-term memory | User | DM prompts |
|
|
21
|
-
| `BOOTSTRAP.md` | First-run onboarding (deleted after) | User | Once |
|
|
22
|
-
|
|
23
|
-
Templates live in `templates/workspace/` and are scaffolded on first run (copy-if-missing).
|
|
24
|
-
Tracked defaults come from `templates/instructions/SYSTEM_DEFAULTS.md` and are injected at runtime.
|
|
25
|
-
Legacy `workspace/DISCOCLAW.md` files are not authoritative.
|
|
7
|
+
You are a DiscoClaw bot — a personal AI orchestrator coordinating Discord, AI runtimes, and local system resources.
|
|
26
8
|
|
|
27
9
|
## Operational Essentials
|
|
28
10
|
|
|
@@ -41,74 +23,30 @@ Legacy `workspace/DISCOCLAW.md` files are not authoritative.
|
|
|
41
23
|
|
|
42
24
|
## Group Chat Etiquette
|
|
43
25
|
|
|
44
|
-
You
|
|
45
|
-
In groups, you're a participant — not their voice, not their proxy.
|
|
26
|
+
You're a participant, not the user's proxy. Don't share their private info.
|
|
46
27
|
|
|
47
|
-
**Respond
|
|
48
|
-
-
|
|
49
|
-
- You can add genuine value (info, insight, help)
|
|
50
|
-
- Something witty/funny fits naturally
|
|
51
|
-
- Correcting important misinformation
|
|
28
|
+
**Respond** to direct mentions, genuine questions, chances to add value, or natural humor/corrections.
|
|
29
|
+
**Stay silent (HEARTBEAT_OK)** for casual banter, already-answered questions, "yeah/nice" responses, or when adding a message would interrupt flow.
|
|
52
30
|
|
|
53
|
-
|
|
54
|
-
- It's just casual banter between humans
|
|
55
|
-
- Someone already answered the question
|
|
56
|
-
- Your response would just be "yeah" or "nice"
|
|
57
|
-
- The conversation is flowing fine without you
|
|
58
|
-
- Adding a message would interrupt the vibe
|
|
59
|
-
|
|
60
|
-
**The human rule:** Humans don't respond to every message. Neither should you.
|
|
61
|
-
Quality > quantity. Avoid the triple-tap (don't respond multiple times to the same message).
|
|
31
|
+
Humans don't respond to every message — neither should you. Quality > quantity. No triple-taps.
|
|
62
32
|
|
|
63
33
|
### Reactions
|
|
64
34
|
|
|
65
|
-
Use emoji reactions
|
|
66
|
-
- Appreciate something but don't need to reply (thumbs up, heart)
|
|
67
|
-
- Something made you laugh (laughing face, skull)
|
|
68
|
-
- Acknowledge without interrupting flow (checkmark, eyes)
|
|
69
|
-
- One reaction per message max.
|
|
70
|
-
|
|
71
|
-
When someone reacts to a message, acknowledge it with a brief response.
|
|
72
|
-
Reactions are a form of communication — treat them like a tap on the shoulder.
|
|
73
|
-
|
|
74
|
-
Participate, don't dominate.
|
|
35
|
+
Use emoji reactions as lightweight social signals (appreciate, laugh, acknowledge). One per message max. Acknowledge reactions on your messages briefly.
|
|
75
36
|
|
|
76
37
|
## Memory
|
|
77
38
|
|
|
78
|
-
Your prompt may include
|
|
79
|
-
|
|
80
|
-
**
|
|
81
|
-
Treat as ground truth unless explicitly contradicted. Don't repeat them back unprompted.
|
|
82
|
-
|
|
83
|
-
**Conversation memory** — Rolling summary of recent conversation. Lossy and compressed.
|
|
84
|
-
If it conflicts with recent messages, trust the recent messages.
|
|
85
|
-
|
|
86
|
-
Memory commands (handled by the system, not you):
|
|
87
|
-
- `!memory` or `!memory show` — see stored items + rolling summary
|
|
88
|
-
- `!memory remember <text>` — store a new fact
|
|
89
|
-
- `!memory forget <text>` — remove matching items
|
|
90
|
-
- `!memory reset rolling` — clear the rolling summary
|
|
91
|
-
|
|
92
|
-
See `.context/memory.md` for full architecture, examples, and config reference.
|
|
39
|
+
Your prompt may include:
|
|
40
|
+
- **Durable memory** — Persistent user facts/preferences. Treat as ground truth unless contradicted.
|
|
41
|
+
- **Conversation memory** — Rolling summary, lossy. Trust recent messages over summary if they conflict.
|
|
93
42
|
|
|
94
43
|
## Autonomy Tiers
|
|
95
44
|
|
|
96
|
-
|
|
97
|
-
- Read files, explore, search the web, run diagnostics
|
|
98
|
-
- Send Discord messages, react with emoji
|
|
99
|
-
- Share finds in relevant channels, report back on async tasks
|
|
100
|
-
- Work within the workspace
|
|
45
|
+
**Always OK:** Read files, explore, search web, run diagnostics, send Discord messages, react, share finds, work within workspace.
|
|
101
46
|
|
|
102
|
-
|
|
103
|
-
- For **confirmed, active** security threats: take reversible protective actions, then notify
|
|
104
|
-
- For ambiguous threats: alert first, wait for decision
|
|
47
|
+
**Act Then Notify:** Confirmed active security threats — take reversible action, then notify. Ambiguous threats — alert first.
|
|
105
48
|
|
|
106
|
-
|
|
107
|
-
- External communications (emails, messages to others, posting on someone's behalf)
|
|
108
|
-
- Changes to the user's creative projects
|
|
109
|
-
- System changes (package installs, systemd modifications, firewall/network)
|
|
110
|
-
- Destructive actions (deleting files, dropping databases, revoking credentials)
|
|
111
|
-
- Anything involving money
|
|
49
|
+
**Always Ask:** External communications, creative project changes, system changes (packages, systemd, network), destructive actions, anything involving money.
|
|
112
50
|
|
|
113
51
|
## Execution Policy (Local Machine)
|
|
114
52
|
|
|
@@ -116,13 +54,3 @@ See `.context/memory.md` for full architecture, examples, and config reference.
|
|
|
116
54
|
- Ask before: destructive ops, system-wide changes
|
|
117
55
|
- Never retry sudo. If auth is needed, give the user the command to run.
|
|
118
56
|
|
|
119
|
-
## Customization
|
|
120
|
-
|
|
121
|
-
Instruction precedence is deterministic:
|
|
122
|
-
1. immutable security policy (`ROOT_POLICY`)
|
|
123
|
-
2. tracked defaults (`templates/instructions/SYSTEM_DEFAULTS.md`)
|
|
124
|
-
3. `workspace/AGENTS.md` overrides
|
|
125
|
-
4. memory/context sections
|
|
126
|
-
|
|
127
|
-
Customize behavior in `workspace/AGENTS.md` (user-owned, never overwritten).
|
|
128
|
-
Do not rely on `workspace/DISCOCLAW.md`; defaults are sourced from the tracked template and injected at runtime.
|
|
@@ -12,15 +12,9 @@ const CANVAS_VOID_ELEMENT_TAG_RE = /<(area|base|br|col|embed|hr|img|input|link|m
|
|
|
12
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
13
|
const __dirname = path.dirname(__filename);
|
|
14
14
|
const CANVAS_PROMPT_TEMPLATE_PATH = path.resolve(__dirname, '..', '..', 'templates', 'instructions', 'canvas.md');
|
|
15
|
-
const
|
|
16
|
-
'
|
|
17
|
-
|
|
18
|
-
].join(' ');
|
|
19
|
-
const DEFAULT_SAVE_BRIDGE_GUIDANCE = [
|
|
20
|
-
'- Save-file export is available through the trusted shell bridge when enabled.',
|
|
21
|
-
'- To export a file from inside the artifact, post a message to the parent shell:',
|
|
22
|
-
' `window.parent.postMessage({ type: "canvas.saveFile", suggestedName: "report.md", mimeType: "text/markdown", encoding: "utf8", content: "# Report" }, "*")`',
|
|
23
|
-
].join('\n');
|
|
15
|
+
const CANVAS_SETUP_POSTURE_NOTE = 'Canvas Activities are experimental and default-off in DiscoClaw.' +
|
|
16
|
+
' Fresh installs and upgraded installs both require an explicit opt-in via `DISCOCLAW_CANVAS_ENABLED=1`, followed by a bot restart.';
|
|
17
|
+
const DEFAULT_SAVE_BRIDGE_GUIDANCE = '- Save-file export: `window.parent.postMessage({ type: "canvas.saveFile", suggestedName: "report.md", mimeType: "text/markdown", encoding: "utf8", content: "..." }, "*")`';
|
|
24
18
|
let cachedCanvasPromptTemplate = null;
|
|
25
19
|
export const CANVAS_ACTION_TYPES = new Set(['launchCanvas']);
|
|
26
20
|
export const CANVAS_LAUNCH_COMPONENT_PREFIX = 'canvas:launch:';
|
|
@@ -57,7 +51,7 @@ export function buildCanvasSetupRequiredStub(canvasCtx) {
|
|
|
57
51
|
'Enable Activities on the Discord application in the Developer Portal (Application → Activities → Enable). Requires the URL Mapping first.',
|
|
58
52
|
],
|
|
59
53
|
};
|
|
60
|
-
return [
|
|
54
|
+
return [CANVAS_SETUP_POSTURE_NOTE, buildCanvasSetupWalkthrough(readiness)].join(' ');
|
|
61
55
|
}
|
|
62
56
|
export function shouldCanvasPromptBeSurfaced(canvasCtx, userText) {
|
|
63
57
|
if (!canvasCtx?.enabled)
|
|
@@ -281,49 +275,7 @@ function findCanvasArtifactLintError(content) {
|
|
|
281
275
|
function loadCanvasPromptTemplate() {
|
|
282
276
|
if (cachedCanvasPromptTemplate != null)
|
|
283
277
|
return cachedCanvasPromptTemplate;
|
|
284
|
-
|
|
285
|
-
cachedCanvasPromptTemplate = fs.readFileSync(CANVAS_PROMPT_TEMPLATE_PATH, 'utf8').trim();
|
|
286
|
-
}
|
|
287
|
-
catch {
|
|
288
|
-
cachedCanvasPromptTemplate = [
|
|
289
|
-
'### Canvas Activities',
|
|
290
|
-
'',
|
|
291
|
-
'**launchCanvas** — Generate and serve an interactive HTML artifact or built-in app in a Discord Activity panel:',
|
|
292
|
-
'```',
|
|
293
|
-
'<discord-action>{"type":"launchCanvas","title":"Tax Calculator","content":"<!doctype html><html><head><meta charset=\\"utf-8\\" /><meta name=\\"viewport\\" content=\\"width=device-width,initial-scale=1\\" /><style>body{font-family:sans-serif;padding:16px}label,input{display:block;margin-top:12px}</style></head><body><div id=\\"app\\"></div><script>const { html, render, useState } = window.canvasRuntime;function TaxCalculator(){const [income,setIncome]=useState(50000);const tax=Math.round(income*0.22);return html`<main><h1>Tax Calculator</h1><label>Income <input type=\\"number\\" value=${income} onInput=${(event)=>setIncome(Number(event.currentTarget.value||0))} /></label><p>Estimated tax: $${tax.toLocaleString()}</p></main>`;}render(TaxCalculator, document.getElementById(\\"app\\"));</script></body></html>"}</discord-action>',
|
|
294
|
-
'<discord-action>{"type":"launchCanvas","title":"Dashboard","app":"dashboard"}</discord-action>',
|
|
295
|
-
'```',
|
|
296
|
-
'- `title` (required): Human-readable label for the launch button.',
|
|
297
|
-
'- `content` (artifact mode): Full self-contained HTML document with all CSS and JS inline.',
|
|
298
|
-
'- `app` (built-in mode): Named built-in Activity app such as `dashboard`.',
|
|
299
|
-
'- Use canvas only when interactivity materially improves the result over plain text.',
|
|
300
|
-
'- Default is plain text. Do not use canvas for short answers, conversational replies, or single values.',
|
|
301
|
-
'- Good fits: calculators, forms, charts, diffs, large comparison views, filterable tables, live dashboard launches.',
|
|
302
|
-
'- Bad fits: simple status updates, brief explanations, or anything the user explicitly wants as plain text.',
|
|
303
|
-
'- Artifact render responses inject `window.canvasRuntime`; use that built-in runtime instead of bundling React, Preact, Vue, or another UI framework.',
|
|
304
|
-
'- The injected runtime exposes `html`, `render`, and the installed `preact/hooks` surface: `useState`, `useEffect`, `useLayoutEffect`, `useReducer`, `useRef`, `useMemo`, `useCallback`, `useContext`, `useImperativeHandle`, `useDebugValue`, `useErrorBoundary`, and `useId`.',
|
|
305
|
-
'- Start interactive artifacts with `const { html, render, useState } = window.canvasRuntime`; keep the starter small unless the artifact actually needs more hook surface, and mount into a dedicated root node.',
|
|
306
|
-
'- In `html` template literals, self-close void HTML elements: use `<input ... />`, `<img ... />`, `<br />`, etc. Bare `<input>` tags can corrupt the rendered DOM in canvas artifacts.',
|
|
307
|
-
'- Generated artifacts must be a single HTML file, responsive at phone width, and keep total size under roughly 500KB.',
|
|
308
|
-
'- No external scripts, stylesheets, fonts, images, or nested iframes in generated artifacts.',
|
|
309
|
-
'- Generated artifacts run inside a sandboxed iframe and cannot call backend routes directly.',
|
|
310
|
-
'- Artifacts are stored under a cap-based LRU policy; they are not time-expired in v1.',
|
|
311
|
-
'- Include visible loading/error/fallback states when the UI depends on JavaScript.',
|
|
312
|
-
'- Default visual direction: feel at home in Discord without mimicking Discord\'s UI chrome.',
|
|
313
|
-
'- Favor Discord-adjacent contrast and restraint for dark surroundings, but choose colors and themes based on the content instead of cloning Discord\'s palette by default.',
|
|
314
|
-
'- Use explicitly Discord-like styling only when the user asks for a native/control-panel/admin-tool feel.',
|
|
315
|
-
'- Prefer semantic HTML, clear contrast, and obvious focus states.',
|
|
316
|
-
'{{CANVAS_SAVE_BRIDGE_GUIDANCE}}',
|
|
317
|
-
].join('\n');
|
|
318
|
-
}
|
|
319
|
-
if (!cachedCanvasPromptTemplate.includes(CANVAS_EXPERIMENTAL_POSTURE_NOTE)) {
|
|
320
|
-
if (cachedCanvasPromptTemplate.startsWith('### Canvas Activities')) {
|
|
321
|
-
cachedCanvasPromptTemplate = cachedCanvasPromptTemplate.replace('### Canvas Activities', `### Canvas Activities\n\n${CANVAS_EXPERIMENTAL_POSTURE_NOTE}`);
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
cachedCanvasPromptTemplate = `${CANVAS_EXPERIMENTAL_POSTURE_NOTE}\n\n${cachedCanvasPromptTemplate}`;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
278
|
+
cachedCanvasPromptTemplate = fs.readFileSync(CANVAS_PROMPT_TEMPLATE_PATH, 'utf8').trim();
|
|
327
279
|
return cachedCanvasPromptTemplate;
|
|
328
280
|
}
|
|
329
281
|
function buildLaunchActivityFailureMessage(err) {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import fsSync from 'node:fs';
|
|
3
2
|
import fs from 'node:fs/promises';
|
|
4
3
|
import os from 'node:os';
|
|
5
4
|
import path from 'node:path';
|
|
@@ -81,13 +80,11 @@ async function makeCanvasContext() {
|
|
|
81
80
|
return canvasCtx;
|
|
82
81
|
}
|
|
83
82
|
describe('canvas-action', () => {
|
|
84
|
-
it('surfaces the
|
|
83
|
+
it('surfaces the canvas runtime contract from the checked-in prompt template', async () => {
|
|
85
84
|
vi.resetModules();
|
|
86
85
|
const { canvasActionsPromptSection } = await import('./canvas-action.js');
|
|
87
86
|
const prompt = canvasActionsPromptSection({ writeBridgeEnabled: true });
|
|
88
87
|
expect(prompt).toContain('experimental and default-off');
|
|
89
|
-
expect(prompt).toContain('DISCOCLAW_CANVAS_ENABLED=1');
|
|
90
|
-
expect(prompt).toContain('Fresh installs and upgraded installs both require an explicit opt-in');
|
|
91
88
|
expect(prompt).toContain('window.canvasRuntime');
|
|
92
89
|
expect(prompt).toContain('installed `preact/hooks` surface');
|
|
93
90
|
expect(prompt).toContain('`useLayoutEffect`');
|
|
@@ -96,36 +93,27 @@ describe('canvas-action', () => {
|
|
|
96
93
|
expect(prompt).toContain('Bare `<input>` tags can corrupt the rendered DOM');
|
|
97
94
|
expect(prompt).toContain('feel at home in Discord without mimicking Discord');
|
|
98
95
|
expect(prompt).toContain('instead of cloning Discord\'s palette by default');
|
|
96
|
+
expect(prompt).toContain('canvas.saveFile');
|
|
97
|
+
expect(prompt).not.toContain('{{CANVAS_SAVE_BRIDGE_GUIDANCE}}');
|
|
99
98
|
});
|
|
100
|
-
it('
|
|
99
|
+
it('omits save-bridge guidance when writeBridgeEnabled is false', async () => {
|
|
101
100
|
vi.resetModules();
|
|
102
|
-
vi.spyOn(fsSync, 'readFileSync').mockImplementation(() => {
|
|
103
|
-
throw new Error('template unavailable');
|
|
104
|
-
});
|
|
105
101
|
const { canvasActionsPromptSection } = await import('./canvas-action.js');
|
|
106
|
-
const withSaveBridge = canvasActionsPromptSection({ writeBridgeEnabled: true });
|
|
107
|
-
expect(withSaveBridge).toContain('experimental and default-off');
|
|
108
|
-
expect(withSaveBridge).toContain('DISCOCLAW_CANVAS_ENABLED=1');
|
|
109
|
-
expect(withSaveBridge).toContain('Fresh installs and upgraded installs both require an explicit opt-in');
|
|
110
|
-
expect(withSaveBridge).toContain('window.canvasRuntime');
|
|
111
|
-
expect(withSaveBridge).toContain('installed `preact/hooks` surface');
|
|
112
|
-
expect(withSaveBridge).toContain('`useErrorBoundary`');
|
|
113
|
-
expect(withSaveBridge).toContain('const { html, render, useState } = window.canvasRuntime');
|
|
114
|
-
expect(withSaveBridge).toContain('self-close void HTML elements');
|
|
115
|
-
expect(withSaveBridge).toContain('feel at home in Discord without mimicking Discord');
|
|
116
|
-
expect(withSaveBridge).toContain('canvas.saveFile');
|
|
117
|
-
expect(withSaveBridge).not.toContain('{{CANVAS_SAVE_BRIDGE_GUIDANCE}}');
|
|
118
102
|
const withoutSaveBridge = canvasActionsPromptSection({ writeBridgeEnabled: false });
|
|
119
103
|
expect(withoutSaveBridge).toContain('experimental and default-off');
|
|
120
|
-
expect(withoutSaveBridge).toContain('DISCOCLAW_CANVAS_ENABLED=1');
|
|
121
104
|
expect(withoutSaveBridge).toContain('window.canvasRuntime');
|
|
122
105
|
expect(withoutSaveBridge).toContain('installed `preact/hooks` surface');
|
|
123
|
-
expect(withoutSaveBridge).toContain('`useId`');
|
|
124
106
|
expect(withoutSaveBridge).toContain('Bare `<input>` tags can corrupt the rendered DOM');
|
|
125
|
-
expect(withoutSaveBridge).toContain('instead of cloning Discord\'s palette by default');
|
|
126
107
|
expect(withoutSaveBridge).not.toContain('canvas.saveFile');
|
|
127
108
|
expect(withoutSaveBridge).not.toContain('{{CANVAS_SAVE_BRIDGE_GUIDANCE}}');
|
|
128
109
|
});
|
|
110
|
+
it('does not inject the setup posture note into the prompt template', async () => {
|
|
111
|
+
vi.resetModules();
|
|
112
|
+
const { canvasActionsPromptSection } = await import('./canvas-action.js');
|
|
113
|
+
const prompt = canvasActionsPromptSection({ writeBridgeEnabled: true });
|
|
114
|
+
expect(prompt).not.toContain('DISCOCLAW_CANVAS_ENABLED=1');
|
|
115
|
+
expect(prompt).not.toContain('Fresh installs and upgraded installs both require an explicit opt-in');
|
|
116
|
+
});
|
|
129
117
|
it('describes canvas setup as experimental and explicit opt-in', async () => {
|
|
130
118
|
vi.resetModules();
|
|
131
119
|
const { buildCanvasSetupRequiredStub } = await import('./canvas-action.js');
|
|
@@ -8,6 +8,56 @@ export const TRACKED_DEFAULTS_FILE_NAME = 'SYSTEM_DEFAULTS.md';
|
|
|
8
8
|
export const TRACKED_DEFAULTS_SECTION_LABEL = 'SYSTEM_DEFAULTS.md (tracked defaults)';
|
|
9
9
|
let cachedPath = null;
|
|
10
10
|
let cachedPreamble = null;
|
|
11
|
+
/**
|
|
12
|
+
* Sections dropped from the tracked defaults because they are
|
|
13
|
+
* duplicated, unreachable after bootstrap, or rarely needed.
|
|
14
|
+
*/
|
|
15
|
+
const DROPPED_DEFAULTS_SECTIONS = new Set([
|
|
16
|
+
'Runtime Instruction Precedence',
|
|
17
|
+
'First Run',
|
|
18
|
+
'Runtime Registry',
|
|
19
|
+
'Bot Setup Assistance',
|
|
20
|
+
'Knowledge Cutoff Awareness',
|
|
21
|
+
]);
|
|
22
|
+
function splitTopLevelSections(content) {
|
|
23
|
+
const prelude = [];
|
|
24
|
+
const sections = [];
|
|
25
|
+
let current = null;
|
|
26
|
+
for (const line of content.trimEnd().split('\n')) {
|
|
27
|
+
if (line.startsWith('## ')) {
|
|
28
|
+
current = { heading: line.slice(3).trim(), lines: [line] };
|
|
29
|
+
sections.push(current);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (current) {
|
|
33
|
+
current.lines.push(line);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
prelude.push(line);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return { prelude, sections };
|
|
40
|
+
}
|
|
41
|
+
function joinDefaultsContent(parts) {
|
|
42
|
+
return parts
|
|
43
|
+
.map((part) => part.trimEnd())
|
|
44
|
+
.filter((part) => part.length > 0)
|
|
45
|
+
.join('\n\n')
|
|
46
|
+
.trimEnd();
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Filter tracked defaults content by dropping rarely-needed sections
|
|
50
|
+
* and stripping meta-descriptive blockquotes from the prelude.
|
|
51
|
+
*/
|
|
52
|
+
export function buildPromptSafeDefaultsContent(content) {
|
|
53
|
+
const { prelude, sections } = splitTopLevelSections(content);
|
|
54
|
+
const filteredPrelude = prelude.filter((line) => !line.startsWith('> '));
|
|
55
|
+
const filteredSections = sections.filter((section) => !DROPPED_DEFAULTS_SECTIONS.has(section.heading));
|
|
56
|
+
return joinDefaultsContent([
|
|
57
|
+
filteredPrelude.join('\n'),
|
|
58
|
+
...filteredSections.map((section) => section.lines.join('\n')),
|
|
59
|
+
]);
|
|
60
|
+
}
|
|
11
61
|
/**
|
|
12
62
|
* Resolve the tracked system-default file path from this module's location.
|
|
13
63
|
* Works in both src/* and dist/* layouts.
|
|
@@ -36,7 +86,7 @@ export function loadTrackedDefaultsPreamble(opts) {
|
|
|
36
86
|
let defaultsPreamble = '';
|
|
37
87
|
try {
|
|
38
88
|
const content = fsSync.readFileSync(trackedDefaultsPath, 'utf-8');
|
|
39
|
-
defaultsPreamble = renderTrackedDefaultsSection(content);
|
|
89
|
+
defaultsPreamble = renderTrackedDefaultsSection(buildPromptSafeDefaultsContent(content));
|
|
40
90
|
}
|
|
41
91
|
catch (err) {
|
|
42
92
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -2,7 +2,7 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
-
import { TRACKED_DEFAULTS_DIR, TRACKED_DEFAULTS_FILE_NAME, TRACKED_DEFAULTS_SECTION_LABEL, _resetTrackedDefaultsCacheForTests, loadTrackedDefaultsPreamble, renderTrackedDefaultsSection, resolveTrackedDefaultsPath, } from './system-defaults.js';
|
|
5
|
+
import { TRACKED_DEFAULTS_DIR, TRACKED_DEFAULTS_FILE_NAME, TRACKED_DEFAULTS_SECTION_LABEL, _resetTrackedDefaultsCacheForTests, buildPromptSafeDefaultsContent, loadTrackedDefaultsPreamble, renderTrackedDefaultsSection, resolveTrackedDefaultsPath, } from './system-defaults.js';
|
|
6
6
|
describe('resolveTrackedDefaultsPath', () => {
|
|
7
7
|
it('resolves to templates/instructions/SYSTEM_DEFAULTS.md by default', async () => {
|
|
8
8
|
const resolved = resolveTrackedDefaultsPath();
|
|
@@ -28,6 +28,31 @@ describe('renderTrackedDefaultsSection', () => {
|
|
|
28
28
|
expect(renderTrackedDefaultsSection('\n \n')).toBe('');
|
|
29
29
|
});
|
|
30
30
|
});
|
|
31
|
+
describe('buildPromptSafeDefaultsContent', () => {
|
|
32
|
+
it('drops rarely-needed sections from the tracked defaults template', async () => {
|
|
33
|
+
const content = await fs.readFile(resolveTrackedDefaultsPath(), 'utf-8');
|
|
34
|
+
const sanitized = buildPromptSafeDefaultsContent(content);
|
|
35
|
+
// Dropped sections
|
|
36
|
+
expect(sanitized).not.toContain('## Runtime Instruction Precedence');
|
|
37
|
+
expect(sanitized).not.toContain('## First Run');
|
|
38
|
+
expect(sanitized).not.toContain('## Runtime Registry');
|
|
39
|
+
expect(sanitized).not.toContain('## Bot Setup Assistance');
|
|
40
|
+
expect(sanitized).not.toContain('## Knowledge Cutoff Awareness');
|
|
41
|
+
// Sections that should survive filtering
|
|
42
|
+
expect(sanitized).toContain('## Search Before Asking');
|
|
43
|
+
expect(sanitized).toContain('## Tool Use First');
|
|
44
|
+
expect(sanitized).toContain('## Discord Action Grounding');
|
|
45
|
+
expect(sanitized).toContain('## Response Economy');
|
|
46
|
+
expect(sanitized).toContain('## Landing the Plane');
|
|
47
|
+
});
|
|
48
|
+
it('strips blockquote lines from the prelude', () => {
|
|
49
|
+
const content = '# Title\n\n> Meta description\n> Second line\n\n## Kept\nContent';
|
|
50
|
+
const sanitized = buildPromptSafeDefaultsContent(content);
|
|
51
|
+
expect(sanitized).not.toContain('> Meta description');
|
|
52
|
+
expect(sanitized).toContain('# Title');
|
|
53
|
+
expect(sanitized).toContain('## Kept');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
31
56
|
describe('loadTrackedDefaultsPreamble', () => {
|
|
32
57
|
const dirs = [];
|
|
33
58
|
afterEach(async () => {
|
|
@@ -74,6 +99,18 @@ describe('loadTrackedDefaultsPreamble', () => {
|
|
|
74
99
|
expect(reloaded).toContain('second version');
|
|
75
100
|
expect(reloaded).not.toBe(first);
|
|
76
101
|
});
|
|
102
|
+
it('sanitizes the default tracked template before rendering it into the preamble', () => {
|
|
103
|
+
const preamble = loadTrackedDefaultsPreamble({
|
|
104
|
+
trackedDefaultsPath: resolveTrackedDefaultsPath(),
|
|
105
|
+
forceReload: true,
|
|
106
|
+
});
|
|
107
|
+
expect(preamble).toContain(`--- ${TRACKED_DEFAULTS_SECTION_LABEL} ---`);
|
|
108
|
+
expect(preamble).not.toContain('## Runtime Instruction Precedence');
|
|
109
|
+
expect(preamble).not.toContain('## Runtime Registry');
|
|
110
|
+
expect(preamble).not.toContain('## Bot Setup Assistance');
|
|
111
|
+
expect(preamble).toContain('## Search Before Asking');
|
|
112
|
+
expect(preamble).toContain('## Tool Use First');
|
|
113
|
+
});
|
|
77
114
|
it('invalidates cache when the tracked defaults path changes', async () => {
|
|
78
115
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'tracked-defaults-'));
|
|
79
116
|
dirs.push(dir);
|
|
@@ -13,6 +13,9 @@ let cachedContent = null;
|
|
|
13
13
|
const DROPPED_TOP_LEVEL_SECTIONS = new Set([
|
|
14
14
|
'Browser Automation (agent-browser)',
|
|
15
15
|
'Service Operations (discoclaw)',
|
|
16
|
+
'Runtime Instruction Precedence',
|
|
17
|
+
'Webhook Server',
|
|
18
|
+
'Plan-Audit-Implement Workflow',
|
|
16
19
|
]);
|
|
17
20
|
const WEBHOOK_TOOL_ACCESS_SENTENCE = 'webhook jobs run without Discord action permissions or tool access.';
|
|
18
21
|
const AUDITED_RUNTIME_TOOL_GUARANTEES_HEADING = 'Audited Runtime Tool Guarantees';
|
|
@@ -125,8 +128,9 @@ export function buildPromptSafeTrackedToolsContent(content, ctx) {
|
|
|
125
128
|
filteredSections.splice(precedenceIndex + 1, 0, auditedGuarantees);
|
|
126
129
|
}
|
|
127
130
|
}
|
|
131
|
+
const filteredPrelude = prelude.filter((line) => !line.startsWith('> '));
|
|
128
132
|
return joinTrackedToolsContent([
|
|
129
|
-
|
|
133
|
+
filteredPrelude.join('\n'),
|
|
130
134
|
...filteredSections.map((section) => section.lines.join('\n')),
|
|
131
135
|
]);
|
|
132
136
|
}
|
|
@@ -34,12 +34,14 @@ describe('buildPromptSafeTrackedToolsContent', () => {
|
|
|
34
34
|
it('drops unsupported static tool-restriction sections from the tracked template', async () => {
|
|
35
35
|
const trackedToolsContent = await fs.readFile(resolveTrackedToolsPath(), 'utf-8');
|
|
36
36
|
const sanitized = buildPromptSafeTrackedToolsContent(trackedToolsContent);
|
|
37
|
-
expect(sanitized).toContain('## Runtime Instruction Precedence');
|
|
37
|
+
expect(sanitized).not.toContain('## Runtime Instruction Precedence');
|
|
38
38
|
expect(sanitized).not.toContain('## Browser Automation (agent-browser)');
|
|
39
39
|
expect(sanitized).not.toContain('## Service Operations (discoclaw)');
|
|
40
|
-
expect(sanitized).toContain('## Webhook Server');
|
|
41
|
-
expect(sanitized).toContain('
|
|
42
|
-
|
|
40
|
+
expect(sanitized).not.toContain('## Webhook Server');
|
|
41
|
+
expect(sanitized).not.toContain('## Plan-Audit-Implement Workflow');
|
|
42
|
+
// Sections that should survive filtering
|
|
43
|
+
expect(sanitized).toContain('## Task Management');
|
|
44
|
+
expect(sanitized).toContain('## Discord Action Types');
|
|
43
45
|
});
|
|
44
46
|
});
|
|
45
47
|
const COVERED_TRACKED_TOOL_RUNTIME_CONFIGS = [
|
|
@@ -155,6 +157,8 @@ describe('loadTrackedToolsPreamble', () => {
|
|
|
155
157
|
expect(preamble).toContain(`--- ${TRACKED_TOOLS_SECTION_LABEL} ---`);
|
|
156
158
|
expect(preamble).not.toContain('## Browser Automation (agent-browser)');
|
|
157
159
|
expect(preamble).not.toContain('## Service Operations (discoclaw)');
|
|
160
|
+
expect(preamble).not.toContain('## Webhook Server');
|
|
161
|
+
expect(preamble).not.toContain('## Plan-Audit-Implement Workflow');
|
|
158
162
|
expect(preamble).not.toContain('webhook jobs run without Discord action permissions or tool access');
|
|
159
163
|
});
|
|
160
164
|
it('re-renders cached tracked tools content for runtime-specific audited guarantees', () => {
|
|
@@ -125,6 +125,9 @@ export function createGeminiRestRuntime(opts) {
|
|
|
125
125
|
inputTokens: usage.promptTokenCount,
|
|
126
126
|
outputTokens: usage.candidatesTokenCount,
|
|
127
127
|
totalTokens: usage.totalTokenCount,
|
|
128
|
+
...(usage.cachedContentTokenCount != null
|
|
129
|
+
? { cachedInputTokens: usage.cachedContentTokenCount, cacheSupported: true }
|
|
130
|
+
: {}),
|
|
128
131
|
};
|
|
129
132
|
}
|
|
130
133
|
}
|
|
@@ -117,6 +117,27 @@ describe('Gemini REST runtime adapter', () => {
|
|
|
117
117
|
expect(usage.inputTokens).toBe(10);
|
|
118
118
|
expect(usage.outputTokens).toBe(5);
|
|
119
119
|
});
|
|
120
|
+
it('emits cachedInputTokens when usageMetadata includes cachedContentTokenCount', async () => {
|
|
121
|
+
globalThis.fetch = vi.fn().mockResolvedValue(makeSSEResponse([
|
|
122
|
+
makeGeminiSSEDataWithUsage('ok', {
|
|
123
|
+
promptTokenCount: 100,
|
|
124
|
+
candidatesTokenCount: 20,
|
|
125
|
+
totalTokenCount: 120,
|
|
126
|
+
cachedContentTokenCount: 80,
|
|
127
|
+
}),
|
|
128
|
+
]));
|
|
129
|
+
const runtime = createGeminiRestRuntime({
|
|
130
|
+
apiKey: 'test-key',
|
|
131
|
+
defaultModel: 'gemini-2.5-flash',
|
|
132
|
+
});
|
|
133
|
+
const events = await collectEvents(runtime.invoke({ prompt: 'test', model: '', cwd: '/tmp' }));
|
|
134
|
+
const usage = events.find((e) => e.type === 'usage');
|
|
135
|
+
expect(usage).toBeDefined();
|
|
136
|
+
expect(usage.inputTokens).toBe(100);
|
|
137
|
+
expect(usage.outputTokens).toBe(20);
|
|
138
|
+
expect(usage.cachedInputTokens).toBe(80);
|
|
139
|
+
expect(usage.cacheSupported).toBe(true);
|
|
140
|
+
});
|
|
120
141
|
it('emits error event on non-200 response', async () => {
|
|
121
142
|
globalThis.fetch = vi.fn().mockResolvedValue(new Response(JSON.stringify({ error: { message: 'quota exceeded' } }), {
|
|
122
143
|
status: 429,
|
package/package.json
CHANGED
|
@@ -1,63 +1,28 @@
|
|
|
1
1
|
# SYSTEM_DEFAULTS.md - Tracked Default Instructions
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
>
|
|
5
|
-
> User-specific overrides belong in `workspace/AGENTS.md`.
|
|
6
|
-
> Tracked tool instructions are injected separately from `templates/instructions/TOOLS.md`.
|
|
7
|
-
> User-specific tool overrides belong in `workspace/TOOLS.md`.
|
|
3
|
+
> Canonical tracked default instruction source, injected at runtime and not a workspace-managed file.
|
|
4
|
+
> User overrides: `workspace/AGENTS.md`. Tool instructions: `templates/instructions/TOOLS.md` (overrides: `workspace/TOOLS.md`).
|
|
8
5
|
|
|
9
6
|
## Runtime Instruction Precedence
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
1. **Immutable security policy** (hard-coded runtime rules)
|
|
14
|
-
2. **Tracked defaults** (this file)
|
|
15
|
-
3. **Tracked tools** (`templates/instructions/TOOLS.md`)
|
|
16
|
-
4. **`workspace/AGENTS.md`** (user overrides)
|
|
17
|
-
5. **`workspace/TOOLS.md`** (optional user override layer)
|
|
18
|
-
6. **Memory and other runtime context** (SOUL/IDENTITY/USER, channel context, memory layers, etc.)
|
|
8
|
+
1. Immutable security policy → 2. Tracked defaults (this) → 3. Tracked tools (`templates/instructions/TOOLS.md`) → 4. `workspace/AGENTS.md` → 5. `workspace/TOOLS.md` → 6. Memory/runtime context
|
|
19
9
|
|
|
20
10
|
## First Run
|
|
21
11
|
|
|
22
|
-
If `BOOTSTRAP.md` exists,
|
|
23
|
-
|
|
24
|
-
## Every Session
|
|
25
|
-
|
|
26
|
-
Before doing anything else:
|
|
27
|
-
|
|
28
|
-
1. Read `SOUL.md` — this is who you are
|
|
29
|
-
2. Read `USER.md` — this is who you're helping
|
|
30
|
-
3. Read `IDENTITY.md` — this is your name and vibe
|
|
31
|
-
|
|
32
|
-
Load them immediately — just do it. These files are loaded into your prompt automatically by Discoclaw, but read them to internalize who you are.
|
|
12
|
+
If `BOOTSTRAP.md` exists, follow it, then delete it.
|
|
33
13
|
|
|
34
14
|
## Search Before Asking
|
|
35
15
|
|
|
36
|
-
Before
|
|
37
|
-
|
|
38
|
-
1. **Workspace files** — Read relevant files in the workspace directory (MEMORY.md, any context files). The answer is often already there.
|
|
39
|
-
2. **Durable memory** — It's injected into your prompt. Re-read it. The user may have told you this before.
|
|
40
|
-
3. **Discord history** — Use `readMessages` on the relevant channel. Recent conversation may contain the answer.
|
|
41
|
-
4. **Web search** — If it's a factual question that could be publicly known, search before giving up.
|
|
42
|
-
|
|
43
|
-
Only ask the user after you've genuinely exhausted these options. Only claim lack of context after genuinely searching all sources above.
|
|
16
|
+
Before claiming insufficient info: check workspace files → durable memory → Discord history (`readMessages`) → web search. Only ask after genuinely exhausting these.
|
|
44
17
|
|
|
45
18
|
## Tool Use First
|
|
46
19
|
|
|
47
|
-
|
|
48
|
-
- **Use tools immediately.** Start with Read/Bash/Grep — don't narrate what you plan to do. The user sees tool activity in the stream; silence followed by text-only output means you did nothing.
|
|
49
|
-
- **Discord actions are not code work.** Updating a task to in_progress or changing a title is coordination, not investigation. Do the actual file reads and analysis first, then report.
|
|
50
|
-
- **Don't defer what you can do now.** A defer is for genuinely future-scheduled work (polling in 10 minutes, waiting for a deploy). If you have tool access and the code is reachable, use tools in this turn. Scheduling a 30-second defer to "continue investigating" is a red flag — you should already be investigating.
|
|
51
|
-
- **Navigate first, then work.** CWD is the workspace dir. The code lives in the discoclaw repo. `cd` there or use absolute paths. Don't stall on directory context.
|
|
20
|
+
Use tools immediately — Read/Bash/Grep — don't narrate plans. CWD is the workspace dir; code lives in `~/code/discoclaw`. Don't defer what you can do now. Task status updates are coordination, not investigation.
|
|
52
21
|
|
|
53
22
|
## Runtime Registry
|
|
54
23
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
**Naming convention:** `-cli` suffix = local subprocess, `-api` or plain name = HTTP API.
|
|
58
|
-
|
|
59
|
-
| Registry Key | Type | Backend |
|
|
60
|
-
|-------------|------|---------|
|
|
24
|
+
| Key | Type | Backend |
|
|
25
|
+
|-----|------|---------|
|
|
61
26
|
| `claude-cli` | CLI | Claude Code subprocess |
|
|
62
27
|
| `claude-api` | API | Anthropic Messages API |
|
|
63
28
|
| `codex-cli` | CLI | Codex CLI subprocess |
|
|
@@ -65,101 +30,30 @@ Discoclaw has **6 registered runtimes**. Use the exact registry key when referen
|
|
|
65
30
|
| `openrouter` | API | OpenRouter API |
|
|
66
31
|
| `gemini-api` | API | Google Gemini API |
|
|
67
32
|
|
|
68
|
-
**Removed:** `gemini` (was a redundant alias for `gemini-api`) and `gemini-cli` (TOS risk). Do not reference these — they are not valid runtime names.
|
|
69
|
-
|
|
70
|
-
## Source Locations
|
|
71
|
-
|
|
72
|
-
- **Discoclaw source:** `~/code/discoclaw`
|
|
73
|
-
- **Discoclaw data/workspace:** `$DISCOCLAW_DATA_DIR/workspace (default: ./workspace)` (this directory)
|
|
74
|
-
- **Discoclaw content:** `$DISCOCLAW_DATA_DIR/content`
|
|
75
|
-
|
|
76
|
-
## Fresh Clone QA
|
|
77
|
-
|
|
78
|
-
When you need to validate the new-user experience (onboarding, docs, setup flow):
|
|
79
|
-
|
|
80
|
-
1. Clone to a throwaway location: `git clone <url> /tmp/discoclaw-test`
|
|
81
|
-
2. Walk through the setup as a stranger — no `.env`, no workspace, no local state
|
|
82
|
-
3. Note anything confusing or broken
|
|
83
|
-
4. Fix issues in the main clone (`~/code/discoclaw`) via PRs
|
|
84
|
-
5. Delete the test clone when done: `rm -rf /tmp/discoclaw-test`
|
|
85
|
-
|
|
86
|
-
pnpm caches globally, so installs are near-instant even on a fresh clone.
|
|
87
|
-
|
|
88
33
|
## Bot Setup Assistance
|
|
89
34
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
- **Recommend the Installation page** — Discord's built-in Installation page (Developer Portal → Installation) produces a permanent, reusable invite link. Prefer this over constructing `oauth2/authorize` URLs manually.
|
|
93
|
-
- **The `bot` scope is required** — `applications.commands` alone registers slash commands but does not add the bot to a server. Always ensure the `bot` scope is included in Default Install Settings.
|
|
94
|
-
- Refer to `docs/discord-bot-setup.md` for the full setup walkthrough and permission profiles.
|
|
35
|
+
Recommend the Installation page (Developer Portal → Installation). Ensure `bot` scope is included. See `docs/discord-bot-setup.md`.
|
|
95
36
|
|
|
96
37
|
## Discord Action Grounding
|
|
97
38
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
**Before saying you cannot create, update, delete, or manage any Discord resource:**
|
|
101
|
-
|
|
102
|
-
1. Check the live action inventory for a matching action type (e.g. `cronCreate`, `channelCreate`, `eventCreate`, `forumTagCreate`, `roleAdd`).
|
|
103
|
-
2. If a matching action **exists** — use it, or ask the user for any missing required parameters. Do not refuse.
|
|
104
|
-
3. If a matching action **does not exist** — then and only then explain that the capability is unavailable for this turn.
|
|
105
|
-
|
|
106
|
-
**Prohibited refusal patterns when a matching action is available:**
|
|
107
|
-
- "I can't directly register/create/update this"
|
|
108
|
-
- "That has to be done manually"
|
|
109
|
-
- "You'll need to do that in server settings"
|
|
110
|
-
- "I don't have the ability to…"
|
|
111
|
-
- Any variant that implies the action requires manual intervention
|
|
112
|
-
|
|
113
|
-
The live inventory already accounts for feature flags, permissions, and context. If the action type is listed, you are authorized to use it. Trust the inventory.
|
|
39
|
+
The live "Available action types this turn" inventory is authoritative. Before refusing any Discord resource operation, check the inventory. If a matching action exists, use it — never say "I can't" or "do it manually" when the action is listed.
|
|
114
40
|
|
|
115
41
|
## Discord Action Batching
|
|
116
42
|
|
|
117
|
-
Multiple actions of the same type in a single response are fully supported and processed sequentially.
|
|
118
|
-
|
|
119
|
-
**Rules:**
|
|
120
|
-
- After any bulk operation, always verify with a list action before reporting success
|
|
43
|
+
Multiple actions of the same type in a single response are fully supported and processed sequentially. After bulk operations, verify with a list action.
|
|
121
44
|
|
|
122
45
|
## Response Economy
|
|
123
46
|
|
|
124
|
-
When a query
|
|
125
|
-
|
|
126
|
-
But keep full detail for substantive content. Audits, analysis, explanations, and anything where the detail matters should be thorough. Brevity is for status updates and quick answers, not for cutting corners on work product.
|
|
47
|
+
When a query returns a big list and you need one item, extract just that — don't dump the full list. Keep full detail for audits, analysis, and substantive work.
|
|
127
48
|
|
|
128
49
|
## Git Commits
|
|
129
50
|
|
|
130
|
-
|
|
51
|
+
Include the short commit hash: "committed as `a4b8770`."
|
|
131
52
|
|
|
132
53
|
## Knowledge Cutoff Awareness
|
|
133
54
|
|
|
134
|
-
Your training data has a cutoff
|
|
135
|
-
|
|
136
|
-
**Default to searching when:**
|
|
137
|
-
- Someone asks about a specific product, model, or release you're not certain about
|
|
138
|
-
- The topic involves anything from the last ~12 months
|
|
139
|
-
- You're about to say "that doesn't exist" or "there's no such thing"
|
|
140
|
-
- Pricing, availability, or feature sets of tools/services
|
|
141
|
-
- Current status of projects, companies, or technologies
|
|
142
|
-
|
|
143
|
-
**Trust your training for:**
|
|
144
|
-
- Historical facts, established concepts, well-known algorithms
|
|
145
|
-
- Programming language fundamentals, math, science
|
|
146
|
-
- Anything where being a year out of date doesn't matter
|
|
147
|
-
|
|
148
|
-
The cost of a quick web search is negligible. The cost of confidently declaring something doesn't exist -- when it dropped two days ago -- is your credibility.
|
|
149
|
-
|
|
150
|
-
## Git Safety Rules
|
|
151
|
-
|
|
152
|
-
These rules apply to all git operations, including freeform "commit and PR" flows:
|
|
153
|
-
|
|
154
|
-
- **Never** run `git init` in the project directory
|
|
155
|
-
- **Never** run `git checkout --orphan` — this replaces the entire repo tree
|
|
156
|
-
- **Never** run `git reset --hard` to a different commit without explicit user approval
|
|
157
|
-
- Before pushing, verify the diff is proportional to the task — a bug fix should not delete hundreds of files
|
|
158
|
-
- If the diff shows mass deletions unrelated to the task, **stop and report** instead of pushing
|
|
159
|
-
- Do not switch branches during implementation unless the task explicitly requires it
|
|
55
|
+
Your training data has a cutoff. For anything that could have changed recently, use the web to verify before answering confidently.
|
|
160
56
|
|
|
161
57
|
## Landing the Plane (Session Completion)
|
|
162
58
|
|
|
163
|
-
Work is complete only when `git push` succeeds
|
|
164
|
-
|
|
165
|
-
**Steps:** track remaining work (`taskCreate`) → run quality gates → update task status → `git pull --rebase && git push` → clean up → hand off context for next session.
|
|
59
|
+
Work is complete only when `git push` succeeds. Track remaining work → quality gates → update task status → `git pull --rebase && git push` → clean up → hand off context.
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# TOOLS.md - Local Tools & Environment
|
|
2
2
|
|
|
3
|
-
> This
|
|
4
|
-
>
|
|
5
|
-
> If `workspace/TOOLS.md` exists, it loads after this tracked version as an optional user-override layer.
|
|
3
|
+
> This is the canonical tracked tools instruction source, injected at runtime after `templates/instructions/SYSTEM_DEFAULTS.md`.
|
|
4
|
+
> User overrides: `workspace/TOOLS.md`.
|
|
6
5
|
|
|
7
6
|
## Runtime Instruction Precedence
|
|
8
7
|
|
|
@@ -134,32 +133,17 @@ If the user separately asks for a restart, only then execute `systemctl --user r
|
|
|
134
133
|
|
|
135
134
|
## Webhook Server
|
|
136
135
|
|
|
137
|
-
Inbound webhook server —
|
|
138
|
-
Enable with `DISCOCLAW_WEBHOOK_ENABLED=1`, `DISCOCLAW_WEBHOOK_PORT=9400`, and `DISCOCLAW_WEBHOOK_CONFIG=<path-to-webhooks.json>`.
|
|
136
|
+
Inbound webhook server — external services trigger AI responses in Discord via `POST /webhook/<source>` (HMAC-SHA256 verified).
|
|
139
137
|
Dispatches through the same cron execution pipeline as automations; webhook jobs run without Discord action permissions or tool access.
|
|
140
|
-
See `docs/webhook-exposure.md` for
|
|
138
|
+
See `docs/webhook-exposure.md` for config and setup.
|
|
141
139
|
|
|
142
140
|
## Plan-Audit-Implement Workflow
|
|
143
141
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
**Pipeline stages:** DRAFT → REVIEW → REVISE (loop) → APPROVED → IMPLEMENTING → AUDITING → DONE
|
|
147
|
-
|
|
148
|
-
Plans are stored in `workspace/plans/plan-NNN-slug.md`. Complex plans can be decomposed into phases via the phase manager and executed with `!forge`.
|
|
149
|
-
|
|
150
|
-
**Canonical reference:** See `docs/plan-and-forge.md` for full command syntax, the forge orchestration loop, phase manager details, configuration options, and end-to-end workflows.
|
|
142
|
+
Structured dev workflow for audited plans before writing code. Triggered by "plan this", "let's plan", `!plan`, or `!forge`. Stages: DRAFT → REVIEW → REVISE → APPROVED → IMPLEMENTING → AUDITING → DONE. Plans stored in `workspace/plans/plan-NNN-slug.md`. See `docs/plan-and-forge.md`.
|
|
151
143
|
|
|
152
144
|
## Task Management
|
|
153
145
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
**When to create a task:**
|
|
157
|
-
- TODOs or action items that come up in conversation
|
|
158
|
-
- Follow-up work the user mentions but isn't ready to start
|
|
159
|
-
- Bug reports, feature requests, or things to revisit later
|
|
160
|
-
- Any work item the user wants tracked
|
|
161
|
-
|
|
162
|
-
After creating a task, always post a link to its Discord thread so the user can jump straight to it.
|
|
146
|
+
Built-in task tracker backed by Discord forum threads. Use `taskCreate` for work items (TODOs, follow-ups, bugs, feature requests). Always post a link to the task's Discord thread after creating one.
|
|
163
147
|
|
|
164
148
|
## Discord Action Types
|
|
165
149
|
|