Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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, vendor, testdata)
- 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
126 changes: 94 additions & 32 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,41 +173,45 @@ def _assess_typescript_types(self, repository: Repository) -> Finding:
error_message=None,
)

try:
import json
strict_count = 0
total_count = 0
evidence: 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))
total_count += 1
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:
evidence.append(f"{rel_path}: parse error ({e})")
continue

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:
return Finding.error(
self.attribute, reason=f"Could not parse tsconfig.json: {str(e)}"
)
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"

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:
Expand Down Expand Up @@ -270,6 +279,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