fix: harden orchestration status and skill docs

This commit is contained in:
Affaan Mustafa
2026-03-12 15:07:57 -07:00
parent ddab6f1190
commit 52daf17cb5
15 changed files with 206 additions and 16 deletions

View File

@@ -230,6 +230,8 @@ print(f"Cache creation: {message.usage.cache_creation_input_tokens}")
Process large volumes asynchronously at 50% cost reduction:
```python
import time
batch = client.messages.batches.create(
requests=[
{
@@ -306,6 +308,8 @@ while True:
## Error Handling
```python
import time
from anthropic import APIError, RateLimitError, APIConnectionError
try:

View File

@@ -148,6 +148,7 @@ A pattern I've been using that's made a real difference:
If using a crossposting service (e.g., Postbridge, Buffer, or a custom API), the pattern looks like:
```python
import os
import requests
resp = requests.post(
@@ -160,8 +161,10 @@ resp = requests.post(
"linkedin": {"text": linkedin_version},
"threads": {"text": threads_version}
}
}
},
timeout=30
)
resp.raise_for_status()
```
### Manual Posting

View File

@@ -24,7 +24,11 @@ Exa MCP server must be configured. Add to `~/.claude.json`:
```json
"exa-web-search": {
"command": "npx",
"args": ["-y", "exa-mcp-server"],
"args": [
"-y",
"exa-mcp-server",
"tools=web_search_exa,web_search_advanced_exa,get_code_context_exa,crawling_exa,company_research_exa,linkedin_search_exa,deep_researcher_start,deep_researcher_check"
],
"env": { "EXA_API_KEY": "YOUR_EXA_API_KEY_HERE" }
}
```

View File

@@ -92,6 +92,7 @@ def post_thread(oauth, tweets: list[str]) -> list[str]:
if reply_to:
payload["reply"] = {"in_reply_to_tweet_id": reply_to}
resp = oauth.post("https://api.x.com/2/tweets", json=payload)
resp.raise_for_status()
tweet_id = resp.json()["data"]["id"]
ids.append(tweet_id)
reply_to = tweet_id
@@ -167,6 +168,8 @@ resp = oauth.post(
Always check `x-rate-limit-remaining` and `x-rate-limit-reset` headers.
```python
import time
remaining = int(resp.headers.get("x-rate-limit-remaining", 0))
if remaining < 5:
reset = int(resp.headers.get("x-rate-limit-reset", 0))

View File

@@ -17,6 +17,16 @@ function stripCodeTicks(value) {
return trimmed;
}
function normalizeSessionName(value, fallback = 'session') {
const normalized = String(value || '')
.trim()
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
return normalized || fallback;
}
function parseSection(content, heading) {
if (typeof content !== 'string' || content.length === 0) {
return '';
@@ -246,22 +256,29 @@ function resolveSnapshotTarget(targetPath, cwd = process.cwd()) {
if (fs.existsSync(absoluteTarget) && fs.statSync(absoluteTarget).isFile()) {
const config = JSON.parse(fs.readFileSync(absoluteTarget, 'utf8'));
const repoRoot = path.resolve(config.repoRoot || cwd);
const sessionName = normalizeSessionName(
config.sessionName || path.basename(repoRoot),
'session'
);
const coordinationRoot = path.resolve(
config.coordinationRoot || path.join(repoRoot, '.orchestration')
);
return {
sessionName: config.sessionName,
coordinationDir: path.join(coordinationRoot, config.sessionName),
sessionName,
coordinationDir: path.join(coordinationRoot, sessionName),
repoRoot,
targetType: 'plan'
};
}
const repoRoot = path.resolve(cwd);
const sessionName = normalizeSessionName(targetPath, path.basename(repoRoot));
return {
sessionName: targetPath,
coordinationDir: path.join(cwd, '.claude', 'orchestration', targetPath),
repoRoot: cwd,
sessionName,
coordinationDir: path.join(repoRoot, '.orchestration', sessionName),
repoRoot,
targetType: 'session'
};
}

View File

@@ -56,6 +56,12 @@ function normalizeSeedPaths(seedPaths, repoRoot) {
}
const normalizedPath = relativePath.split(path.sep).join('/');
if (!normalizedPath || normalizedPath === '.') {
throw new Error('seedPaths entries must not target the repo root');
}
if (normalizedPath === '.git' || normalizedPath.startsWith('.git/')) {
throw new Error(`seedPaths entries must not target git metadata: ${entry}`);
}
if (seen.has(normalizedPath)) {
continue;
}

View File

@@ -20,9 +20,32 @@ function usage() {
function parseArgs(argv) {
const args = argv.slice(2);
const target = args.find(arg => !arg.startsWith('--'));
const writeIndex = args.indexOf('--write');
const writePath = writeIndex >= 0 ? args[writeIndex + 1] : null;
let target = null;
let writePath = null;
for (let index = 0; index < args.length; index += 1) {
const arg = args[index];
if (arg === '--write') {
const candidate = args[index + 1];
if (!candidate || candidate.startsWith('--')) {
throw new Error('--write requires an output path');
}
writePath = candidate;
index += 1;
continue;
}
if (arg.startsWith('--')) {
throw new Error(`Unknown flag: ${arg}`);
}
if (target) {
throw new Error('Expected a single session name or plan path');
}
target = arg;
}
return { target, writePath };
}
@@ -56,4 +79,4 @@ if (require.main === module) {
}
}
module.exports = { main };
module.exports = { main, parseArgs };

View File

@@ -82,7 +82,7 @@ If the user chooses niche or core + niche, continue to category selection below
### 2b: Choose Skill Categories
There are 35 skills organized into 7 categories. Use `AskUserQuestion` with `multiSelect: true`:
There are 41 skills organized into 8 categories. Use `AskUserQuestion` with `multiSelect: true`:
```
Question: "Which skill categories do you want to install?"

View File

@@ -161,8 +161,10 @@ resp = requests.post(
"linkedin": {"text": linkedin_version},
"threads": {"text": threads_version}
}
}
},
timeout=30
)
resp.raise_for_status()
```
### Manual Posting

View File

@@ -27,7 +27,7 @@ Exa MCP server must be configured. Add to `~/.claude.json`:
"args": [
"-y",
"exa-mcp-server",
"tools=web_search_exa,get_code_context_exa,crawling_exa,company_research_exa,linkedin_search_exa,deep_researcher_start,deep_researcher_check"
"tools=web_search_exa,web_search_advanced_exa,get_code_context_exa,crawling_exa,company_research_exa,linkedin_search_exa,deep_researcher_start,deep_researcher_check"
],
"env": { "EXA_API_KEY": "YOUR_EXA_API_KEY_HERE" }
}

View File

@@ -253,7 +253,7 @@ estimate_cost(
estimate_type: "unit_price",
endpoints: {
"fal-ai/nano-banana-pro": {
"num_images": 1
"unit_quantity": 1
}
}
)

View File

@@ -92,6 +92,7 @@ def post_thread(oauth, tweets: list[str]) -> list[str]:
if reply_to:
payload["reply"] = {"in_reply_to_tweet_id": reply_to}
resp = oauth.post("https://api.x.com/2/tweets", json=payload)
resp.raise_for_status()
tweet_id = resp.json()["data"]["id"]
ids.append(tweet_id)
reply_to = tweet_id

View File

@@ -204,7 +204,34 @@ test('resolveSnapshotTarget handles plan files and direct session names', () =>
const fromSession = resolveSnapshotTarget('workflow-visual-proof', repoRoot);
assert.strictEqual(fromSession.targetType, 'session');
assert.ok(fromSession.coordinationDir.endsWith(path.join('.claude', 'orchestration', 'workflow-visual-proof')));
assert.ok(fromSession.coordinationDir.endsWith(path.join('.orchestration', 'workflow-visual-proof')));
} finally {
fs.rmSync(tempRoot, { recursive: true, force: true });
}
});
test('resolveSnapshotTarget normalizes plan session names and defaults to the repo name', () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-orch-target-'));
const repoRoot = path.join(tempRoot, 'My Repo');
fs.mkdirSync(repoRoot, { recursive: true });
const namedPlanPath = path.join(repoRoot, 'named-plan.json');
const defaultPlanPath = path.join(repoRoot, 'default-plan.json');
fs.writeFileSync(namedPlanPath, JSON.stringify({
sessionName: 'Workflow Visual Proof',
repoRoot
}));
fs.writeFileSync(defaultPlanPath, JSON.stringify({ repoRoot }));
try {
const namedPlan = resolveSnapshotTarget(namedPlanPath, repoRoot);
assert.strictEqual(namedPlan.sessionName, 'workflow-visual-proof');
assert.ok(namedPlan.coordinationDir.endsWith(path.join('.orchestration', 'workflow-visual-proof')));
const defaultPlan = resolveSnapshotTarget(defaultPlanPath, repoRoot);
assert.strictEqual(defaultPlan.sessionName, 'my-repo');
assert.ok(defaultPlan.coordinationDir.endsWith(path.join('.orchestration', 'my-repo')));
} finally {
fs.rmSync(tempRoot, { recursive: true, force: true });
}

View File

@@ -144,6 +144,17 @@ test('normalizeSeedPaths rejects paths outside the repo root', () => {
);
});
test('normalizeSeedPaths rejects repo root and git metadata paths', () => {
assert.throws(
() => normalizeSeedPaths(['.'], '/tmp/ecc'),
/must not target the repo root/
);
assert.throws(
() => normalizeSeedPaths(['.git/config'], '/tmp/ecc'),
/must not target git metadata/
);
});
test('materializePlan keeps worker instructions inside the worktree boundary', () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-orchestrator-test-'));

View File

@@ -0,0 +1,89 @@
'use strict';
const assert = require('assert');
const { parseArgs } = require('../../scripts/orchestration-status');
console.log('=== Testing orchestration-status.js ===\n');
let passed = 0;
let failed = 0;
function test(desc, fn) {
try {
fn();
console.log(`${desc}`);
passed++;
} catch (error) {
console.log(`${desc}: ${error.message}`);
failed++;
}
}
test('parseArgs reads a target with an optional write path', () => {
assert.deepStrictEqual(
parseArgs([
'node',
'scripts/orchestration-status.js',
'workflow-visual-proof',
'--write',
'/tmp/snapshot.json'
]),
{
target: 'workflow-visual-proof',
writePath: '/tmp/snapshot.json'
}
);
});
test('parseArgs does not treat the write path as the target', () => {
assert.deepStrictEqual(
parseArgs([
'node',
'scripts/orchestration-status.js',
'--write',
'/tmp/snapshot.json',
'workflow-visual-proof'
]),
{
target: 'workflow-visual-proof',
writePath: '/tmp/snapshot.json'
}
);
});
test('parseArgs rejects missing write values and unknown flags', () => {
assert.throws(
() => parseArgs([
'node',
'scripts/orchestration-status.js',
'workflow-visual-proof',
'--write'
]),
/--write requires an output path/
);
assert.throws(
() => parseArgs([
'node',
'scripts/orchestration-status.js',
'workflow-visual-proof',
'--unknown'
]),
/Unknown flag/
);
});
test('parseArgs rejects multiple positional targets', () => {
assert.throws(
() => parseArgs([
'node',
'scripts/orchestration-status.js',
'first',
'second'
]),
/Expected a single session name or plan path/
);
});
console.log(`\n=== Results: ${passed} passed, ${failed} failed ===`);
if (failed > 0) process.exit(1);