Skip to content
Draft
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
31 changes: 31 additions & 0 deletions src/calculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,50 @@
* Used for testing QuantCode autonomous agent capabilities.
*/

/**
* Add two numbers.
*
* @param a - The first addend.
* @param b - The second addend.
* @returns The sum of `a` and `b`.
*/
export function add(a: number, b: number): number {
return a + b
}

/**
* Subtract one number from another.
*
* @param a - The minuend.
* @param b - The subtrahend.
* @returns The difference `a - b`.
*/
export function subtract(a: number, b: number): number {
return a - b
}

/**
* Multiply two numbers.
*
* @param a - The first factor.
* @param b - The second factor.
* @returns The product of `a` and `b`.
*/
export function multiply(a: number, b: number): number {
return a * b
}

// BUG: Division by zero is not handled
/**
* Divide one number by another.
*
* Division by zero is not guarded: it follows IEEE 754 and returns
* `Infinity`, `-Infinity` or `NaN` rather than throwing.
*
* @param a - The dividend.
* @param b - The divisor.
* @returns The quotient `a / b`.
*/
export function divide(a: number, b: number): number {
return a / b
}
15 changes: 14 additions & 1 deletion src/date-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
*
* BUG: off-by-one — uses Math.floor where Math.round is needed for days,
* causing "1 day ago" to appear for anything from 12h to 47h.
*
* @param date - The date to describe.
* @param now - The reference point to compare against. Defaults to the current time.
* @returns "just now" within a minute, otherwise a minutes, hours or days string,
* suffixed "ago" for past dates or prefixed "in" for future dates.
*/
export function formatRelative(date: Date, now: Date = new Date()): string {
const diffMs = now.getTime() - date.getTime()
Expand All @@ -30,14 +35,22 @@ export function formatRelative(date: Date, now: Date = new Date()): string {
}

/**
* Returns true if two dates fall on the same calendar day.
* Returns true if two dates fall on the same calendar day (local time).
*
* @param a - The first date.
* @param b - The second date.
* @returns `true` if both dates share the same year, month and day, otherwise `false`.
*/
export function isSameDay(a: Date, b: Date): boolean {
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate()
}

/**
* Add a number of days to a date (returns a new Date).
*
* @param date - The starting date; it is not mutated.
* @param days - The number of days to add; negative values subtract.
* @returns A new Date offset by the given number of days.
*/
export function addDays(date: Date, days: number): Date {
const result = new Date(date)
Expand Down
36 changes: 36 additions & 0 deletions src/string-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,50 @@
* String utility functions.
*/

/**
* Upper-case the first character of a string, leaving the rest unchanged.
*
* @param str - The string to capitalise.
* @returns The string with its first character upper-cased, or `str` itself if it is empty.
*/
export function capitalize(str: string): string {
if (!str) return str
return str.charAt(0).toUpperCase() + str.slice(1)
}

/**
* Reverse the characters of a string (by UTF-16 code unit).
*
* @param str - The string to reverse.
* @returns A new string with the characters in reverse order.
*/
export function reverse(str: string): string {
return str.split("").reverse().join("")
}

// TODO: implement truncate — should truncate at a word boundary, with "..."
// counting toward maxLength. Return unchanged if str.length <= maxLength.
/**
* Truncate a string to a maximum length. Not yet implemented.
*
* @param str - The string to truncate.
* @param maxLength - The maximum permitted length of the result.
* @returns The truncated string.
* @throws {Error} Always, with message "not implemented", until implemented.
*/
export function truncate(str: string, maxLength: number): string {
throw new Error("not implemented")
}

/**
* Convert a string to a URL-friendly slug: lower-cased, with each run of
* characters other than ASCII letters and digits (a-z, 0-9) replaced by a single
* hyphen and leading or trailing hyphens removed. Non-ASCII characters, including
* accented letters such as "é", are treated as separators and are not preserved.
*
* @param str - The string to convert.
* @returns The slugified string.
*/
export function slugify(str: string): string {
return str
.toLowerCase()
Expand All @@ -25,6 +54,13 @@ export function slugify(str: string): string {
}

// BUG: This doesn't handle multiple consecutive spaces
/**
* Count the words in a string, splitting on single space characters.
* Consecutive spaces are not collapsed and so can inflate the count.
*
* @param str - The string to count words in.
* @returns The number of words, or 0 if the string is empty or whitespace only.
*/
export function wordCount(str: string): number {
if (!str.trim()) return 0
return str.split(" ").length
Expand Down
52 changes: 52 additions & 0 deletions src/task-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,21 @@ export interface Task {
completedAt?: Date
}

/**
* In-memory task manager that stores tasks with priorities and status tracking.
*/
export class TaskManager {
private tasks: Map<string, Task> = new Map()
private nextId = 1

/**
* Create a new task with status `pending` and store it.
*
* @param title - The task title.
* @param priority - The task priority. Defaults to `"medium"`.
* @param description - Optional longer description of the task.
* @returns The newly created task.
*/
add(title: string, priority: Priority = "medium", description?: string): Task {
const task: Task = {
id: String(this.nextId++),
Expand All @@ -33,17 +44,35 @@ export class TaskManager {
return task
}

/**
* Look up a task by id.
*
* @param id - The task id.
* @returns The matching task, or `undefined` if none exists.
*/
get(id: string): Task | undefined {
return this.tasks.get(id)
}

/**
* List tasks, optionally filtered by status and/or priority.
*
* @param filter - Optional filter; when both fields are set a task must match both.
* @returns The matching tasks in insertion order.
*/
list(filter?: { status?: Status; priority?: Priority }): Task[] {
let result = Array.from(this.tasks.values())
if (filter?.status) result = result.filter((t) => t.status === filter.status)
if (filter?.priority) result = result.filter((t) => t.priority === filter.priority)
return result
}

/**
* Mark a task as completed and record its completion time.
*
* @param id - The id of the task to complete.
* @returns `true` if the task was found and completed, `false` if not found.
*/
complete(id: string): boolean {
const task = this.tasks.get(id)
if (!task) return false
Expand All @@ -53,18 +82,41 @@ export class TaskManager {
}

// TODO: implement — remove a task by id, return true if removed, false if not found
/**
* Remove a task by id. Not yet implemented.
*
* @param id - The id of the task to remove.
* @returns `true` if a task was removed, `false` if not found (once implemented).
* @throws {Error} Always, with message "not implemented", until implemented.
*/
remove(id: string): boolean {
throw new Error("not implemented")
}

// TODO: implement — update title/description/priority of a task
// return true if updated, false if not found
/**
* Update the title, description and/or priority of a task. Not yet implemented.
*
* @param id - The id of the task to update.
* @param changes - The fields to change.
* @returns `true` if the task was updated, `false` if not found (once implemented).
* @throws {Error} Always, with message "not implemented", until implemented.
*/
update(id: string, changes: Partial<Pick<Task, "title" | "description" | "priority">>): boolean {
throw new Error("not implemented")
}

// TODO: implement — return all tasks sorted by the given field
// priority sort order: high > medium > low
/**
* Return all tasks sorted by the given field, with priority ordered high > medium > low.
* Not yet implemented.
*
* @param field - The field to sort by.
* @returns The sorted tasks (once implemented).
* @throws {Error} Always, with message "not implemented", until implemented.
*/
sortBy(field: "priority" | "createdAt" | "status"): Task[] {
throw new Error("not implemented")
}
Expand Down
16 changes: 16 additions & 0 deletions src/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
*
* BUG: the regex does not allow subdomains (e.g. user@mail.example.com fails)
* and rejects valid TLDs longer than 4 chars (e.g. .museum, .travel).
*
* @param value - The string to test.
* @returns `true` if the string matches the email pattern, otherwise `false`.
*/
export function isEmail(value: string): boolean {
// BUG: too restrictive — missing subdomain support and long TLDs
Expand All @@ -17,6 +20,11 @@ export function isEmail(value: string): boolean {
* Returns true if the string is a valid URL (http or https).
*
* BUG: rejects URLs with ports (e.g. http://localhost:3000)
*
* @param value - The string to test.
* @returns `true` if the string parses as an http(s) URL with no port other than the
* protocol's default (the URL parser drops default ports, so `http://x.com:80` passes),
* otherwise `false`. Unparseable input returns `false` rather than throwing.
*/
export function isUrl(value: string): boolean {
try {
Expand All @@ -31,6 +39,11 @@ export function isUrl(value: string): boolean {
/**
* Returns true if the string contains only alphanumeric characters and underscores,
* starts with a letter, and is between minLen and maxLen characters.
*
* @param value - The string to test.
* @param minLen - Minimum permitted length (inclusive). Defaults to 3.
* @param maxLen - Maximum permitted length (inclusive). Defaults to 20.
* @returns `true` if the string is a valid username, otherwise `false`.
*/
export function isUsername(value: string, minLen = 3, maxLen = 20): boolean {
if (value.length < minLen || value.length > maxLen) return false
Expand All @@ -40,6 +53,9 @@ export function isUsername(value: string, minLen = 3, maxLen = 20): boolean {
/**
* Returns true if the string is a valid Australian phone number.
* Accepts formats: 04XX XXX XXX, +614XX XXX XXX, (02) XXXX XXXX, 02 XXXX XXXX
*
* @param value - The phone number to test; spaces, hyphens, dots and parentheses are ignored.
* @returns `true` if the cleaned number is a valid Australian number, otherwise `false`.
*/
export function isAustralianPhone(value: string): boolean {
const cleaned = value.replace(/[\s\-().]/g, "")
Expand Down
Loading