mustard-claude 3.1.8 → 3.1.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mustard-claude",
3
- "version": "3.1.8",
3
+ "version": "3.1.11",
4
4
  "description": "Framework-agnostic CLI for Claude Code project setup",
5
5
  "type": "module",
6
6
  "bin": {
@@ -55,7 +55,7 @@ node scripts/sync-registry.js --force
55
55
  - PreToolUse hooks use `permissionDecision` response format
56
56
  - PostToolUse hooks use `decision` response format
57
57
  - Every new hook must be registered in `settings.json` with a timeout
58
- - Task dispatch failures (API overload) are logged to `pipeline-state.lastDispatchFailure`; `/resume` auto-recovers within 10 min
58
+ - Task dispatch failures (API overload, HTTP 5xx, tool result missing) are logged to `pipeline-state.lastDispatchFailure`; `/resume` auto-recovers within 10 min
59
59
  - Generated files must start with `<!-- mustard:generated -->` header
60
60
  - Skills must have YAML frontmatter BEFORE the `<!-- mustard:generated -->` line
61
61
 
@@ -69,8 +69,8 @@ Before the normal detect-and-confirm flow, scan the newest pipeline state for a
69
69
  ## Context
70
70
  - Branch: {from git}
71
71
  - Files changed: {run `node .claude/scripts/diff-context.js`}
72
- - Last agent: {from `.claude/.agent-memory/_index.json` last entry}
73
- - Last action: {summary from last agent memory entry}
72
+ - Last agent: {Read `.claude/.agent-memory/_index.json` and pick the last entry's `agent_type`. If the file or `.agent-memory/` directory is missing, print literal `(none)` — do NOT probe with `ls`/`grep`, it surfaces noisy exit codes}
73
+ - Last action: {from the same last entry's `summary` field. If missing, print literal `(no prior memory)`}
74
74
  - Decisions: {decisions[] from pipeline state, if any}
75
75
 
76
76
  ## Next Action
@@ -554,7 +554,7 @@ describe("subagent-tracker.js overload detection", () => {
554
554
  assert.equal(r.code, 0);
555
555
  const state = JSON.parse(fs.readFileSync(pipelinePath, "utf8"));
556
556
  assert.ok(state.lastDispatchFailure, "flag must be set");
557
- assert.equal(state.lastDispatchFailure.reason, "api_overload");
557
+ assert.equal(state.lastDispatchFailure.reason, "dispatch_failure");
558
558
  assert.equal(state.lastDispatchFailure.agentType, "general-purpose");
559
559
  assert.equal(state.lastDispatchFailure.description, "test dispatch");
560
560
  } finally {
@@ -562,6 +562,40 @@ describe("subagent-tracker.js overload detection", () => {
562
562
  }
563
563
  });
564
564
 
565
+ it("should flag lastDispatchFailure on tool result missing infrastructure error", async () => {
566
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "infra-missing-"));
567
+ const pipelinePath = setupPipelineState(tmpDir);
568
+ try {
569
+ const r = await dispatchTaskResult(tmpDir, {
570
+ is_error: true,
571
+ content: "Tool result missing due to internal error",
572
+ });
573
+ assert.equal(r.code, 0);
574
+ const state = JSON.parse(fs.readFileSync(pipelinePath, "utf8"));
575
+ assert.ok(state.lastDispatchFailure, "flag must be set on infra failure");
576
+ assert.equal(state.lastDispatchFailure.reason, "dispatch_failure");
577
+ } finally {
578
+ fs.rmSync(tmpDir, { recursive: true, force: true });
579
+ }
580
+ });
581
+
582
+ it("should flag lastDispatchFailure on HTTP 503 service unavailable", async () => {
583
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "infra-503-"));
584
+ const pipelinePath = setupPipelineState(tmpDir);
585
+ try {
586
+ const r = await dispatchTaskResult(tmpDir, {
587
+ is_error: true,
588
+ content: "Error 503: service unavailable",
589
+ });
590
+ assert.equal(r.code, 0);
591
+ const state = JSON.parse(fs.readFileSync(pipelinePath, "utf8"));
592
+ assert.ok(state.lastDispatchFailure, "flag must be set on 5xx");
593
+ assert.equal(state.lastDispatchFailure.reason, "dispatch_failure");
594
+ } finally {
595
+ fs.rmSync(tmpDir, { recursive: true, force: true });
596
+ }
597
+ });
598
+
565
599
  it("should NOT flag on happy-path agent that merely documents rate limiting", async () => {
566
600
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "overload-docs-"));
567
601
  const pipelinePath = setupPipelineState(tmpDir);
@@ -72,9 +72,10 @@ function isRtkAvailable() {
72
72
  */
73
73
  function rtkRewrite(cmd) {
74
74
  try {
75
- // rtk rewrite expects the raw command as args
76
- // On Windows, shell: true is needed for proper quoting
77
- const result = execSync(`rtk rewrite ${cmd}`, {
75
+ // rtk rewrite expects the raw command as a single argv element.
76
+ // Using execFileSync avoids shell re-parsing, which would strip quotes
77
+ // and corrupt regex patterns containing brackets (e.g. grep '[x]').
78
+ const result = execFileSync('rtk', ['rewrite', cmd], {
78
79
  encoding: 'utf8',
79
80
  stdio: ['pipe', 'pipe', 'ignore'], // ignore stderr
80
81
  timeout: 3000,
@@ -195,15 +195,18 @@ function handlePostToolUse(data, stateDir) {
195
195
 
196
196
  const toolResponse = data.tool_response || {};
197
197
  const responseText = JSON.stringify(toolResponse).toLowerCase();
198
- // Detect overload conservatively: require is_error=true (Claude Code sets
199
- // this on Task tool failures) AND at least one overload keyword. This
200
- // avoids false positives on agents that merely *document* rate limiting
201
- // or error handling in their returned content.
202
- const isOverload =
198
+ // Detect dispatch failures conservatively: require is_error=true (Claude
199
+ // Code sets this on Task tool failures) AND at least one failure keyword.
200
+ // Covers:
201
+ // - API overload / rate limiting (429, 529, throttle, too many requests)
202
+ // - Infrastructure errors (tool result missing, HTTP 5xx, service unavailable)
203
+ // The regex avoids false positives on agents that merely *document* error
204
+ // handling in their returned content (see "unrelated error" test below).
205
+ const isDispatchFailure =
203
206
  toolResponse.is_error === true &&
204
- /overload|rate.?limit|\b429\b|\b529\b|throttl|too many requests/.test(responseText);
207
+ /overload|rate.?limit|\b429\b|\b529\b|throttl|too many requests|tool result missing|\b50[0-4]\b|service unavailable/.test(responseText);
205
208
 
206
- if (!isOverload) return;
209
+ if (!isDispatchFailure) return;
207
210
 
208
211
  const projectDir = path.resolve(stateDir, '..', '..');
209
212
  const statesDir = path.join(projectDir, '.claude', '.pipeline-states');
@@ -231,7 +234,7 @@ function handlePostToolUse(data, stateDir) {
231
234
  const state = JSON.parse(fs.readFileSync(newest, 'utf8'));
232
235
  state.lastDispatchFailure = {
233
236
  at: new Date().toISOString(),
234
- reason: 'api_overload',
237
+ reason: 'dispatch_failure',
235
238
  agentType: toolInput.subagent_type || 'unknown',
236
239
  description: toolInput.description || '',
237
240
  prompt: (toolInput.prompt || '').slice(0, 2000),