clideck 1.31.16 → 1.31.18

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/sessions.js CHANGED
@@ -7,12 +7,14 @@ const activity = require('./activity');
7
7
  const transcript = require('./transcript');
8
8
  const telemetry = require('./telemetry-receiver');
9
9
  const opencodeBridge = require('./opencode-bridge');
10
+ const piBridge = require('./pi-bridge');
10
11
  const plugins = require('./plugin-loader');
12
+ const { presetForCommand } = require('./preset-utils');
13
+ const { stripAnsi } = require('./ansi-utils');
11
14
 
12
15
  const THEMES = require('./themes');
13
16
  const MAX_BUFFER = 2 * 1024 * 1024;
14
17
  const { PORT, localUrl } = require('./runtime');
15
- const ANSI_RE = /\x1b\[[0-9;?]*[ -/]*[@-~]|\x1b\].*?(?:\x07|\x1b\\)|\x1b./g;
16
18
  const PRESETS = JSON.parse(require('fs').readFileSync(join(__dirname, 'agent-presets.json'), 'utf8'));
17
19
  for (const p of PRESETS) if (p.presetId === 'shell') p.command = defaultShell;
18
20
  const { DATA_DIR } = require('./paths');
@@ -37,6 +39,9 @@ function broadcast(msg) {
37
39
  const raw = JSON.stringify(msg);
38
40
  for (const c of clients) if (c.readyState === 1) c.send(raw);
39
41
  if (msg.type === 'session.status') {
42
+ // Status broadcasts currently also apply the local state transition. This is
43
+ // intentional for now but couples transport with session state; if this area
44
+ // changes, split it into a dedicated setSessionStatus() transition first.
40
45
  const s = sessions.get(msg.id);
41
46
  if (s) {
42
47
  s.working = !!msg.working;
@@ -59,14 +64,7 @@ function broadcast(msg) {
59
64
 
60
65
  // --- Spawn a PTY and wire up a session ---
61
66
 
62
- function presetForCommand(cmd) {
63
- if (cmd?.presetId) {
64
- const preset = PRESETS.find(p => p.presetId === cmd.presetId);
65
- if (preset) return preset;
66
- }
67
- const bin = binName(cmd?.command);
68
- return PRESETS.find(p => binName(p.command) === bin);
69
- }
67
+ function matchPreset(cmd) { return presetForCommand(cmd, PRESETS); }
70
68
 
71
69
  function commandEnv(cmd) {
72
70
  const env = {};
@@ -78,7 +76,7 @@ function commandEnv(cmd) {
78
76
  }
79
77
 
80
78
  function buildTelemetryEnv(id, cmd) {
81
- const preset = presetForCommand(cmd);
79
+ const preset = matchPreset(cmd);
82
80
  const telemetryEnabled = cmd.telemetryEnabled ?? (preset?.presetId === 'claude-code');
83
81
  const env = { CLIDECK_SESSION_ID: id, CLIDECK_PORT: String(PORT), CLIDECK_URL: localUrl() };
84
82
  if (!preset?.telemetryEnv || !telemetryEnabled) return env;
@@ -114,10 +112,10 @@ function spawnSession(id, cmd, parts, cwd, name, themeId, commandId, savedToken,
114
112
  }
115
113
 
116
114
  const sessionIdRe = cmd.sessionIdPattern ? new RegExp(cmd.sessionIdPattern, 'i') : null;
117
- const preset = presetForCommand(cmd);
115
+ const preset = matchPreset(cmd);
118
116
  const session = { name, themeId, commandId, cwd, pty: term, chunks: [], chunksSize: 0, sessionToken: savedToken || null, projectId: projectId || null, presetId: preset?.presetId || 'shell', working: undefined };
119
117
  sessions.set(id, session);
120
- transcript.setFinalizeOnIdle(id, ['claude-code', 'codex', 'gemini-cli', 'opencode', 'clideck-agent'].includes(session.presetId) ? session.presetId : null);
118
+ transcript.setFinalizeOnIdle(id, ['claude-code', 'codex', 'gemini-cli', 'opencode', 'pi', 'clideck-agent'].includes(session.presetId) ? session.presetId : null);
121
119
 
122
120
  // Always watch telemetry-backed agents so OTLP fallback matching can attach
123
121
  // early events to this session even when the agent omits clideck.session_id.
@@ -134,7 +132,7 @@ function spawnSession(id, cmd, parts, cwd, name, themeId, commandId, savedToken,
134
132
  // Capture session ID from output
135
133
  if (sessionIdRe && !session.sessionToken) {
136
134
  const joined = session.chunks.join('');
137
- const match = joined.match(sessionIdRe) || joined.replace(ANSI_RE, '').match(sessionIdRe);
135
+ const match = joined.match(sessionIdRe) || stripAnsi(joined).match(sessionIdRe);
138
136
  if (match) {
139
137
  session.sessionToken = match[1];
140
138
  console.log(`Session ${id.slice(0, 8)}: captured token via output regex: ${match[1].slice(0, 12)}…`);
@@ -153,6 +151,7 @@ function spawnSession(id, cmd, parts, cwd, name, themeId, commandId, savedToken,
153
151
  activity.clear(id);
154
152
  telemetry.clear(id);
155
153
  opencodeBridge.clear(id);
154
+ piBridge.clear(id);
156
155
  plugins.clearStatus(id);
157
156
  // If resumable and token captured, move to resumable list (keep transcript for search)
158
157
  if (!s.ephemeral && cmd.canResume && cmd.resumeCommand && s.sessionToken) {
@@ -196,12 +195,12 @@ function create(msg, ws, cfg) {
196
195
  return;
197
196
  }
198
197
 
199
- const createdPresetId = presetForCommand(cmd)?.presetId || 'shell';
198
+ const createdPresetId = matchPreset(cmd)?.presetId || 'shell';
200
199
  const installId = msg.installId || undefined;
201
200
  broadcast({ type: 'created', id, name, themeId, commandId: cmd.id, presetId: createdPresetId, projectId, installId });
202
201
 
203
202
  // Immediate setup notification if config not detected
204
- const preset = presetForCommand(cmd);
203
+ const preset = matchPreset(cmd);
205
204
  if (preset && (preset.telemetrySetup || preset.bridge) && !(cmd.telemetryEnabled && cmd.telemetryStatus?.ok)) {
206
205
  broadcast({ type: 'session.needsSetup', id });
207
206
  }
@@ -228,7 +227,7 @@ function createProgrammatic(opts, cfg) {
228
227
  const s = sessions.get(id);
229
228
  if (s && opts.ephemeral) s.ephemeral = true;
230
229
 
231
- const presetId = presetForCommand(cmd)?.presetId || 'shell';
230
+ const presetId = matchPreset(cmd)?.presetId || 'shell';
232
231
  broadcast({ type: 'created', id, name, themeId, commandId: cmd.id, presetId, projectId });
233
232
  return { id };
234
233
  }
@@ -278,7 +277,7 @@ function resume(msg, ws, cfg) {
278
277
  resumable = resumable.filter(s => s.id !== id);
279
278
  broadcast({ type: 'sessions.resumable', list: getResumable(cfg) });
280
279
 
281
- const resumePresetId = presetForCommand(cmd)?.presetId || saved.presetId || 'shell';
280
+ const resumePresetId = matchPreset(cmd)?.presetId || saved.presetId || 'shell';
282
281
  broadcast({ type: 'created', id, name: saved.name, themeId: saved.themeId || saved.profileId || 'default', commandId: saved.commandId, presetId: resumePresetId, projectId: saved.projectId || null, muted: !!saved.muted, resumed: true, lastPreview: saved.lastPreview || '' });
283
282
  }
284
283
 
@@ -305,9 +304,13 @@ function input(msg) {
305
304
  // manual flows.
306
305
  if (!plugins.shouldAutoApproveMenu(msg.id)) s._resolvedMenuKey = s._menuKey;
307
306
  if (s._menuActiveVersion) s._menuConsumedVersion = s._menuActiveVersion;
307
+ const menuStartsWork = s._menuStartsWork !== false;
308
308
  s._menuKey = '';
309
+ s._menuStartsWork = undefined;
309
310
  broadcast({ type: 'session.menu', id: msg.id, choices: [] });
310
- broadcast({ type: 'session.status', id: msg.id, working: true, source: 'menu-input' });
311
+ if (menuStartsWork) {
312
+ broadcast({ type: 'session.status', id: msg.id, working: true, source: 'menu-input' });
313
+ }
311
314
  return;
312
315
  }
313
316
  writeSessionInput(msg.id, data);
@@ -337,7 +340,7 @@ function setMute(id, muted) {
337
340
 
338
341
  function close(msg, cfg) {
339
342
  const s = sessions.get(msg.id);
340
- if (s) { s.pty.kill(); telemetry.clear(msg.id); transcript.clear(msg.id); plugins.clearStatus(msg.id); sessions.delete(msg.id); broadcast({ type: 'closed', id: msg.id }); }
343
+ if (s) { s.pty.kill(); telemetry.clear(msg.id); opencodeBridge.clear(msg.id); piBridge.clear(msg.id); transcript.clear(msg.id); plugins.clearStatus(msg.id); sessions.delete(msg.id); broadcast({ type: 'closed', id: msg.id }); }
341
344
  // Also remove from resumable list if present
342
345
  const before = resumable.length;
343
346
  resumable = resumable.filter(r => r.id !== msg.id);
@@ -370,6 +373,7 @@ function restart(msg, ws, cfg) {
370
373
  activity.clear(id);
371
374
  telemetry.clear(id);
372
375
  opencodeBridge.clear(id);
376
+ piBridge.clear(id);
373
377
  transcript.clear(id);
374
378
 
375
379
  s.pty.kill();
@@ -395,6 +399,7 @@ function restart(msg, ws, cfg) {
395
399
  function list() {
396
400
  return [...sessions].map(([id, s]) => ({
397
401
  id, name: s.name, themeId: s.themeId, commandId: s.commandId, presetId: s.presetId || 'shell', projectId: s.projectId, muted: !!s.muted,
402
+ working: !!s.working,
398
403
  // Last preview text for sidebar display on reconnect
399
404
  lastPreview: s.lastPreview || '', lastActivityAt: s.lastActivityAt || null,
400
405
  menu: s._menuKey ? JSON.parse(s._menuKey) : undefined,
@@ -422,7 +427,7 @@ function getResumable(cfg) {
422
427
  if (s.presetId) return s;
423
428
  const cmd = (cfg.commands || []).find(c => c.id === s.commandId);
424
429
  if (!cmd) return { ...s, presetId: 'shell' };
425
- const preset = presetForCommand(cmd);
430
+ const preset = matchPreset(cmd);
426
431
  return { ...s, presetId: preset?.presetId || 'shell' };
427
432
  });
428
433
  }
@@ -434,7 +439,7 @@ function sendBuffers(ws) {
434
439
  ws.send(JSON.stringify({ type: 'output', id, data, replay: true }));
435
440
  continue;
436
441
  }
437
- if (['claude-code', 'codex', 'gemini-cli', 'opencode', 'clideck-agent'].includes(s.presetId) && !s.working) {
442
+ if (['claude-code', 'codex', 'gemini-cli', 'opencode', 'pi', 'clideck-agent'].includes(s.presetId) && !s.working) {
438
443
  const text = transcript.getReplayText(id, s.presetId);
439
444
  if (text) {
440
445
  ws.send(JSON.stringify({ type: 'session.history', id, text, replay: true }));
@@ -2,7 +2,8 @@
2
2
  // CLI agents export telemetry events here; we capture agent session IDs
3
3
  // (for resume) and detect whether telemetry is configured (setup prompts).
4
4
 
5
- const ioActivity = require('./activity');
5
+ const { updateClaudeSessionToken } = require('./claude-session');
6
+
6
7
  const activity = new Map(); // sessionId → has received events
7
8
  const lastEvent = new Map(); // sessionId → last OTEL event name (+ kind)
8
9
  const pendingSetup = new Map(); // sessionId → timer (waiting for first event)
@@ -10,21 +11,14 @@ const codexMenuPoll = new Map(); // sessionId → interval (polling for menu aft
10
11
  const codexPendingStop = new Map(); // sessionId → ts (notify hook arrived; wait for next response.completed)
11
12
  const codexOutputDone = new Map(); // sessionId → ts (fallback if notify never fires)
12
13
  const codexPendingIdle = new Map(); // sessionId → timer (tiny settle before committing idle)
14
+ // Codex does not currently expose one reliable "fully idle" signal across streamed
15
+ // output, tool calls, and approval menus. This state machine is the best available
16
+ // approach for now; do not add more timing patches without fixture tests from real
17
+ // Codex event sequences.
13
18
  const codexToolPhasePending = new Set(); // sessionId set once Codex has announced a tool-call phase, cleared when the phase resolves
14
19
  const codexPendingTools = new Map(); // sessionId → Set(callId) for approved Codex tool calls still awaiting a result
15
20
  let broadcastFn = null;
16
21
  let sessionsFn = null;
17
- const CLAUDE_SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
18
-
19
- function updateClaudeSessionToken(sess, token, clideckId) {
20
- const next = String(token || '').trim();
21
- if (!sess || sess.presetId !== 'claude-code' || !CLAUDE_SESSION_ID_RE.test(next)) return false;
22
- if (sess.sessionToken === next) return false;
23
- const prev = sess.sessionToken;
24
- sess.sessionToken = next;
25
- console.log(`Telemetry: updated Claude session ID for ${clideckId.slice(0, 8)}: ${prev ? prev.slice(0, 12) + '... -> ' : ''}${next.slice(0, 12)}...`);
26
- return true;
27
- }
28
22
 
29
23
  function getPendingToolSet(id) {
30
24
  let set = codexPendingTools.get(id);
@@ -218,7 +212,7 @@ function handleLogs(req, res) {
218
212
  : (attrs['session.id'] || attrs['conversation.id']);
219
213
  if (agentSessionId && sess) {
220
214
  if (serviceName === 'claude-code') {
221
- updateClaudeSessionToken(sess, agentSessionId, resolvedId);
215
+ updateClaudeSessionToken(sess, agentSessionId, resolvedId, { label: 'Telemetry' });
222
216
  continue;
223
217
  }
224
218
  // Prefer interactive session ID (Gemini sends non-interactive init events first)
@@ -289,14 +283,6 @@ function markCodexStart(id, source = 'hook') {
289
283
  broadcastFn?.({ type: 'session.status', id, working: true, source });
290
284
  }
291
285
 
292
- function markCodexIdle(id, source = 'hook') {
293
- codexPendingStop.delete(id);
294
- codexOutputDone.delete(id);
295
- codexToolPhasePending.delete(id);
296
- clearPendingTools(id);
297
- scheduleCodexIdle(id, source);
298
- }
299
-
300
286
  function scheduleCodexIdle(id, source) {
301
287
  cancelCodexPendingIdle(id);
302
288
  const timer = setTimeout(() => {
@@ -326,9 +312,4 @@ function clear(id) {
326
312
 
327
313
  function getLastEvent(id) { return lastEvent.get(id) || ''; }
328
314
 
329
- // Returns true if we've received telemetry events for this session
330
- function hasEvents(id) {
331
- return activity.has(id);
332
- }
333
-
334
- module.exports = { init, handleLogs, clear, hasEvents, getLastEvent, cancelCodexMenuPoll, watchSession, armCodexStop, markCodexStart, markCodexIdle };
315
+ module.exports = { init, handleLogs, clear, getLastEvent, cancelCodexMenuPoll, watchSession, armCodexStop, markCodexStart };
package/transcript.js CHANGED
@@ -4,9 +4,9 @@ const { DATA_DIR } = require('./paths');
4
4
  const builder = require('./transcript-normalizer');
5
5
  const parser = require('./transcript-parser');
6
6
  const candidate = require('./transcript-candidate');
7
+ const { stripAnsi } = require('./ansi-utils');
7
8
 
8
9
  const DIR = join(DATA_DIR, 'transcripts');
9
- const ANSI_RE = /\x1b[\[\]()#;?]*[0-9;]*[a-zA-Z@`~]|\x1b\].*?(?:\x07|\x1b\\)|\x1b.|\r|\x07/g;
10
10
  const MAX_CACHE = 50 * 1024;
11
11
  const LEGACY_SUFFIXES = ['-parsed.jsonl', '.screen'];
12
12
 
@@ -153,7 +153,7 @@ function trackOutput(id, data) {
153
153
  function flush(id) {
154
154
  const buf = outputBuf[id];
155
155
  if (!buf?.text) return;
156
- const clean = buf.text.replace(ANSI_RE, '').replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '');
156
+ const clean = stripAnsi(buf.text).replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '');
157
157
  const lines = clean.split('\n').map(l => l.trim()).filter(l => l.length > 2);
158
158
  buf.text = '';
159
159
  if (lines.length) store(id, 'agent', lines.join('\n'));
@@ -238,6 +238,7 @@ function getReplayText(id, presetId) {
238
238
  codex: { user: '›', agent: '•' },
239
239
  'gemini-cli': { user: '>', agent: '✦' },
240
240
  opencode: { user: '›', agent: '•' },
241
+ pi: { user: '›', agent: '•' },
241
242
  }[presetId] || { user: '›', agent: '•' };
242
243
  return entries.map(e => `${e.role === 'user' ? marks.user : marks.agent} ${e.text}`).join('\n\n');
243
244
  }
@@ -1,211 +0,0 @@
1
- ---
2
- name: autonomous-session
3
- description: Run one agent session autonomously until a clearly defined goal is achieved or truly blocked. Use when the user wants the agent to keep working without repeated check-ins, across coding, research, writing, analysis, marketing, planning, or other general session work.
4
- ---
5
-
6
- # Autonomous Session
7
-
8
- Use this skill when the user wants one agent session to keep working toward a goal without stopping for routine confirmation.
9
-
10
- This skill is general-purpose. It is not limited to coding. It applies to research, writing, planning, analysis, marketing, documentation, and other session-based work.
11
-
12
- ## Core Contract
13
-
14
- You are not waiting for permission after every step.
15
-
16
- - Understand the goal precisely.
17
- - Keep working until the goal is achieved or truly blocked.
18
- - Do not stop just to ask whether you should continue.
19
- - Do not produce filler progress updates instead of real work.
20
- - Do not redefine the goal casually once work has started.
21
-
22
- ## First Check: Can This Session Actually Run Autonomously?
23
-
24
- Before doing substantial work, verify that the session can proceed without routine approval interruptions.
25
-
26
- If the environment clearly requires recurring user approval for normal work, stop immediately and tell the user, in plain language, that autonomous mode cannot work until approvals are removed or auto-approval is enabled.
27
-
28
- Do not try to fake autonomy in a session that will keep pausing for user approval.
29
-
30
- ## Goal Setup
31
-
32
- Before starting the main work, convert the user request into a concrete internal brief.
33
-
34
- Your internal brief must include:
35
-
36
- - Goal: what outcome must exist at the end.
37
- - Constraints: what must not happen.
38
- - Definition of done: how you will recognize completion.
39
- - Open questions: only the questions that would materially change the work.
40
-
41
- If the goal is already clear enough, do not ask questions. Start working.
42
-
43
- If the goal is not clear enough to work effectively, ask a small batch of targeted clarifying questions first. Ask only what is necessary to remove ambiguity. Do not interrogate the user unnecessarily.
44
-
45
- Examples of when to ask questions first:
46
-
47
- - The user wants research but has not said what decision the research is meant to support.
48
- - The user wants writing but has not said audience, tone, or deliverable type.
49
- - The user wants implementation but has not said what behavior should change.
50
- - The user wants “improve this” but there is no clear success condition.
51
-
52
- Examples of when not to ask questions first:
53
-
54
- - The user already gave a concrete task, constraints, and acceptance bar.
55
- - You can infer a safe, reasonable working interpretation without risking the wrong output.
56
-
57
- ## Goal Memory
58
-
59
- If the task involves a real project or working directory, create and maintain a short session memory file early in the process.
60
-
61
- Preferred filename:
62
-
63
- ```text
64
- autonomous-session.md
65
- ```
66
-
67
- Keep it concise and practical. It should contain:
68
-
69
- - Goal
70
- - Constraints
71
- - Definition of done
72
- - Key decisions
73
- - What has been completed
74
- - Remaining gaps
75
-
76
- If a different filename is more natural in the project, use that instead.
77
-
78
- If there is no meaningful project directory or file-based workflow, maintain the same structure mentally and in your session responses instead of forcing a file.
79
-
80
- Update this memory whenever the goal changes materially or when major decisions are made. Keep it short. It is a working anchor, not a diary.
81
-
82
- ## Operating Rules
83
-
84
- ### 1. Work Forward
85
-
86
- After the goal is clear, move directly into execution.
87
-
88
- - Prefer real progress over commentary.
89
- - Prefer concrete artifacts over abstract planning.
90
- - Prefer finishing a meaningful chunk before reporting back.
91
-
92
- ### 2. Do Not Stop Early
93
-
94
- Do not ask:
95
-
96
- - “Should I continue?”
97
- - “Do you want me to keep going?”
98
- - “Is this enough for now?”
99
-
100
- The default is to continue until done or blocked.
101
-
102
- ### 3. Use Judgment, Not Blind Persistence
103
-
104
- Autonomy does not mean mindless looping.
105
-
106
- If an approach is not working:
107
-
108
- - reassess,
109
- - read the available context again,
110
- - change tactics,
111
- - keep moving.
112
-
113
- Do not repeat the same failing pattern without learning from it.
114
-
115
- ### 4. Respect the Actual Task
116
-
117
- Do not turn every task into implementation work.
118
-
119
- - For research tasks, gather, compare, synthesize, and conclude.
120
- - For writing tasks, produce and refine the requested deliverable.
121
- - For planning tasks, produce the plan and then, if appropriate, execute it.
122
- - For coding tasks, inspect, implement, verify, and refine.
123
-
124
- Stay aligned with the actual requested outcome.
125
-
126
- ### 5. Completion Must Be Real
127
-
128
- Only stop when one of these is true:
129
-
130
- - The goal is achieved.
131
- - The remaining blocker genuinely requires user input, approval, credentials, or a missing external dependency.
132
- - The request is fundamentally ambiguous and cannot be resolved safely without clarification.
133
-
134
- When stopping, say which of those is true.
135
-
136
- ## Blockers
137
-
138
- A blocker is real only if you cannot reasonably move forward without the user.
139
-
140
- Examples of real blockers:
141
-
142
- - Missing credentials, API keys, access, or required permissions.
143
- - A strategic choice the user must make because it changes the deliverable substantially.
144
- - Multiple valid directions remain and choosing one would be risky or expensive without user preference.
145
- - The environment prevents autonomous execution through approval prompts or missing capabilities.
146
-
147
- Examples of non-blockers:
148
-
149
- - You are unsure which tactic is best.
150
- - The first attempt failed.
151
- - More reading is needed.
152
- - The work is tedious.
153
- - You can make a reasonable assumption and continue.
154
-
155
- ## Interruptions And Mid-Task User Messages
156
-
157
- If the user sends a new message during the work:
158
-
159
- - incorporate it,
160
- - update the goal brief if needed,
161
- - continue from the new state.
162
-
163
- Do not stubbornly follow an outdated plan when the user has clarified or redirected the work.
164
-
165
- If the new message changes the goal materially, update the session memory file.
166
-
167
- ## Reporting Style
168
-
169
- Use sparse, meaningful updates.
170
-
171
- Good updates:
172
-
173
- - major completed milestone,
174
- - important decision,
175
- - real blocker,
176
- - final completion summary.
177
-
178
- Bad updates:
179
-
180
- - empty status chatter,
181
- - repeated reassurance,
182
- - asking for permission to continue.
183
-
184
- ## Completion Message
185
-
186
- When the goal is done, report:
187
-
188
- - what was accomplished,
189
- - any important decisions or tradeoffs,
190
- - any residual risks or follow-up items.
191
-
192
- Keep it concise and outcome-focused.
193
-
194
- ## Behavior Summary
195
-
196
- If this skill is active, your default behavior is:
197
-
198
- 1. Check whether autonomous execution is actually possible.
199
- 2. Clarify the goal only if necessary.
200
- 3. Create a short working brief or memory file when appropriate.
201
- 4. Work continuously toward the goal.
202
- 5. Adapt when an approach fails.
203
- 6. Stop only when done or truly blocked.
204
-
205
- ## Hard Rules
206
-
207
- - Do not ask “should I continue?”
208
- - Do not stop because of ordinary uncertainty.
209
- - Do not continue blindly when the goal is unclear.
210
- - Do not pretend a blocker exists when you can still make progress.
211
- - Do not treat a status update as completion.