Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,9 @@ Type annotations give agents reliable information about what a function expects

**TypeScript**:

- `strict` mode enabled in tsconfig.json
- `strict` mode enabled in tsconfig.json (supports JSONC with comments)
- All tsconfig.json files checked (monorepo support: root + subdirectories, excluding node_modules)
- Scoring: proportional to strict-enabled configs (e.g., 2/3 strict = 67%)
Comment on lines +273 to +275
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Document that partial strict scores still fail.

This now reads like 2/3 strict = 67% is the user-facing outcome, but the assessor only returns pass when every discovered tsconfig.json is strict and treats malformed configs as non-strict. Please add that 100% is required to pass so the docs match the implementation.

As per coding guidelines, "Keep docs/attributes.md in sync when changing assessor scoring logic, thresholds, partial credit rules, recognized paths, or pass/fail conditions"

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/attributes.md` around lines 273 - 275, Update the tsconfig strict-mode
scoring description in the listed bullets so it states that only 100%
strict-enabled tsconfig.json files result in a "pass" (partial scores like "2/3
= 67%" are reported but do not pass), and explicitly note that malformed
tsconfig.json files are treated as non-strict; edit the lines that currently
read "Scoring: proportional to strict-enabled configs (e.g., 2/3 strict = 67%)"
to mention the proportional score is shown but the assessor requires 100% to
return pass and malformed configs count as non-strict so readers see the
implementation matches the docs.

- No `any` types (use `unknown` if needed)
- Interfaces for complex objects

Expand Down
133 changes: 103 additions & 30 deletions src/agentready/assessors/code_quality.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,15 @@ def _assess_python_types(self, repository: Repository) -> Finding:
)

def _assess_typescript_types(self, repository: Repository) -> Finding:
"""Assess TypeScript type configuration."""
tsconfig_path = repository.path / "tsconfig.json"
"""Assess TypeScript type configuration across all tsconfig.json files.

if not tsconfig_path.exists():
Supports monorepos with per-package tsconfig.json and JSONC comments.
"""
import json

tsconfig_files = self._find_tsconfig_files(repository)

if not tsconfig_files:
return Finding(
attribute=self.attribute,
status="fail",
Expand All @@ -168,42 +173,57 @@ def _assess_typescript_types(self, repository: Repository) -> Finding:
error_message=None,
)

try:
import json
strict_count = 0
total_count = 0
evidence: list[str] = []
errors: list[str] = []

with open(tsconfig_path, "r") as f:
tsconfig = json.load(f)
for tsconfig_path in tsconfig_files:
rel_path = str(tsconfig_path.relative_to(repository.path))
try:
raw = tsconfig_path.read_text(encoding="utf-8")
cleaned = self._strip_json_comments(raw)
tsconfig = json.loads(cleaned)
except (OSError, json.JSONDecodeError) as e:
errors.append(f"{rel_path}: parse error ({e})")
continue

total_count += 1
strict = tsconfig.get("compilerOptions", {}).get("strict", False)

if strict:
return Finding(
attribute=self.attribute,
status="pass",
score=100.0,
measured_value="strict mode enabled",
threshold="strict mode enabled",
evidence=["tsconfig.json has strict: true"],
remediation=None,
error_message=None,
)
strict_count += 1
evidence.append(f"{rel_path}: strict: true")
else:
return Finding(
attribute=self.attribute,
status="fail",
score=50.0,
measured_value="strict mode disabled",
threshold="strict mode enabled",
evidence=["tsconfig.json missing strict: true"],
remediation=self._create_remediation(),
error_message=None,
)
evidence.append(f"{rel_path}: strict mode disabled")
Comment on lines +180 to +196
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "TypeScript assessor logic:"
sed -n '181,197p' src/agentready/assessors/code_quality.py

echo
echo 'Search for any tsconfig "extends" handling in the assessor and tests:'
rg -n --type=py '\bextends\b|compilerOptions.*strict|_strip_json_comments|_find_tsconfig_files' \
  src/agentready/assessors/code_quality.py \
  tests/unit/test_assessors_typescript.py

Repository: ambient-code/agentready

Length of output: 2357


Fix TypeScript strict scoring to account for tsconfig inheritance (extends).

src/agentready/assessors/code_quality.py only scores strict from each tsconfig.json’s direct compilerOptions.strict (tsconfig.get("compilerOptions", {}).get("strict", False), lines 181-197). There’s no "extends" handling in the assessor (or in tests/unit/test_assessors_typescript.py), so leaf configs that inherit strict from a base config will be incorrectly treated as non-strict. Add extends resolution (effective compilerOptions) and cover this with unit tests for inherited strict.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/agentready/assessors/code_quality.py` around lines 181 - 197, The
evaluator currently reads each tsconfig file directly and checks
compilerOptions.strict without resolving "extends"; update the logic that
iterates over tsconfig_files (the loop using tsconfig_path, rel_path, raw,
cleaned and json.loads and calling self._strip_json_comments) to resolve and
merge extended configs so the effective compilerOptions.strict is computed
(handle relative extends, absolute paths, node-style package references, merge
parent compilerOptions into child, detect and prevent circular extends), then
use that effective compilerOptions.strict for strict_count/evidence; also add
unit tests in tests/unit/test_assessors_typescript.py that include a base
tsconfig with "strict": true and a child that extends it to assert the assessor
treats the child as strict.


except (OSError, json.JSONDecodeError) as e:
if total_count == 0:
err_detail = "; ".join(errors) if errors else "no parseable tsconfig.json"
return Finding.error(
self.attribute, reason=f"Could not parse tsconfig.json: {str(e)}"
self.attribute,
reason=f"Could not parse any tsconfig.json: {err_detail}",
)

score = self.calculate_proportional_score(
measured_value=(strict_count / total_count) * 100,
threshold=100.0,
higher_is_better=True,
)
status = "pass" if strict_count == total_count else "fail"

if errors:
evidence.extend(errors)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

return Finding(
attribute=self.attribute,
status=status,
score=score,
measured_value=f"{strict_count}/{total_count} strict",
threshold="all tsconfig.json files strict",
evidence=evidence,
remediation=self._create_remediation() if status == "fail" else None,
error_message=None,
)

@staticmethod
def _strip_go_non_code(content: str) -> str:
"""Strip comments and string literal contents from Go source.
Expand Down Expand Up @@ -270,6 +290,59 @@ def _strip_go_non_code(content: str) -> str:

return "".join(out)

@staticmethod
def _strip_json_comments(text: str) -> str:
"""Strip // and /* */ comments from JSONC, preserving string contents."""
out: list[str] = []
i = 0
n = len(text)
while i < n:
c = text[i]

if c == '"':
out.append(c)
i += 1
while i < n and text[i] != '"':
if text[i] == "\\" and i + 1 < n:
out.append(text[i])
out.append(text[i + 1])
i += 2
else:
out.append(text[i])
i += 1
if i < n:
out.append(text[i])
i += 1
continue

if c == "/" and i + 1 < n and text[i + 1] == "/":
i += 2
while i < n and text[i] != "\n":
i += 1
continue

if c == "/" and i + 1 < n and text[i + 1] == "*":
i += 2
while i + 1 < n and not (text[i] == "*" and text[i + 1] == "/"):
i += 1
i += 2
continue

out.append(c)
i += 1

return "".join(out)

def _find_tsconfig_files(self, repository: Repository) -> list:
"""Find all tsconfig.json files, excluding node_modules/vendor/testdata."""
found = []
for tsconfig in repository.path.rglob("tsconfig.json"):
parts = tsconfig.parts
if "node_modules" in parts or "vendor" in parts or "testdata" in parts:
continue
found.append(tsconfig)
return sorted(found)
Comment on lines +325 to +333
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prune excluded directories during traversal.

This still walks node_modules / vendor / testdata and only filters matching files afterward. On large monorepos, descending into those trees can dominate assessor runtime and defeats the intended exclusion behavior. Use a prunable walk (os.walk/Path.walk) and remove excluded dirs before recursing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/agentready/assessors/code_quality.py` around lines 336 - 344, The current
_find_tsconfig_files method uses repository.path.rglob which still descends into
large excluded trees; change it to a prunable walk (e.g., use
os.walk(repository.path) or Path.iterdir with recursive stack) and remove
"node_modules", "vendor", and "testdata" from the directories list before
recursing so those trees are never traversed; collect any tsconfig.json files
you encounter into found (preserving return of sorted list) and keep function
name _find_tsconfig_files and repository.path as the location to modify.


def _assess_go_types(self, repository: Repository) -> Finding:
"""Assess Go type safety.

Expand Down
Loading
Loading