mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-17 16:37:43 +00:00
Collapse the sprawling, partly-stale docs/sync-and-op-log/ tree into a
small authoritative set and make the sync-correctness invariant
partly lint-enforced instead of convention-only.
Docs:
- Delete superseded/duplicate/provably-stale design, plan, and
background-research docs (quick-reference, the architecture-diagrams
monolith, the "Hybrid Manifest" docs describing code that does not
exist, completed long-term plans, LLM-synthesis analyses).
- Salvage load-bearing decision history into the surviving docs before
deletion: rejected-alternatives rationale -> operation-log-architecture
("Why this architecture"); vector-clock pruning incident history ->
vector-clocks.md; archive-payload optimization -> architecture E.7.
- Add contributor-sync-model.md as the single-invariant entry point
(one user intent = one op; replayed/remote ops must not re-trigger
effects), with a decision table mapping to the enforcing linters.
- Repoint external/internal cross-refs; add CONTRIBUTING.md + CLAUDE.md
pointers; record the migration in a dated docs/plans/ design doc.
Enforcement (new eslint-local-rules):
- no-actions-in-effects (error): effects must inject LOCAL_ACTIONS /
ALL_ACTIONS, never the raw @ngrx/effects Actions stream.
- no-multi-entity-effect (warn, heuristic): flags a literal returned
array of >=2 action-creator calls; docstring + valid-case specs pin
exactly which shapes are and are not detected.
- run-specs.js runner wired into `npm run lint` via test:lint-rules;
refuses to run under test-framework globals and counts RuleTester.run
invocations so a spec that asserts nothing fails instead of passing.
- Correct the ALL_ACTIONS JSDoc in local-actions.token.ts to match
reality (archive-operation-handler uses LOCAL_ACTIONS).
Reviewed via parallel multi-agent review; findings W1/W2/W4 and a
dangling doc anchor addressed.
70 lines
2.6 KiB
JavaScript
70 lines
2.6 KiB
JavaScript
/**
|
|
* Runs every `*.spec.js` in eslint-local-rules/rules via ESLint's RuleTester.
|
|
*
|
|
* RuleTester.run() executes synchronously and throws on the first failing
|
|
* case ONLY when no test-framework globals (describe/it) are present — with
|
|
* them, RuleTester defers cases to the framework and merely requiring the spec
|
|
* asserts nothing. This runner therefore:
|
|
* (a) refuses to run if such globals are present, and
|
|
* (b) counts RuleTester.run() invocations per spec and fails any spec that
|
|
* required cleanly but never called run() (e.g. run() commented out, or
|
|
* wrapped in a never-invoked function) — so a spec can no longer be a
|
|
* silent pass that asserts nothing.
|
|
*
|
|
* Wired as `npm run test:lint-rules` (the unit suite — Karma over *.spec.ts —
|
|
* does not run these .spec.js files). Run with bare `node`, never under
|
|
* jest/mocha.
|
|
*/
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { RuleTester } = require('eslint');
|
|
|
|
// (a) A test-framework global means RuleTester would NOT throw synchronously,
|
|
// so requiring a spec would assert nothing. Fail loudly instead of lying.
|
|
if (typeof globalThis.describe === 'function' || typeof globalThis.it === 'function') {
|
|
// eslint-disable-next-line no-console
|
|
console.error(
|
|
'lint-rule specs: REFUSING TO RUN — a test-framework global (describe/it) ' +
|
|
'is present, so RuleTester defers instead of throwing and nothing would ' +
|
|
'be asserted. Run with bare `node eslint-local-rules/run-specs.js`.',
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
// (b) Count run() calls so a spec that asserts nothing fails instead of passing.
|
|
let runCalls = 0;
|
|
const originalRun = RuleTester.prototype.run;
|
|
RuleTester.prototype.run = function countedRun(...args) {
|
|
runCalls += 1;
|
|
return originalRun.apply(this, args);
|
|
};
|
|
|
|
const rulesDir = path.join(__dirname, 'rules');
|
|
const specs = fs.readdirSync(rulesDir).filter((f) => f.endsWith('.spec.js'));
|
|
|
|
let failed = false;
|
|
for (const spec of specs) {
|
|
const runsBefore = runCalls;
|
|
try {
|
|
require(path.join(rulesDir, spec));
|
|
if (runCalls === runsBefore) {
|
|
failed = true;
|
|
// eslint-disable-next-line no-console
|
|
console.error(
|
|
`FAIL ${spec}\n required cleanly but never called RuleTester.run() — asserted nothing`,
|
|
);
|
|
}
|
|
} catch (err) {
|
|
failed = true;
|
|
// eslint-disable-next-line no-console
|
|
console.error(`FAIL ${spec}\n${(err && err.stack) || err}`);
|
|
}
|
|
}
|
|
|
|
// eslint-disable-next-line no-console
|
|
console.log(
|
|
failed
|
|
? '\nlint-rule specs: FAILED'
|
|
: `\nlint-rule specs: ${specs.length} file(s) passed (${runCalls} RuleTester.run call(s))`,
|
|
);
|
|
process.exit(failed ? 1 : 0);
|