alignfirst 0.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +31 -0
  2. package/bin/alignfirst.mjs +3 -0
  3. package/dist/cli-error.d.ts +2 -0
  4. package/dist/cli-error.js +2 -0
  5. package/dist/cli.d.ts +10 -0
  6. package/dist/cli.js +98 -0
  7. package/dist/command-form.d.ts +3 -0
  8. package/dist/command-form.js +8 -0
  9. package/dist/commands/config.d.ts +2 -0
  10. package/dist/commands/config.js +55 -0
  11. package/dist/commands/developers.d.ts +2 -0
  12. package/dist/commands/developers.js +36 -0
  13. package/dist/commands/docmap.d.ts +2 -0
  14. package/dist/commands/docmap.js +22 -0
  15. package/dist/commands/doctor.d.ts +2 -0
  16. package/dist/commands/doctor.js +167 -0
  17. package/dist/commands/guide.d.ts +2 -0
  18. package/dist/commands/guide.js +126 -0
  19. package/dist/commands/plans.d.ts +2 -0
  20. package/dist/commands/plans.js +154 -0
  21. package/dist/commands/setup.d.ts +2 -0
  22. package/dist/commands/setup.js +254 -0
  23. package/dist/commands/sync.d.ts +2 -0
  24. package/dist/commands/sync.js +63 -0
  25. package/dist/commands/ticket.d.ts +2 -0
  26. package/dist/commands/ticket.js +119 -0
  27. package/dist/context.d.ts +15 -0
  28. package/dist/context.js +1 -0
  29. package/dist/errors.d.ts +2 -0
  30. package/dist/errors.js +6 -0
  31. package/dist/executables.d.ts +1 -0
  32. package/dist/executables.js +24 -0
  33. package/dist/git.d.ts +5 -0
  34. package/dist/git.js +52 -0
  35. package/dist/overlay.d.ts +19 -0
  36. package/dist/overlay.js +83 -0
  37. package/dist/parse-args.d.ts +1 -0
  38. package/dist/parse-args.js +11 -0
  39. package/dist/plans/archive.d.ts +4 -0
  40. package/dist/plans/archive.js +68 -0
  41. package/dist/plans/layout.d.ts +6 -0
  42. package/dist/plans/layout.js +19 -0
  43. package/dist/plans/link.d.ts +2 -0
  44. package/dist/plans/link.js +36 -0
  45. package/dist/plans/mode.d.ts +9 -0
  46. package/dist/plans/mode.js +31 -0
  47. package/dist/plans/ticket.d.ts +21 -0
  48. package/dist/plans/ticket.js +104 -0
  49. package/dist/project-config.d.ts +27 -0
  50. package/dist/project-config.js +79 -0
  51. package/dist/protocols.d.ts +2 -0
  52. package/dist/protocols.js +9 -0
  53. package/dist/skills.d.ts +9 -0
  54. package/dist/skills.js +54 -0
  55. package/dist/version-guard.d.ts +8 -0
  56. package/dist/version-guard.js +24 -0
  57. package/package.json +48 -0
  58. package/templates/guide/code-review/correctness-reviewer.md +53 -0
  59. package/templates/guide/code-review/intent-reviewer.md +22 -0
  60. package/templates/guide/code-review/module-javascript.md +80 -0
  61. package/templates/guide/code-review/module-python.md +77 -0
  62. package/templates/guide/code-review/module-typescript-strict.md +84 -0
  63. package/templates/guide/code-review/quality-reviewer.md +54 -0
  64. package/templates/guide/code-review/reviewer-common.md +62 -0
  65. package/templates/guide/code-review/safety-reviewer.md +66 -0
  66. package/templates/guide/core.md +54 -0
  67. package/templates/guide/overview.md +57 -0
  68. package/templates/guide/protocols/aad.md +85 -0
  69. package/templates/guide/protocols/catchup.md +13 -0
  70. package/templates/guide/protocols/description.md +51 -0
  71. package/templates/guide/protocols/merge.md +65 -0
  72. package/templates/guide/protocols/plan.md +256 -0
  73. package/templates/guide/protocols/review.md +114 -0
  74. package/templates/guide/protocols/spec.md +78 -0
package/dist/skills.js ADDED
@@ -0,0 +1,54 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { CliError } from "./cli-error.js";
5
+ export const STUB_SKILLS = [
6
+ "alignfirst",
7
+ "alspec",
8
+ "alplan",
9
+ "al",
10
+ "alcatchup",
11
+ "almerge",
12
+ "alreview",
13
+ "aldescription",
14
+ ];
15
+ export const SKILL_ROOTS = [".agents/skills", ".claude/skills", ".codex/skills"];
16
+ export function findInstalledSkill(home, name) {
17
+ for (const relativeRoot of SKILL_ROOTS) {
18
+ const root = join(home, relativeRoot);
19
+ const skillFile = join(root, name, "SKILL.md");
20
+ if (!existsSync(skillFile))
21
+ continue;
22
+ return { root, version: readSkillVersion(skillFile) };
23
+ }
24
+ return;
25
+ }
26
+ function readSkillVersion(path) {
27
+ const content = readFileSync(path, "utf-8");
28
+ const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content)?.[1];
29
+ if (frontmatter === undefined)
30
+ return;
31
+ const metadata = /^metadata:\s*\r?\n((?:^[ \t]+.*(?:\r?\n|$))*)/m.exec(frontmatter)?.[1];
32
+ if (metadata === undefined)
33
+ return;
34
+ return /^\s+version:\s*"?([^"\r\n]+)"?\s*$/m.exec(metadata)?.[1]?.trim();
35
+ }
36
+ export function installStubSkills(ctx, agents) {
37
+ const skillArgs = STUB_SKILLS.flatMap((skill) => ["--skill", skill]);
38
+ const agentArgs = agents.flatMap((agent) => ["--agent", agent]);
39
+ try {
40
+ execFileSync("npx", [
41
+ "-y",
42
+ "skills",
43
+ "add",
44
+ "https://github.com/paleo/alignfirst",
45
+ "--global",
46
+ "--yes",
47
+ ...skillArgs,
48
+ ...agentArgs,
49
+ ], { cwd: ctx.cwd, env: ctx.env, stdio: "inherit" });
50
+ }
51
+ catch {
52
+ throw new CliError("Failed to install the AlignFirst skills globally.");
53
+ }
54
+ }
@@ -0,0 +1,8 @@
1
+ import type { ProjectConfig } from "./project-config.js";
2
+ export interface CliRangeResult {
3
+ range: string;
4
+ satisfied: boolean;
5
+ }
6
+ export declare function defaultCliRange(version: string): string;
7
+ export declare function checkCliRange(config: ProjectConfig | undefined, installedVersion: string, commandArgs: string[]): void;
8
+ export declare function cliRangeResult(config: ProjectConfig | undefined, installedVersion: string): CliRangeResult | undefined;
@@ -0,0 +1,24 @@
1
+ import semver from "semver";
2
+ import { CliError } from "./cli-error.js";
3
+ export function defaultCliRange(version) {
4
+ const parsed = semver.parse(version);
5
+ if (parsed === null)
6
+ throw new Error(`Invalid installed version: ${version}`);
7
+ const upper = parsed.major === 0 ? `0.${parsed.minor + 1}.0` : `${parsed.major + 1}.0.0`;
8
+ return `>=${version} <${upper}`;
9
+ }
10
+ export function checkCliRange(config, installedVersion, commandArgs) {
11
+ const result = cliRangeResult(config, installedVersion);
12
+ if (result === undefined || result.satisfied)
13
+ return;
14
+ const command = commandArgs.join(" ");
15
+ throw new CliError(`alignfirst ${installedVersion} is installed; this project requires ${result.range}.\n` +
16
+ `Run a matching version: npx -y alignfirst@"${result.range}" ${command}\n` +
17
+ `Or install it globally: npm install -g alignfirst@"${result.range}"`);
18
+ }
19
+ export function cliRangeResult(config, installedVersion) {
20
+ const range = config?.cli;
21
+ if (range === undefined)
22
+ return;
23
+ return { range, satisfied: semver.satisfies(installedVersion, range) };
24
+ }
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "alignfirst",
3
+ "version": "0.1.0-beta.0",
4
+ "license": "CC0-1.0",
5
+ "author": "Thomas MUR",
6
+ "description": "The AlignFirst CLI: protocols, plans and docs in one command.",
7
+ "keywords": [
8
+ "alignfirst",
9
+ "cli",
10
+ "ai",
11
+ "agent",
12
+ "plans",
13
+ "docmap"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/paleo/alignfirst.git",
18
+ "directory": "packages/alignfirst"
19
+ },
20
+ "engines": {
21
+ "node": ">=22.11.0"
22
+ },
23
+ "packageManager": "npm@11.19.0",
24
+ "type": "module",
25
+ "bin": {
26
+ "alignfirst": "bin/alignfirst.mjs"
27
+ },
28
+ "files": [
29
+ "bin",
30
+ "dist",
31
+ "templates"
32
+ ],
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "dependencies": {
37
+ "@paleo/docmap": "~0.9.1",
38
+ "arktype": "^2.2.3",
39
+ "semver": "^7.8.5"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "~24.13.3",
43
+ "@types/semver": "~7.8.0",
44
+ "rimraf": "~6.1.3",
45
+ "typescript": "~7.0.2",
46
+ "vitest": "~4.1.11"
47
+ }
48
+ }
@@ -0,0 +1,53 @@
1
+ # Perspective — Correctness
2
+
3
+ Logic bugs pass linters and compilation; this perspective covers exactly what the tooling cannot.
4
+
5
+ ## Logic and Edge Cases
6
+
7
+ | Signal | Question | Severity |
8
+ | --- | --- | --- |
9
+ | New or modified condition | Are the bounds right? `<` vs `<=`, first and last element, equality case. | 🔴 |
10
+ | Loop over a collection | What happens when it is empty? Does the code after the loop assume it ran at least once? | 🔴 |
11
+ | Access to a field of a possibly absent object | Is the absent case handled, or merely tolerated by the typing? | 🔴 |
12
+ | Missing `else` or `default` branch | Is the uncovered case impossible, or just not considered? If impossible, is it proven (exhaustive type, invariant) or assumed? | 🔴 |
13
+ | Early return added | Does it skip code that had to run — cleanup, logging, release? | 🔴 |
14
+ | Comparison of values that can be zero, empty string, or false | Does the test distinguish "absent" from "present but falsy"? | 🔴 |
15
+ | Money or quantity computation | Floating point where exact decimals are required? Rounding applied once, at the right place? | 🔴 |
16
+ | Date, time, duration | Explicit or implicit timezone? Does the code assume UTC, server time, or user time — and is it the right one? Daylight-saving transitions handled? | 🔴 |
17
+ | Sort, deduplication, object comparison | Is the criterion total and stable? Are two "equal" elements equal in the sense the domain expects? | 🟡 |
18
+ | Collection modified while being iterated | Intentional, and defined behavior for this collection type? | 🔴 |
19
+ | Possible concurrent writes (handler, job, worker) | Can two simultaneous executions interfere? A non-atomic read-then-write is a race condition. | 🔴 |
20
+ | Non-idempotent operation on a retryable path | Does a replay produce a duplicate — double charge, double send? | 🔴 |
21
+
22
+ ## Resilience
23
+
24
+ | Signal | Question | Severity |
25
+ | --- | --- | --- |
26
+ | New network call | Timeout defined? Without an explicit one, the default is often infinite. | 🔴 |
27
+ | Retry policy added | Bounded, with backoff? Is the retried operation idempotent? | 🔴 |
28
+ | Error-catching block added | Is the error handled, or swallowed? A swallowed error turns a loud failure into silent corruption. | 🔴 |
29
+ | Error caught and re-thrown | Is the original cause preserved? | 🟡 |
30
+ | Multi-step operation without a transaction | What remains if step 3 of 5 fails? Is an inconsistent intermediate state visible to another reader? | 🔴 |
31
+ | Batch processing | Does one failing item stop the whole batch? Is that the intended behavior? | 🟡 |
32
+ | Feature flag added | Is the default the old behavior? Is the disabled path tested? | 🟡 |
33
+
34
+ ## Around the Diff
35
+
36
+ The most useful findings come from here, because nobody looks for them.
37
+
38
+ | Signal | Question | Severity |
39
+ | --- | --- | --- |
40
+ | Function modified | Who calls it? Do callers outside the diff assume the old behavior? | 🔴 |
41
+ | One occurrence of a pattern fixed | Does the same pattern exist elsewhere, unfixed? Report once, with the count. | 🟡 |
42
+ | Code the diff touches contains a bug unrelated to the diff | Report it as 🟣, without requiring a fix in this PR. | 🟣 |
43
+ | Constant, enumeration, or type union extended | Was every place that exhausts it updated? | 🔴 |
44
+ | Documented behavior modified | Do the documentation, comments, or repo instruction files now state something false? | 🟡 |
45
+
46
+ ## Agent-Written Code
47
+
48
+ Plausible, well-formed code that is wrong about its assumptions. These checks add to the above.
49
+
50
+ | Signal | Question | Severity |
51
+ | --- | --- | --- |
52
+ | Call to a library method or option | Does it exist in the **installed version**? Check the manifest, not memory. APIs removed between major versions are the most frequent case. | 🔴 |
53
+ | Clean, complete implementation of the nominal case | Are the failure paths handled? This is the systematic deficit of generated code. | 🔴 |
@@ -0,0 +1,22 @@
1
+ # Perspective — Intent
2
+
3
+ Evaluate the change as a whole: what it tries to accomplish, and whether the implementation is a proper way to accomplish it.
4
+
5
+ 1. Derive the **intent** from the diff: what is this branch trying to accomplish? If the intent cannot be stated in one or two sentences, that is itself a finding.
6
+ 2. Describe **how it is done**: the approach taken to implement the intent.
7
+ 3. **Assess** the approach:
8
+ - Is there a simpler design that achieves the same intent?
9
+ - Does the change fit the architecture and conventions of the codebase, or work against them?
10
+ - Does it leave the codebase healthier than before?
11
+ - Is the size proportionate to the intent? Layers, options, and generality nobody asked for cost as much as missing pieces.
12
+ - Does the diff mix a refactor with a behavior change? If they cannot be told apart, say so — it is what makes a review reliable or not.
13
+ 4. Report portions of code that deserve a **rewrite** as findings: 🟡, or 🔴 when the flaw defeats the intent. Observations about the change as a whole belong in the assessment, not in the findings list.
14
+
15
+ ## Report
16
+
17
+ In addition to the findings, your report must contain:
18
+
19
+ - **Intent** — one or two sentences.
20
+ - **How it's done** — a short description.
21
+ - **Assessment** — is this the optimal way to implement this intent? Be direct. If yes, say so briefly. If not, explain the better approach.
22
+ - **Verdict** — one of: mergeable as is, mergeable after fixes, needs rework. The verdict replaces the perspective summary.
@@ -0,0 +1,80 @@
1
+ # Ecosystem Module — JavaScript and Non-Strict TypeScript
2
+
3
+ For diffs in JavaScript, or in TypeScript without `strict`. Apply the section matching your perspective, on top of your perspective file.
4
+
5
+ Without `strict`, the type system verifies little: nullability passes, `any` spreads implicitly. The checks a strict compiler would do become review work — which makes this module larger than the strict one.
6
+
7
+ <!-- Maintainers: the JavaScript-runtime rows also live in module-typescript-strict.md; keep them in sync. -->
8
+
9
+ ## What the Tooling Covers (all perspectives)
10
+
11
+ Check what runs in the repo before starting, and skip what is covered: ESLint rules (`eqeqeq`, `no-floating-promises`, …), `checkJs` with JSDoc, the formatter. If a tool is absent, its items belong to the review.
12
+
13
+ ## Correctness
14
+
15
+ Nullability first — nothing checks it here:
16
+
17
+ | Signal | Question | Severity |
18
+ | --- | --- | --- |
19
+ | Property access on a value that can be `null` or `undefined` | Is the absent case handled on every path that reaches this access? | 🔴 |
20
+ | Function parameter assumed present | What happens when a caller omits it? | 🔴 |
21
+ | Index access on an array or record | The element can be `undefined`: is that handled? | 🔴 |
22
+ | Truthiness test on a value that can be `0`, `""`, or `false` | `if (x)` is false for these. Was `if (x != null)` intended? | 🔴 |
23
+ | `??` replaced by `\|\|` or the reverse | `\|\|` triggers on every falsy value, `??` only on `null` and `undefined`. On a number or boolean, the difference is a bug. | 🔴 |
24
+ | `find`, `pop`, `shift`, `at`, `match` | They return `undefined` or `null`. Is that case handled? | 🔴 |
25
+ | `==` used where types can differ | Coercion: `"" == 0` is true. Is `===` intended? Unless ESLint `eqeqeq` covers it. | 🔴 |
26
+ | Arithmetic or concatenation mixing strings and numbers | `"1" + 1` is `"11"`, `"2" * 1` is `2`. Is the conversion explicit? | 🔴 |
27
+ | `typeof x === "object"` | True for `null` too. | 🟡 |
28
+ | `NaN` possible (failed `parseInt`/`Number`, missing field) | `NaN` propagates silently and every comparison with it is false. Checked with `Number.isNaN`? | 🔴 |
29
+
30
+ Then the runtime pitfalls, shared with strict TypeScript:
31
+
32
+ | Signal | Question | Severity |
33
+ | --- | --- | --- |
34
+ | Async function called without `await` or `.catch` | Floating promise: the result is ignored and a rejection surfaces far from its origin. | 🔴 |
35
+ | `await` missing where the result is read | The code manipulates the promise instead of the value. Frequent symptom: an always-true condition. | 🔴 |
36
+ | `async` callback passed to a sync-expecting API (`forEach`, `filter`, `sort`, event handler) | The return is ignored, errors are lost, order is not guaranteed. | 🔴 |
37
+ | `try`/`catch` around an async call | Is the `await` **inside** the `try`? Outside it, the `catch` misses the rejection — unless the rejection is deliberately handled elsewhere. | 🔴 |
38
+ | `await` in a loop | Sequential on purpose, or parallelizable? | 🟡 |
39
+ | `Promise.all` on a collection of unbounded size | Unbounded parallelism: connection exhaustion, rate limiting downstream. | 🔴 |
40
+ | `Promise.all` where partial failure is acceptable — or `allSettled` whose statuses are not inspected | Is the failure mode chosen on purpose? `allSettled` keeps the successes; uninspected, it swallows the failures. | 🔴 |
41
+ | State updated after an async operation (frontend) | The component may be unmounted, or an older response may arrive after a newer one. Cancellation or guard? | 🔴 |
42
+ | `sort()` on numbers without a comparator | Lexicographic: `[10, 9, 1]` becomes `[1, 10, 9]`. | 🔴 |
43
+ | Arithmetic on money | Binary floats have no decimal exactness. Integers in the smallest unit, or a decimal library. | 🔴 |
44
+ | Integer from an external system | JSON deserialization silently loses precision beyond the safe range. | 🔴 |
45
+ | `Date` built from a string | Non-ISO parsing is implementation-dependent. Implicit timezone: client or server? | 🔴 |
46
+ | Copy via `{...x}` or `Object.assign` | Shallow: nested structures stay shared. Is sharing intended, or are they mutated downstream? | 🔴 |
47
+ | Mutation of a received parameter, array, or object | Does the caller expect its value to change? | 🔴 |
48
+ | `JSON.stringify` on an object holding `undefined`, `Map`, `Set`, `BigInt`, or a date | Silent loss, or exception. Can such values actually reach this payload? | 🟡 |
49
+ | Regex with the `g` flag reused | `lastIndex` persists between calls: alternating results. | 🔴 |
50
+ | Regex built from user input | Escaping, and catastrophic backtracking. | 🔴 |
51
+ | `this` in a function extracted or passed by reference | Context lost. | 🟡 |
52
+ | Comparison of objects or arrays | Reference comparison. | 🟡 |
53
+ | Error wrapped in a new one | Is `cause` used to keep the origin? | 🟡 |
54
+ | New import | Is the package in the manifest? Does the imported method exist in the **installed version**? | 🔴 |
55
+ | Import cycle introduced | Depending on evaluation order, a value can be `undefined` at load time. | 🔴 |
56
+
57
+ ## Change Safety
58
+
59
+ Every boundary is unchecked: no compiler backs the annotations.
60
+
61
+ | Signal | Question | Severity |
62
+ | --- | --- | --- |
63
+ | HTTP response, queue message, file content, `localStorage` consumed directly | Is there runtime validation — schema, predicate, parser — before the fields are used? | 🔴 |
64
+ | Environment variable read | It is `string \| undefined`. Validated at startup, or read ad hoc? | 🔴 |
65
+ | URL, route, or form parameter | Always a string. Is the conversion checked? `Number("abc")` yields `NaN` without an error. | 🔴 |
66
+ | JSDoc types or non-strict TS annotations on external data | They are documentation, not verification. Where is the runtime check? | 🔴 |
67
+ | Import from a deep path of a package | Public API, or implementation detail? | 🟡 |
68
+
69
+ ## Quality
70
+
71
+ | Signal | Question | Severity |
72
+ | --- | --- | --- |
73
+ | In non-strict TS: `as X` or `!` on a value of external origin | The assertion is a promise by the developer, not a verification — and here nothing limits its blast radius. What does it rest on? | 🔴 |
74
+ | In non-strict TS: `any` added | Real dynamic boundary, or surrender? | 🟡 |
75
+ | In non-strict TS: code added that would fail under `strict` | Does it push the repo further away from ever enabling it? | 🟡 |
76
+ | `var` introduced | Function-scoped, hoisted. `let`/`const` unless there is a reason. | 🟡 |
77
+ | Side effect at module load (I/O, global mutation) | Import order becomes behavior. Intended? | 🟡 |
78
+ | Built-in prototype extended or mutated | — | 🟡 |
79
+ | Assertion on a promise without `await` in a test | The test passes no matter what. | 🔴 |
80
+ | Module mock in a test | Does the mock respect the real signature? | 🟡 |
@@ -0,0 +1,77 @@
1
+ # Ecosystem Module — Python
2
+
3
+ For diffs in Python. Apply the section matching your perspective, on top of your perspective file.
4
+
5
+ ## What the Tooling Covers (all perspectives)
6
+
7
+ Check what runs in the repo before starting, and skip what is covered:
8
+
9
+ | Tool | Covers |
10
+ | --- | --- |
11
+ | Ruff / Flake8 | Mutable default argument, bare `except:`, `== None`, unused import or variable |
12
+ | Ruff (`ASYNC`, `S` rules) | Blocking call in async context, common security patterns (`shell=True`, `assert` in production) |
13
+ | mypy / pyright | Inconsistent signatures, unhandled `Optional` — **depending on the configured strictness**; without strict mode, most typing is unchecked |
14
+ | Black / Ruff format | All formatting |
15
+
16
+ If a tool is absent, its items belong to the review.
17
+
18
+ ## Correctness
19
+
20
+ | Signal | Question | Severity |
21
+ | --- | --- | --- |
22
+ | Parameter default that is a list, dict, set, or function call | Evaluated once, at definition: the object is shared across calls. Unless the linter covers it. | 🔴 |
23
+ | Class attribute initialized with a mutable value | Shared by all instances. Intended? | 🔴 |
24
+ | Function defined in a loop, or comprehension capturing the loop variable | Late binding: the function reads the value at call time, not definition time. | 🔴 |
25
+ | List or dict assigned to another variable, then modified | Reference copy, not value. Also watch shallow copies of nested structures. | 🔴 |
26
+ | Truthiness test on a value that can be `0`, `""`, `[]`, `{}` | `if x:` is false for these. Was `if x is not None:` intended? | 🔴 |
27
+ | `is` comparing values (beyond `None`, `True`, `False`) | Identity, not equality. Works by accident on small ints and interned strings. | 🔴 |
28
+ | Collection modified while iterated | Undefined behavior. Iterate over a copy or build a new collection. | 🔴 |
29
+ | Custom objects in a `dict` or `set` | Are `__hash__` and `__eq__` consistent? A mutable object as key is a trap. | 🟡 |
30
+ | `datetime` built without timezone | Naive vs aware comparison raises. `datetime.now()` without a timezone in domain code is almost always a bug. | 🔴 |
31
+ | `except:` or `except Exception:` added | Catches what it should not. Which precise exception is meant? | 🔴 |
32
+ | `except ...: pass`, or `except ...: return None` | Can the caller distinguish "absent" from "failed"? | 🔴 |
33
+ | `try` block covering several operations | Does it catch exceptions from lines other than the one targeted? Narrow it. | 🟡 |
34
+ | Exception caught and re-raised | `raise New(...) from e` keeps the cause; without `from e` it is lost. | 🟡 |
35
+ | `logger.error` in an `except` | `logger.exception` captures the traceback; `logger.error` does not. | 🟡 |
36
+ | `assert` validating an input | Assertions vanish under `-O`. Not a validation mechanism. | 🔴 |
37
+ | `finally` containing `return` or `break` | It silently discards any in-flight exception. Is discarding intended? | 🔴 |
38
+ | `open`, connection, cursor, lock, session acquired without `with` | Released on all paths, including exception and early return? | 🔴 |
39
+ | Custom context manager added | Does `__exit__` release on exception? Does it return a truthy value, swallowing the exception? | 🔴 |
40
+ | Coroutine called without `await` | It never runs, silently. Search for this systematically: the most frequent async bug. | 🔴 |
41
+ | Blocking call in an `async` function (`requests`, `time.sleep`, sync DB call) | Blocks the whole event loop, not just the task. | 🔴 |
42
+ | `asyncio.create_task` without keeping the reference | The task can be collected before completion. Keep the reference, attach error handling. | 🔴 |
43
+ | `asyncio.gather` with `return_exceptions=True` | Are the returned exceptions inspected, or treated as results? Without the flag: one exception cancels the others — intended? | 🔴 |
44
+ | Mutable state shared between tasks or threads | Protected? Non-atomic read-then-write. | 🔴 |
45
+ | `ThreadPoolExecutor` or `multiprocessing` introduced | Are the passed objects serializable? Are worker exceptions retrieved? | 🟡 |
46
+ | `Optional` return added | Do all callers handle the absent case? | 🔴 |
47
+
48
+ ## Change Safety
49
+
50
+ | Signal | Question | Severity |
51
+ | --- | --- | --- |
52
+ | Relation accessed in a loop (ORM) | Lazy loading fires per iteration: N+1. Eager loading possible? | 🔴 |
53
+ | Query built with `.format()`, f-string, or concatenation | Parameterize. Allowlist for dynamic identifiers. | 🔴 |
54
+ | `filter` without `limit` on a growing table | Bounded volume? | 🔴 |
55
+ | Session or transaction with a wide scope | Does it contain a network call? Does it close on error paths? | 🔴 |
56
+ | Auto-generated migration | Reviewed? Auto-generation regularly produces unwanted column drops and re-creations. Cross-check with the safety perspective's migration items. | 🔴 |
57
+ | `bulk_*` or mass insertion | Signals, validation, and application-level defaults are bypassed. Intended? | 🟡 |
58
+ | External data (JSON, environment, HTTP response) annotated with a precise type | The annotation is a declaration. Is there runtime validation at the boundary? | 🔴 |
59
+ | `pickle`, `marshal`, `shelve`, `yaml.load` on untrusted data | Arbitrary code execution. `yaml.safe_load` for YAML. | 🔴 |
60
+ | `eval`, `exec`, `compile` with non-constant input | — | 🔴 |
61
+ | `subprocess` with `shell=True`, or command built by concatenation | Pass an argument list. | 🔴 |
62
+ | `os.path.join` with a user-supplied segment | Does the resolved path stay under the base directory? | 🔴 |
63
+ | `random` used for a token, password, or session id | `secrets` for anything cryptographic. | 🔴 |
64
+ | `verify=False`, permissive TLS context | — | 🔴 |
65
+
66
+ ## Quality
67
+
68
+ | Signal | Question | Severity |
69
+ | --- | --- | --- |
70
+ | `Any` introduced in a public signature | Real dynamic boundary, or surrender? | 🟡 |
71
+ | `# type: ignore` added | Targeted at a precise error code, with a comment? | 🟡 |
72
+ | `cast()` used | It rests on a guarantee the checker cannot see. Does that guarantee exist? | 🟡 |
73
+ | `pytest.raises` without `match=` | Verifies that *some* exception of the type is raised, not that it comes from the right place. | 🟡 |
74
+ | Fixture with `module` or `session` scope holding mutable state | State leaks between tests; order-dependent results. | 🟡 |
75
+ | `mock.patch` on an import path | Patched where the target is *used*, not where it is defined? Classic failure: the patch does not take, the test passes while testing nothing. | 🔴 |
76
+ | Bare `Mock()` configured | Accepts any attribute and any call. `autospec=True` or `spec=` constrains to the real contract. | 🟡 |
77
+ | Test depending on `datetime.now`, the filesystem, or the network | Flakiness. | 🟡 |
@@ -0,0 +1,84 @@
1
+ # Ecosystem Module — Strict TypeScript
2
+
3
+ For diffs in TypeScript with `strict` enabled. Apply the section matching your perspective, on top of your perspective file.
4
+
5
+ <!-- Maintainers: the JavaScript-runtime rows also live in module-javascript.md; keep them in sync. -->
6
+
7
+ ## What the Tooling Covers (all perspectives)
8
+
9
+ With `strict` active, skip nullability findings on typed values — the compiler handles them. Check which of these are also active, and skip what they cover:
10
+
11
+ | Setting | If active, skip |
12
+ | --- | --- |
13
+ | `noUncheckedIndexedAccess` | Index access assumed defined |
14
+ | `exactOptionalPropertyTypes` | Absent property vs property set to `undefined` |
15
+ | ESLint `no-floating-promises` | Unawaited promises |
16
+ | ESLint `no-misused-promises` | Promise passed where a boolean or `void` is expected |
17
+ | Formatter (Prettier, Biome) | All formatting |
18
+
19
+ The limit that shapes this whole module: **the compiler verifies nothing at runtime.** Everything entering the program from outside is typed by declaration, not by verification. That is the main source of bugs that pass compilation.
20
+
21
+ ## Correctness
22
+
23
+ | Signal | Question | Severity |
24
+ | --- | --- | --- |
25
+ | Truthiness test on a value that can be `0`, `""`, or `false` | `if (x)` is false for these. Was `if (x != null)` intended? | 🔴 |
26
+ | `??` replaced by `\|\|` or the reverse | `\|\|` triggers on every falsy value, `??` only on `null` and `undefined`. On a number or boolean, the difference is a bug. | 🔴 |
27
+ | `find`, `pop`, `shift`, `at`, `match` | They return `undefined` or `null`. Is that case handled? | 🔴 |
28
+ | Custom type predicate (`x is T`) added | Does the body really verify what the signature claims? A false predicate is a lie the compiler propagates everywhere. | 🔴 |
29
+ | `switch` on a union, without `default` or exhaustiveness check | A future member will pass silently. A `default` assigning to `never` forces a compile error. | 🟡 |
30
+ | Async function called without `await` or `.catch` | Floating promise: the result is ignored and a rejection surfaces far from its origin. Unless ESLint covers it. | 🔴 |
31
+ | `await` missing where the result is read | The code manipulates the promise instead of the value. Frequent symptom: an always-true condition. | 🔴 |
32
+ | `async` callback passed to a sync-expecting API (`forEach`, `filter`, `sort`, event handler) | The return is ignored, errors are lost, order is not guaranteed. | 🔴 |
33
+ | `try`/`catch` around an async call | Is the `await` **inside** the `try`? Outside it, the `catch` misses the rejection — unless the rejection is deliberately handled elsewhere. | 🔴 |
34
+ | `await` in a loop | Sequential on purpose, or parallelizable? | 🟡 |
35
+ | `Promise.all` on a collection of unbounded size | Unbounded parallelism: connection exhaustion, rate limiting downstream. | 🔴 |
36
+ | `Promise.all` where partial failure is acceptable — or `allSettled` whose statuses are not inspected | Is the failure mode chosen on purpose? `allSettled` keeps the successes; uninspected, it swallows the failures. | 🔴 |
37
+ | State updated after an async operation (frontend) | The component may be unmounted, or an older response may arrive after a newer one. Cancellation or guard? | 🔴 |
38
+ | `sort()` on numbers without a comparator | Lexicographic: `[10, 9, 1]` becomes `[1, 10, 9]`. | 🔴 |
39
+ | Arithmetic on money | Binary floats have no decimal exactness. Integers in the smallest unit, or a decimal library. | 🔴 |
40
+ | Integer from an external system | JSON deserialization silently loses precision beyond the safe range. | 🔴 |
41
+ | `Date` built from a string | Non-ISO parsing is implementation-dependent. Implicit timezone: client or server? | 🔴 |
42
+ | Copy via `{...x}` or `Object.assign` | Shallow: nested structures stay shared. Is sharing intended, or are they mutated downstream? | 🔴 |
43
+ | Mutation of a received parameter, array, or object | Does the caller expect its value to change? `readonly` prevents nothing at runtime. | 🔴 |
44
+ | `JSON.stringify` on an object holding `undefined`, `Map`, `Set`, `BigInt`, or a date | Silent loss, or exception. Can such values actually reach this payload? | 🟡 |
45
+ | Regex with the `g` flag reused | `lastIndex` persists between calls: alternating results. | 🔴 |
46
+ | Regex built from user input | Escaping, and catastrophic backtracking. | 🔴 |
47
+ | `catch (e)` | `e` is `unknown`: the thrown value is not necessarily an `Error`. Is `e.message` read without a check? | 🟡 |
48
+ | Error wrapped in a new one | Is `cause` used to keep the origin? | 🟡 |
49
+ | Discriminated error type extended | Do all consumption points handle the new member? | 🔴 |
50
+ | New import | Is the package in the manifest? Does the imported method exist in the **installed version**? | 🔴 |
51
+ | Import cycle introduced | Depending on evaluation order, a value can be `undefined` at load time. | 🔴 |
52
+
53
+ ## Change Safety
54
+
55
+ The type of external data is a declaration, not a guarantee — the most important point of this module.
56
+
57
+ | Signal | Question | Severity |
58
+ | --- | --- | --- |
59
+ | HTTP response, queue message, file content, `localStorage` typed without validation | Is there runtime validation — schema, predicate, parser? Otherwise the type is a wish. | 🔴 |
60
+ | Environment variable read | Its real type is `string \| undefined`. Validated at startup, or read ad hoc with a `!`? | 🔴 |
61
+ | URL, route, or form parameter | Always a string. Is the conversion checked? `Number("abc")` yields `NaN` without an error. | 🔴 |
62
+ | Validation schema added or modified | Is the type inferred from the schema the source of truth, or does a parallel interface exist that can drift? | 🟡 |
63
+ | Type shared between client and server | Do both sides read the same definition, or two copies? | 🟡 |
64
+ | Import from a deep path of a package | Public API, or implementation detail? | 🟡 |
65
+
66
+ ## Quality
67
+
68
+ The keywords `as`, `any`, and `!` are the signature of low-quality typing: each one turns off a verification.
69
+
70
+ | Signal | Question | Severity |
71
+ | --- | --- | --- |
72
+ | `as X` on a value of external origin | The assertion is a promise by the developer, not a verification. What does it rest on? | 🔴 |
73
+ | `as unknown as X`, or double assertion | Bypasses the guard against non-overlapping assertions. Almost always a wrong model. | 🔴 |
74
+ | `!` (non-null assertion) added | Does the guarantee really exist, or does it mask an unhandled case? On an environment variable or a search result, almost always the latter. | 🔴 |
75
+ | `any` introduced | Real dynamic boundary, or surrender? `unknown` forces narrowing; `any` disables everything — downstream callers included. | 🟡 |
76
+ | `@ts-ignore` or `@ts-expect-error` added | `@ts-expect-error` fails when the error disappears; `@ts-ignore` never does. Is the reason commented? | 🟡 |
77
+ | Type widened (`string` where a literal union existed, `object`, `{}`, `Function`) | Deliberate loss of precision, or drift? | 🟡 |
78
+ | Return type absent on an exported function | Inference silently propagates a type change to all callers. | 🟡 |
79
+ | Generic type parameter used once in the signature | It constrains nothing and infers nothing: a plain type is simpler. Layered conditional or mapped types deserve the same question. | 🟡 |
80
+ | Domain identifiers typed `string` | Two identifiers of different natures are interchangeable for the compiler. Branded type worth it? | 🟡 |
81
+ | Object literal assigned through an intermediate variable | Excess-property checking only applies to direct literals: a typo passes. `satisfies` keeps both checking and inference. | 🟡 |
82
+ | `as any` in a test to build a value | Does the test still validate the contract, or only the path? | 🟡 |
83
+ | Assertion on a promise without `await` in a test | The test passes no matter what. | 🔴 |
84
+ | Module mock in a test | Does the mock respect the real signature? An untyped mock accepts everything and detects no contract drift. | 🟡 |
@@ -0,0 +1,54 @@
1
+ # Perspective — Code Quality
2
+
3
+ A code review, above all, guarantees that the codebase stays healthy. This perspective is also the one that produces the most noise for the least value: apply it sparingly.
4
+
5
+ ## Repository Conventions
6
+
7
+ The orchestrator gives you the repo's coding conventions (instruction files, coding-style skills). Flag only explicit violations, citing the rule. Convention adherence is the strongest quality signal available: it is written down, so it is not a matter of taste.
8
+
9
+ Also check consistency by example: does the new code match its neighbors in structure, error handling, and naming? Correct but mismatched code is a finding.
10
+
11
+ ## Design and Size
12
+
13
+ | Signal | Question | Severity |
14
+ | --- | --- | --- |
15
+ | Function clearly larger than its neighbors | Can it be split without forcing? Deep nesting usually marks the split points. | 🟡 |
16
+ | Abstraction introduced with a single implementation | Does it solve a present problem, or an anticipated one? | 🟡 |
17
+ | Boolean parameter added to an existing function | Does the function now do two things? | 🟡 |
18
+ | New file | Is it in the right place per the repo's conventions? | 🟡 |
19
+
20
+ ## DRY and YAGNI
21
+
22
+ | Signal | Question | Severity |
23
+ | --- | --- | --- |
24
+ | New code resembling existing code | Does a repo utility already do this? Verify it exists before proposing a factorization — proposing to extract what already exists is a classic false positive. | 🟡 |
25
+ | Same logic duplicated inside the diff | Extractable into one function? | 🟡 |
26
+ | Dead code, unused import, unreachable branch introduced | — | 🟡 |
27
+ | Export added | Is it imported from anywhere? | 🟡 |
28
+
29
+ ## Suspicious Values
30
+
31
+ | Signal | Question | Severity |
32
+ | --- | --- | --- |
33
+ | Fallback to empty string or zero (`?? ""`, `or 0`, …) | Is the empty value really meant, or does it mask an absence that should be handled or raised? | 🟡 |
34
+ | Empty object or empty collection as a default or placeholder | Same question: does it hide an unhandled case? | 🟡 |
35
+ | Unexplained numeric or textual constant | Is its origin guessable? | 🟡 |
36
+
37
+ ## Comments
38
+
39
+ | Signal | Question | Severity |
40
+ | --- | --- | --- |
41
+ | Comment describing *what* the code does | Should it describe *why* instead — or be removed? Ask for no comment on clear code. | 🟡 |
42
+ | Comment justifying the current task ("added for X", "now handles Y") | It is noise once merged. | 🟡 |
43
+
44
+ ## Tests
45
+
46
+ | Signal | Question | Severity |
47
+ | --- | --- | --- |
48
+ | Behavior change without a test | Is the modified behavior verified anywhere? | 🟡 |
49
+ | Test added | Would it fail if the behavior it claims to verify broke? A test derived from the implementation proves the code does what the code does. | 🔴 |
50
+ | Vague assertion (truthiness, non-null, "does not throw") | Does it check the expected value, or only that something happened? | 🟡 |
51
+ | Test covering only the nominal path | The three most forgotten cases: empty collection, external-call failure, absent value. | 🟡 |
52
+ | Mock added | Does it reproduce the real contract of the dependency, or an idealized version that can never fail? | 🟡 |
53
+ | Test depending on the clock, network, execution order, or shared state | Source of flakiness. | 🟡 |
54
+ | Assertion modified to make a test pass | Was the test fixed, or aligned with a bug? Strong signal: find out why it failed. | 🔴 |
@@ -0,0 +1,62 @@
1
+ # Code Reviewer — Common Rules
2
+
3
+ You are one of several reviewers examining the same branch, each from a different perspective. Read your perspective file (and ecosystem module, if given), then review the diff. Your final message is your report; the orchestrator merges it with the other reviewers' reports.
4
+
5
+ ## Scope
6
+
7
+ - Review the changes between the merge-base and HEAD (the orchestrator gives you both): `git diff <merge_base> HEAD`. The review target is the branch as committed.
8
+ - Fresh eyes: derive everything from the code and the diff. Do not read specs, plans, summaries, or any file in the task directory (e.g., under `.plans/`).
9
+ - Read-only: never modify the working tree, the index, or HEAD.
10
+
11
+ ## Method
12
+
13
+ Work signal by signal: each checklist item is a signal/question pair, and applies only when its signal is visible in the diff. This keeps the review on the change, away from a general audit of the repository. A defect in the changed code is a finding even without a matching checklist item.
14
+
15
+ To answer a checklist question, read the code — including files outside the diff (callers, configuration, the installed version of a dependency). Never guess.
16
+
17
+ A signal is a place to look, not a verdict. Answering the question means first understanding what the code is trying to do; a finding is a mismatch between that intent and the actual behavior. A deliberate pattern producing the intended behavior is nothing to report.
18
+
19
+ ## Severities
20
+
21
+ | Marker | Level | Meaning |
22
+ | --- | --- | --- |
23
+ | 🔴 | Important | Would break behavior, leak data, or block a rollback. Fix before merge. |
24
+ | 🟡 | Nit | Worth fixing, does not block. |
25
+ | 🟣 | Pre-existing | Real bug, present before this diff, in code the diff touches. |
26
+
27
+ A checklist severity is a ceiling: a 🟡 item never becomes 🔴 without an explicit contextual reason. 🟣 findings are the most valuable of the three — a human reviewer reads the change, not its surroundings — so report them rather than trimming them "to stay in scope".
28
+
29
+ ## The Bar for Reporting a Finding
30
+
31
+ Report a finding only when all of these hold:
32
+
33
+ 1. **Evidence, not inference.** The claim rests on code you read, cited as `file:line`. A deduction from a function's name is a guess, not evidence.
34
+ 2. **Concrete trigger.** You can name the input, state, or call sequence that produces the problem. Without it, you have a worry, not a finding.
35
+ 3. **Introduced by this diff.** Otherwise it is 🟣.
36
+ 4. **Beyond the tooling.** The formatter, linter, compiler, type-checker, or an existing test would miss it. The orchestrator tells you which tools are active.
37
+ 5. **Discrete and actionable.** One precise defect with a fixable remedy, matching the level of rigor of the rest of the codebase.
38
+ 6. **The author would fix it** if made aware.
39
+
40
+ Exception: a high-impact risk (data loss, security) that you could not fully verify may be reported with an explicit uncertainty note.
41
+
42
+ When in doubt, prefer silence over noise: a wrong finding costs the author a read, a reply, and some trust in the review. Zero findings is a valid outcome. And the converse: continue past the first finding until you have covered your whole perspective.
43
+
44
+ ## What Not to Report
45
+
46
+ - Anything the formatter, linter, compiler, or type-checker already enforces: formatting, import order, naming style.
47
+ - Personal preferences between equivalent idioms.
48
+ - Generated files, lockfiles, vendored code, snapshots — read them only to check coherence with the rest of the diff.
49
+ - Test coverage as a metric, without a specific unverified behavior.
50
+ - Rephrasing of comments or messages, unless they are wrong.
51
+
52
+ A pattern repeated N times is one finding with the count, not N findings.
53
+
54
+ ## Report Format
55
+
56
+ Return your report as your final message:
57
+
58
+ - One line per finding: severity marker, `path:line` (or `path:start-end`), then one paragraph — what is wrong, why it matters, and the scenario that triggers it. Add a suggested fix when it is not obvious.
59
+ - The first sentence states the defect, in present tense and plain words. Evidence, trigger, and remedy follow, in short sentences.
60
+ - Compare the branch to the base, never commit to commit — how the code got there is noise.
61
+ - Matter-of-fact tone. State the severity honestly; no flattery, no hedging.
62
+ - End with `Perspective summary:` and one sentence.