mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-19 11:20:48 +08:00
docs+chore: add README Security section; fix lint regressions on main
- README: add a visible ## Security section (official sources, vuln reporting via SECURITY.md, GateGuard/IOC/AgentShield guardrails, security guide); make stats line a plain paragraph to clear MD028 - eslint: empty catch comment in run-with-flags.js; drop unneeded escape in github-coordination/parsing.js; remove unused execFileSync import in its test (#2236 follow-ups) - markdownlint: wrap bare URLs in rules/vue/*.md (#2250 follow-up) npm run lint green; full suite 2836/2836.
This commit is contained in:
parent
d90d921137
commit
351ccc5a3c
13
README.md
13
README.md
@ -20,7 +20,7 @@
|
|||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> **Official sources only.** Install ECC only from verified channels: the GitHub repository [github.com/affaan-m/ECC](https://github.com/affaan-m/ECC), the npm packages [`ecc-universal`](https://www.npmjs.com/package/ecc-universal) and [`ecc-agentshield`](https://www.npmjs.com/package/ecc-agentshield), the [GitHub App](https://github.com/apps/ecc-tools), the plugin slug `ecc@ecc`, and the project website [ecc.tools](https://ecc.tools). Third-party re-uploads and unofficial mirrors are not maintained or reviewed by the project and may contain malware.
|
> **Official sources only.** Install ECC only from verified channels: the GitHub repository [github.com/affaan-m/ECC](https://github.com/affaan-m/ECC), the npm packages [`ecc-universal`](https://www.npmjs.com/package/ecc-universal) and [`ecc-agentshield`](https://www.npmjs.com/package/ecc-agentshield), the [GitHub App](https://github.com/apps/ecc-tools), the plugin slug `ecc@ecc`, and the project website [ecc.tools](https://ecc.tools). Third-party re-uploads and unofficial mirrors are not maintained or reviewed by the project and may contain malware.
|
||||||
|
|
||||||
> **211.9K+ stars** | **32.5K+ forks** | **230+ contributors** | **12+ language ecosystems** | **Cross-harness agent workflows**
|
**211.9K+ stars** | **32.5K+ forks** | **230+ contributors** | **12+ language ecosystems** | **Cross-harness agent workflows**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -1808,6 +1808,17 @@ These configs work for my workflow. You should:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
ECC takes supply-chain and agent safety seriously.
|
||||||
|
|
||||||
|
- **Official sources only.** Install ECC only from the verified channels listed in the banner at the top of this README — the [GitHub repo](https://github.com/affaan-m/ECC), the `ecc-universal` / `ecc-agentshield` npm packages, the [GitHub App](https://github.com/apps/ecc-tools), the plugin slug `ecc@ecc`, and [ecc.tools](https://ecc.tools). Third-party re-uploads and mirrors are unreviewed and may ship malware.
|
||||||
|
- **Report a vulnerability.** Use the private process in [SECURITY.md](SECURITY.md) (GitHub private vulnerability reporting). Please do not open public issues for security reports.
|
||||||
|
- **Built-in guardrails.** GateGuard gates destructive shell commands (including `rm`, force/path `git checkout`, and destructive `find -exec`) before they run; the supply-chain IOC scanner runs in CI; and [AgentShield](#agentshield--security-auditor) audits your own agent, hook, MCP, permission, and secret surfaces (`/security-scan`).
|
||||||
|
- **Deep dive.** See the [Security Guide](./the-security-guide.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Sponsors
|
## Sponsors
|
||||||
|
|
||||||
Featured sponsors are at the top of this README — full list and tiers in [SPONSORS.md](SPONSORS.md). [Become a sponsor](https://github.com/sponsors/affaan-m).
|
Featured sponsors are at the top of this README — full list and tiers in [SPONSORS.md](SPONSORS.md). [Become a sponsor](https://github.com/sponsors/affaan-m).
|
||||||
|
|||||||
@ -51,4 +51,4 @@ const open = defineModel<boolean>('open', { default: false })
|
|||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
- ECC skills: `frontend-patterns`, `vite-patterns`.
|
- ECC skills: `frontend-patterns`, `vite-patterns`.
|
||||||
- Docs: https://vuejs.org/api/sfc-script-setup.html · https://vuejs.org/guide/essentials/reactivity-fundamentals.html · https://eslint.vuejs.org/
|
- Docs: <https://vuejs.org/api/sfc-script-setup.html> · <https://vuejs.org/guide/essentials/reactivity-fundamentals.html> · <https://eslint.vuejs.org/>
|
||||||
|
|||||||
@ -42,4 +42,4 @@ vue-tsc --noEmit
|
|||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
- ECC skills: `frontend-patterns`, `vite-patterns`.
|
- ECC skills: `frontend-patterns`, `vite-patterns`.
|
||||||
- Docs: https://github.com/vuejs/language-tools (vue-tsc) · https://eslint.vuejs.org/ · https://github.com/feature-sliced/steiger
|
- Docs: <https://github.com/vuejs/language-tools> (vue-tsc) · <https://eslint.vuejs.org/> · <https://github.com/feature-sliced/steiger>
|
||||||
|
|||||||
@ -53,4 +53,4 @@ queryClient.invalidateQueries({ queryKey: ['auction', id] })
|
|||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
- ECC skills: `frontend-patterns`, `vite-patterns`.
|
- ECC skills: `frontend-patterns`, `vite-patterns`.
|
||||||
- Docs: https://pinia.vuejs.org/ · https://router.vuejs.org/ · https://tanstack.com/query/latest/docs/framework/vue/overview · https://vuejs.org/guide/reusability/composables.html
|
- Docs: <https://pinia.vuejs.org/> · <https://router.vuejs.org/> · <https://tanstack.com/query/latest/docs/framework/vue/overview> · <https://vuejs.org/guide/reusability/composables.html>
|
||||||
|
|||||||
@ -43,4 +43,4 @@ paths:
|
|||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
- ECC skills: `frontend-patterns`, `vite-patterns`.
|
- ECC skills: `frontend-patterns`, `vite-patterns`.
|
||||||
- Docs: https://vuejs.org/guide/best-practices/security.html · https://github.com/cure53/DOMPurify · https://github.com/braintree/sanitize-url
|
- Docs: <https://vuejs.org/guide/best-practices/security.html> · <https://github.com/cure53/DOMPurify> · <https://github.com/braintree/sanitize-url>
|
||||||
|
|||||||
@ -50,4 +50,4 @@ expect(wrapper.emitted('bid')).toBeTruthy()
|
|||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
- ECC skills: `frontend-patterns`, `vite-patterns`.
|
- ECC skills: `frontend-patterns`, `vite-patterns`.
|
||||||
- Docs: https://test-utils.vuejs.org/api/ · https://pinia.vuejs.org/cookbook/testing.html · https://vitest.dev/
|
- Docs: <https://test-utils.vuejs.org/api/> · <https://pinia.vuejs.org/cookbook/testing.html> · <https://vitest.dev/>
|
||||||
|
|||||||
@ -100,7 +100,6 @@ function getPluginRoot() {
|
|||||||
return path.resolve(__dirname, '..', '..');
|
return path.resolve(__dirname, '..', '..');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//Safely extract target context from hook stdin JSON for dry-run preview.
|
//Safely extract target context from hook stdin JSON for dry-run preview.
|
||||||
|
|
||||||
function extractTargetContext(raw) {
|
function extractTargetContext(raw) {
|
||||||
@ -118,6 +117,7 @@ function extractTargetContext(raw) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
// best-effort field extraction; ignore malformed input
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -126,10 +126,7 @@ function extractTargetContext(raw) {
|
|||||||
|
|
||||||
function buildDryRunPreview(hookId, relScriptPath, profilesCsv, raw) {
|
function buildDryRunPreview(hookId, relScriptPath, profilesCsv, raw) {
|
||||||
const ctx = extractTargetContext(raw);
|
const ctx = extractTargetContext(raw);
|
||||||
const parts = [
|
const parts = [`[DryRun] Hook "${hookId}" would execute: ${relScriptPath}`, `(enabled=true, profiles=${profilesCsv || 'default'})`];
|
||||||
`[DryRun] Hook "${hookId}" would execute: ${relScriptPath}`,
|
|
||||||
`(enabled=true, profiles=${profilesCsv || 'default'})`,
|
|
||||||
];
|
|
||||||
|
|
||||||
if (ctx.tool) {
|
if (ctx.tool) {
|
||||||
parts.push(`tool=${ctx.tool}`);
|
parts.push(`tool=${ctx.tool}`);
|
||||||
|
|||||||
@ -7,17 +7,12 @@ function escapeRegExp(str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function normalizeBodyForComparison(body) {
|
function normalizeBodyForComparison(body) {
|
||||||
return (body || '').replace(/"lastSyncAt"\s*:\s*[^,\}\n]+/g, '"lastSyncAt": NORMALIZED');
|
return (body || '').replace(/"lastSyncAt"\s*:\s*[^,}\n]+/g, '"lastSyncAt": NORMALIZED');
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractCoordinationState(body, policy = DEFAULT_POLICY) {
|
function extractCoordinationState(body, policy = DEFAULT_POLICY) {
|
||||||
const marker = escapeRegExp(policy.sectionMarker || DEFAULT_SECTION_MARKER);
|
const marker = escapeRegExp(policy.sectionMarker || DEFAULT_SECTION_MARKER);
|
||||||
const regex = new RegExp(
|
const regex = new RegExp(`<!--\\s*${marker}:start\\s*-->\\s*` + '```json\\s*([\\s\\S]*?)\\s*```' + `\\s*<!--\\s*${marker}:end\\s*-->`, 'm');
|
||||||
`<!--\\s*${marker}:start\\s*-->\\s*` +
|
|
||||||
'```json\\s*([\\s\\S]*?)\\s*```' +
|
|
||||||
`\\s*<!--\\s*${marker}:end\\s*-->`,
|
|
||||||
'm'
|
|
||||||
);
|
|
||||||
const match = String(body || '').match(regex);
|
const match = String(body || '').match(regex);
|
||||||
|
|
||||||
if (!match) {
|
if (!match) {
|
||||||
@ -28,9 +23,7 @@ function extractCoordinationState(body, policy = DEFAULT_POLICY) {
|
|||||||
const parsed = JSON.parse(match[1]);
|
const parsed = JSON.parse(match[1]);
|
||||||
return parsed && typeof parsed === 'object' ? parsed : null;
|
return parsed && typeof parsed === 'object' ? parsed : null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new SyntaxError(
|
throw new SyntaxError(`Malformed coordination JSON in body: ${error.message} — raw: ${match[1].slice(0, 120)}`);
|
||||||
`Malformed coordination JSON in body: ${error.message} — raw: ${match[1].slice(0, 120)}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,7 +33,9 @@ function extractIssueReferences(text) {
|
|||||||
for (const match of source.matchAll(/(?:^|[^\d])#(\d+)\b/g)) {
|
for (const match of source.matchAll(/(?:^|[^\d])#(\d+)\b/g)) {
|
||||||
refs.add(Number.parseInt(match[1], 10));
|
refs.add(Number.parseInt(match[1], 10));
|
||||||
}
|
}
|
||||||
return Array.from(refs).filter(Number.isFinite).sort((a, b) => a - b);
|
return Array.from(refs)
|
||||||
|
.filter(Number.isFinite)
|
||||||
|
.sort((a, b) => a - b);
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractTasks(body) {
|
function extractTasks(body) {
|
||||||
@ -62,7 +57,7 @@ function extractTasks(body) {
|
|||||||
if (taskMatch) {
|
if (taskMatch) {
|
||||||
tasks.push({
|
tasks.push({
|
||||||
title: taskMatch[2].trim(),
|
title: taskMatch[2].trim(),
|
||||||
done: taskMatch[1].toLowerCase() === 'x',
|
done: taskMatch[1].toLowerCase() === 'x'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -98,26 +93,17 @@ function renderCoordinationState(state, policy = DEFAULT_POLICY) {
|
|||||||
lastAction: state.lastAction || 'sync',
|
lastAction: state.lastAction || 'sync',
|
||||||
lastActionAt: state.lastActionAt || new Date().toISOString(),
|
lastActionAt: state.lastActionAt || new Date().toISOString(),
|
||||||
lastSyncAt: state.lastSyncAt || new Date().toISOString(),
|
lastSyncAt: state.lastSyncAt || new Date().toISOString(),
|
||||||
notes: state.notes || null,
|
notes: state.notes || null
|
||||||
};
|
};
|
||||||
|
|
||||||
return [
|
return [`<!-- ${marker}:start -->`, '```json', JSON.stringify(payload, null, 2), '```', `<!-- ${marker}:end -->`].join('\n');
|
||||||
`<!-- ${marker}:start -->`,
|
|
||||||
'```json',
|
|
||||||
JSON.stringify(payload, null, 2),
|
|
||||||
'```',
|
|
||||||
`<!-- ${marker}:end -->`,
|
|
||||||
].join('\n');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeIssueBody(issue, nextState, policy = DEFAULT_POLICY) {
|
function mergeIssueBody(issue, nextState, policy = DEFAULT_POLICY) {
|
||||||
const body = String(issue.body || '');
|
const body = String(issue.body || '');
|
||||||
const markerEscaped = escapeRegExp(policy.sectionMarker || DEFAULT_SECTION_MARKER);
|
const markerEscaped = escapeRegExp(policy.sectionMarker || DEFAULT_SECTION_MARKER);
|
||||||
const rendered = renderCoordinationState(nextState, policy);
|
const rendered = renderCoordinationState(nextState, policy);
|
||||||
const regex = new RegExp(
|
const regex = new RegExp(`\\n?<!--\\s*${markerEscaped}:start\\s*-->[\\s\\S]*?<!--\\s*${markerEscaped}:end\\s*-->\\n?`, 'm');
|
||||||
`\\n?<!--\\s*${markerEscaped}:start\\s*-->[\\s\\S]*?<!--\\s*${markerEscaped}:end\\s*-->\\n?`,
|
|
||||||
'm'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (regex.test(body)) {
|
if (regex.test(body)) {
|
||||||
return body.replace(regex, `\n${rendered}\n`).trim() + '\n';
|
return body.replace(regex, `\n${rendered}\n`).trim() + '\n';
|
||||||
@ -139,5 +125,5 @@ module.exports = {
|
|||||||
mergeIssueBody,
|
mergeIssueBody,
|
||||||
normalizeBodyForComparison,
|
normalizeBodyForComparison,
|
||||||
parseStringList,
|
parseStringList,
|
||||||
renderCoordinationState,
|
renderCoordinationState
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,7 +6,7 @@ const assert = require('assert');
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { execFileSync, spawnSync } = require('child_process');
|
const { spawnSync } = require('child_process');
|
||||||
|
|
||||||
const { createStateStore } = require('../../scripts/lib/state-store');
|
const { createStateStore } = require('../../scripts/lib/state-store');
|
||||||
|
|
||||||
@ -23,7 +23,9 @@ function cleanup(dirPath) {
|
|||||||
function writeGhShim(rootDir, responses) {
|
function writeGhShim(rootDir, responses) {
|
||||||
const shimPath = path.join(rootDir, 'gh-shim.js');
|
const shimPath = path.join(rootDir, 'gh-shim.js');
|
||||||
const logPath = path.join(rootDir, 'gh-calls.jsonl');
|
const logPath = path.join(rootDir, 'gh-calls.jsonl');
|
||||||
fs.writeFileSync(shimPath, `
|
fs.writeFileSync(
|
||||||
|
shimPath,
|
||||||
|
`
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const responses = ${JSON.stringify(responses)};
|
const responses = ${JSON.stringify(responses)};
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
@ -42,7 +44,8 @@ if (args[0] === 'issue' && (args[1] === 'edit' || args[1] === 'comment')) {
|
|||||||
}
|
}
|
||||||
console.error('Unexpected gh args: ' + key);
|
console.error('Unexpected gh args: ' + key);
|
||||||
process.exit(3);
|
process.exit(3);
|
||||||
`);
|
`
|
||||||
|
);
|
||||||
return { shimPath, logPath };
|
return { shimPath, logPath };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,11 +54,11 @@ function run(args = [], options = {}) {
|
|||||||
cwd: options.cwd || path.join(__dirname, '..', '..'),
|
cwd: options.cwd || path.join(__dirname, '..', '..'),
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
...(options.env || {}),
|
...(options.env || {})
|
||||||
},
|
},
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
stdio: ['pipe', 'pipe', 'pipe'],
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
timeout: 10000,
|
timeout: 10000
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,20 +93,13 @@ async function runTests() {
|
|||||||
let passed = 0;
|
let passed = 0;
|
||||||
let failed = 0;
|
let failed = 0;
|
||||||
|
|
||||||
if (await test('claims an epic issue, updates GitHub state, and caches a work item', async () => {
|
if (
|
||||||
|
await test('claims an epic issue, updates GitHub state, and caches a work item', async () => {
|
||||||
const rootDir = createTempDir('github-coordination-claim-');
|
const rootDir = createTempDir('github-coordination-claim-');
|
||||||
const dbPath = path.join(rootDir, 'state.db');
|
const dbPath = path.join(rootDir, 'state.db');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const epicBody = [
|
const epicBody = ['# Ship GitHub-native coordination', '', 'We want deterministic epic state.', '', '## Tasks', '- [ ] Claim the epic', '- [ ] Validate the epic'].join('\n');
|
||||||
'# Ship GitHub-native coordination',
|
|
||||||
'',
|
|
||||||
'We want deterministic epic state.',
|
|
||||||
'',
|
|
||||||
'## Tasks',
|
|
||||||
'- [ ] Claim the epic',
|
|
||||||
'- [ ] Validate the epic',
|
|
||||||
].join('\n');
|
|
||||||
const issueView = {
|
const issueView = {
|
||||||
number: 12,
|
number: 12,
|
||||||
title: 'Ship GitHub-native coordination',
|
title: 'Ship GitHub-native coordination',
|
||||||
@ -112,18 +108,18 @@ async function runTests() {
|
|||||||
state: 'OPEN',
|
state: 'OPEN',
|
||||||
labels: [{ name: 'epic' }],
|
labels: [{ name: 'epic' }],
|
||||||
author: { login: 'maintainer' },
|
author: { login: 'maintainer' },
|
||||||
updatedAt: '2026-06-01T12:00:00Z',
|
updatedAt: '2026-06-01T12:00:00Z'
|
||||||
};
|
};
|
||||||
const shim = writeGhShim(rootDir, {
|
const shim = writeGhShim(rootDir, {
|
||||||
'issue view 12 --repo affaan-m/ECC --json number,title,body,url,state,labels,author,updatedAt,assignees': issueView,
|
'issue view 12 --repo affaan-m/ECC --json number,title,body,url,state,labels,author,updatedAt,assignees': issueView
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = run(['claim', '12', '--repo', 'affaan-m/ECC', '--actor', 'codex', '--db', dbPath, '--json'], {
|
const result = run(['claim', '12', '--repo', 'affaan-m/ECC', '--actor', 'codex', '--db', dbPath, '--json'], {
|
||||||
cwd: rootDir,
|
cwd: rootDir,
|
||||||
env: {
|
env: {
|
||||||
ECC_GH_SHIM: shim.shimPath,
|
ECC_GH_SHIM: shim.shimPath,
|
||||||
ECC_GH_SHIM_LOG: shim.logPath,
|
ECC_GH_SHIM_LOG: shim.logPath
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
assert.strictEqual(result.status, 0, result.stderr);
|
assert.strictEqual(result.status, 0, result.stderr);
|
||||||
const payload = parseJson(result.stdout);
|
const payload = parseJson(result.stdout);
|
||||||
@ -131,7 +127,11 @@ async function runTests() {
|
|||||||
assert.strictEqual(payload.owner, 'codex');
|
assert.strictEqual(payload.owner, 'codex');
|
||||||
assert.strictEqual(payload.project.state, 'in-progress');
|
assert.strictEqual(payload.project.state, 'in-progress');
|
||||||
|
|
||||||
const logEntries = fs.readFileSync(shim.logPath, 'utf8').trim().split(/\r?\n/).map(line => JSON.parse(line));
|
const logEntries = fs
|
||||||
|
.readFileSync(shim.logPath, 'utf8')
|
||||||
|
.trim()
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map(line => JSON.parse(line));
|
||||||
assert.ok(logEntries.some(entry => entry.args[0] === 'issue' && entry.args[1] === 'edit'));
|
assert.ok(logEntries.some(entry => entry.args[0] === 'issue' && entry.args[1] === 'edit'));
|
||||||
assert.ok(logEntries.some(entry => entry.args[0] === 'issue' && entry.args[1] === 'comment'));
|
assert.ok(logEntries.some(entry => entry.args[0] === 'issue' && entry.args[1] === 'comment'));
|
||||||
|
|
||||||
@ -144,9 +144,13 @@ async function runTests() {
|
|||||||
} finally {
|
} finally {
|
||||||
cleanup(rootDir);
|
cleanup(rootDir);
|
||||||
}
|
}
|
||||||
})) passed++; else failed++;
|
})
|
||||||
|
)
|
||||||
|
passed++;
|
||||||
|
else failed++;
|
||||||
|
|
||||||
if (await test('unblocks an epic when dependencies are closed', async () => {
|
if (
|
||||||
|
await test('unblocks an epic when dependencies are closed', async () => {
|
||||||
const rootDir = createTempDir('github-coordination-unblock-');
|
const rootDir = createTempDir('github-coordination-unblock-');
|
||||||
const dbPath = path.join(rootDir, 'state.db');
|
const dbPath = path.join(rootDir, 'state.db');
|
||||||
|
|
||||||
@ -158,7 +162,8 @@ async function runTests() {
|
|||||||
'',
|
'',
|
||||||
'<!-- ecc-coordination:start -->',
|
'<!-- ecc-coordination:start -->',
|
||||||
'```json',
|
'```json',
|
||||||
JSON.stringify({
|
JSON.stringify(
|
||||||
|
{
|
||||||
schemaVersion: 'ecc.github.coordination.v1',
|
schemaVersion: 'ecc.github.coordination.v1',
|
||||||
kind: 'epic',
|
kind: 'epic',
|
||||||
status: 'blocked',
|
status: 'blocked',
|
||||||
@ -173,10 +178,13 @@ async function runTests() {
|
|||||||
lastAction: 'claim',
|
lastAction: 'claim',
|
||||||
lastActionAt: '2026-06-01T13:00:00Z',
|
lastActionAt: '2026-06-01T13:00:00Z',
|
||||||
lastSyncAt: '2026-06-01T13:00:00Z',
|
lastSyncAt: '2026-06-01T13:00:00Z',
|
||||||
notes: null,
|
notes: null
|
||||||
}, null, 2),
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
),
|
||||||
'```',
|
'```',
|
||||||
'<!-- ecc-coordination:end -->',
|
'<!-- ecc-coordination:end -->'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
const openIssue = {
|
const openIssue = {
|
||||||
number: 1,
|
number: 1,
|
||||||
@ -186,7 +194,7 @@ async function runTests() {
|
|||||||
state: 'OPEN',
|
state: 'OPEN',
|
||||||
labels: [{ name: 'epic' }, { name: 'coordination:blocked' }],
|
labels: [{ name: 'epic' }, { name: 'coordination:blocked' }],
|
||||||
author: { login: 'codex' },
|
author: { login: 'codex' },
|
||||||
updatedAt: '2026-06-01T13:00:00Z',
|
updatedAt: '2026-06-01T13:00:00Z'
|
||||||
};
|
};
|
||||||
const closedDependency = {
|
const closedDependency = {
|
||||||
number: 2,
|
number: 2,
|
||||||
@ -196,26 +204,30 @@ async function runTests() {
|
|||||||
state: 'CLOSED',
|
state: 'CLOSED',
|
||||||
labels: [{ name: 'blocked-by-release' }],
|
labels: [{ name: 'blocked-by-release' }],
|
||||||
author: { login: 'maintainer' },
|
author: { login: 'maintainer' },
|
||||||
updatedAt: '2026-06-01T10:00:00Z',
|
updatedAt: '2026-06-01T10:00:00Z'
|
||||||
};
|
};
|
||||||
const shim = writeGhShim(rootDir, {
|
const shim = writeGhShim(rootDir, {
|
||||||
'issue list --repo affaan-m/ECC --state all --limit 100 --json number,title,body,url,state,labels,author,updatedAt,assignees': [openIssue, closedDependency],
|
'issue list --repo affaan-m/ECC --state all --limit 100 --json number,title,body,url,state,labels,author,updatedAt,assignees': [openIssue, closedDependency],
|
||||||
'issue view 1 --repo affaan-m/ECC --json number,title,body,url,state,labels,author,updatedAt,assignees': openIssue,
|
'issue view 1 --repo affaan-m/ECC --json number,title,body,url,state,labels,author,updatedAt,assignees': openIssue
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = run(['unblock', '--repo', 'affaan-m/ECC', '--db', dbPath, '--json'], {
|
const result = run(['unblock', '--repo', 'affaan-m/ECC', '--db', dbPath, '--json'], {
|
||||||
cwd: rootDir,
|
cwd: rootDir,
|
||||||
env: {
|
env: {
|
||||||
ECC_GH_SHIM: shim.shimPath,
|
ECC_GH_SHIM: shim.shimPath,
|
||||||
ECC_GH_SHIM_LOG: shim.logPath,
|
ECC_GH_SHIM_LOG: shim.logPath
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
assert.strictEqual(result.status, 0, result.stderr);
|
assert.strictEqual(result.status, 0, result.stderr);
|
||||||
const payload = parseJson(result.stdout);
|
const payload = parseJson(result.stdout);
|
||||||
assert.strictEqual(payload.count, 1);
|
assert.strictEqual(payload.count, 1);
|
||||||
assert.strictEqual(payload.items[0].status, 'ready');
|
assert.strictEqual(payload.items[0].status, 'ready');
|
||||||
|
|
||||||
const logEntries = fs.readFileSync(shim.logPath, 'utf8').trim().split(/\r?\n/).map(line => JSON.parse(line));
|
const logEntries = fs
|
||||||
|
.readFileSync(shim.logPath, 'utf8')
|
||||||
|
.trim()
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map(line => JSON.parse(line));
|
||||||
assert.ok(logEntries.some(entry => entry.args[0] === 'issue' && entry.args[1] === 'edit'));
|
assert.ok(logEntries.some(entry => entry.args[0] === 'issue' && entry.args[1] === 'edit'));
|
||||||
assert.ok(logEntries.some(entry => entry.args[0] === 'issue' && entry.args[1] === 'comment'));
|
assert.ok(logEntries.some(entry => entry.args[0] === 'issue' && entry.args[1] === 'comment'));
|
||||||
|
|
||||||
@ -226,7 +238,10 @@ async function runTests() {
|
|||||||
} finally {
|
} finally {
|
||||||
cleanup(rootDir);
|
cleanup(rootDir);
|
||||||
}
|
}
|
||||||
})) passed++; else failed++;
|
})
|
||||||
|
)
|
||||||
|
passed++;
|
||||||
|
else failed++;
|
||||||
|
|
||||||
process.stdout.write(`\nResults: Passed: ${passed}, Failed: ${failed}\n`);
|
process.stdout.write(`\nResults: Passed: ${passed}, Failed: ${failed}\n`);
|
||||||
process.exit(failed > 0 ? 1 : 0);
|
process.exit(failed > 0 ? 1 : 0);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user