mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 13:43:26 +08:00
fix: harden orchestration status and skill docs
This commit is contained in:
@@ -230,6 +230,8 @@ print(f"Cache creation: {message.usage.cache_creation_input_tokens}")
|
|||||||
Process large volumes asynchronously at 50% cost reduction:
|
Process large volumes asynchronously at 50% cost reduction:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
import time
|
||||||
|
|
||||||
batch = client.messages.batches.create(
|
batch = client.messages.batches.create(
|
||||||
requests=[
|
requests=[
|
||||||
{
|
{
|
||||||
@@ -306,6 +308,8 @@ while True:
|
|||||||
## Error Handling
|
## Error Handling
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
import time
|
||||||
|
|
||||||
from anthropic import APIError, RateLimitError, APIConnectionError
|
from anthropic import APIError, RateLimitError, APIConnectionError
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -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:
|
If using a crossposting service (e.g., Postbridge, Buffer, or a custom API), the pattern looks like:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
import os
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
resp = requests.post(
|
resp = requests.post(
|
||||||
@@ -160,8 +161,10 @@ resp = requests.post(
|
|||||||
"linkedin": {"text": linkedin_version},
|
"linkedin": {"text": linkedin_version},
|
||||||
"threads": {"text": threads_version}
|
"threads": {"text": threads_version}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
timeout=30
|
||||||
)
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
```
|
```
|
||||||
|
|
||||||
### Manual Posting
|
### Manual Posting
|
||||||
|
|||||||
@@ -24,7 +24,11 @@ Exa MCP server must be configured. Add to `~/.claude.json`:
|
|||||||
```json
|
```json
|
||||||
"exa-web-search": {
|
"exa-web-search": {
|
||||||
"command": "npx",
|
"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" }
|
"env": { "EXA_API_KEY": "YOUR_EXA_API_KEY_HERE" }
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ def post_thread(oauth, tweets: list[str]) -> list[str]:
|
|||||||
if reply_to:
|
if reply_to:
|
||||||
payload["reply"] = {"in_reply_to_tweet_id": reply_to}
|
payload["reply"] = {"in_reply_to_tweet_id": reply_to}
|
||||||
resp = oauth.post("https://api.x.com/2/tweets", json=payload)
|
resp = oauth.post("https://api.x.com/2/tweets", json=payload)
|
||||||
|
resp.raise_for_status()
|
||||||
tweet_id = resp.json()["data"]["id"]
|
tweet_id = resp.json()["data"]["id"]
|
||||||
ids.append(tweet_id)
|
ids.append(tweet_id)
|
||||||
reply_to = 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.
|
Always check `x-rate-limit-remaining` and `x-rate-limit-reset` headers.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
import time
|
||||||
|
|
||||||
remaining = int(resp.headers.get("x-rate-limit-remaining", 0))
|
remaining = int(resp.headers.get("x-rate-limit-remaining", 0))
|
||||||
if remaining < 5:
|
if remaining < 5:
|
||||||
reset = int(resp.headers.get("x-rate-limit-reset", 0))
|
reset = int(resp.headers.get("x-rate-limit-reset", 0))
|
||||||
|
|||||||
@@ -17,6 +17,16 @@ function stripCodeTicks(value) {
|
|||||||
return trimmed;
|
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) {
|
function parseSection(content, heading) {
|
||||||
if (typeof content !== 'string' || content.length === 0) {
|
if (typeof content !== 'string' || content.length === 0) {
|
||||||
return '';
|
return '';
|
||||||
@@ -246,22 +256,29 @@ function resolveSnapshotTarget(targetPath, cwd = process.cwd()) {
|
|||||||
if (fs.existsSync(absoluteTarget) && fs.statSync(absoluteTarget).isFile()) {
|
if (fs.existsSync(absoluteTarget) && fs.statSync(absoluteTarget).isFile()) {
|
||||||
const config = JSON.parse(fs.readFileSync(absoluteTarget, 'utf8'));
|
const config = JSON.parse(fs.readFileSync(absoluteTarget, 'utf8'));
|
||||||
const repoRoot = path.resolve(config.repoRoot || cwd);
|
const repoRoot = path.resolve(config.repoRoot || cwd);
|
||||||
|
const sessionName = normalizeSessionName(
|
||||||
|
config.sessionName || path.basename(repoRoot),
|
||||||
|
'session'
|
||||||
|
);
|
||||||
const coordinationRoot = path.resolve(
|
const coordinationRoot = path.resolve(
|
||||||
config.coordinationRoot || path.join(repoRoot, '.orchestration')
|
config.coordinationRoot || path.join(repoRoot, '.orchestration')
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sessionName: config.sessionName,
|
sessionName,
|
||||||
coordinationDir: path.join(coordinationRoot, config.sessionName),
|
coordinationDir: path.join(coordinationRoot, sessionName),
|
||||||
repoRoot,
|
repoRoot,
|
||||||
targetType: 'plan'
|
targetType: 'plan'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const repoRoot = path.resolve(cwd);
|
||||||
|
const sessionName = normalizeSessionName(targetPath, path.basename(repoRoot));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sessionName: targetPath,
|
sessionName,
|
||||||
coordinationDir: path.join(cwd, '.claude', 'orchestration', targetPath),
|
coordinationDir: path.join(repoRoot, '.orchestration', sessionName),
|
||||||
repoRoot: cwd,
|
repoRoot,
|
||||||
targetType: 'session'
|
targetType: 'session'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,12 @@ function normalizeSeedPaths(seedPaths, repoRoot) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const normalizedPath = relativePath.split(path.sep).join('/');
|
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)) {
|
if (seen.has(normalizedPath)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,9 +20,32 @@ function usage() {
|
|||||||
|
|
||||||
function parseArgs(argv) {
|
function parseArgs(argv) {
|
||||||
const args = argv.slice(2);
|
const args = argv.slice(2);
|
||||||
const target = args.find(arg => !arg.startsWith('--'));
|
let target = null;
|
||||||
const writeIndex = args.indexOf('--write');
|
let writePath = null;
|
||||||
const writePath = writeIndex >= 0 ? args[writeIndex + 1] : 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 };
|
return { target, writePath };
|
||||||
}
|
}
|
||||||
@@ -56,4 +79,4 @@ if (require.main === module) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { main };
|
module.exports = { main, parseArgs };
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ If the user chooses niche or core + niche, continue to category selection below
|
|||||||
|
|
||||||
### 2b: Choose Skill Categories
|
### 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?"
|
Question: "Which skill categories do you want to install?"
|
||||||
|
|||||||
@@ -161,8 +161,10 @@ resp = requests.post(
|
|||||||
"linkedin": {"text": linkedin_version},
|
"linkedin": {"text": linkedin_version},
|
||||||
"threads": {"text": threads_version}
|
"threads": {"text": threads_version}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
timeout=30
|
||||||
)
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
```
|
```
|
||||||
|
|
||||||
### Manual Posting
|
### Manual Posting
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ Exa MCP server must be configured. Add to `~/.claude.json`:
|
|||||||
"args": [
|
"args": [
|
||||||
"-y",
|
"-y",
|
||||||
"exa-mcp-server",
|
"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" }
|
"env": { "EXA_API_KEY": "YOUR_EXA_API_KEY_HERE" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ estimate_cost(
|
|||||||
estimate_type: "unit_price",
|
estimate_type: "unit_price",
|
||||||
endpoints: {
|
endpoints: {
|
||||||
"fal-ai/nano-banana-pro": {
|
"fal-ai/nano-banana-pro": {
|
||||||
"num_images": 1
|
"unit_quantity": 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ def post_thread(oauth, tweets: list[str]) -> list[str]:
|
|||||||
if reply_to:
|
if reply_to:
|
||||||
payload["reply"] = {"in_reply_to_tweet_id": reply_to}
|
payload["reply"] = {"in_reply_to_tweet_id": reply_to}
|
||||||
resp = oauth.post("https://api.x.com/2/tweets", json=payload)
|
resp = oauth.post("https://api.x.com/2/tweets", json=payload)
|
||||||
|
resp.raise_for_status()
|
||||||
tweet_id = resp.json()["data"]["id"]
|
tweet_id = resp.json()["data"]["id"]
|
||||||
ids.append(tweet_id)
|
ids.append(tweet_id)
|
||||||
reply_to = tweet_id
|
reply_to = tweet_id
|
||||||
|
|||||||
@@ -204,7 +204,34 @@ test('resolveSnapshotTarget handles plan files and direct session names', () =>
|
|||||||
|
|
||||||
const fromSession = resolveSnapshotTarget('workflow-visual-proof', repoRoot);
|
const fromSession = resolveSnapshotTarget('workflow-visual-proof', repoRoot);
|
||||||
assert.strictEqual(fromSession.targetType, 'session');
|
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 {
|
} finally {
|
||||||
fs.rmSync(tempRoot, { recursive: true, force: true });
|
fs.rmSync(tempRoot, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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', () => {
|
test('materializePlan keeps worker instructions inside the worktree boundary', () => {
|
||||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-orchestrator-test-'));
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-orchestrator-test-'));
|
||||||
|
|
||||||
|
|||||||
89
tests/scripts/orchestration-status.test.js
Normal file
89
tests/scripts/orchestration-status.test.js
Normal 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);
|
||||||
Reference in New Issue
Block a user