feat: add retention feedback loop and honest support matrix (#2681)

* feat: add retention feedback loop

* test: retire obsolete README parity row guard

* fix: harden public feedback guidance

* fix: let feedback CLI output flush

* test: keep feedback help coverage focused
This commit is contained in:
haelyra
2026-08-04 21:42:25 -04:00
committed by GitHub
parent 7a5757e6c0
commit f1fec0e539
21 changed files with 486 additions and 93 deletions
+8
View File
@@ -0,0 +1,8 @@
blank_issues_enabled: true
contact_links:
- name: ECC questions and setup help
url: https://github.com/affaan-m/ECC/discussions/categories/q-a
about: Ask a public question or get help from the community.
- name: Private security report
url: https://github.com/affaan-m/ECC/security/advisories/new
about: Report vulnerabilities privately. Do not put secrets in a public issue.
@@ -0,0 +1,40 @@
name: Feature idea
description: Describe the outcome you need and your current workaround.
title: "[Idea] "
labels:
- enhancement
- needs-triage
body:
- type: markdown
attributes:
value: |
This is a public GitHub issue. Do not include secrets, prompts, customer data, private repository details, or unredacted paths.
- type: textarea
id: outcome
attributes:
label: What outcome do you need?
description: Describe the job to be done, not an implementation if you do not have one in mind.
validations:
required: true
- type: textarea
id: workaround
attributes:
label: What do you do today?
description: Optional. A workaround helps us understand urgency and scope.
- type: dropdown
id: harness
attributes:
label: Which harness is affected?
options:
- All harnesses
- Claude Code
- Codex
- Cursor
- OpenCode
- GitHub Copilot
- Another harness
- type: textarea
id: success
attributes:
label: What would success look like?
description: Optional acceptance criteria or a small example.
@@ -0,0 +1,93 @@
name: Install or runtime problem
description: Tell us what failed without writing a full diagnostic report.
title: "[Problem] "
labels:
- bug
- needs-triage
- area:install
body:
- type: markdown
attributes:
value: |
Thanks for reporting this. Keep it short: what happened and which setup you used are enough to start.
This issue is public. Do not paste secrets, prompts, private repository names, or unredacted home/project paths. ECC never uploads diagnostics automatically.
- type: dropdown
id: impact
attributes:
label: What is the impact?
options:
- ECC will not install
- ECC installs, but nothing loads
- Some components are missing or silently ignored
- ECC is duplicated or conflicts with another install
- A hook or command interrupts normal work
- Doctor or repair does not recover the install
- Other runtime problem
validations:
required: true
- type: textarea
id: happened
attributes:
label: What happened?
description: Include the shortest error or symptom that explains the problem.
placeholder: I expected …, but …
validations:
required: true
- type: dropdown
id: harness
attributes:
label: Harness
options:
- Claude Code
- Codex app or CLI
- Cursor
- OpenCode
- GitHub Copilot
- Kimi Code
- Gemini CLI
- Zed
- Antigravity
- Qwen
- Hermes
- OpenClaw
- CodeBuddy or JoyCode
- Other
validations:
required: true
- type: dropdown
id: install_method
attributes:
label: Install method
options:
- Claude plugin marketplace
- ecc or ecc-install CLI
- Manual clone or copy
- Codex sync script
- Codex marketplace plugin
- Harness-specific installer target
- Unknown
- Other
- type: dropdown
id: operating_system
attributes:
label: Operating system
options:
- Windows (native)
- Windows (WSL)
- macOS
- Linux
- Other
validations:
required: true
- type: input
id: versions
attributes:
label: ECC and harness versions
description: If known. A tag, commit, or package version is enough.
placeholder: ECC 2.1.0; Claude Code 2.x
- type: textarea
id: diagnostics
attributes:
label: Optional redacted diagnostics
description: Paste only the relevant lines from `ecc doctor`. Remove paths, repository names, prompts, tokens, and secrets.
+56
View File
@@ -0,0 +1,56 @@
name: Quick product feedback
description: One required choice and an optional sentence. Leaving ECC is valid feedback.
title: "[Feedback] "
labels:
- feedback
- needs-triage
body:
- type: markdown
attributes:
value: |
Thank you for telling us what got in the way. This form is intentionally short.
This is a public GitHub issue. Do not include secrets, prompts, customer data, or private repository details.
Report a vulnerability through [GitHub's private security advisory form](https://github.com/affaan-m/ECC/security/advisories/new), not here. Non-vulnerability security or trust concerns are welcome in this form.
- type: dropdown
id: reason
attributes:
label: What best describes your feedback?
options:
- I could not install or activate ECC
- ECC made the agent slower or the output worse
- ECC used too much token or context budget
- Hooks or gates interrupted normal work
- ECC was too complicated or required too much configuration
- My harness or operating system was missing or unreliable
- I had a security or trust concern
- A feature I needed was missing
- Support was too slow
- I was only testing and no longer need it
- Something worked especially well
- Other
validations:
required: true
- type: dropdown
id: harness
attributes:
label: Where did you use ECC?
options:
- Claude Code
- Codex
- Cursor
- OpenCode
- GitHub Copilot
- Another harness
- I did not get far enough to use it
- type: textarea
id: change
attributes:
label: What is the one change that would matter most?
description: Optional. One sentence is plenty.
- type: textarea
id: keep
attributes:
label: What should ECC keep?
description: Optional. Tell us what was valuable even if the overall experience did not work.
+14
View File
@@ -152,6 +152,20 @@ executable instructions or policy.
---
## Install Health & Feedback CLI
These lifecycle commands are also available through the `ecc` CLI.
| Command | What it does |
|---------|-------------|
| `ecc list-installed` | Show installs recorded in ECC's managed state |
| `ecc doctor` | Diagnose missing or drifted managed files and point failures to the short problem form |
| `ecc repair` | Restore missing or drifted managed files |
| `ecc uninstall` | Remove only install-state-managed files and optionally show the 20-second exit-feedback route |
| `ecc feedback` | Show the public problem, quick-feedback, and feature routes without reading files or uploading diagnostics |
---
## Learning & Improvement
| Command | What it does |
+35 -28
View File
@@ -114,7 +114,7 @@ Instead of rebuilding that process in every prompt, you install it once and make
> Optimize the context window. Persist everything else.
ECC is MIT-licensed open source. It works best with Claude Code today, with first-class Codex support and adapters for Cursor, OpenCode, Gemini, Zed, GitHub Copilot, Antigravity, Qwen, and other harnesses.
ECC is MIT-licensed open source. It works best with Claude Code today, has a supported Codex sync path, and provides capability-limited adapters for Cursor, OpenCode, Gemini, Zed, GitHub Copilot, Antigravity, Qwen, and other harnesses. See the [support status matrix](#platform-support) before assuming feature parity.
Access to 67 agents, 281 skills, and 94 legacy command shims, plus hooks, rules, memory, continuous learning, and AgentShield security scanning. The agents are specialized for planning, review, build repair, security, architecture, and domain work.
@@ -141,6 +141,8 @@ You can use ECC with Claude Code, Codex, and other harnesses at the same time. C
If you already layered multiple installs and things look duplicated, skip straight to [Reset / Uninstall ECC](#reset--uninstall-ecc).
**Install trouble?** Open the short [install or runtime problem form](https://github.com/affaan-m/ECC/issues/new?template=install-problem.yml), or run `ecc feedback`. ECC never uploads diagnostics automatically.
### Claude Code
Run these commands inside Claude Code:
@@ -535,6 +537,8 @@ node scripts/uninstall.js --dry-run
node scripts/uninstall.js
```
If you are leaving, the uninstall command prints an optional [20-second feedback form](https://github.com/affaan-m/ECC/issues/new?template=quick-feedback.yml). It is a public GitHub issue, never blocks uninstall, and ECC does not upload diagnostics. You can also run `ecc feedback` at any time to see the problem, feedback, and feature routes.
Plugin users should remove the plugin from Claude Code, then delete only the rule folders they manually copied and no longer want. ECC only removes files recorded in its install-state. It does not claim unrelated files in your harness directories.
If you stacked methods, clean up in this order:
@@ -1305,7 +1309,16 @@ See [`rules/README.md`](rules/README.md) for installation and structure details.
## Cross-Platform Support
ECC fully supports **Windows, macOS, and Linux**, alongside tight integration across major IDEs (Cursor, Zed, OpenCode, Antigravity) and CLI harnesses. All hooks and scripts are written in Node.js for maximum compatibility.
ECC's core Node.js CLI and managed installers run on **Windows, macOS, and Linux**, but optional capabilities are not at full parity. Some continuous-learning, GAN, and orchestration paths still require Bash or Python; harnesses also expose different hook, agent, and skill APIs.
| Platform | Status | Current limitation |
|---|---|---|
| Linux | Supported core | Optional features may require Bash, Python, or provider-specific tools. |
| macOS | Supported core | The standalone GAN shell path is not compatible with the system Bash 3.2 and currently has a score-parsing defect ([#2674](https://github.com/affaan-m/ECC/issues/2674)). |
| Windows + WSL | Supported core | WSL follows the Linux paths; Windows host integrations still vary by harness. |
| Windows native | Supported with limitations | Continuous-learning v2's observer daemon and memory-vault writes have open native-Windows defects ([#2489](https://github.com/affaan-m/ECC/issues/2489), [#2626](https://github.com/affaan-m/ECC/issues/2626)). Shell-backed optional features require Git Bash/WSL or are unavailable. |
Treat `stable`, `beta`, `experimental`, and `instruction-only` below as capability statements, not marketing tiers.
<details>
<summary><strong>Package manager detection</strong></summary>
@@ -1400,31 +1413,25 @@ See [affaan-m/ECC#2065](https://github.com/affaan-m/ECC/issues/2065).
## Platform Support
| Harness | ECC distribution | Main instruction surface | Automation |
| Harness | Status | Recommended distribution | Important limitation |
|---|---|---|---|
| Claude Code | Plugin or selective installer | `CLAUDE.md`, rules, skills, agents | Native plugin hooks |
| Codex | Sync flow, repo config, experimental ECC marketplace | `AGENTS.md`, skills, `.codex/config.toml` | Git hooks and Codex-native configuration |
| Cursor | Project adapter | `.cursor/rules/`, scoped agents | Cursor hook adapter |
| OpenCode | Built plugin plus selective installer | `opencode.json`, instructions, commands | OpenCode plugin events |
| GitHub Copilot | Checked-in instruction layer | `copilot-instructions.md`, prompt files | No ECC hook runtime |
| Claude Code | Stable primary | Plugin or selective installer | The plugin advertises the installed catalog to the model; use a selective/manual profile when context footprint matters. Optional shell-backed skills are not portable to every OS. |
| Codex | Supported sync; marketplace experimental | Repo config or `sync-ecc-to-codex.sh` | No ECC hook runtime. The marketplace package can omit shared repository content from Codex's cache; use sync for the reliable path. |
| Cursor | Beta project adapter | Selective installer into `.cursor/` | Agent discovery varies by Cursor build, and ECC's installer paths do not yet expose identical hook sets ([#2419](https://github.com/affaan-m/ECC/issues/2419)). |
| OpenCode | Beta built plugin | Build plugin, then selective installer | ECC ships a subset of the catalog and the reference config pins Anthropic models; select models available to your provider ([#2617](https://github.com/affaan-m/ECC/issues/2617)). |
| GitHub Copilot | Instruction-only | Checked-in instructions and prompt files | No ECC hooks, runtime agents, delegation, or native skill discovery. |
| Gemini, Zed, Antigravity, Qwen, Hermes, OpenClaw, Kimi, CodeBuddy, JoyCode | Experimental/minimal adapters | Harness-specific selective target | File placement and instruction portability are tested; full Claude feature parity is not claimed. |
### Cross-Tool Feature Parity
### Cross-tool capability map
| Feature | Claude Code | Cursor IDE | Codex CLI | OpenCode | GitHub Copilot |
|---------|-----------------------|------------|-----------|----------|----------------|
| **Agents** | 67 | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 | N/A |
| **Commands** | 94 | Shared | Instruction-based | 35 | 5 prompts |
| **Skills** | 281 | Shared | 10 (native format) | 37 | Via instructions |
| **Hook Events** | 8 types | 15 types | None yet | 11 types | None |
| **Hook Scripts** | 20+ scripts | 16 scripts (DRY adapter) | N/A | Plugin hooks | N/A |
| **Rules** | 34 (common + lang) | 34 (YAML frontmatter) | Instruction-based | 13 instructions | 1 always-on file |
| **Custom Tools** | Via hooks | Via hooks | N/A | 6 native tools | N/A |
| **MCP Servers** | 14 | Shared (mcp.json) | 7 (auto-merged via TOML parser) | Full | N/A |
| **Config Format** | settings.json | hooks.json + rules/ | config.toml | opencode.json | copilot-instructions.md + settings.json |
| **Context File** | CLAUDE.md + AGENTS.md | AGENTS.md | AGENTS.md | AGENTS.md | copilot-instructions.md |
| **Secret Detection** | Hook-based | beforeSubmitPrompt hook | Sandbox-based | Hook-based | Instruction-based |
| **Auto-Format** | PostToolUse hook | afterFileEdit hook | N/A | file.edited hook | N/A |
| **Version** | Plugin | Plugin | Reference config | 2.1.0 | Instruction layer |
| Capability | Claude Code | Codex | Cursor | OpenCode | GitHub Copilot |
|---|---|---|---|---|---|
| Instructions | Native | Native `AGENTS.md` | Project rules | Plugin instructions | Native instruction file |
| Skills | Native installed set | Native synced set | Build-dependent/project set | Built subset | Prompt/instruction references only |
| Agents/delegation | Native agents | Codex multi-agent roles | Build-dependent project agents | Plugin agents | Not supported |
| ECC hooks | Native plugin hooks | Not supported | Cursor hook adapter; install-path differences remain | Plugin events | Not supported |
| MCP configuration | Available, explicit activation | TOML merge through sync | Explicit project/user config | Provider/plugin config | Not supplied by ECC |
| Parity with Claude Code | Primary reference | Partial | Partial | Partial | Not a parity target |
**Key architectural decisions:**
- **AGENTS.md** at root is the universal cross-tool file (read by Claude Code, Cursor, Codex, and OpenCode; GitHub Copilot uses `.github/copilot-instructions.md` instead)
@@ -1518,7 +1525,7 @@ alwaysApply: false
<details>
<summary><strong>Codex macOS app + CLI support in depth</strong></summary>
ECC provides **first-class Codex support** for both the macOS app and CLI, with a reference configuration, Codex-specific AGENTS.md supplement, and shared skills. For repo navigation, surface ownership, and PR diff packet guidance, start with [`docs/CODEX-NAVIGATION-GUIDE.md`](docs/CODEX-NAVIGATION-GUIDE.md).
ECC provides a supported Codex repo/sync path for the macOS app and CLI, with a reference configuration, Codex-specific AGENTS.md supplement, and shared skills. The ECC marketplace route remains experimental. For repo navigation, surface ownership, and PR diff packet guidance, start with [`docs/CODEX-NAVIGATION-GUIDE.md`](docs/CODEX-NAVIGATION-GUIDE.md).
```bash
# Run Codex CLI in the repo: AGENTS.md and .codex/ are auto-detected
@@ -1597,7 +1604,7 @@ The adapter writes ECC-managed files under `.zed/` and keeps BYOK/OpenRouter cre
<details>
<summary><strong>OpenCode support in depth</strong></summary>
ECC provides **full OpenCode support** including plugins and hooks.
ECC provides a beta OpenCode plugin integration with instructions, a catalog subset, commands, custom tools, and hook events. It does not provide feature parity with Claude Code, and the reference model IDs must exist in the user's configured provider.
```bash
# Install OpenCode
@@ -1921,8 +1928,8 @@ Each component is fully independent.
Yes. ECC is cross-platform:
- **Cursor**: Pre-translated configs in `.cursor/`. See [Platform Support](#platform-support).
- **Gemini CLI**: Experimental project-local support via `.gemini/GEMINI.md` and shared installer plumbing.
- **OpenCode**: Full plugin support in `.opencode/`.
- **Codex**: First-class support for both macOS app and CLI, with adapter drift guards and SessionStart fallback.
- **OpenCode**: Beta plugin integration in `.opencode/`; provider model selection and catalog parity remain limited.
- **Codex**: Supported repo/sync path for macOS app and CLI; ECC's marketplace package remains experimental.
- **GitHub Copilot (VS Code)**: Instruction and prompt layer via `.github/copilot-instructions.md`, `.vscode/settings.json`, and `.github/prompts/`.
- **Antigravity**: Tightly integrated setup for workflows, skills, and flattened rules in `.agent/`. See [Antigravity Guide](docs/ANTIGRAVITY-GUIDE.md).
- **JoyCode / CodeBuddy**: Project-local selective install adapters for commands, agents, skills, and flattened rules. See [JoyCode Adapter Guide](docs/JOYCODE-GUIDE.md).
+1
View File
@@ -98,6 +98,7 @@
"scripts/discussion-audit.js",
"scripts/doctor.js",
"scripts/ecc.js",
"scripts/feedback.js",
"scripts/memory.js",
"scripts/memory-mcp.mjs",
"scripts/gemini-adapt-agents.js",
-51
View File
@@ -133,38 +133,6 @@ function parseReadmeExpectations(readmeContent) {
});
}
const parityPatterns = [
{
category: 'agents',
regex: /^\|\s*(?:\*\*)?Agents(?:\*\*)?\s*\|\s*(\d+)\s*\|\s*Shared\s*\(AGENTS\.md\)\s*\|\s*Shared\s*\(AGENTS\.md\)\s*\|\s*12\s*\|(?:\s*N\/A\s*\|)?$/im,
source: 'README.md parity table'
},
{
category: 'commands',
regex: /^\|\s*(?:\*\*)?Commands(?:\*\*)?\s*\|\s*(\d+)\s*\|\s*Shared\s*\|\s*Instruction-based\s*\|\s*\d+\s*\|(?:\s*\d+\s+prompts\s*\|)?$/im,
source: 'README.md parity table'
},
{
category: 'skills',
regex: /^\|\s*(?:\*\*)?Skills(?:\*\*)?\s*\|\s*(\d+)\s*\|\s*Shared\s*\|\s*10\s*\(native format\)\s*\|\s*37\s*\|(?:\s*Via instructions\s*\|)?$/im,
source: 'README.md parity table'
}
];
for (const pattern of parityPatterns) {
const match = readmeContent.match(pattern.regex);
if (!match) {
throw new Error(`${pattern.source} is missing the ${pattern.category} row`);
}
expectations.push({
category: pattern.category,
mode: 'exact',
expected: Number(match[1]),
source: `${pattern.source} (${pattern.category})`
});
}
return expectations;
}
@@ -439,25 +407,6 @@ function syncEnglishReadme(content, catalog) {
(_, prefix, __, suffix) => `${prefix}${catalog.skills.count}${suffix}`,
'README.md comparison table (skills)'
);
nextContent = replaceOrThrow(
nextContent,
/^(\|\s*(?:\*\*)?Agents(?:\*\*)?\s*\|\s*)(\d+)(\s*\|\s*Shared\s*\(AGENTS\.md\)\s*\|\s*Shared\s*\(AGENTS\.md\)\s*\|\s*12\s*\|(?:\s*N\/A\s*\|)?)$/im,
(_, prefix, __, suffix) => `${prefix}${catalog.agents.count}${suffix}`,
'README.md parity table (agents)'
);
nextContent = replaceOrThrow(
nextContent,
/^(\|\s*(?:\*\*)?Commands(?:\*\*)?\s*\|\s*)(\d+)(\s*\|\s*Shared\s*\|\s*Instruction-based\s*\|\s*\d+\s*\|(?:\s*\d+\s+prompts\s*\|)?)$/im,
(_, prefix, __, suffix) => `${prefix}${catalog.commands.count}${suffix}`,
'README.md parity table (commands)'
);
nextContent = replaceOrThrow(
nextContent,
/^(\|\s*(?:\*\*)?Skills(?:\*\*)?\s*\|\s*)(\d+)(\s*\|\s*Shared\s*\|\s*10\s*\(native format\)\s*\|\s*37\s*\|(?:\s*Via instructions\s*\|)?)$/im,
(_, prefix, __, suffix) => `${prefix}${catalog.skills.count}${suffix}`,
'README.md parity table (skills)'
);
return nextContent;
}
+6
View File
@@ -3,6 +3,7 @@
const os = require('os');
const { buildDoctorReport } = require('./lib/install-lifecycle');
const { SUPPORTED_INSTALL_TARGETS } = require('./lib/install-manifests');
const { problemReportLines } = require('./lib/feedback-links');
function showHelp(exitCode = 0) {
console.log(`
@@ -58,6 +59,7 @@ function statusLabel(status) {
function printHuman(report) {
if (report.results.length === 0) {
console.log('No ECC install-state files found for the current home/project context.');
console.log(`\n${problemReportLines().join('\n')}`);
return;
}
@@ -78,6 +80,10 @@ function printHuman(report) {
}
console.log(`\nSummary: checked=${report.summary.checkedCount}, ok=${report.summary.okCount}, warnings=${report.summary.warningCount}, errors=${report.summary.errorCount}`);
if (report.summary.errorCount > 0 || report.summary.warningCount > 0) {
console.log(`\n${problemReportLines().join('\n')}`);
}
}
function main() {
+6
View File
@@ -47,6 +47,10 @@ const COMMANDS = {
script: 'doctor.js',
description: 'Diagnose missing or drifted ECC-managed files',
},
feedback: {
script: 'feedback.js',
description: 'Open the shortest path to report a problem, feedback, or an idea',
},
repair: {
script: 'repair.js',
description: 'Restore drifted or missing ECC-managed files',
@@ -99,6 +103,7 @@ const PRIMARY_COMMANDS = [
'memory',
'list-installed',
'doctor',
'feedback',
'repair',
'auto-update',
'status',
@@ -152,6 +157,7 @@ Examples:
ecc memory search "migration blockers" --target-harness hermes
ecc list-installed --json
ecc doctor --target cursor
ecc feedback
ecc repair --dry-run
ecc auto-update --dry-run
ecc status --json
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env node
const {
FEEDBACK_ROUTES,
getFeedbackPayload,
} = require('./lib/feedback-links');
function showHelp() {
process.stdout.write(`
Usage: ecc feedback [--json] [--help|-h]
Print ECC's low-friction public feedback routes. This command never uploads
diagnostics or reads project files.
`);
}
function parseArgs(argv) {
return argv.slice(2).reduce((parsed, arg) => {
if (arg === '--json') {
return { ...parsed, json: true };
}
if (arg === '--help' || arg === '-h') {
return { ...parsed, help: true };
}
throw new Error(`Unknown argument: ${arg}`);
}, { json: false, help: false });
}
function printHuman() {
process.stdout.write([
'ECC feedback',
'',
`Install or runtime problem:\n${FEEDBACK_ROUTES.problem}`,
'',
`Quick feedback (public GitHub issue):\n${FEEDBACK_ROUTES.feedback}`,
'',
`Feature idea:\n${FEEDBACK_ROUTES.feature}`,
'',
'ECC does not upload diagnostics or read project files. Redact sensitive information before posting publicly.',
'',
].join('\n'));
}
function main() {
try {
const options = parseArgs(process.argv);
if (options.help) {
showHelp();
return;
}
if (options.json) {
process.stdout.write(`${JSON.stringify(getFeedbackPayload(), null, 2)}\n`);
} else {
printHuman();
}
} catch (error) {
process.stderr.write(`Error: ${error.message}\n`);
process.exitCode = 1;
}
}
main();
+39
View File
@@ -0,0 +1,39 @@
const REPOSITORY_ISSUES_URL = 'https://github.com/affaan-m/ECC/issues/new';
const FEEDBACK_ROUTES = Object.freeze({
problem: `${REPOSITORY_ISSUES_URL}?template=install-problem.yml`,
feedback: `${REPOSITORY_ISSUES_URL}?template=quick-feedback.yml`,
feature: `${REPOSITORY_ISSUES_URL}?template=feature-request.yml`,
});
function getFeedbackPayload() {
return {
schemaVersion: 'ecc.feedback.v1',
privacy: 'public-github',
diagnosticsUploaded: false,
routes: { ...FEEDBACK_ROUTES },
};
}
function problemReportLines() {
return [
'Report this problem (public GitHub issue):',
FEEDBACK_ROUTES.problem,
'ECC does not upload diagnostics. Redact paths, repository names, prompts, and secrets before sharing output.',
];
}
function exitFeedbackLines() {
return [
'Optional 20-second exit feedback (public GitHub issue):',
FEEDBACK_ROUTES.feedback,
'ECC does not upload diagnostics or block uninstall.',
];
}
module.exports = {
FEEDBACK_ROUTES,
exitFeedbackLines,
getFeedbackPayload,
problemReportLines,
};
+5
View File
@@ -3,6 +3,7 @@
const os = require('os');
const { repairInstalledStates } = require('./lib/install-lifecycle');
const { SUPPORTED_INSTALL_TARGETS } = require('./lib/install-manifests');
const { problemReportLines } = require('./lib/feedback-links');
function showHelp(exitCode = 0) {
console.log(`
@@ -64,6 +65,10 @@ function printHuman(result) {
}
console.log(`\nSummary: checked=${result.summary.checkedCount}, ${result.dryRun ? 'planned' : 'repaired'}=${result.dryRun ? result.summary.plannedRepairCount : result.summary.repairedCount}, errors=${result.summary.errorCount}`);
if (result.summary.errorCount > 0) {
console.log(`\n${problemReportLines().join('\n')}`);
}
}
function main() {
+5
View File
@@ -3,6 +3,7 @@
const os = require('os');
const { uninstallInstalledStates } = require('./lib/install-lifecycle');
const { SUPPORTED_INSTALL_TARGETS } = require('./lib/install-manifests');
const { exitFeedbackLines } = require('./lib/feedback-links');
function showHelp(exitCode = 0) {
console.log(`
@@ -64,6 +65,10 @@ function printHuman(result) {
}
console.log(`\nSummary: checked=${result.summary.checkedCount}, ${result.dryRun ? 'planned' : 'uninstalled'}=${result.dryRun ? result.summary.plannedRemovalCount : result.summary.uninstalledCount}, errors=${result.summary.errorCount}`);
if (!result.dryRun) {
console.log(`\n${exitFeedbackLines().join('\n')}`);
}
}
function main() {
+3 -7
View File
@@ -498,7 +498,7 @@ function runTests() {
cleanupTestDir(testDir);
})) passed++; else failed++;
if (test('fails when README parity table counts drift', () => {
if (test('does not require obsolete cross-harness parity counts in README', () => {
const testDir = createTestDir();
const {
readmePath,
@@ -526,11 +526,7 @@ function runTests() {
MARKETPLACE_JSON_PATH: marketplaceJsonPath,
});
assert.strictEqual(result.code, 1, 'Should fail when README parity table drifts');
assert.ok(
(result.stdout + result.stderr).includes('README.md parity table'),
'Should mention the README parity table mismatch'
);
assert.strictEqual(result.code, 0, 'Catalog counts should be validated from inventory surfaces, not parity claims');
cleanupTestDir(testDir);
})) passed++; else failed++;
@@ -622,7 +618,7 @@ function runTests() {
assert.ok(readme.includes('|-- agents/ # 1 specialized subagents for delegation'), 'Should sync README project tree agents count');
assert.ok(readme.includes('| Agents | PASS: 1 agents |'), 'Should sync README comparison table');
assert.ok(readme.includes('| Skills | 16 | .agents/skills/ |'), 'Should not rewrite unrelated README tables');
assert.ok(readme.includes('| **Agents** | 1 | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 |'), 'Should sync README parity table');
assert.ok(readme.includes('| **Agents** | 7 | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 |'), 'Should leave obsolete parity prose untouched');
assert.ok(agentsDoc.includes('providing 1 specialized agents, 1 skills, 1 commands'), 'Should sync AGENTS summary');
assert.ok(agentsDoc.includes('skills/ — 1 workflow skills and domain knowledge'), 'Should sync AGENTS structure');
assert.ok(zhRootReadme.includes('你现在可以使用 1 个代理、1 个技能和 1 个命令'), 'Should sync README.zh-CN quick-start summary');
-7
View File
@@ -477,13 +477,6 @@ test('.opencode/package-lock.json root version matches package.json', () => {
assert.strictEqual(opencodePackageLock.packages[''].version, expectedVersion);
});
test('README version row matches package.json', () => {
const readme = fs.readFileSync(path.join(repoRoot, 'README.md'), 'utf8');
const match = readme.match(new RegExp(`^\\| \\*\\*Version\\*\\* \\| Plugin \\| Plugin \\| Reference config \\| (${semverPattern}) \\|(?: Instruction layer \\|)?$`, 'm'));
assert.ok(match, 'Expected README version summary row');
assert.strictEqual(match[1], expectedVersion);
});
test('user-facing docs do not use overlong legacy marketplace install commands', () => {
const markdownFiles = [
path.join(repoRoot, 'README.md'),
+15
View File
@@ -77,6 +77,21 @@ function runTests() {
let passed = 0;
let failed = 0;
if (test('points users without install state to the guided problem report', () => {
const homeDir = createTempDir('doctor-home-');
const projectRoot = createTempDir('doctor-project-');
try {
const result = run([], { cwd: projectRoot, homeDir });
assert.strictEqual(result.code, 0, result.stderr);
assert.ok(result.stdout.includes('install-problem.yml'));
assert.ok(result.stdout.includes('does not upload diagnostics'));
} finally {
cleanup(homeDir);
cleanup(projectRoot);
}
})) passed++; else failed++;
if (test('reports a healthy install with exit code 0', () => {
const homeDir = createTempDir('doctor-home-');
const projectRoot = createTempDir('doctor-project-');
+8
View File
@@ -75,6 +75,7 @@ function main() {
assert.match(result.stdout, /work-items/);
assert.match(result.stdout, /platform-audit/);
assert.match(result.stdout, /security-ioc-scan/);
assert.match(result.stdout, /feedback/);
}],
['delegates explicit install command', () => {
const result = runCli(['install', '--dry-run', '--json', 'typescript']);
@@ -196,6 +197,13 @@ function main() {
assert.strictEqual(result.status, 0, result.stderr);
assert.match(result.stdout, /Usage: node scripts\/repair\.js/);
}],
['delegates feedback command', () => {
const result = runCli(['feedback', '--json']);
assert.strictEqual(result.status, 0, result.stderr);
const payload = parseJson(result.stdout);
assert.strictEqual(payload.schemaVersion, 'ecc.feedback.v1');
assert.strictEqual(payload.diagnosticsUploaded, false);
}],
['supports help for the auto-update subcommand', () => {
const result = runCli(['help', 'auto-update']);
assert.strictEqual(result.status, 0, result.stderr);
+83
View File
@@ -0,0 +1,83 @@
/**
* Tests for scripts/feedback.js
*/
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');
const SCRIPT = path.join(__dirname, '..', '..', 'scripts', 'feedback.js');
function run(args = []) {
return spawnSync('node', [SCRIPT, ...args], {
encoding: 'utf8',
maxBuffer: 1024 * 1024,
});
}
function test(name, fn) {
try {
fn();
console.log(`${name}`);
return true;
} catch (error) {
console.log(`${name}`);
console.log(` Error: ${error.message}`);
return false;
}
}
const TEST_CASES = [
['prints low-friction feedback routes without collecting diagnostics', () => {
const result = run();
assert.strictEqual(result.status, 0, result.stderr);
assert.match(result.stdout, /Quick feedback/);
assert.match(result.stdout, /install-problem\.yml/);
assert.match(result.stdout, /quick-feedback\.yml/);
assert.match(result.stdout, /feature-request\.yml/);
assert.match(result.stdout, /public GitHub issue/);
assert.match(result.stdout, /does not upload diagnostics/i);
}],
['emits machine-readable feedback routes', () => {
const result = run(['--json']);
assert.strictEqual(result.status, 0, result.stderr);
const payload = JSON.parse(result.stdout);
assert.strictEqual(payload.schemaVersion, 'ecc.feedback.v1');
assert.strictEqual(payload.privacy, 'public-github');
assert.match(payload.routes.problem, /install-problem\.yml/);
assert.match(payload.routes.feedback, /quick-feedback\.yml/);
assert.match(payload.routes.feature, /feature-request\.yml/);
assert.strictEqual(payload.diagnosticsUploaded, false);
}],
['documents both help flags and returns after printing help', () => {
for (const flag of ['--help', '-h']) {
const result = run([flag]);
assert.strictEqual(result.status, 0, result.stderr);
assert.match(result.stdout, /Usage: ecc feedback \[--json\] \[--help\|-h\]/);
assert.doesNotMatch(result.stdout, /^ECC feedback$/m);
}
}],
['lets stdout and stderr flush through natural process exit', () => {
const source = fs.readFileSync(SCRIPT, 'utf8');
assert.doesNotMatch(source, /process\.exit\(/);
assert.match(source, /process\.exitCode = 1/);
}],
['rejects unknown arguments', () => {
const result = run(['--send-diagnostics']);
assert.strictEqual(result.status, 1);
assert.match(result.stderr, /Unknown argument/);
}],
];
function main() {
console.log('\n=== Testing feedback.js ===\n');
const passed = TEST_CASES.filter(([name, fn]) => test(name, fn)).length;
const failed = TEST_CASES.length - passed;
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
process.exitCode = failed > 0 ? 1 : 0;
}
main();
@@ -42,6 +42,7 @@ function buildExpectedPublishPaths(repoRoot) {
const extraPaths = [
"manifests",
"scripts/ecc.js",
"scripts/feedback.js",
"scripts/catalog.js",
"scripts/ci/scan-supply-chain-iocs.js",
"scripts/ci/supply-chain-advisory-sources.js",
@@ -148,6 +149,7 @@ function main() {
"scripts/ci/supply-chain-advisory-sources.js",
"scripts/consult.js",
"scripts/control-pane.js",
"scripts/feedback.js",
"scripts/ito.js",
"scripts/memory.js",
"scripts/memory-mcp.mjs",
+2
View File
@@ -109,6 +109,8 @@ function runTests() {
});
assert.strictEqual(uninstallResult.code, 0, uninstallResult.stderr);
assert.ok(uninstallResult.stdout.includes('Uninstall summary'));
assert.ok(uninstallResult.stdout.includes('quick-feedback.yml'));
assert.ok(uninstallResult.stdout.includes('public GitHub issue'));
assert.ok(!fs.existsSync(managedPath));
assert.ok(!fs.existsSync(statePath));
assert.ok(fs.existsSync(unrelatedPath));