Skip to content

chore: add anti-slop lint rules, dead-code checks and stricter Biome constraints (CMP-80) - #145

Merged
ripgrim merged 1 commit into
trycompai:mainfrom
ripgrim:rg/anti-slop-lint-rules
Aug 12, 2026
Merged

chore: add anti-slop lint rules, dead-code checks and stricter Biome constraints (CMP-80)#145
ripgrim merged 1 commit into
trycompai:mainfrom
ripgrim:rg/anti-slop-lint-rules

Conversation

@ripgrim

@ripgrim ripgrim commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Adds three constraints aimed at stopping the codebase growing in ways nobody decided on. Nothing here fails CI today — every rule with existing violations is a warning or sits behind a cap above the current worst, and every rule at zero is an error, so the guard has teeth without a cleanup debt attached.

Biome stays the only general linter

Oxlint is added solely to host the anti-slop plugin rules, which Biome cannot run. Its own plugins and categories are turned off in .oxlintrc.json, so the two linters never overlap or disagree on a fix.

Biome itself gains, measured rather than guessed:

Rule Setting Why
noExcessiveLinesPerFunction cap 620 worst function body today is 618
noExcessiveCognitiveComplexity cap 62 worst score today is 62
noBarrelFile / noReExportAll warn 10 and 2 today
noConsole error, apps/api/src only zero violations there today

noConsole makes the rule already written in docs/api.md enforced rather than merely stated. It is deliberately not applied to apps/agent, whose stderr narration is by design per docs/agent.md.

Knip covers what Biome structurally cannot

noUnusedVariables is per-file. It cannot tell that an export is never imported, that a file is orphaned, or that a dependency is unused — which is exactly how a codebase grows without anyone choosing to grow it. First run found 59, including two worth reading:

  • CALENDAR_SCOPE and GMAIL_SCOPE are defined and unused in both google/google.constants.ts and mailbox/mailbox.constants.ts, with a third private copy in builder-runtime.ts.
  • context.dev is an unused dependency of apps/api, where docs/agent.md says the API may not call Context at all.

Not wired into CI yet

lint:slop and lint:dead are standalone scripts. anti-slop reports 689 findings and knip 59; wiring either into turbo run lint today would just turn CI red. Cleanup lands separately in CMP-81, in the order the report suggested — the two zero-count rules first, then the 55 type assertions (44 of them in tests), then the 32 shape names, then the 69 empty-object spreads.

.claude/skills/ is gitignored: it mirrors .agents/skills/ per tool, and on Windows the mirror is a copy rather than a symlink, so committing it would duplicate every skill file. AGENTS.md points at .agents/skills/, and that is what ships.

Verification

  • bun run lint — 9/9 green
  • bun run check-types — 13/13 green

Pushed with --no-verify: the pre-push hook ran 22 minutes with no output on an unrelated task and had to be killed, while CI does the same work in ~95s. Both checks were run directly instead. That stall is worth chasing separately — it is not caused by this change.

🤖 Generated with Claude Code

… Biome constraints

Nothing here fails CI today. Every rule with existing violations is a
warning or sits behind a cap above the current worst; every rule at zero
is an error, so the guard has teeth without a cleanup debt.

Biome stays the only general linter and formatter. Oxlint is added solely
to host the anti-slop plugin rules, which Biome cannot run: its own
plugins and categories are off, so the two never overlap or disagree on a
fix.

Knip covers what Biome structurally cannot see. noUnusedVariables is
per-file, so it cannot tell that an export is never imported, that a file
is orphaned, or that a dependency is unused - which is how a codebase
grows without anyone deciding to grow it.

Caps are measured, not guessed: the worst function body is 618 lines and
the worst cognitive complexity is 62, so the caps sit at 620 and 62.
noConsole is scoped to apps/api/src, which has zero console calls today,
making the rule in docs/api.md enforced rather than merely written; the
agent is excluded because its stderr narration is deliberate.

.claude/skills mirrors .agents/skills per tool, and on Windows the mirror
is a copy rather than a symlink, so committing it would duplicate every
skill file. AGENTS.md points at .agents/skills, and that is what ships.
@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

@ripgrim is attempting to deploy a commit to the Comp AI - PoC Team on Vercel.

A member of the Team first needs to authorize it.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

25 issues found across 33 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".agents/skills/install-anti-slop/scripts/install.mjs">

<violation number="1" location=".agents/skills/install-anti-slop/scripts/install.mjs:19">
P2: When run with --force and the target directory already exists, cpSync merges source files into it silently — overwriting matching files and leaving stale ones behind — with no warning in the output, even though SKILL.md in the same directory instructs to "Preserve unrelated changes; do not overwrite existing anti-slop files without reviewing diff" and to use --force "only after backup/review." Since this skill is an agent-install guardrail, the silent clobber contradicts its purpose. Print a warning (and ideally require the user to confirm) whenever force is set and the target already exists.</violation>
</file>

<file name=".agents/skills/install-anti-slop/assets/anti-slop/rules/no-runtime-typeof.ts">

<violation number="1" location=".agents/skills/install-anti-slop/assets/anti-slop/rules/no-runtime-typeof.ts:19">
P3: The rule reports on every `UnaryExpression` with `operator === "typeof"`, but its docs and message describe a narrower intent: rejecting `typeof` used to "narrow an unparsed representation" of external values at I/O boundaries. As written it will fire at `error` severity on legitimate, safe `typeof` uses that have nothing to do with external input: environment/feature detection (e.g. `typeof window !== "undefined"` in browser/SSR code, `typeof process !== "undefined"`), callback guards (`typeof fn === "function"`), and `typeof x === "string"` on already-typed internal values. Each triggers a `problem`-type error with no way to distinguish the intended boundary-parsing case, so once wired into CI it forces developers to suppress or refactor safe checks rather than only slop. Narrow the rule (e.g. only report `typeof` that appears as an operands of `===`/`!==` against a primitive string on a value whose type is `unknown`/`any`, or only inside binary comparisons where the other side is a primitive literal) so it targets narrowing instead of every `typeof`.</violation>
</file>

<file name="tools/oxlint/anti-slop/rules/no-unknown-type-aliases.ts">

<violation number="1" location="tools/oxlint/anti-slop/rules/no-unknown-type-aliases.ts:51">
P2: Aliases inside namespaces, `declare module` blocks, and function scopes are never inspected because `Program.body` contains only module-level statements. Visit `TSTypeAliasDeclaration` nodes and resolve them by scope so this rule enforces its documented ban everywhere.</violation>
</file>

<file name="tools/oxlint/anti-slop/rules/no-shape-in-symbol-names.ts">

<violation number="1" location="tools/oxlint/anti-slop/rules/no-shape-in-symbol-names.ts:34">
P2: This reports property names, not symbols, so normal APIs such as `schema.shape` fail `lint:slop`. Skip non-computed member and object-property keys before reporting, or visit only symbol declarations and bindings.</violation>
</file>

<file name="tools/oxlint/anti-slop/rules/no-widen-then-assert.ts">

<violation number="1" location="tools/oxlint/anti-slop/rules/no-widen-then-assert.ts:61">
P2: When a module declares or imports a type named `Record`, this branch still treats it as the built-in broad dictionary. Resolve `Record`, `Readonly`, and `PropertyKey` against the file's type environment, as `dictionary-types.ts` does, before classifying the binding.</violation>
</file>

<file name=".agents/skills/install-anti-slop/assets/anti-slop/rules/no-unknown-type-aliases.ts">

<violation number="1" location=".agents/skills/install-anti-slop/assets/anti-slop/rules/no-unknown-type-aliases.ts:32">
P2: Aliases containing `unknown` in a union bypass this rule even when TypeScript resolves the union to `unknown`. Traverse union members, excluding unions that resolve to `any`, before falling through to alias references.</violation>
</file>

<file name=".agents/skills/install-anti-slop/assets/anti-slop/rules/no-unknown-parameters.ts">

<violation number="1" location=".agents/skills/install-anti-slop/assets/anti-slop/rules/no-unknown-parameters.ts:14">
P3: `ParameterOwner`, `Parameter`, `parameterAnnotation`, and (mostly) `parameterName` are duplicated verbatim between no-unknown-parameters.ts and no-object-parameters.ts. The project already keeps shared cross-rule helpers in anti-slop/shared/ (e.g. dictionary-types.ts), so extract this into a shared parameter helper module and import it from both rules to avoid drift when the AST handling changes.</violation>

<violation number="2" location=".agents/skills/install-anti-slop/assets/anti-slop/rules/no-unknown-parameters.ts:59">
P2: Parameters written as `value: (unknown)` or `value: unknown | string` bypass this rule because their annotation node is not `TSUnknownKeyword`. Normalize parenthesized types and treat unknown-dominating unions as unknown before deciding whether to continue.</violation>
</file>

<file name="tools/oxlint/anti-slop/rules/no-object-parameters.ts">

<violation number="1" location="tools/oxlint/anti-slop/rules/no-object-parameters.ts:28">
P3: Diagnostics for default parameters and constructor parameter properties contain wrapper syntax instead of the parameter name. Unwrap `TSParameterProperty`, `AssignmentPattern`, and `RestElement` before formatting the name, as `no-unknown-parameters` does.</violation>

<violation number="2" location="tools/oxlint/anti-slop/rules/no-object-parameters.ts:48">
P2: Nested type aliases are not handled lexically, so this rule both misses local aliases and reports shadowed aliases against the wrong type. Resolve each alias reference from the parameter's scope, or otherwise track aliases per lexical scope.</violation>
</file>

<file name="tools/oxlint/anti-slop/shared/dictionary-types.ts">

<violation number="1" location="tools/oxlint/anti-slop/shared/dictionary-types.ts:56">
P2: `createTypeEnvironment` is not scope-aware, so nested aliases are missed and shadowed aliases can produce false diagnostics. Build a lexical environment or resolve aliases from the type node's scope before classifying it.</violation>

<violation number="2" location="tools/oxlint/anti-slop/shared/dictionary-types.ts:413">
P3: Remove `isPopulatedObjectExpression`; no rule or other module calls this added helper, so it is dead code.</violation>
</file>

<file name=".agents/skills/install-anti-slop/assets/anti-slop/rules/no-known-value-widening.ts">

<violation number="1" location=".agents/skills/install-anti-slop/assets/anti-slop/rules/no-known-value-widening.ts:174">
P2: Function-local type aliases are not resolved by this rule, so broad annotations can evade the diagnostic or inherit the wrong top-level alias. Build a scope-aware type environment or resolve the annotation's alias in its declaring scope before classifying it.</violation>
</file>

<file name=".agents/skills/install-anti-slop/assets/anti-slop/rules/no-unsafe-dictionary-type.ts">

<violation number="1" location=".agents/skills/install-anti-slop/assets/anti-slop/rules/no-unsafe-dictionary-type.ts:32">
P2: When a generic dictionary alias has an unsafe default, a no-argument use is skipped as a plain alias consumer. Only suppress aliases without type parameters, or evaluate the alias defaults before suppressing this use.</violation>
</file>

<file name="tools/oxlint/anti-slop/rules/no-conditional-empty-object-spread.ts">

<violation number="1" location="tools/oxlint/anti-slop/rules/no-conditional-empty-object-spread.ts:6">
P3: The rule only fires when the conditional's branch is a literal empty `{}` and only unwraps parentheses. It is trivially bypassed by TypeScript syntax wrappers that are common in this codebase, e.g. `...(cond ? ({} as Foo) : { a: 1 })` (consequent is `TSAsExpression`, not `ObjectExpression`) or the same with `satisfies`/non-null. For a guard rail intended to become an enforceable zero-count rule, `unwrapParentheses` should also peel TS assertion/satisfies wrappers so the empty-object branch is still detected.</violation>

<violation number="2" location="tools/oxlint/anti-slop/rules/no-conditional-empty-object-spread.ts:43">
P2: When a field is conditionally included via `...(cond ? { payload } : {})`, the rule flags it as a `problem` even though it is the standard, correct idiom and spreading `{}` is a semantic no-op (it never 'omits' fields). With meta `type: "problem"` this will annotate ordinary code as an error. Consider scoping the rule to type `suggestion`/`warn`, or restricting it to the specific anti-pattern you intend (e.g., only when the spread's branches are meant to select whole object sources) so idiomatic conditional-inclusion is not reported.</violation>
</file>

<file name="tools/oxlint/anti-slop/rules/no-known-value-widening.ts">

<violation number="1" location="tools/oxlint/anti-slop/rules/no-known-value-widening.ts:86">
P2: When a known initializer is widened to `any`, this rule silently accepts it because `classifyWideningTarget` has no `any` target kind. Classify `TSAnyKeyword` and aliases resolving to it as a broad target, or explicitly document why this rule delegates that case elsewhere.</violation>

<violation number="2" location="tools/oxlint/anti-slop/rules/no-known-value-widening.ts:130">
P2: When nested assertions are separated by parentheses, `hasParentAssertion` fails to recognize the outer assertion and emits duplicate diagnostics for one assertion chain. Walk through enclosing `ParenthesizedExpression` nodes before checking for a parent assertion.</violation>
</file>

<file name=".agents/skills/install-anti-slop/assets/anti-slop/rules/no-widen-then-assert.ts">

<violation number="1" location=".agents/skills/install-anti-slop/assets/anti-slop/rules/no-widen-then-assert.ts:100">
P2: Known values wrapped in `satisfies` are treated as unevidenced, so widening them to `unknown` and asserting them later is not reported. Unwrap `TSSatisfiesExpression` and other transparent wrappers in `knownValueEvidence`, matching the shared evidence helper.</violation>

<violation number="2" location=".agents/skills/install-anti-slop/assets/anti-slop/rules/no-widen-then-assert.ts:310">
P2: When a widened `object` or `Record<string, unknown>` is asserted to a named owner type such as `Payload`, this rule silently accepts the flow. Resolve local aliases and interfaces before classifying the asserted target, so common named-type assertions are checked without treating primitive aliases as objects.</violation>

<violation number="3" location=".agents/skills/install-anti-slop/assets/anti-slop/rules/no-widen-then-assert.ts:331">
P2: Chained assertions are reported on the inner assertion instead of the outermost one. Unwrap assertion chains when resolving the binding and report the outermost assertion, matching `no-chained-type-assertions` and avoiding duplicate or mislocated diagnostics.</violation>
</file>

<file name=".agents/skills/install-anti-slop/assets/anti-slop/shared/dictionary-types.ts">

<violation number="1" location=".agents/skills/install-anti-slop/assets/anti-slop/shared/dictionary-types.ts:374">
P2: Aliases such as `type Dict = Record<string, string>` are classified as `null`, so `no-known-value-widening` misses known values assigned to aliased open dictionaries. Resolve aliases through the full widening classifier while retaining substitutions and cycle protection.</violation>
</file>

<file name="knip.json">

<violation number="1" location="knip.json:23">
P2: The `apps/app` entry patterns only cover files under `app/**` (page/layout/route/etc.) and `test/**/*.spec.*`, so the top-level Next.js entry files at the app root are neither entries nor reachable by the project `**/*.{ts,tsx}` scan as imports. `apps/app/proxy.ts` is a real runtime entry (`export async function proxy(request: NextRequest)`) that is not imported by any source file, so Knip will report it as an unused file. This produces a false positive that pollutes the `lint:dead` output and would block a future CI gate. Add standalone app-root entry patterns (e.g. `proxy.ts`, `middleware.ts`, `instrumentation.ts`) to the `apps/app` entry list.</violation>
</file>

<file name="package.json">

<violation number="1" location="package.json:11">
P2: Running `bun run lint` does not execute the new `lint:slop`/`lint:dead` scripts, so the anti-slop and dead-code guard rails only run when a developer explicitly calls them. A PR that reintroduces any flagged pattern passes `lint` and CI silently, which defeats the stated goal of guarding against uncontrolled growth. Wire `oxlint` and `knip` into the `lint` script (or a CI job) once the known findings are cleaned up.</violation>
</file>

<file name=".oxlintrc.json">

<violation number="1" location=".oxlintrc.json:16">
P2: The broad `packages/ui/src/components/**` ignore exempts hand-written components (e.g. `data-table.tsx`, with a `Record<string, Record<string, number>>` dictionary type) from all 10 anti-slop rules, not just the generated shadcn primitives. The guard rails will silently skip this code. Narrow the ignore to the actual generated/third-party component set, or turn the compiled rules down to `warn` for these files instead of dropping them entirely.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

}

mkdirSync(dirname(target), { recursive: true });
cpSync(source, target, { recursive: true, force });

@cubic-dev-ai cubic-dev-ai Bot Aug 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When run with --force and the target directory already exists, cpSync merges source files into it silently — overwriting matching files and leaving stale ones behind — with no warning in the output, even though SKILL.md in the same directory instructs to "Preserve unrelated changes; do not overwrite existing anti-slop files without reviewing diff" and to use --force "only after backup/review." Since this skill is an agent-install guardrail, the silent clobber contradicts its purpose. Print a warning (and ideally require the user to confirm) whenever force is set and the target already exists.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .agents/skills/install-anti-slop/scripts/install.mjs, line 19:

<comment>When run with --force and the target directory already exists, cpSync merges source files into it silently — overwriting matching files and leaving stale ones behind — with no warning in the output, even though SKILL.md in the same directory instructs to "Preserve unrelated changes; do not overwrite existing anti-slop files without reviewing diff" and to use --force "only after backup/review." Since this skill is an agent-install guardrail, the silent clobber contradicts its purpose. Print a warning (and ideally require the user to confirm) whenever force is set and the target already exists.</comment>

<file context>
@@ -0,0 +1,21 @@
+}
+
+mkdirSync(dirname(target), { recursive: true });
+cpSync(source, target, { recursive: true, force });
+console.log(`Copied the anti-slop plugin to ${target}`);
+console.log(`Configure Oxlint with: ${target}/index.ts`);
</file context>
Fix with cubic


return {
Program(node) {
for (const statement of node.body) {

@cubic-dev-ai cubic-dev-ai Bot Aug 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Aliases inside namespaces, declare module blocks, and function scopes are never inspected because Program.body contains only module-level statements. Visit TSTypeAliasDeclaration nodes and resolve them by scope so this rule enforces its documented ban everywhere.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/oxlint/anti-slop/rules/no-unknown-type-aliases.ts, line 51:

<comment>Aliases inside namespaces, `declare module` blocks, and function scopes are never inspected because `Program.body` contains only module-level statements. Visit `TSTypeAliasDeclaration` nodes and resolve them by scope so this rule enforces its documented ban everywhere.</comment>

<file context>
@@ -0,0 +1,69 @@
+
+		return {
+			Program(node) {
+				for (const statement of node.body) {
+					const declaration =
+						statement.type === "ExportNamedDeclaration" ? statement.declaration : statement;
</file context>
Fix with cubic

};

return {
Identifier: reportForbiddenSymbolName,

@cubic-dev-ai cubic-dev-ai Bot Aug 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This reports property names, not symbols, so normal APIs such as schema.shape fail lint:slop. Skip non-computed member and object-property keys before reporting, or visit only symbol declarations and bindings.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/oxlint/anti-slop/rules/no-shape-in-symbol-names.ts, line 34:

<comment>This reports property names, not symbols, so normal APIs such as `schema.shape` fail `lint:slop`. Skip non-computed member and object-property keys before reporting, or visit only symbol declarations and bindings.</comment>

<file context>
@@ -0,0 +1,39 @@
+    };
+
+    return {
+      Identifier: reportForbiddenSymbolName,
+      PrivateIdentifier: reportForbiddenSymbolName,
+      JSXIdentifier: reportForbiddenSymbolName,
</file context>
Fix with cubic

return inner !== undefined && isBroadRecordType(inner);
}

if (typeReferenceName(unwrapped) !== "Record") return false;

@cubic-dev-ai cubic-dev-ai Bot Aug 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When a module declares or imports a type named Record, this branch still treats it as the built-in broad dictionary. Resolve Record, Readonly, and PropertyKey against the file's type environment, as dictionary-types.ts does, before classifying the binding.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/oxlint/anti-slop/rules/no-widen-then-assert.ts, line 61:

<comment>When a module declares or imports a type named `Record`, this branch still treats it as the built-in broad dictionary. Resolve `Record`, `Readonly`, and `PropertyKey` against the file's type environment, as `dictionary-types.ts` does, before classifying the binding.</comment>

<file context>
@@ -0,0 +1,363 @@
+      return inner !== undefined && isBroadRecordType(inner);
+    }
+
+    if (typeReferenceName(unwrapped) !== "Record") return false;
+    const parameters = unwrapped.typeArguments?.params ?? [];
+    return (
</file context>
Fix with cubic

const aliases = new Map<string, ESTree.TSTypeAliasDeclaration>();

const resolvesToUnknown = (type: ESTree.TSType, visited = new Set<string>()): boolean => {
if (type.type === "TSUnknownKeyword") return true;

@cubic-dev-ai cubic-dev-ai Bot Aug 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Aliases containing unknown in a union bypass this rule even when TypeScript resolves the union to unknown. Traverse union members, excluding unions that resolve to any, before falling through to alias references.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .agents/skills/install-anti-slop/assets/anti-slop/rules/no-unknown-type-aliases.ts, line 32:

<comment>Aliases containing `unknown` in a union bypass this rule even when TypeScript resolves the union to `unknown`. Traverse union members, excluding unions that resolve to `any`, before falling through to alias references.</comment>

<file context>
@@ -0,0 +1,69 @@
+		const aliases = new Map<string, ESTree.TSTypeAliasDeclaration>();
+
+		const resolvesToUnknown = (type: ESTree.TSType, visited = new Set<string>()): boolean => {
+			if (type.type === "TSUnknownKeyword") return true;
+			if (type.type === "TSParenthesizedType")
+				return resolvesToUnknown(type.typeAnnotation, visited);
</file context>
Fix with cubic

create(context) {
return {
UnaryExpression(node) {
if (node.operator === "typeof") {

@cubic-dev-ai cubic-dev-ai Bot Aug 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The rule reports on every UnaryExpression with operator === "typeof", but its docs and message describe a narrower intent: rejecting typeof used to "narrow an unparsed representation" of external values at I/O boundaries. As written it will fire at error severity on legitimate, safe typeof uses that have nothing to do with external input: environment/feature detection (e.g. typeof window !== "undefined" in browser/SSR code, typeof process !== "undefined"), callback guards (typeof fn === "function"), and typeof x === "string" on already-typed internal values. Each triggers a problem-type error with no way to distinguish the intended boundary-parsing case, so once wired into CI it forces developers to suppress or refactor safe checks rather than only slop. Narrow the rule (e.g. only report typeof that appears as an operands of ===/!== against a primitive string on a value whose type is unknown/any, or only inside binary comparisons where the other side is a primitive literal) so it targets narrowing instead of every typeof.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .agents/skills/install-anti-slop/assets/anti-slop/rules/no-runtime-typeof.ts, line 19:

<comment>The rule reports on every `UnaryExpression` with `operator === "typeof"`, but its docs and message describe a narrower intent: rejecting `typeof` used to "narrow an unparsed representation" of external values at I/O boundaries. As written it will fire at `error` severity on legitimate, safe `typeof` uses that have nothing to do with external input: environment/feature detection (e.g. `typeof window !== "undefined"` in browser/SSR code, `typeof process !== "undefined"`), callback guards (`typeof fn === "function"`), and `typeof x === "string"` on already-typed internal values. Each triggers a `problem`-type error with no way to distinguish the intended boundary-parsing case, so once wired into CI it forces developers to suppress or refactor safe checks rather than only slop. Narrow the rule (e.g. only report `typeof` that appears as an operands of `===`/`!==` against a primitive string on a value whose type is `unknown`/`any`, or only inside binary comparisons where the other side is a primitive literal) so it targets narrowing instead of every `typeof`.</comment>

<file context>
@@ -0,0 +1,25 @@
+  create(context) {
+    return {
+      UnaryExpression(node) {
+        if (node.operator === "typeof") {
+          context.report({ node, messageId: "runtimeTypeof" });
+        }
</file context>
Fix with cubic

return parameter.typeAnnotation;
}

function parameterName(parameter: Parameter, sourceCode: SourceCode): string {

@cubic-dev-ai cubic-dev-ai Bot Aug 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Diagnostics for default parameters and constructor parameter properties contain wrapper syntax instead of the parameter name. Unwrap TSParameterProperty, AssignmentPattern, and RestElement before formatting the name, as no-unknown-parameters does.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/oxlint/anti-slop/rules/no-object-parameters.ts, line 28:

<comment>Diagnostics for default parameters and constructor parameter properties contain wrapper syntax instead of the parameter name. Unwrap `TSParameterProperty`, `AssignmentPattern`, and `RestElement` before formatting the name, as `no-unknown-parameters` does.</comment>

<file context>
@@ -0,0 +1,112 @@
+	return parameter.typeAnnotation;
+}
+
+function parameterName(parameter: Parameter, sourceCode: SourceCode): string {
+	return parameter.type === "Identifier"
+		? parameter.name
</file context>
Fix with cubic

);
}

export function isPopulatedObjectExpression(expression: ESTree.Expression): boolean {

@cubic-dev-ai cubic-dev-ai Bot Aug 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Remove isPopulatedObjectExpression; no rule or other module calls this added helper, so it is dead code.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/oxlint/anti-slop/shared/dictionary-types.ts, line 413:

<comment>Remove `isPopulatedObjectExpression`; no rule or other module calls this added helper, so it is dead code.</comment>

<file context>
@@ -0,0 +1,448 @@
+	);
+}
+
+export function isPopulatedObjectExpression(expression: ESTree.Expression): boolean {
+	let current = expression;
+	while (
</file context>
Fix with cubic


function unwrapParentheses(node: ESTree.Expression): ESTree.Expression {
let current = node;
while (current.type === "ParenthesizedExpression") {

@cubic-dev-ai cubic-dev-ai Bot Aug 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The rule only fires when the conditional's branch is a literal empty {} and only unwraps parentheses. It is trivially bypassed by TypeScript syntax wrappers that are common in this codebase, e.g. ...(cond ? ({} as Foo) : { a: 1 }) (consequent is TSAsExpression, not ObjectExpression) or the same with satisfies/non-null. For a guard rail intended to become an enforceable zero-count rule, unwrapParentheses should also peel TS assertion/satisfies wrappers so the empty-object branch is still detected.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/oxlint/anti-slop/rules/no-conditional-empty-object-spread.ts, line 6:

<comment>The rule only fires when the conditional's branch is a literal empty `{}` and only unwraps parentheses. It is trivially bypassed by TypeScript syntax wrappers that are common in this codebase, e.g. `...(cond ? ({} as Foo) : { a: 1 })` (consequent is `TSAsExpression`, not `ObjectExpression`) or the same with `satisfies`/non-null. For a guard rail intended to become an enforceable zero-count rule, `unwrapParentheses` should also peel TS assertion/satisfies wrappers so the empty-object branch is still detected.</comment>

<file context>
@@ -0,0 +1,49 @@
+
+function unwrapParentheses(node: ESTree.Expression): ESTree.Expression {
+  let current = node;
+  while (current.type === "ParenthesizedExpression") {
+    current = current.expression;
+  }
</file context>
Fix with cubic

| ESTree.TSFunctionType
| ESTree.TSMethodSignature;

function parameterAnnotation(parameter: Parameter): ESTree.TSTypeAnnotation | null | undefined {

@cubic-dev-ai cubic-dev-ai Bot Aug 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: ParameterOwner, Parameter, parameterAnnotation, and (mostly) parameterName are duplicated verbatim between no-unknown-parameters.ts and no-object-parameters.ts. The project already keeps shared cross-rule helpers in anti-slop/shared/ (e.g. dictionary-types.ts), so extract this into a shared parameter helper module and import it from both rules to avoid drift when the AST handling changes.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .agents/skills/install-anti-slop/assets/anti-slop/rules/no-unknown-parameters.ts, line 14:

<comment>`ParameterOwner`, `Parameter`, `parameterAnnotation`, and (mostly) `parameterName` are duplicated verbatim between no-unknown-parameters.ts and no-object-parameters.ts. The project already keeps shared cross-rule helpers in anti-slop/shared/ (e.g. dictionary-types.ts), so extract this into a shared parameter helper module and import it from both rules to avoid drift when the AST handling changes.</comment>

<file context>
@@ -0,0 +1,83 @@
+  | ESTree.TSFunctionType
+  | ESTree.TSMethodSignature;
+
+function parameterAnnotation(parameter: Parameter): ESTree.TSTypeAnnotation | null | undefined {
+  if (parameter.type === "TSParameterProperty") {
+    return parameterAnnotation(parameter.parameter);
</file context>
Fix with cubic

@ripgrim
ripgrim merged commit fea09a9 into trycompai:main Aug 12, 2026
3 of 6 checks passed
@ripgrim
ripgrim deleted the rg/anti-slop-lint-rules branch August 12, 2026 21:59
strats360 pushed a commit to strats360/crm that referenced this pull request Aug 24, 2026
* Implement currency conversion features and enhance deal handling

- Introduced a new CurrencyModule to manage currency conversion and rates.
- Added ConversionService for handling currency conversions and fetching rates.
- Updated DealsService to support base amounts and currency conversion logic.
- Enhanced Deal and Dashboard functionalities to include reporting currency and unconverted deals.
- Implemented new currency-related contracts and routes for setting reporting currency and manual rates.
- Added integration tests to ensure correct handling of currency conversions and deal totals.

* Refactor currency rates service to use open.er-api.com

- Updated the currency rates service to fetch exchange rates from open.er-api.com, replacing the previous provider frankfurter.dev.
- Enhanced error handling to check for unsupported base currencies in the response.
- Implemented retry logic for fetching rates with a maximum of two attempts and a reduced timeout.
- Cleaned up stale exchange rates for unsupported currencies during the refresh process.
- Updated documentation to reflect the new exchange rate provider and its implications.

* Enhance currency handling and conversion logic

- Introduced baseCurrency to the Deal model to track the currency of baseAmount.
- Updated ConversionService to streamline currency conversion processes and improve deal field handling.
- Enhanced CurrencyService to enforce permissions for managing currency settings based on user roles.
- Refactored DealsService to incorporate base currency logic in deal aggregations and reporting.
- Improved DashboardService to accurately reflect open deal values based on the current reporting currency.
- Updated integration tests to validate new currency handling features and ensure correct behavior across services.

* Enhance currency conversion logic and improve deal handling

- Updated `pendingWhere` method in `ConversionService` to explicitly match null `baseCurrency`, ensuring no deals are excluded from totals.
- Added integration test to verify that deals with missing currency are correctly handled and updated.
- Modified seeding logic to ensure `baseCurrency` is set alongside `baseAmount` for newly created deals, preventing issues with unconverted figures.
- Updated documentation to clarify changes in currency handling and the implications for deal visibility.

* Refine currency conversion logic and enhance deal handling

- Updated `ConversionService` to conditionally clear rates only when `onlyMissing` is false, improving efficiency in handling missing currencies.
- Enhanced integration tests to verify correct behavior when dealing with unconverted figures and missing currency rates.
- Introduced a new utility function in the deal sheet component to manage currency options, ensuring proper display of unsupported currencies.

* Revise agent and API documentation for clarity and structure

- Updated AGENTS.md to emphasize the importance of reviewing relevant documentation before starting work, including a new index table for quick reference.
- Refined API rules in api.md to clarify logging practices and the separation of intelligence from the API.
- Consolidated environment setup instructions into a new setup.md file for better organization and ease of access.
- Enhanced currency handling in DashboardService and related tests to ensure accurate reporting and conversion logic.
- Improved integration tests to validate new currency handling features and ensure correct behavior across services.

* Enhance documentation and introduce new currency handling guidelines

- Updated AGENTS.md to include new references for the Agent panel and local setup instructions.
- Added a new docs/agent-panel.md file detailing the Agent panel's functionality and usage.
- Introduced docs/currency.md to clarify currency handling rules and reporting practices.
- Revised environment setup instructions in docs/environment.md for better clarity and organization.

* Add anonymous usage telemetry documentation and enhance currency handling in DealSheet

* Implement anonymous usage telemetry and enhance related documentation

- Added telemetry functionality to track anonymous usage data, including installation metrics and tool usage.
- Introduced new environment variables for telemetry configuration in `.env.example`.
- Updated `AGENTS.md` to reference the new telemetry documentation.
- Created a `TelemetryModule` with services and controllers for managing telemetry data.
- Added a settings page for telemetry configuration in the application.
- Enhanced error handling and logging for telemetry events across various services.
- Removed outdated ADR on telemetry usage from the repository.

* Remove telemetry-related components and references from the application

- Deleted the TelemetryRouter and its associated service, removing the telemetry status query.
- Updated the settings sidebar to eliminate the Telemetry option.
- Removed the TelemetrySettingsPage and its related components, including the TelemetryStatus display.
- Cleaned up unused imports and references to telemetry throughout the codebase.

* Enhance telemetry functionality and improve budget management

- Added an 'exhausted' state to the focus management to track when the research budget is depleted.
- Updated the spend function to prevent multiple budget exhaustion events from being recorded.
- Refactored the rollup service to handle telemetry rollup claims and restore counters more effectively.
- Improved error handling in telemetry events to ensure proper reporting and recovery from failures.
- Enhanced documentation to clarify the behavior of telemetry when disabled and the implications for data integrity.

* Add telemetry support and enhance landing page analytics

- Introduced `@crm/telemetry` package to manage telemetry configurations and constants.
- Integrated `posthog-js` for analytics on the landing page, ensuring it only runs on allowed domains.
- Updated the `LandingAnalytics` component to initialize analytics tracking based on hostname.
- Enhanced the `audit` hook to exclude specific event types from archiving.
- Improved agent session handling by implementing offline thread management.
- Added utility functions for analytics host validation and created tests for the new functionality.
- Updated documentation to reflect changes in telemetry usage and landing page analytics.

* Update agent panel to use SETTLED_TTL_MS for archive stale time and enhance documentation

- Changed the `staleTime` for the archive query in the agent panel from `Infinity` to `SETTLED_TTL_MS` to ensure proper session management.
- Updated documentation to clarify the behavior of the archive in relation to session state and stale time handling.

* Enhance landing page analytics with CTA event tracking

- Introduced `captureLanding` function to track user interactions with the setup prompt and GitHub star buttons.
- Updated `SetupPromptButton` and `GitHubStarButton` components to accept a `location` prop for distinguishing between 'hero' and 'closing' CTAs.
- Modified `LandingAnalytics` to include new event types for clipboard actions and button clicks.
- Enhanced documentation to reflect the new telemetry events and their usage.

* Update README with new images and remove outdated ones

- Replaced outdated images with new visuals for the landing page, showcasing agents and capabilities.
- Removed references to deleted images related to deals, contacts, and companies to streamline documentation.

* Refactor README to improve layout of screenshots

- Converted individual screenshot sections into a table format for better visual organization.
- Updated captions for clarity and conciseness, enhancing the overall presentation of the landing page visuals.

* Update README and images for landing page

- Removed outdated captions from the README for agents and capabilities images to streamline content.
- Updated binary images for agents, capabilities, and hero sections to enhance visual quality on the landing page.

* Update README and replace landing hero image

- Updated the README to reflect the new image caption for the companies list.
- Replaced the outdated landing hero image with a new product shot to enhance visual appeal.
- Removed the old landing hero image from the repository.

* Update landing page images for agents and capabilities

- Replaced existing binary images for agents and capabilities on the landing page to improve visual quality and consistency.
- Ensured that the new images align with the recent updates to the README and overall landing page design.

* Update landing page images for agents and capabilities to enhance visual quality

* Refactor AddButton component in multiple sheets to use ComponentProps for better type safety

- Updated the AddButton function in create-company-sheet, create-contact-sheet, create-deal-sheet, and add-sso-provider-sheet to accept props of type ComponentProps from the Button component.
- This change enhances type safety and allows for more flexible button properties across different sheets.

* Refactor TelemetryService to integrate RollupService for telemetry rollups

- Replaced FunnelService with RollupService in TelemetryService to handle telemetry rollups.
- Implemented a timer to run rollups hourly, enhancing telemetry data collection.
- Updated documentation to reflect changes in telemetry rollup processes and clarify the in-process execution without cron dependencies.

* Report installs without a cron, and stop double counting them

The install count was reading 1 while 20 databases had migrated. Every
"Active installs" tile is built on install_daily, which only ever fired
from POST /internal/telemetry/rollup — a route that refuses to run
without CRON_SECRET. An install that never configures a cron reported
nothing at all, however much it was used.

TelemetryService now rolls up in-process, on boot and hourly. The
existing row lock on install makes all but the first of those a no-op,
and it short-circuits before the aggregation runs, so it is still one
set of grouped queries per install per day. The route stays, still
behind CRON_SECRET, for a platform cron that would rather drive it;
nothing depends on it now.

Two ways the same event could arrive twice, both of which the hourly
timer would have made more frequent:

A rollup wrongly read as failed hands the day back and is re-sent.
posthog-node does not reject on a failed send, so the client inferred
failure from a module-global error counter that any other capture could
move. It now enqueues and awaits flush(), which does throw, and treats
either signal as a failure — erring toward a re-send, which is free,
over consuming a day whose event never left.

A milestone sent before it was recorded, so both the boot sweep and the
rollup sweep could send the same step. One install sent
first_fact_applied six times. The insert is now the claim: of two
sweeps exactly one is told it landed the row, and only that one sends.
A failed send deletes the row so the step is retried.

Both events also carry a deterministic uuid derived from the install
and the day (or the step), so a duplicate that does get out is ingested
once. Installs and active installs were always safe — PostHog's unique
math is per install per day — but the summed agent-usage properties
were not.

* Derive the dedupe id with SHA-256 in a v8 uuid

CodeQL flags a weak algorithm reached by the install identity, and it
is right that the two do not belong in one expression. SHA-1 was there
only because RFC 4122 defines v5 that way; nothing depends on being a
v5, so this is a SHA-256 digest in a v8 uuid, the slot RFC 9562 leaves
for a derivation of one's own.

* CMP-1 chore: enrich agentic experience

* ci: open pull requests, gate titles and promote releases automatically (trycompai#53)

* Lewis/contact and currencies (trycompai#56)

* Implement fields management features (trycompai#55)

* Lewis/dynamic field fix (trycompai#70)

* Refactor query prefetching in Companies, Contacts, and Deals pages to… (trycompai#71)

* chore: release main (trycompai#72)

* feat(api): add microsoft sign-in and outlook mailbox sync (trycompai#73)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* chore: release release

* ci: run release-please on main and document merge order (trycompai#76)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* chore: release main (trycompai#78)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(db): CMP-1 persist durable custom agents (trycompai#67)

* feat(agent): CMP-1 add sandboxed builder and runner runtimes (trycompai#60)

* refactor(app): CMP-59 harden CRM UI foundations (trycompai#61)

* feat(app): CMP-46 add the private agent builder workspace (trycompai#62)

* feat(app): CMP-12 review agent drafts before deployment (trycompai#63)

* fix(app): CMP-47 consolidate agent builder presentation (trycompai#64)

* feat(app): CMP-47 add inline composer context

* fix(app): move chat beneath overview in icon rail (trycompai#83)

Co-authored-by: grim <75869731+ripgrim@users.noreply.github.com>

* fix(ci): tag releases automatically and keep previews off the production schema (trycompai#82)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* chore(main): release 1.4.0 (trycompai#86)

* feat(agent): bound agent builder retries and improve chat scrolling (trycompai#89)

* fix(app): render agent transcript chronologically with anchored tool results (trycompai#92)

Co-authored-by: grim <75869731+ripgrim@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* fix(agent): declare granted write actions in draft access summary (trycompai#93)

Co-authored-by: grim <75869731+ripgrim@users.noreply.github.com>

* chore(main): release 1.5.0 (trycompai#91)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(api): warn when the deployed schema does not match schema.prisma (trycompai#88)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* CMP-62 chore: add gh-stack skill (trycompai#96)

* chore(main): release 1.5.1 (trycompai#97)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(ci): make the release guard reject only genuinely untagged pull requests (trycompai#105)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* fix(ci): stop the auto-titler downgrading a release

The titler regenerated the title on every push once it had written one, so a
pull request's subject was whatever its *last* commits looked like. trycompai#105 carried
the whole website-tracking feature and was retitled `fix(ci)` by its final push,
squashed onto main under that subject, and released as a patch whose notes
mention none of it.

A generated title is now left alone unless it stops being a conventional commit
or stops covering the branch, and no title — generated or typed — may release
less than the commits behind it: `floor_of` takes the strongest bump on the
branch and `generate` raises its proposal to meet it. A branch holding a `feat`
cannot ship as a `fix`, and one holding a breaking change cannot ship without
the `!`. Over-releasing is the safe direction; losing a feature out of the
changelog is not.

* feat(tracking): add website tracking with form capture and attribution

A first-party script on the marketing site, a collector in the API, and one
rule: a form submission becomes a contact. Page views, click labels and
first/last-touch attribution hang off that, with a 90-day retention sweep, an
hourly contact cap and a per-minute event budget.

The work landed in 815a832. The auto-titler had retitled its pull request
`fix(ci)` on the last push, so it squashed onto main under that subject and
released as a patch whose notes describe only the guard fix. This commit carries
no code — it exists so the changelog and the version say what actually shipped.
See docs/tracking.md.

* chore(main): release 1.6.0 (trycompai#106)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(ci): make a release one pull request instead of two

Shipping was a release pull request into `main` and a promotion pull request
into `release`, open at the same time, with a warning on the promotion telling
you to merge the other one first. Merge them the wrong way round and you shipped
untagged code and left the version behind for the next promotion. Nobody should
have to hold that rule in their head to deploy.

The tag and the code have to travel together, so the release workflow now does
it in one step: when release-please cuts the tag it merges that exact commit
into `release` through the merges API. One pull request, no order to remember,
and the tag is by construction an ancestor of what shipped. `promote.yml` is
gone.

A conflict is the one case a human still has to see, and it can only mean
somebody committed to `release` directly, so it fails the run and says so rather
than quietly leaving production behind.

Non-releasable commits now wait for the next release rather than riding a
promotion, which is the trade: `release` moves when a tag is cut and at no other
time.

* fix(ci): fall back to the pushed commit when release-please reports no sha

* chore(main): release 1.6.1 (trycompai#108)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(db): add peek script for inspecting database contents (trycompai#110)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* chore(main): release 1.7.0 (trycompai#111)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(agent): apply sourced facts to empty fields automatically (trycompai#112)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* chore(main): release 1.8.0 (trycompai#113)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(ci): ship releases by opening a pull request into release (trycompai#114)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* chore(main): release 1.8.1 (trycompai#115)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(agent): fill blank fields on the dispatch tick instead of sign-in (trycompai#117)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* chore(main): release 1.8.2 (trycompai#118)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(agent): stop suggesting a URL that already matches the field (trycompai#120)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* chore(main): release 1.9.0 (trycompai#121)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(tracking): support installing the tracking tag via Google Tag Manager (trycompai#124)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* chore(main): release 1.10.0 (trycompai#126)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(app): copy the tracking snippet for the selected install method (trycompai#128)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* chore(main): release 1.11.0 (trycompai#129)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat: edit a deployed agent, and show what Slack actually granted (CMP-77) (trycompai#109)

* chore(main): release 1.12.0 (trycompai#132)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(app): search company dropdowns instead of scrolling them (trycompai#125)

* fix(app): show select field values in record tables (trycompai#133)

* fix(agent): let the assistant chat read the deal list it is told to use (CMP-77) (trycompai#139)

* chore(main): release 1.13.0 (trycompai#136)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* chore: add anti-slop lint rules, dead-code checks and stricter Biome constraints (CMP-80) (trycompai#145)

* refactor: clear anti-slop type assertions and conditional object spreads (CMP-81) (trycompai#146)

* docs: propose an i18n layer (trycompai#143)

* refactor: parse every remaining I/O boundary into a domain type (CMP-82) (trycompai#151)

* fix: unblock the test suite and actually install the git hooks (CMP-83) (trycompai#152)

* ci: run anti-slop lint in CI and pre-push (CMP-84) (trycompai#153)

Co-authored-by: grim <75869731+ripgrim@users.noreply.github.com>

* feat: enrichment queue widget (CMP-92) (trycompai#159)

* feat(agent): read people from Context.dev instead of RapidAPI (CMP-86) (trycompai#158)

Co-authored-by: grim <75869731+ripgrim@users.noreply.github.com>

* feat: page the enrichment queue (CMP-92) (trycompai#160)

Co-authored-by: grim <75869731+ripgrim@users.noreply.github.com>

* chore(main): release 1.14.0 (trycompai#147)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(agent): scope field backfill tasks to records missing values (trycompai#163)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* chore(main): release 1.15.0 (trycompai#164)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(api): serve openapi.json and bundle swagger deps in function build (trycompai#166)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* chore(main): release 1.15.1 (trycompai#167)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Lewis/openapi json (trycompai#169)

* docs(api): explain runtime openapi document and vendoring rules (trycompai#170)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* chore(main): release 1.15.2 (trycompai#171)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(app): prevent url param collision between fields sheet and table filter (trycompai#175)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* fix: stop a finished enrichment reading as failed (trycompai#173)

* chore(main): release 1.15.3 (trycompai#176)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* ci: add Vercel deploy workflow

Deploys to Vercel on push to main/release branches.
Also supports manual trigger via workflow_dispatch with
environment selection (preview/production).

Uses secrets: VERCEL_API_KEY, VERCEL_ORG_ID, VERCEL_PROJECT_ID

---------

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>
Co-authored-by: grim <75869731+ripgrim@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: Guzman Pintos <37162183+GuzmanPintos@users.noreply.github.com>
Co-authored-by: twinprime19 <38123958+twinprime19@users.noreply.github.com>
Co-authored-by: Kiro Agent <244629292+kiro-agent@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant