From 97ca9e0b86a3da87aa14f0b34260d8a7d339beb7 Mon Sep 17 00:00:00 2001 From: "a.khanteev" Date: Mon, 24 Aug 2026 13:58:21 +0400 Subject: [PATCH 1/2] docs: add baseline OpenSpec specifications --- openspec/specs/gitlab/jobs/spec.md | 43 +++++++++++ openspec/specs/gitlab/merge-requests/spec.md | 58 +++++++++++++++ openspec/specs/gitlab/pipelines/spec.md | 48 +++++++++++++ openspec/specs/gitlab/repositories/spec.md | 67 +++++++++++++++++ openspec/specs/kaiten/task-comments/spec.md | 53 ++++++++++++++ openspec/specs/kaiten/tasks/spec.md | 67 +++++++++++++++++ openspec/specs/shared/caching/spec.md | 58 +++++++++++++++ .../specs/shared/config-resolution/spec.md | 72 +++++++++++++++++++ .../specs/shared/output-formatting/spec.md | 62 ++++++++++++++++ 9 files changed, 528 insertions(+) create mode 100644 openspec/specs/gitlab/jobs/spec.md create mode 100644 openspec/specs/gitlab/merge-requests/spec.md create mode 100644 openspec/specs/gitlab/pipelines/spec.md create mode 100644 openspec/specs/gitlab/repositories/spec.md create mode 100644 openspec/specs/kaiten/task-comments/spec.md create mode 100644 openspec/specs/kaiten/tasks/spec.md create mode 100644 openspec/specs/shared/caching/spec.md create mode 100644 openspec/specs/shared/config-resolution/spec.md create mode 100644 openspec/specs/shared/output-formatting/spec.md diff --git a/openspec/specs/gitlab/jobs/spec.md b/openspec/specs/gitlab/jobs/spec.md new file mode 100644 index 0000000..5d7c560 --- /dev/null +++ b/openspec/specs/gitlab/jobs/spec.md @@ -0,0 +1,43 @@ +# GitLab Jobs Specification + +## Purpose + +Allow read-only inspection of GitLab CI/CD jobs and job traces. + +## Requirements + +### Requirement: List jobs + +The system SHALL support listing jobs for a required project selector and optional pipeline, search, status, ref, scope, limit, refresh, and full-object controls according to the command surface. + +#### Scenario: List jobs for a pipeline + +- **WHEN** the user runs `glc jobs list --project --pipeline ` +- **THEN** the command returns matching jobs using the stable job summary by default + +### Requirement: Retrieve one job + +The system SHALL support retrieving one job with a required project and job identifier. + +#### Scenario: Get a job + +- **WHEN** the user runs `glc jobs get --project --job ` +- **THEN** the command returns the selected job or a not-found error + +### Requirement: Read a job trace + +The system SHALL support retrieving a job's trace as raw text for shell and diagnostic use. + +#### Scenario: Get a job trace + +- **WHEN** the user runs `glc jobs trace --project --job ` +- **THEN** stdout contains the trace text without JSON decoration + +### Requirement: Summarize job data + +The system SHALL provide stable job summaries containing identifier, pipeline, name, stage, status, ref, duration, and web URL, with full upstream data available only when requested. + +#### Scenario: Default job output + +- **WHEN** a job list or get command is run without `--full` +- **THEN** the output uses the restricted stable schema diff --git a/openspec/specs/gitlab/merge-requests/spec.md b/openspec/specs/gitlab/merge-requests/spec.md new file mode 100644 index 0000000..bd5263f --- /dev/null +++ b/openspec/specs/gitlab/merge-requests/spec.md @@ -0,0 +1,58 @@ +# GitLab Merge Requests Specification + +## Purpose + +Provide agent-friendly inspection of GitLab merge requests, their related datasets, versions, changes, and review snapshots. + +## Requirements + +### Requirement: List and retrieve merge requests + +The system SHALL support merge request listing and retrieval with explicit group/project scoping and filters for search, state, sort, author, target/source branch, scope, date range, limit, refresh, and full payloads where applicable. + +#### Scenario: List recently updated merge requests + +- **WHEN** the user runs `glc mrs list --project --since ` +- **THEN** the command translates the date specification to an API boundary and returns matching merge requests + +#### Scenario: Retrieve related merge request data + +- **WHEN** the user runs `glc mrs get --project --mr --with ` +- **THEN** the command returns the merge request together with the requested related datasets + +### Requirement: Inspect merge request history and changes + +The system SHALL support listing commits, pipelines, diff versions, and changes for a merge request, with optional diff text for changes and an explicit version selector. + +#### Scenario: List merge request commits + +- **WHEN** the user runs `glc mrs commits --project --mr ` +- **THEN** the command returns summarized commits for that merge request + +#### Scenario: Request a merge request patch + +- **WHEN** the user runs `glc mrs changes --project --mr --patch` +- **THEN** the change items include diff text when GitLab provides it + +### Requirement: Generate an agent-friendly snapshot bundle + +The system SHALL support writing a deterministic merge request snapshot bundle containing a manifest and navigable overview, timeline, index, and per-file change artifacts for the requested datasets. + +#### Scenario: Snapshot the latest diff + +- **WHEN** the user runs `glc mrs snapshot --project --mr --version latest --output-dir ` +- **THEN** the command resolves `latest` to a concrete version and writes the snapshot to the requested directory + +#### Scenario: Snapshot a changed file with unavailable diff + +- **WHEN** GitLab marks a changed file as collapsed or too large +- **THEN** the snapshot keeps the file in metadata and explicitly indicates that its diff is unavailable + +### Requirement: Return stable merge request summaries and anonymized identities + +The system SHALL include stable identifiers, project, title, state, branches, timestamps, status, and web URL in default merge request summaries, and SHALL anonymize user identities in rendered output. + +#### Scenario: Render a merge request list + +- **WHEN** a merge request list is rendered without `--full` +- **THEN** each item uses the stable summary schema and does not expose the raw author identity diff --git a/openspec/specs/gitlab/pipelines/spec.md b/openspec/specs/gitlab/pipelines/spec.md new file mode 100644 index 0000000..8332b63 --- /dev/null +++ b/openspec/specs/gitlab/pipelines/spec.md @@ -0,0 +1,48 @@ +# GitLab Pipelines Specification + +## Purpose + +Allow read-only inspection of GitLab CI/CD pipelines for a selected project. + +## Requirements + +### Requirement: List pipelines + +The system SHALL support listing pipelines for a required project selector with optional search, state, sort, limit, refresh, and full-object controls. + +#### Scenario: List recent pipelines + +- **WHEN** the user runs `glc pipelines list --project ` +- **THEN** the command returns matching pipelines using the stable pipeline summary by default + +#### Scenario: Limit pipeline results + +- **WHEN** the user supplies `--limit ` +- **THEN** the command returns no more than the requested number of pipeline items + +### Requirement: Retrieve one pipeline + +The system SHALL support retrieving one pipeline using a required project and pipeline identifier. + +#### Scenario: Get a pipeline + +- **WHEN** the user runs `glc pipelines get --project --pipeline ` +- **THEN** the command returns the selected pipeline or a not-found error + +### Requirement: Summarize pipeline data + +The system SHALL provide stable pipeline summaries containing identifier, project, ref, status, source, SHA, creation time, and web URL, with full upstream data available only when requested. + +#### Scenario: Default pipeline output + +- **WHEN** a pipeline command is run without `--full` +- **THEN** the output uses the restricted stable schema + +### Requirement: Honor read and cache controls + +The system SHALL perform pipeline operations as read-only requests, use the shared cache by default, bypass it with `--refresh`, and emit cache diagnostics only when verbose mode is requested. + +#### Scenario: Refresh pipeline data + +- **WHEN** the user runs a pipeline command with `--refresh` +- **THEN** the command bypasses an existing cached response and fetches current data diff --git a/openspec/specs/gitlab/repositories/spec.md b/openspec/specs/gitlab/repositories/spec.md new file mode 100644 index 0000000..860f2fb --- /dev/null +++ b/openspec/specs/gitlab/repositories/spec.md @@ -0,0 +1,67 @@ +# GitLab Repositories Specification + +## Purpose + +Allow agents and shell users to inspect GitLab groups, projects, and repository contents through read-only, resource-first commands. + +## Requirements + +### Requirement: Inspect groups and namespaces + +The system SHALL support listing groups, retrieving one group, and rendering a group tree, with optional group, search, state, sort, limit, refresh, and full-object controls. + +#### Scenario: List top-level groups + +- **WHEN** the user runs `glc groups list` without a group selector +- **THEN** the command returns matching groups using the default summary schema + +#### Scenario: Render a group tree + +- **WHEN** the user runs `glc groups tree --group ` +- **THEN** the command returns a hierarchical representation of the selected namespace + +### Requirement: Inspect projects + +The system SHALL support listing projects, retrieving one project, and rendering a project tree, using explicit group or project selectors and optional search, state, sort, limit, refresh, and full-object controls. + +#### Scenario: List projects in a group + +- **WHEN** the user runs `glc projects list --group ` +- **THEN** the command returns projects in that group using the restricted project summary by default + +#### Scenario: Retrieve one project + +- **WHEN** the user runs `glc projects get --project ` +- **THEN** the command returns the selected project or a not-found error + +### Requirement: Inspect repository trees and refs + +The system SHALL support listing repository tree entries, listing branches or tags, and reading one repository file using an explicit project selector and optional path, ref, page, limit, search, refresh, and full-object controls. + +#### Scenario: Browse a repository path + +- **WHEN** the user runs `glc repos tree --project --ref --path ` +- **THEN** the command returns repository entries for that path and ref + +#### Scenario: Read a repository file + +- **WHEN** the user runs `glc repos file --project --path --ref ` +- **THEN** the command returns the file contents in a raw-friendly form + +### Requirement: Use stable summaries with opt-in full payloads + +The system SHALL return restricted stable summaries by default and SHALL return the upstream GitLab objects only when `--full` is requested. + +#### Scenario: Default project listing + +- **WHEN** a project list is requested without `--full` +- **THEN** each item contains the documented stable project fields rather than the complete GitLab payload + +### Requirement: Keep repository exploration read-only + +The system SHALL perform only read operations for group, project, and repository commands and SHALL require explicit selectors for resource-specific operations. + +#### Scenario: Missing project selector + +- **WHEN** a repository operation is invoked without its required project selector +- **THEN** the command fails before making a resource request diff --git a/openspec/specs/kaiten/task-comments/spec.md b/openspec/specs/kaiten/task-comments/spec.md new file mode 100644 index 0000000..3af4a5d --- /dev/null +++ b/openspec/specs/kaiten/task-comments/spec.md @@ -0,0 +1,53 @@ +# Kaiten Task Comments Specification + +## Purpose + +Provide read-only retrieval and stable rendering of comments attached to a Kaiten task. + +## Requirements + +### Requirement: Retrieve comments for a task + +The system SHALL support `ktc task-comments get --task ` and SHALL require the task identifier before making the request. + +#### Scenario: Get task comments + +- **WHEN** the user runs `ktc task-comments get --task ` +- **THEN** the command requests the task's comments and returns normalized comment records + +#### Scenario: Missing task selector + +- **WHEN** the comments command is run without `--task` +- **THEN** the command fails with a CLI argument error + +### Requirement: Normalize comment records + +The system SHALL represent each comment with its identifier, author information, content, and creation/update timestamps when available. + +#### Scenario: Comment has alternate upstream field names + +- **WHEN** Kaiten returns comment text or timestamps under supported alternate field names +- **THEN** the normalized record exposes the canonical `content`, `created_at`, and `updated_at` fields + +### Requirement: Render comments safely + +The system SHALL support Markdown and JSON output, apply field projection before rendering, and anonymize comment authors in rendered output. + +#### Scenario: Render a comment list as Markdown + +- **WHEN** comments are requested without `--json` +- **THEN** stdout contains a Markdown comment list with a shortened anonymized author identity + +#### Scenario: Render comments as JSON + +- **WHEN** `--json` is requested +- **THEN** stdout contains valid JSON with the normalized comment records and anonymized identity values + +### Requirement: Use read-only request and cache controls + +The system SHALL fetch comments through the read-only Kaiten client, cache GET responses by default, bypass cached data with `--refresh`, and report request diagnostics only in verbose mode. + +#### Scenario: Refresh comments + +- **WHEN** the user runs the comments command with `--refresh` +- **THEN** the command bypasses the cached comment response and requests current data diff --git a/openspec/specs/kaiten/tasks/spec.md b/openspec/specs/kaiten/tasks/spec.md new file mode 100644 index 0000000..8e18f0a --- /dev/null +++ b/openspec/specs/kaiten/tasks/spec.md @@ -0,0 +1,67 @@ +# Kaiten Tasks Specification + +## Purpose + +Allow agents and shell users to find, inspect, and summarize Kaiten tasks through a read-oriented command surface. + +## Requirements + +### Requirement: List the current user's tasks + +The system SHALL support `ktc tasks mine`, resolve the current Kaiten user, collect visible cards, normalize them into task records, and apply the shared task filters and limit controls. + +#### Scenario: List my open tasks + +- **WHEN** the user runs `ktc tasks mine --state open` +- **THEN** the command returns only open tasks assigned to the resolved current user + +#### Scenario: Current user cannot be resolved + +- **WHEN** the current user endpoint cannot provide a usable user identity +- **THEN** the command fails with an authentication/configuration error rather than returning unrelated tasks + +### Requirement: Find tasks across selected Kaiten scope + +The system SHALL support task search by text and filters for assignee, space, board, state, date boundaries, and result limit. + +#### Scenario: Find tasks by board and text + +- **WHEN** the user runs `ktc tasks find --space --board ` +- **THEN** the command scans the selected scope, applies the text and entity filters, sorts matches by recency, and returns them + +#### Scenario: Unknown space or board + +- **WHEN** a supplied space or board selector matches no entity +- **THEN** the command fails with a not-found error identifying the selector + +### Requirement: Retrieve one task with lookup fallback + +The system SHALL support `ktc tasks get --id ` and SHALL try the direct card endpoint before falling back to scanning accessible spaces and boards when necessary. + +#### Scenario: Direct task lookup succeeds + +- **WHEN** the direct card endpoint returns the requested task +- **THEN** the command returns the normalized task without scanning every board + +#### Scenario: Direct lookup misses + +- **WHEN** the direct lookup cannot find the task +- **THEN** the command searches accessible boards and returns the task if found, otherwise reports that the task was not found + +### Requirement: Normalize task records + +The system SHALL expose stable task records with identifiers, title, description, archived/state/status flags, assignee, space, board, column, lane, type, relations, timestamps, and URL when available. + +#### Scenario: Derive task status + +- **WHEN** a card is archived, completed, or located in a typed workflow column +- **THEN** the normalized task reports the corresponding `archived`, `done`, `in_progress`, or `open` status and `is_open` value + +### Requirement: Keep task discovery read-only and agent-friendly + +The system SHALL use read-only Kaiten requests, shared caching with `--refresh` bypass, deterministic sorting, optional field projection, and anonymized user information in rendered output. + +#### Scenario: Render task details as Markdown + +- **WHEN** a task is retrieved without `--json` +- **THEN** the output includes core facts and brief parent/child relations in Markdown without exposing raw user identity diff --git a/openspec/specs/shared/caching/spec.md b/openspec/specs/shared/caching/spec.md new file mode 100644 index 0000000..c2cc159 --- /dev/null +++ b/openspec/specs/shared/caching/spec.md @@ -0,0 +1,58 @@ +# File Caching Specification + +## Purpose + +Provide deterministic JSON-file caching for read-oriented CLI requests. + +## Requirements + +### Requirement: Cache entries by stable request key + +The system SHALL store each cache entry as JSON under a filename derived from a SHA-256 digest of the request key, together with the key, scope, timestamps, status, and data supplied by the caller. + +#### Scenario: Store and retrieve a cache entry + +- **WHEN** a caller stores a value and later requests the same key before expiry +- **THEN** the cache returns the stored entry and its data without requiring a network request + +#### Scenario: Missing cache entry + +- **WHEN** no file exists for a requested key +- **THEN** the cache returns no entry + +### Requirement: Honor expiry and refresh bypass + +The system SHALL treat an entry with an expired `expiresAt` timestamp as unavailable and SHALL bypass cache reads when the caller requests refresh. + +#### Scenario: Expired entry + +- **WHEN** a cache entry has expired +- **THEN** a read returns no entry so the caller can fetch fresh data + +#### Scenario: Explicit refresh + +- **WHEN** a read is performed with `refresh` +- **THEN** the cache returns no entry even if a valid entry exists + +### Requirement: Inspect and clear cache entries by scope + +The system SHALL list valid JSON entries with metadata sorted newest first, ignore malformed entries during listing, and clear only entries matching supplied non-empty scope filters. + +#### Scenario: List cache status + +- **WHEN** a caller requests cache status +- **THEN** the result includes entry keys, scopes, timestamps, and file sizes + +#### Scenario: Clear a scoped cache + +- **WHEN** a caller clears the cache with a project or group scope +- **THEN** only matching entries are removed and the removed entries are reported + +### Requirement: Keep cache failures explicit + +The system SHALL tolerate a missing cache directory as an empty cache and SHALL propagate filesystem failures other than missing files or intentionally ignored malformed entries. + +#### Scenario: Cache directory does not exist + +- **WHEN** a cache is listed before it has been created +- **THEN** the result is an empty list diff --git a/openspec/specs/shared/config-resolution/spec.md b/openspec/specs/shared/config-resolution/spec.md new file mode 100644 index 0000000..695a5ad --- /dev/null +++ b/openspec/specs/shared/config-resolution/spec.md @@ -0,0 +1,72 @@ +# Config Resolution Specification + +## Purpose + +Provide platform-agnostic configuration discovery, normalization, persistence, and source reporting for the workspace CLIs. + +## Requirements + +### Requirement: Resolve tool configuration paths by platform + +The system SHALL resolve each tool's configuration file from the operating system conventions and SHALL allow the environment and test/runtime options to override the relevant roots. + +#### Scenario: Resolve the default configuration path + +- **WHEN** a tool requests its configuration path without an explicit override +- **THEN** the system returns a tool-specific `config.json` path under the platform's standard configuration directory + +#### Scenario: Resolve a cache directory + +- **WHEN** a tool has no configured cache directory +- **THEN** the system returns a tool-specific cache directory under the platform's standard cache location + +### Requirement: Apply configuration precedence + +The system SHALL resolve each configured key using environment values before persisted configuration values, and SHALL use a built-in default when neither source provides a value. + +#### Scenario: Environment overrides persisted configuration + +- **WHEN** the same key is present in the environment and in the tool configuration file +- **THEN** the effective value comes from the environment and its source is reported as `env` + +#### Scenario: Missing optional configuration + +- **WHEN** an optional key is absent from both sources +- **THEN** the system returns its default value or `null` according to the key's contract + +### Requirement: Validate and normalize configuration values + +The system SHALL trim and validate string, URL, boolean, and cache-path values before using or persisting them, and SHALL report invalid configuration as a CLI error. + +#### Scenario: Invalid JSON configuration + +- **WHEN** the persisted configuration is not valid JSON or is not an object +- **THEN** the system reports an invalid configuration error with the configuration path + +#### Scenario: Invalid URL configuration + +- **WHEN** a configured service URL is not a valid URL +- **THEN** the system rejects the value with a configuration error + +### Requirement: Persist configuration safely + +The system SHALL create missing configuration directories, write configuration through a temporary file, replace the target atomically, and use restrictive POSIX modes where supported. + +#### Scenario: Initialize a new configuration + +- **WHEN** the user runs a tool's configuration initialization without `--force` and no file exists +- **THEN** the system writes the selected non-empty values and returns the path, configured keys, and their sources + +#### Scenario: Refuse to overwrite an existing configuration + +- **WHEN** the configuration file exists and initialization is run without `--force` +- **THEN** the system fails with a configuration error and preserves the existing file + +### Requirement: Avoid exposing secrets in configuration views + +The system SHALL expose effective configuration metadata and paths without printing raw authentication tokens. + +#### Scenario: Inspect effective configuration + +- **WHEN** the user requests the effective configuration +- **THEN** the output identifies the configuration path and configured keys while redacting secret values diff --git a/openspec/specs/shared/output-formatting/spec.md b/openspec/specs/shared/output-formatting/spec.md new file mode 100644 index 0000000..5935cfa --- /dev/null +++ b/openspec/specs/shared/output-formatting/spec.md @@ -0,0 +1,62 @@ +# Output Formatting Specification + +## Purpose + +Define common machine-readable output, field projection, error envelopes, diagnostics, and anonymization primitives used by the CLIs. + +## Requirements + +### Requirement: Provide predictable JSON output + +The system SHALL serialize successful structured results as JSON and SHALL support compact serialization when requested. + +#### Scenario: Pretty JSON output + +- **WHEN** a command returns structured data without compact mode +- **THEN** stdout contains valid indented JSON followed by a newline + +#### Scenario: Compact JSON output + +- **WHEN** compact mode is requested +- **THEN** stdout contains equivalent JSON without pretty indentation + +### Requirement: Project selected fields + +The system SHALL apply a comma-separated field projection to objects and arrays before rendering output when fields are requested. + +#### Scenario: Project an object + +- **WHEN** the user requests a selected field set +- **THEN** the output contains those fields in the same object shape and omits unselected fields + +#### Scenario: Project a list + +- **WHEN** the user applies a field set to an array +- **THEN** the projection is applied independently to every item + +### Requirement: Emit a machine-readable error envelope + +The system SHALL represent JSON-mode failures as an object with `ok: false` and an `error` object containing a code and message, with optional details. + +#### Scenario: Command failure in JSON mode + +- **WHEN** parsing, configuration, API, or runtime handling fails while JSON mode is requested +- **THEN** stdout contains the JSON error envelope and the process uses the command's exit code + +### Requirement: Normalize diagnostic reports and redact secrets + +The system SHALL provide a stable diagnostic shape containing tool, version, configuration path, authentication availability/source, cache location, checks, and missing values, and SHALL redact authentication tokens. + +#### Scenario: Diagnostic report with a token + +- **WHEN** a doctor command includes authentication information +- **THEN** the report indicates whether a token is available and its source without exposing the token + +### Requirement: Preserve stable anonymized identities + +The system SHALL normalize identity values before hashing them and SHALL use deterministic `sha256:` identities when a product-specific output policy requires anonymization. + +#### Scenario: Same identity appears repeatedly + +- **WHEN** the same normalized user identity is rendered more than once +- **THEN** it receives the same anonymized identity in every output From 098602206b460bbb80665dcb7cf93d4618489590 Mon Sep 17 00:00:00 2001 From: "a.khanteev" Date: Mon, 24 Aug 2026 14:09:13 +0400 Subject: [PATCH 2/2] fix: stabilize date tests and add pre-commit checks --- .githooks/pre-commit | 10 ++++++++++ README.md | 6 ++++++ package.json | 1 + packages/cli-core/test/date.test.js | 26 ++++++++++++++++---------- scripts/setup-git-hooks.mjs | 17 +++++++++++++++++ 5 files changed, 50 insertions(+), 10 deletions(-) create mode 100755 .githooks/pre-commit create mode 100644 scripts/setup-git-hooks.mjs diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..e58c6ef --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,10 @@ +#!/bin/sh + +set -eu + +if ! command -v pnpm >/dev/null 2>&1; then + echo "pre-commit: pnpm is required to run the test suite" >&2 + exit 1 +fi + +exec pnpm test diff --git a/README.md b/README.md index c86a9aa..63d3b89 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,12 @@ That means: ## Dev Install +Install dependencies from the repository root to configure the pre-commit hook. It runs the workspace test suite before each commit: + +```bash +pnpm install +``` + To create development commands for `glc` and `ktc` under `~/.local/bin/`: ```bash diff --git a/package.json b/package.json index 01540f4..bc6b743 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ }, "scripts": { "dev:install": "node ./scripts/dev-install.mjs", + "prepare": "node ./scripts/setup-git-hooks.mjs", "lint": "pnpm -r lint", "test": "pnpm -r test", "pack": "pnpm -r pack:check", diff --git a/packages/cli-core/test/date.test.js b/packages/cli-core/test/date.test.js index 1a366e3..e1aaff7 100644 --- a/packages/cli-core/test/date.test.js +++ b/packages/cli-core/test/date.test.js @@ -9,19 +9,25 @@ test("parseDateSpec: ISO8601", () => { }); test("parseDateSpec: relative durations", () => { - const now = Date.now(); - + const assertCloseToExpected = (input, adjust) => { + const result = parseDateSpec(input); + const expected = new Date(); + adjust(expected); + + assert.ok( + Math.abs(result.getTime() - expected.getTime()) < 1000, + `${input} should resolve close to the expected relative date` + ); + }; + // Hours - const h = parseDateSpec("1h"); - assert.ok(now - h.getTime() >= 60 * 60 * 1000); - + assertCloseToExpected("1h", (date) => date.setHours(date.getHours() - 1)); + // Days - const d = parseDateSpec("1d"); - assert.ok(now - d.getTime() >= 24 * 60 * 60 * 1000); - + assertCloseToExpected("1d", (date) => date.setDate(date.getDate() - 1)); + // Weeks - const w = parseDateSpec("1w"); - assert.ok(now - w.getTime() >= 7 * 24 * 60 * 60 * 1000); + assertCloseToExpected("1w", (date) => date.setDate(date.getDate() - 7)); }); test("parseDateSpec: months and years (approximate check)", () => { diff --git a/scripts/setup-git-hooks.mjs b/scripts/setup-git-hooks.mjs new file mode 100644 index 0000000..891e54a --- /dev/null +++ b/scripts/setup-git-hooks.mjs @@ -0,0 +1,17 @@ +import { execFileSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const scriptDir = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(scriptDir, ".."); + +if (!fs.existsSync(path.join(repoRoot, ".git"))) { + process.exit(0); +} + +execFileSync("git", ["-C", repoRoot, "config", "core.hooksPath", ".githooks"], { + stdio: "inherit" +}); + +process.stdout.write("Configured Git hooks from .githooks\n");