cclaw-cli 0.5.8 → 0.5.9

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.
@@ -118,6 +118,22 @@ function tokensFromRule(rule) {
118
118
  }
119
119
  return [];
120
120
  }
121
+ /**
122
+ * Extract required keywords from validation rules that contain comma-separated
123
+ * concept lists. Activates only for rules with structured enumerations like
124
+ * "failure modes, error surface, data-flow paths" — not for short rules.
125
+ */
126
+ function extractRequiredKeywords(rule) {
127
+ const colonMatch = /:\s*(.+)$/u.exec(rule);
128
+ if (!colonMatch)
129
+ return [];
130
+ const tail = colonMatch[1];
131
+ const parts = tail.split(/,\s*(?:and\s+)?/u).map((p) => p.trim().replace(/\.$/u, ""));
132
+ const phrases = parts.filter((p) => p.length >= 4 && !/^(must|should|at least|if |or )/iu.test(p));
133
+ if (phrases.length < 3)
134
+ return [];
135
+ return phrases;
136
+ }
121
137
  function validateSectionBody(sectionBody, rule) {
122
138
  const bodyLines = sectionBody.split(/\r?\n/).map((line) => line.trim());
123
139
  const meaningful = meaningfulLineCount(sectionBody);
@@ -185,6 +201,19 @@ function validateSectionBody(sectionBody, rule) {
185
201
  }
186
202
  }
187
203
  }
204
+ const keywords = extractRequiredKeywords(rule);
205
+ if (keywords.length > 0) {
206
+ const bodyLower = sectionBody.toLowerCase();
207
+ const found = keywords.filter((kw) => bodyLower.includes(kw.toLowerCase()));
208
+ const threshold = Math.ceil(keywords.length * 0.5);
209
+ if (found.length < threshold) {
210
+ const missing = keywords.filter((kw) => !bodyLower.includes(kw.toLowerCase()));
211
+ return {
212
+ ok: false,
213
+ details: `Rule expects keywords (${threshold}/${keywords.length} minimum): missing ${missing.join(", ")}.`
214
+ };
215
+ }
216
+ }
188
217
  return {
189
218
  ok: true,
190
219
  details: "Section heading and content satisfy lint heuristics."
@@ -73,6 +73,14 @@ The original premise (“add notifications”) was reframed to **“ensure users
73
73
  | **12-MONTH IDEAL** | Unified notification center with reliable multi-channel fan-out and user-level routing preferences. |
74
74
  | **Alignment verdict** | Aligned: this scope builds the durability foundation without prematurely committing to channel expansion. |
75
75
 
76
+ ### Mode-Specific Analysis
77
+
78
+ **Selected mode:** SELECTIVE EXPANSION
79
+
80
+ - **Hold-scope baseline:** SSE live updates + REST fallback is the minimum that meets the "know when action is needed" reframe. Accepted as baseline.
81
+ - **Expansion evaluated — degraded-state UX (accepted):** Adding an explicit "live updates paused" banner and polling fallback turns a reliability gap into a visible, recoverable state. Low incremental effort (S), high user trust payoff.
82
+ - **Expansion evaluated — real-time channel upgrade (deferred):** WebSocket channel provides lower latency but requires new infra (connection pool, auth handshake). Not justified for current load; deferred to post-v1 validation.
83
+
76
84
  ### Implementation Alternatives
77
85
 
78
86
  | Option | Summary | Effort (S/M/L/XL) | Risk | Pros | Cons | Reuses |
@@ -309,10 +309,15 @@ is_plan_mode_safe_tool() {
309
309
  todowrite|todoread|todo_write|todo_read) return 0 ;;
310
310
  webfetch|websearch|web_fetch|web_search|fetchmcpresource) return 0 ;;
311
311
  switchmode|switch_mode) return 0 ;;
312
+ task|delegate) return 0 ;;
312
313
  *) return 1 ;;
313
314
  esac
314
315
  }
315
316
 
317
+ is_cclaw_cli_payload() {
318
+ printf '%s' "$1" | grep -Eq '(cclaw |npx cclaw |/cc-|/cc[^[:alnum:]_-])'
319
+ }
320
+
316
321
  is_preimplementation_stage() {
317
322
  case "$1" in
318
323
  brainstorm|scope|design|spec|plan) return 0 ;;
@@ -370,7 +375,7 @@ fi
370
375
 
371
376
  if is_preimplementation_stage "$CURRENT_STAGE" && ! is_plan_mode_safe_tool "$TOOL_LOWER"; then
372
377
  if ! is_mutating_tool "$TOOL_LOWER"; then
373
- if ! printf '%s' "$PAYLOAD_LOWER" | grep -Eq '\.cclaw/'; then
378
+ if ! printf '%s' "$PAYLOAD_LOWER" | grep -Eq '\.cclaw/' && ! is_cclaw_cli_payload "$PAYLOAD_LOWER"; then
374
379
  if [ -n "$REASONS" ]; then
375
380
  REASONS="$REASONS,non_safe_tool_in_plan_stage_\${CURRENT_STAGE}"
376
381
  else
@@ -393,9 +398,10 @@ fi
393
398
  SHOULD_RECORD_FLOW_READ=0
394
399
  case "$TOOL_LOWER" in
395
400
  read|readfile|open|view|cat) SHOULD_RECORD_FLOW_READ=1 ;;
401
+ shell|runcommand|run_command|execcommand|exec_command|terminal) SHOULD_RECORD_FLOW_READ=1 ;;
396
402
  esac
397
403
 
398
- if [ "$SHOULD_RECORD_FLOW_READ" -eq 1 ] && printf '%s' "$PAYLOAD_LOWER" | grep -Eq '\.cclaw/state/flow-state\.json'; then
404
+ if [ "$SHOULD_RECORD_FLOW_READ" -eq 1 ] && printf '%s' "$PAYLOAD_LOWER" | grep -Eq '(\.cclaw/state/flow-state\.json|cclaw doctor|cclaw sync)'; then
399
405
  TMP_STATE_FILE="$GUARD_STATE_FILE.tmp.$$"
400
406
  if command -v jq >/dev/null 2>&1 && [ -f "$GUARD_STATE_FILE" ]; then
401
407
  jq --arg ts "$TS" --argjson epoch "$NOW_EPOCH" '
@@ -143,6 +143,8 @@ function stageCompletionProtocol(schema) {
143
143
  const gateIds = schema.requiredGates.map((g) => g.id);
144
144
  const gateList = gateIds.map((id) => `\`${id}\``).join(", ");
145
145
  const nextStage = schema.next === "done" ? null : schema.next;
146
+ const mandatory = schema.mandatoryDelegations;
147
+ const delegationLogRel = `${RUNTIME_ROOT}/state/delegation-log.json`;
146
148
  const stateUpdate = nextStage
147
149
  ? ` - Set \`currentStage\` to \`"${nextStage}"\`
148
150
  - Add \`"${stage}"\` to \`completedStages\` array
@@ -151,23 +153,33 @@ function stageCompletionProtocol(schema) {
151
153
  : ` - Add \`"${stage}"\` to \`completedStages\` array
152
154
  - Move all gate IDs for this stage (${gateList}) into \`stageGateCatalog.${stage}.passed\`
153
155
  - Clear \`stageGateCatalog.${stage}.blocked\``;
156
+ const delegationBlock = mandatory.length > 0
157
+ ? `0. **Delegation pre-flight** (BLOCKING):
158
+ - Mandatory agents for this stage: ${mandatory.map((a) => `\`${a}\``).join(", ")}.
159
+ - For each mandatory agent: confirm it was dispatched (via Task/delegate) and completed, OR record an explicit waiver with reason in \`${delegationLogRel}\`.
160
+ - Write a JSON entry per agent: \`{ "stage": "${stage}", "agent": "<name>", "mode": "mandatory", "status": "completed"|"waived", "waiverReason": "<if waived>", "ts": "<ISO timestamp>" }\`.
161
+ - If the harness does not support delegation, record status \`"waived"\` with reason \`"harness_limitation"\`.
162
+ - **Do NOT proceed to step 1 until every mandatory agent has an entry in the delegation log.**
163
+ `
164
+ : "";
154
165
  let nextAction;
155
166
  if (nextStage) {
156
167
  const nextSchema = stageSchema(nextStage);
157
168
  const nextDescription = nextSchema.skillDescription.charAt(0).toLowerCase() + nextSchema.skillDescription.slice(1);
158
- nextAction = `3. Tell the user:\n\n > **Stage \`${stage}\` complete.** Next: **${nextStage}** — ${nextDescription}\n >\n > Run \`/cc-next\` to continue.`;
169
+ nextAction = `4. Tell the user:\n\n > **Stage \`${stage}\` complete.** Next: **${nextStage}** — ${nextDescription}\n >\n > Run \`/cc-next\` to continue.`;
159
170
  }
160
171
  else {
161
- nextAction = `3. Tell the user:\n\n > **Flow complete.** All stages finished. The project is ready for release.`;
172
+ nextAction = `4. Tell the user:\n\n > **Flow complete.** All stages finished. The project is ready for release.`;
162
173
  }
163
174
  return `## Stage Completion Protocol
164
175
 
165
176
  When all required gates are satisfied and the artifact is written:
166
177
 
167
- 1. **Update \`${RUNTIME_ROOT}/state/flow-state.json\`:**
178
+ ${delegationBlock}1. **Update \`${RUNTIME_ROOT}/state/flow-state.json\`:**
168
179
  ${stateUpdate}
169
180
  - For each passed gate, add an entry to \`guardEvidence\`: \`"<gate_id>": "<artifact path or excerpt proving the gate>"\`. Do NOT leave \`guardEvidence\` empty.
170
181
  2. **Persist artifact** at \`${RUNTIME_ROOT}/artifacts/${schema.artifactFile}\`. Do NOT manually copy into \`${RUNTIME_ROOT}/runs/\`; archival is handled by \`cclaw archive\`.
182
+ 3. **Doctor pre-flight** — Run \`npx cclaw doctor\` (or the installed cclaw binary). If any check fails, resolve the issue (missing delegation entry, artifact section, gate evidence) and re-run until all checks pass. Do NOT proceed to the next step while doctor reports failures.
171
183
  ${nextAction}
172
184
 
173
185
  **STOP.** Do not load the next stage skill yourself. The user will run \`/cc-next\` when ready (same session or new session).
@@ -352,12 +352,15 @@ const SCOPE = {
352
352
  { section: "Premise Challenge", required: true, validationRule: "Must contain explicit answers to: right problem? direct path? what if nothing?" },
353
353
  { section: "Implementation Alternatives", required: true, validationRule: "2-3 options with Name, Summary, Effort, Risk, Pros, Cons, and Reuses. Must include minimal viable and ideal architecture options." },
354
354
  { section: "Scope Mode", required: true, validationRule: "Must state selected mode and rationale with default heuristic justification." },
355
+ { section: "Mode-Specific Analysis", required: true, validationRule: "Must document the analysis matching the selected scope mode: EXPAND (10x and delight opportunities), SELECTIVE (hold-scope baseline then cherry-picked expansions), HOLD (minimum-change-set hardening), REDUCE (ruthless cuts and follow-up split)." },
355
356
  { section: "In Scope / Out of Scope", required: true, validationRule: "Two separate explicit lists. Out-of-scope must not be empty." },
356
357
  { section: "Discretion Areas", required: true, validationRule: "Explicit list of implementer decision zones, or 'None' if scope is fully locked." },
357
358
  { section: "Deferred Items", required: true, validationRule: "Each item has one-line rationale. If empty, state 'None' explicitly." },
358
359
  { section: "Error & Rescue Registry", required: true, validationRule: "Each scoped capability has: failure mode, detection method, fallback decision." },
359
360
  { section: "Completion Dashboard", required: true, validationRule: "Lists checklist findings, count of resolved decisions, and unresolved decisions (or 'None')." },
360
- { section: "Scope Summary", required: true, validationRule: "Clean summary: mode, strongest challenges, recommended path, accepted scope, deferred, excluded." }
361
+ { section: "Scope Summary", required: true, validationRule: "Clean summary: mode, strongest challenges, recommended path, accepted scope, deferred, excluded." },
362
+ { section: "Dream State Mapping", required: false, validationRule: "If present (complex projects): CURRENT STATE, THIS PLAN, 12-MONTH IDEAL, and alignment verdict." },
363
+ { section: "Temporal Interrogation", required: false, validationRule: "If present (complex projects): timeline simulation table with decision pressures and lock-now vs defer verdicts." }
361
364
  ],
362
365
  namedAntiPattern: {
363
366
  title: "Scope Is Obvious From Context",
@@ -76,6 +76,14 @@ export const ARTIFACT_TEMPLATES = {
76
76
  - [ ] hold
77
77
  - [ ] reduce
78
78
 
79
+ ## Mode-Specific Analysis
80
+ - **Selected mode:**
81
+ - **Analysis:**
82
+ - (EXPAND: 10x opportunities, delight features)
83
+ - (SELECTIVE: hold-scope baseline, cherry-picked expansions)
84
+ - (HOLD: minimum-change-set hardening)
85
+ - (REDUCE: ruthless cuts, follow-up split)
86
+
79
87
  ## In Scope / Out of Scope
80
88
 
81
89
  ### In Scope
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "0.5.8",
3
+ "version": "0.5.9",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {