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
7 changes: 4 additions & 3 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@ This directory contains a complete setup for Prometheus, Grafana, a Go applicati
- `scripts/fix-docker-compose-imagehash.sh`: Automatically updates `:latest` or tagged images in Compose files to their `sha256` digests, adding the original tag as a comment.
- `scripts/check-unfixed-imagehash.sh`: Verifies that all images in a Compose file are pinned with `sha256`.

## `linear-pick-one`
A TypeScript tool to fetch tasks from a Linear custom view and pick one randomly.
## `linear-tools`
A TypeScript toolset to fetch task lists from Linear custom views and run helper commands.

### Usage
1. Set `LINEAR_API_KEY` environment variable
2. `pnpm fetch <linear-view-url>`: Fetch tasks and save to `linear/list-YYYY-MM-DD.md`
3. `pnpm pick`: Randomly select one task from the latest list
3. `pnpm fetch:recently-done <linear-view-url> [days]`: Save completed tasks within the last 7 days (or custom range) to `linear/recently-done-YYYY-MM-DD.md`
4. `pnpm pick`: Randomly select one task from the latest list

### Development
- Uses `@linear/sdk` for Linear API communication
Expand Down
6 changes: 3 additions & 3 deletions .github/renovate.json5
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
'npm',
],
matchFileNames: [
'linear-pick-one/**',
'linear-tools/**',
],
groupName: 'linear-pick-one-dependencies',
description: 'Group npm updates for linear-pick-one',
groupName: 'linear-tools-dependencies',
description: 'Group npm updates for linear-tools',
},
],
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
name: Linear Pick One CI
name: Linear Tools CI

on:
push:
paths:
- 'linear-pick-one/**'
- '.github/workflows/linear-pick-one.yml'
- 'linear-tools/**'
- '.github/workflows/linear-tools.yml'
pull_request:
paths:
- 'linear-pick-one/**'
- '.github/workflows/linear-pick-one.yml'
- 'linear-tools/**'
- '.github/workflows/linear-tools.yml'

jobs:
test:
Expand All @@ -18,13 +18,13 @@ jobs:
- uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1
- name: Install dependencies
run: |
cd linear-pick-one
cd linear-tools
pnpm install --frozen-lockfile
- name: Type check
run: |
cd linear-pick-one
cd linear-tools
pnpm typecheck
- name: Test
run: |
cd linear-pick-one
cd linear-tools
pnpm test
16 changes: 8 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Project Structure & Module Organization
This repository is a small monorepo for development playgrounds.

- `linear-pick-one/`: TypeScript CLI to fetch tasks from a Linear custom view and pick one task.
- `linear-tools/`: TypeScript CLI utilities to fetch tasks from Linear custom views.
- `prometheus-starter/`: Monitoring demo stack (Go app + Prometheus + Grafana + k6).
- `scripts/`: Repository-wide maintenance scripts (for example image hash checks/fixes and copy helpers).
- `.github/workflows/`: CI for each module and repository policy checks.
Expand All @@ -13,10 +13,10 @@ This repository is a small monorepo for development playgrounds.
Run commands from the repository root unless noted.

- `mise install`: Install pinned tool versions.
- `cd linear-pick-one && pnpm install --frozen-lockfile`: Install JS dependencies.
- `cd linear-pick-one && pnpm typecheck`: Run strict TypeScript checks.
- `cd linear-pick-one && pnpm test`: Run Vitest tests once.
- `cd linear-pick-one && pnpm build`: Compile to `dist/`.
- `cd linear-tools && pnpm install --frozen-lockfile`: Install JS dependencies.
- `cd linear-tools && pnpm typecheck`: Run strict TypeScript checks.
- `cd linear-tools && pnpm test`: Run Vitest tests once.
- `cd linear-tools && pnpm build`: Compile to `dist/`.
- `cd prometheus-starter/app && go test -v ./...`: Run Go unit tests.
- `cd prometheus-starter && docker compose up -d`: Start integration stack locally.
- `./scripts/check-unfixed-imagehash.sh <compose-file>`: Verify images are SHA-pinned.
Expand All @@ -26,16 +26,16 @@ Run commands from the repository root unless noted.
- Go: Follow `gofmt` defaults and idiomatic Go naming.
- Tests: keep test files colocated with code (`*.test.ts`, `*_test.go`).
- Scripts: Bash with `set -e`/`set -euo pipefail` where possible.
- Keep module boundaries clear; avoid cross-module coupling between `linear-pick-one` and `prometheus-starter`.
- Keep module boundaries clear; avoid cross-module coupling between `linear-tools` and `prometheus-starter`.

## Testing Guidelines
- `linear-pick-one` uses `vitest` (`vitest run` in CI).
- `linear-tools` uses `vitest` (`vitest run` in CI).
- `prometheus-starter/app` uses Go’s standard `testing` package.
- Add tests for behavior changes and bug fixes before merging.
- For Compose changes, ensure images are pinned to `@sha256:...` digests or CI will fail.

## Commit & Pull Request Guidelines
- Use Conventional-Commit-like prefixes seen in history: `feat:`, `fix:`, `chore:`, `docs:`, `ci:`, `test:` (optional scope, e.g. `fix(linear-pick-one): ...`).
- Use Conventional-Commit-like prefixes seen in history: `feat:`, `fix:`, `chore:`, `docs:`, `ci:`, `test:` (optional scope, e.g. `fix(linear-tools): ...`).
- Keep commits focused and reviewable.
- PRs should include:
- What changed and why.
Expand Down
File renamed without changes.
37 changes: 33 additions & 4 deletions linear-pick-one/README.md → linear-tools/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# linear-pick-one
# linear-tools

Linear のカスタムビューからタスク一覧を取得し、ランダムに1つを選ぶツールです。
Linear のカスタムビューからタスク一覧を取得するためのツール群です。
`Recently Done` ビューの直近1週間タスク一覧出力と、タスクのランダム選択に対応しています。

## セットアップ

```bash
cd linear-pick-one
cd linear-tools
pnpm install
```

Expand All @@ -31,7 +32,23 @@ pnpm fetch "https://linear.app/your-team/view/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx

タスク一覧が `linear/list-YYYY-MM-DD.md` に保存されます。

### 2. ランダムに1つ選ぶ
### 2. Recently Done の直近タスク一覧を取得

`Recently Done` ビュー URL を指定し、完了日が直近7日以内のタスクを出力します。

```bash
pnpm fetch:recently-done "https://linear.app/your-team/view/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
```

任意で日数を指定できます(例: 14日)。

```bash
pnpm fetch:recently-done "https://linear.app/your-team/view/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 14
```

出力は `linear/recently-done-YYYY-MM-DD.md` です。

### 3. ランダムに1つ選ぶ

```bash
pnpm pick
Expand All @@ -57,6 +74,18 @@ pnpm pick
- **URL**: https://linear.app/team/issue/ABC-456
```

`linear/recently-done-YYYY-MM-DD.md` の形式:

```markdown
# Linear Recently Done Tasks (Last 7 Days)

## ABC-123

- **Title**: タスクのタイトル
- **URL**: https://linear.app/team/issue/ABC-123
- **Completed At**: 2026-02-10
```

## 開発

### テスト
Expand Down
5 changes: 3 additions & 2 deletions linear-pick-one/package.json → linear-tools/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "linear-pick-one",
"name": "linear-tools",
"version": "1.0.0",
"description": "Fetch tasks from Linear view and pick one randomly",
"description": "Utilities for fetching and working with Linear task lists",
"private": true,
"type": "module",
"scripts": {
Expand All @@ -10,6 +10,7 @@
"test": "vitest run",
"test:watch": "vitest",
"fetch": "tsx src/fetch-tasks.ts",
"fetch:recently-done": "tsx src/fetch-recently-done.ts",
"pick": "./scripts/pick-random.sh"
},
"keywords": [],
Expand Down
File renamed without changes.
53 changes: 53 additions & 0 deletions linear-tools/src/fetch-recently-done.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { LinearClient } from "@linear/sdk";
import * as path from "node:path";
import {
fetchTasksFromView,
filterTasksCompletedWithinDays,
parseViewUrl,
saveRecentlyDoneTasksToFile,
} from "./fetch-tasks";

async function main() {
const viewUrl = process.argv[2];
const daysArg = process.argv[3];

if (!viewUrl) {
console.error("Usage: pnpm fetch:recently-done <linear-view-url> [days]");
process.exit(1);
}

const days = daysArg ? Number.parseInt(daysArg, 10) : 7;
if (!Number.isInteger(days) || days <= 0) {
console.error("Error: days must be a positive integer");
process.exit(1);
}

const apiKey = process.env.LINEAR_API_KEY;
if (!apiKey) {
console.error("Error: LINEAR_API_KEY environment variable is not set");
process.exit(1);
}

try {
const { viewId } = parseViewUrl(viewUrl);
const client = new LinearClient({ apiKey });

console.log(`Fetching recently done tasks from view: ${viewId}`);
const tasks = await fetchTasksFromView(client, viewId);
const recentTasks = filterTasksCompletedWithinDays(tasks, days);

const outputDir = path.join(process.cwd(), "linear");
const filepath = saveRecentlyDoneTasksToFile(recentTasks, outputDir, days);

console.log(
`Saved ${recentTasks.length} tasks completed in last ${days} days to: ${filepath}`
);
} catch (error) {
console.error("Error:", error instanceof Error ? error.message : error);
process.exit(1);
}
}

if (import.meta.url === `file://${process.argv[1]}`) {
main();
}
Comment on lines +51 to +53
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

pnpm fetch:recently-donetsx src/fetch-recently-done.ts のように相対パスで起動されるため、import.meta.url(絶対file URL)と file://${process.argv[1]}(相対っぽいURL) が一致せず main() が実行されない可能性が高いです。fileURLToPath(import.meta.url)path.resolve(process.argv[1]) を比較する(または pathToFileURL を使って argv[1] を正規化する)方式に変更してください。

Copilot uses AI. Check for mistakes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { describe, it, expect } from "vitest";
import { parseViewUrl, formatTasksToMarkdown, type Task } from "./fetch-tasks";
import {
parseViewUrl,
formatTasksToMarkdown,
formatRecentlyDoneTasksToMarkdown,
filterTasksCompletedWithinDays,
type Task,
} from "./fetch-tasks";

describe("parseViewUrl", () => {
it("should extract viewId from Linear view URL", () => {
Expand Down Expand Up @@ -44,11 +50,13 @@ describe("formatTasksToMarkdown", () => {
identifier: "ABC-123",
title: "Fix bug",
url: "https://linear.app/team/issue/ABC-123",
completedAt: null,
},
{
identifier: "ABC-456",
title: "Add feature",
url: "https://linear.app/team/issue/ABC-456",
completedAt: null,
},
];

Expand All @@ -74,3 +82,62 @@ describe("formatTasksToMarkdown", () => {
expect(result).toBe("# Linear Tasks\n\nNo tasks found.\n");
});
});

describe("filterTasksCompletedWithinDays", () => {
it("should keep only tasks completed in the last 7 days", () => {
const now = new Date("2026-02-11T00:00:00.000Z");
const tasks: Task[] = [
{
identifier: "ABC-001",
title: "Recent task",
url: "https://linear.app/team/issue/ABC-001",
completedAt: new Date("2026-02-10T12:00:00.000Z"),
},
{
identifier: "ABC-002",
title: "Old task",
url: "https://linear.app/team/issue/ABC-002",
completedAt: new Date("2026-02-01T12:00:00.000Z"),
},
{
identifier: "ABC-003",
title: "No completion date",
url: "https://linear.app/team/issue/ABC-003",
completedAt: null,
},
{
identifier: "ABC-004",
title: "Boundary task",
url: "https://linear.app/team/issue/ABC-004",
completedAt: new Date("2026-02-04T00:00:00.000Z"),
},
];

const result = filterTasksCompletedWithinDays(tasks, 7, now);
expect(result.map((task) => task.identifier)).toEqual(["ABC-001", "ABC-004"]);
});
});

describe("formatRecentlyDoneTasksToMarkdown", () => {
it("should format recently done tasks to markdown list", () => {
const tasks: Task[] = [
{
identifier: "ABC-123",
title: "Fix bug",
url: "https://linear.app/team/issue/ABC-123",
completedAt: new Date("2026-02-10T12:00:00.000Z"),
},
];

const result = formatRecentlyDoneTasksToMarkdown(tasks, 7);
const expected = `# Linear Recently Done Tasks (Last 7 Days)

## ABC-123

- **Title**: Fix bug
- **URL**: https://linear.app/team/issue/ABC-123
- **Completed At**: 2026-02-10
`;
expect(result).toBe(expected);
});
});
Loading