@shipbench/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/dist/chunk-7T2E4KSJ.js +68 -0
- package/dist/index.d.ts +252 -0
- package/dist/index.js +1763 -0
- package/dist/layout-BS-84zwS.d.ts +228 -0
- package/dist/layout.d.ts +1 -0
- package/dist/layout.js +14 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 gmmurray
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# @shipbench/core
|
|
2
|
+
|
|
3
|
+
The headless library behind [ShipBench](https://github.com/gmmurray/shipbench) —
|
|
4
|
+
Git-native project management where the task board lives in the repository as
|
|
5
|
+
plain Markdown.
|
|
6
|
+
|
|
7
|
+
This package parses, validates, and writes that board. It has **no filesystem
|
|
8
|
+
access, no UI, and no network calls of its own**: every read and write goes
|
|
9
|
+
through a `StorageAdapter` you supply, so the same logic runs against a local
|
|
10
|
+
checkout, the GitHub API, or an in-memory fixture in a test.
|
|
11
|
+
|
|
12
|
+
Most people want the [`shipbench` CLI](https://www.npmjs.com/package/shipbench)
|
|
13
|
+
instead. Use this package when you are building a tool on top of the
|
|
14
|
+
convention.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @shipbench/core
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { FsAdapter, loadConfig, listTasks, createTask } from '@shipbench/core';
|
|
26
|
+
|
|
27
|
+
const adapter = new FsAdapter(process.cwd());
|
|
28
|
+
const config = await loadConfig(adapter);
|
|
29
|
+
|
|
30
|
+
const { tasks, warnings } = await listTasks(adapter, config);
|
|
31
|
+
|
|
32
|
+
await createTask(adapter, config, {
|
|
33
|
+
title: 'Build the API',
|
|
34
|
+
priority: 'high',
|
|
35
|
+
tags: ['backend'],
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Adapters
|
|
40
|
+
|
|
41
|
+
Two implementations ship with the package:
|
|
42
|
+
|
|
43
|
+
- **`FsAdapter`** — full read/write against the local filesystem.
|
|
44
|
+
- **`GitHubAdapter`** — reads `.shipbench/` through the GitHub Contents API.
|
|
45
|
+
Cross-runtime, so it works in Node, browsers, and Cloudflare Workers.
|
|
46
|
+
|
|
47
|
+
The interface is deliberately small so additional hosts stay easy to add:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
interface ReadableStorageAdapter {
|
|
51
|
+
readFile(path: string): Promise<string>;
|
|
52
|
+
readFileIfExists(path: string): Promise<string | null>;
|
|
53
|
+
listFiles(directory: string): Promise<string[]>;
|
|
54
|
+
readFiles(paths: string[]): Promise<Map<string, string>>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface StorageAdapter extends ReadableStorageAdapter {
|
|
58
|
+
writeFile(path: string, content: string): Promise<void>;
|
|
59
|
+
writeFiles(files: Map<string, string>): Promise<void>;
|
|
60
|
+
deleteFile(path: string): Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Consumers that only display data should hold a `ReadableStorageAdapter` — it
|
|
65
|
+
turns an accidental write into a compile error.
|
|
66
|
+
|
|
67
|
+
## What it gives you
|
|
68
|
+
|
|
69
|
+
- **Tasks** — `createTask`, `getTask`, `updateTask`, `moveTask`, `reorderTask`,
|
|
70
|
+
`deleteTask`, `listTasks`, `searchTasks`
|
|
71
|
+
- **Archiving** — `archiveTask`, `unarchiveTask`, `listArchivedTasks`
|
|
72
|
+
- **Dependencies** — `listAvailableTasks`, `listBlockedTasks`,
|
|
73
|
+
`buildTaskDependencyGraph`
|
|
74
|
+
- **Comments** — `addComment`, `editComment`, `deleteComment`
|
|
75
|
+
- **Config and setup** — `loadConfig`, `validateConfig`, `initProject`,
|
|
76
|
+
`DEFAULT_CONFIG`
|
|
77
|
+
- **Slugs** — `slugify`, `resolveSlugCollision`
|
|
78
|
+
|
|
79
|
+
## Design rules worth knowing
|
|
80
|
+
|
|
81
|
+
**Strict on write, graceful on read.** Invalid statuses and priorities are
|
|
82
|
+
rejected on write. On read, a task with an unrecognized status comes back with a
|
|
83
|
+
validation warning rather than being dropped — the board never silently loses
|
|
84
|
+
work.
|
|
85
|
+
|
|
86
|
+
**Unknown frontmatter is preserved.** Core passes through fields it does not own,
|
|
87
|
+
with a warning. It never strips data it did not write.
|
|
88
|
+
|
|
89
|
+
**Timestamps are managed for you.** `created` is set once; `updated` moves on
|
|
90
|
+
every mutation. Both ISO 8601.
|
|
91
|
+
|
|
92
|
+
**Partial configs are fine.** `config.json` is deep-merged over `DEFAULT_CONFIG`
|
|
93
|
+
at read time, so any field can be omitted.
|
|
94
|
+
|
|
95
|
+
## The `/layout` subpath
|
|
96
|
+
|
|
97
|
+
Manual task ordering is shared contract, not private logic — hosts running
|
|
98
|
+
optimistic updates must produce the same order core would write. It is exported
|
|
99
|
+
from a pure subpath that imports only types:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { orderedTasksForColumn, layoutAfterMove } from '@shipbench/core/layout';
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Import ordering helpers from `@shipbench/core/layout`, never the package root —
|
|
106
|
+
the root re-exports `FsAdapter`, which pulls in `node:fs` and will break a
|
|
107
|
+
browser bundle.
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
MIT
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// src/layout.ts
|
|
2
|
+
function byCreatedDesc(a, b) {
|
|
3
|
+
return Date.parse(b.frontmatter.created) - Date.parse(a.frontmatter.created);
|
|
4
|
+
}
|
|
5
|
+
function byUpdatedDesc(a, b) {
|
|
6
|
+
return Date.parse(b.frontmatter.updated) - Date.parse(a.frontmatter.updated);
|
|
7
|
+
}
|
|
8
|
+
function layoutWithoutTask(layout, slug, existingSlugs) {
|
|
9
|
+
const next = {};
|
|
10
|
+
for (const [columnId, slugs] of Object.entries(layout)) {
|
|
11
|
+
next[columnId] = slugs.filter(
|
|
12
|
+
(candidate) => candidate !== slug && (existingSlugs === void 0 || existingSlugs.has(candidate))
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
return next;
|
|
16
|
+
}
|
|
17
|
+
function layoutAfterMove({
|
|
18
|
+
layout,
|
|
19
|
+
tasks,
|
|
20
|
+
slug,
|
|
21
|
+
toStatus,
|
|
22
|
+
position,
|
|
23
|
+
doneColumn
|
|
24
|
+
}) {
|
|
25
|
+
const existingSlugs = new Set(tasks.map((task) => task.slug));
|
|
26
|
+
const next = layoutWithoutTask(layout, slug, existingSlugs);
|
|
27
|
+
delete next[doneColumn];
|
|
28
|
+
if (toStatus === doneColumn) return next;
|
|
29
|
+
const currentOrder = next[toStatus] ?? [];
|
|
30
|
+
const placed = new Set(currentOrder);
|
|
31
|
+
const leftovers = tasks.filter(
|
|
32
|
+
(task) => task.frontmatter.status === toStatus && task.slug !== slug && !placed.has(task.slug)
|
|
33
|
+
).slice().sort(byCreatedDesc).map((task) => task.slug);
|
|
34
|
+
const destination = [...currentOrder, ...leftovers];
|
|
35
|
+
const insertAt = position < 0 || position > destination.length ? destination.length : position;
|
|
36
|
+
destination.splice(insertAt, 0, slug);
|
|
37
|
+
next[toStatus] = destination;
|
|
38
|
+
return next;
|
|
39
|
+
}
|
|
40
|
+
function orderedTasksForColumn(tasks, layout, columnId, validStatuses, doneColumn) {
|
|
41
|
+
if (!validStatuses.has(columnId)) {
|
|
42
|
+
return tasks.filter((task) => !validStatuses.has(task.frontmatter.status)).slice().sort(byCreatedDesc);
|
|
43
|
+
}
|
|
44
|
+
if (columnId === doneColumn) {
|
|
45
|
+
return tasks.filter((task) => task.frontmatter.status === columnId).slice().sort(byUpdatedDesc);
|
|
46
|
+
}
|
|
47
|
+
const inColumn = tasks.filter((task) => task.frontmatter.status === columnId);
|
|
48
|
+
const bySlug = new Map(inColumn.map((task) => [task.slug, task]));
|
|
49
|
+
const ordered = [];
|
|
50
|
+
const placed = /* @__PURE__ */ new Set();
|
|
51
|
+
for (const slug of layout?.[columnId] ?? []) {
|
|
52
|
+
const task = bySlug.get(slug);
|
|
53
|
+
if (task) {
|
|
54
|
+
ordered.push(task);
|
|
55
|
+
placed.add(slug);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const leftovers = inColumn.filter((task) => !placed.has(task.slug)).slice().sort(byCreatedDesc);
|
|
59
|
+
return [...ordered, ...leftovers];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export {
|
|
63
|
+
byCreatedDesc,
|
|
64
|
+
byUpdatedDesc,
|
|
65
|
+
layoutWithoutTask,
|
|
66
|
+
layoutAfterMove,
|
|
67
|
+
orderedTasksForColumn
|
|
68
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { S as StorageAdapter, R as ReadableStorageAdapter, T as Task, a as ShipbenchConfig, L as LoadConfigOptions, P as ProjectWarning, b as TaskFrontmatter, c as TaskReadResult, B as BoardLayout } from './layout-BS-84zwS.js';
|
|
2
|
+
export { d as BoardAPI, C as ColumnDef, e as ConfigLoadWarning, D as DoneDisplayConfig, f as PriorityConfig, g as TaskComment, h as TaskValidationWarning, i as byCreatedDesc, j as byUpdatedDesc, l as layoutAfterMove, k as layoutWithoutTask, o as orderedTasksForColumn } from './layout-BS-84zwS.js';
|
|
3
|
+
|
|
4
|
+
declare class FsAdapter implements StorageAdapter {
|
|
5
|
+
private rootDir;
|
|
6
|
+
constructor(rootDir: string);
|
|
7
|
+
private resolve;
|
|
8
|
+
readFile(path: string): Promise<string>;
|
|
9
|
+
readFileIfExists(path: string): Promise<string | null>;
|
|
10
|
+
writeFile(path: string, content: string): Promise<void>;
|
|
11
|
+
deleteFile(path: string): Promise<void>;
|
|
12
|
+
listFiles(directory: string): Promise<string[]>;
|
|
13
|
+
readFiles(paths: string[]): Promise<Map<string, string>>;
|
|
14
|
+
writeFiles(files: Map<string, string>): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface GitHubAdapterOptions {
|
|
18
|
+
owner: string;
|
|
19
|
+
repo: string;
|
|
20
|
+
token: string;
|
|
21
|
+
branch?: string;
|
|
22
|
+
/** Inject a custom fetch (useful for tests). Defaults to global fetch. */
|
|
23
|
+
fetch?: typeof fetch;
|
|
24
|
+
/** Override the commit author message prefix. */
|
|
25
|
+
commitMessagePrefix?: string;
|
|
26
|
+
}
|
|
27
|
+
declare class GitHubApiError extends Error {
|
|
28
|
+
readonly status: number;
|
|
29
|
+
readonly statusText: string;
|
|
30
|
+
readonly operation: string;
|
|
31
|
+
readonly path: string;
|
|
32
|
+
constructor(options: {
|
|
33
|
+
status: number;
|
|
34
|
+
statusText: string;
|
|
35
|
+
operation: string;
|
|
36
|
+
path: string;
|
|
37
|
+
body?: string;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* GitHub Contents API adapter.
|
|
42
|
+
*
|
|
43
|
+
* Implements the read-only `StorageAdapter` surface (satisfies `BoardAPI`
|
|
44
|
+
* consumption in Harbor's remote mode). `writeFile` / `writeFiles` are
|
|
45
|
+
* concrete methods used only by Harbor's onboarding flows (seed commits,
|
|
46
|
+
* workspace init, config recovery) — never wired up to the interface
|
|
47
|
+
* because ongoing task CRUD never routes through GitHub. Delete is
|
|
48
|
+
* intentionally unimplemented: nothing in the current or planned
|
|
49
|
+
* architecture ever deletes via the GitHub API.
|
|
50
|
+
*/
|
|
51
|
+
declare class GitHubAdapter implements ReadableStorageAdapter {
|
|
52
|
+
private owner;
|
|
53
|
+
private repo;
|
|
54
|
+
private token;
|
|
55
|
+
private branch;
|
|
56
|
+
private fetchImpl;
|
|
57
|
+
private commitMessagePrefix;
|
|
58
|
+
constructor(options: GitHubAdapterOptions);
|
|
59
|
+
private contentsUrl;
|
|
60
|
+
private contentsReadUrl;
|
|
61
|
+
private headers;
|
|
62
|
+
private errorFromResponse;
|
|
63
|
+
private readFileContent;
|
|
64
|
+
readFile(path: string): Promise<string>;
|
|
65
|
+
readFileIfExists(path: string): Promise<string | null>;
|
|
66
|
+
/** Returns the SHA of an existing file, or undefined if it does not exist. */
|
|
67
|
+
private getSha;
|
|
68
|
+
writeFile(path: string, content: string): Promise<void>;
|
|
69
|
+
listFiles(directory: string): Promise<string[]>;
|
|
70
|
+
readFiles(paths: string[]): Promise<Map<string, string>>;
|
|
71
|
+
writeFiles(files: Map<string, string>): Promise<void>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface TaskAvailabilityOptions {
|
|
75
|
+
/** Column whose tasks are candidates. Defaults to `config.default_column`. */
|
|
76
|
+
status?: string;
|
|
77
|
+
/** Already-read tasks from `.shipbench/tasks/archive/`. */
|
|
78
|
+
archivedTasks?: readonly Task[];
|
|
79
|
+
/** Archive file slugs, including files whose contents did not parse. */
|
|
80
|
+
archivedSlugs?: Iterable<string>;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Returns tasks in the actionable column whose dependencies are all complete.
|
|
84
|
+
* This function is pure and performs no adapter reads.
|
|
85
|
+
*/
|
|
86
|
+
declare function listAvailableTasks(tasks: readonly Task[], config: ShipbenchConfig, options?: TaskAvailabilityOptions): Task[];
|
|
87
|
+
/**
|
|
88
|
+
* Returns tasks in the actionable column with at least one unsatisfied
|
|
89
|
+
* dependency. This function is pure and performs no adapter reads.
|
|
90
|
+
*/
|
|
91
|
+
declare function listBlockedTasks(tasks: readonly Task[], config: ShipbenchConfig, options?: TaskAvailabilityOptions): Task[];
|
|
92
|
+
|
|
93
|
+
declare function loadConfig(adapter: ReadableStorageAdapter, options?: LoadConfigOptions): Promise<ShipbenchConfig>;
|
|
94
|
+
declare function validateConfig(config: ShipbenchConfig): string[];
|
|
95
|
+
|
|
96
|
+
declare const DEFAULT_CONFIG: ShipbenchConfig;
|
|
97
|
+
|
|
98
|
+
interface TaskDependencyGraphNode {
|
|
99
|
+
status: string;
|
|
100
|
+
depends_on: string[];
|
|
101
|
+
blocks: string[];
|
|
102
|
+
}
|
|
103
|
+
type TaskDependencyGraph = Record<string, TaskDependencyGraphNode>;
|
|
104
|
+
interface TaskDependencyGraphOptions {
|
|
105
|
+
/** Already-read tasks from `.shipbench/tasks/archive/`. */
|
|
106
|
+
archivedTasks?: readonly Task[];
|
|
107
|
+
/** Archive file slugs, including files whose contents did not parse. */
|
|
108
|
+
archivedSlugs?: Iterable<string>;
|
|
109
|
+
}
|
|
110
|
+
interface TaskDependencyIndex {
|
|
111
|
+
liveTasksBySlug: ReadonlyMap<string, Task>;
|
|
112
|
+
archivedTasksBySlug: ReadonlyMap<string, Task>;
|
|
113
|
+
/** Archive file identities, including files whose contents did not parse. */
|
|
114
|
+
archivedSlugs: ReadonlySet<string>;
|
|
115
|
+
}
|
|
116
|
+
type TaskDependencyResolution = {
|
|
117
|
+
kind: 'live';
|
|
118
|
+
status: string;
|
|
119
|
+
task: Task;
|
|
120
|
+
} | {
|
|
121
|
+
kind: 'archived';
|
|
122
|
+
status: 'archived';
|
|
123
|
+
task?: Task;
|
|
124
|
+
} | {
|
|
125
|
+
kind: 'missing';
|
|
126
|
+
status: 'missing';
|
|
127
|
+
};
|
|
128
|
+
declare function createTaskDependencyIndex(liveTasks: readonly Task[], archivedTasks?: readonly Task[], archivedSlugs?: Iterable<string>): TaskDependencyIndex;
|
|
129
|
+
declare function resolveTaskDependency(slug: string, index: TaskDependencyIndex): TaskDependencyResolution;
|
|
130
|
+
declare function dependencyStatus(slug: string, index: TaskDependencyIndex): string;
|
|
131
|
+
declare function taskDependenciesAreSatisfied(task: Task, index: TaskDependencyIndex, doneColumn: string): boolean;
|
|
132
|
+
/**
|
|
133
|
+
* Builds forward and reverse dependency adjacency for live tasks plus any
|
|
134
|
+
* supplied archived tasks. Referenced slugs outside that set are represented
|
|
135
|
+
* as missing nodes so callers never need a second lookup.
|
|
136
|
+
*/
|
|
137
|
+
declare function buildTaskDependencyGraph(liveTasks: readonly Task[], options?: TaskDependencyGraphOptions): TaskDependencyGraph;
|
|
138
|
+
|
|
139
|
+
interface GithubRepositoryParts {
|
|
140
|
+
owner: string;
|
|
141
|
+
repo: string;
|
|
142
|
+
}
|
|
143
|
+
declare function parseGithubUrl(input: string): GithubRepositoryParts | null;
|
|
144
|
+
declare function parseGithubRemoteUrl(input: string): GithubRepositoryParts | null;
|
|
145
|
+
declare function normalizeGithubUrl(input: string): string | null;
|
|
146
|
+
declare function normalizeGithubRemoteUrl(input: string): string | null;
|
|
147
|
+
|
|
148
|
+
interface InitProjectOptions {
|
|
149
|
+
name: string;
|
|
150
|
+
}
|
|
151
|
+
interface MissingProjectInitializationState {
|
|
152
|
+
kind: 'missing';
|
|
153
|
+
}
|
|
154
|
+
interface InitializedProjectInitializationState {
|
|
155
|
+
kind: 'initialized';
|
|
156
|
+
config: ShipbenchConfig;
|
|
157
|
+
warnings: ProjectWarning[];
|
|
158
|
+
}
|
|
159
|
+
interface IncompleteProjectInitializationState {
|
|
160
|
+
kind: 'incomplete';
|
|
161
|
+
paths: string[];
|
|
162
|
+
}
|
|
163
|
+
interface MalformedProjectInitializationState {
|
|
164
|
+
kind: 'malformed';
|
|
165
|
+
errors: string[];
|
|
166
|
+
}
|
|
167
|
+
interface InvalidProjectInitializationState {
|
|
168
|
+
kind: 'invalid';
|
|
169
|
+
errors: string[];
|
|
170
|
+
}
|
|
171
|
+
type ProjectInitializationState = MissingProjectInitializationState | InitializedProjectInitializationState | IncompleteProjectInitializationState | MalformedProjectInitializationState | InvalidProjectInitializationState;
|
|
172
|
+
interface InitProjectResult {
|
|
173
|
+
created: boolean;
|
|
174
|
+
config: ShipbenchConfig;
|
|
175
|
+
warnings: ProjectWarning[];
|
|
176
|
+
}
|
|
177
|
+
type FailedProjectInitializationState = Exclude<ProjectInitializationState, MissingProjectInitializationState | InitializedProjectInitializationState>;
|
|
178
|
+
declare class ProjectInitializationError extends Error {
|
|
179
|
+
readonly state: FailedProjectInitializationState;
|
|
180
|
+
constructor(state: FailedProjectInitializationState);
|
|
181
|
+
}
|
|
182
|
+
declare function inspectProjectInitialization(adapter: ReadableStorageAdapter): Promise<ProjectInitializationState>;
|
|
183
|
+
declare function initProject(adapter: StorageAdapter, options: InitProjectOptions): Promise<InitProjectResult>;
|
|
184
|
+
|
|
185
|
+
type TaskSearchField = 'title' | 'tags' | 'body';
|
|
186
|
+
interface TaskSearchMatch {
|
|
187
|
+
slug: string;
|
|
188
|
+
title: string;
|
|
189
|
+
matched_fields: TaskSearchField[];
|
|
190
|
+
snippet?: string;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Splits the query on whitespace and finds tasks in which every
|
|
194
|
+
* case-insensitive term occurs as a substring of a title, tag, or Markdown
|
|
195
|
+
* body. Terms may occur in different fields. Input order is preserved so
|
|
196
|
+
* callers can sort before search and limit the returned matches afterward.
|
|
197
|
+
*/
|
|
198
|
+
declare function searchTasks(tasks: readonly Task[], query: string): TaskSearchMatch[];
|
|
199
|
+
|
|
200
|
+
declare function slugify(title: string): string;
|
|
201
|
+
declare function resolveSlugCollision(slug: string, existingSlugs: Set<string>): string;
|
|
202
|
+
|
|
203
|
+
type Awaitable<T> = T | PromiseLike<T>;
|
|
204
|
+
interface GetTaskOptions {
|
|
205
|
+
archived?: boolean;
|
|
206
|
+
}
|
|
207
|
+
interface ListTasksOptions {
|
|
208
|
+
/** Already-read or concurrently-loading tasks from `tasks/archive/`. */
|
|
209
|
+
archivedTasks?: Awaitable<readonly Task[]>;
|
|
210
|
+
/** Archive file slugs, including files whose contents did not parse. */
|
|
211
|
+
archivedSlugs?: Awaitable<Iterable<string>>;
|
|
212
|
+
}
|
|
213
|
+
declare class ArchiveBlockedError extends Error {
|
|
214
|
+
readonly slug: string;
|
|
215
|
+
readonly dependentSlugs: string[];
|
|
216
|
+
constructor(slug: string, dependentSlugs: string[]);
|
|
217
|
+
}
|
|
218
|
+
declare function listTasks(adapter: ReadableStorageAdapter, config: ShipbenchConfig, options?: ListTasksOptions): Promise<TaskReadResult>;
|
|
219
|
+
declare function getTask(adapter: ReadableStorageAdapter, config: ShipbenchConfig, slug: string, options?: GetTaskOptions): Promise<Task | null>;
|
|
220
|
+
declare function listArchivedTasks(adapter: ReadableStorageAdapter, config: ShipbenchConfig): Promise<TaskReadResult>;
|
|
221
|
+
/**
|
|
222
|
+
* Returns every task-file slug represented by a read, including malformed files
|
|
223
|
+
* that produced a warning instead of a Task.
|
|
224
|
+
*/
|
|
225
|
+
declare function taskFileSlugs(result: TaskReadResult): string[];
|
|
226
|
+
declare function createTask(adapter: StorageAdapter, config: ShipbenchConfig, title: string, fields?: Partial<TaskFrontmatter>): Promise<Task>;
|
|
227
|
+
declare function updateTask(adapter: StorageAdapter, config: ShipbenchConfig, slug: string, fields: Partial<TaskFrontmatter>, body?: string): Promise<{
|
|
228
|
+
task: Task;
|
|
229
|
+
layout?: BoardLayout;
|
|
230
|
+
}>;
|
|
231
|
+
declare function addComment(adapter: StorageAdapter, config: ShipbenchConfig, slug: string, text: string): Promise<Task>;
|
|
232
|
+
declare function editComment(adapter: StorageAdapter, config: ShipbenchConfig, slug: string, index: number, text: string): Promise<Task>;
|
|
233
|
+
declare function deleteComment(adapter: StorageAdapter, config: ShipbenchConfig, slug: string, index: number): Promise<Task>;
|
|
234
|
+
/**
|
|
235
|
+
* Move a task to a (possibly different) column and position. `position` is a
|
|
236
|
+
* 0-based index into the destination column's `layout` entry; `-1` appends.
|
|
237
|
+
* Writes the task file only when status changes; always writes layout.json
|
|
238
|
+
* with the updated order. Returns both the (possibly status-changed) task
|
|
239
|
+
* and the authoritative new layout so callers can reconcile.
|
|
240
|
+
*/
|
|
241
|
+
declare function reorderTask(adapter: StorageAdapter, config: ShipbenchConfig, slug: string, toStatus: string, position: number): Promise<{
|
|
242
|
+
task: Task;
|
|
243
|
+
layout: BoardLayout;
|
|
244
|
+
}>;
|
|
245
|
+
declare function moveTask(adapter: StorageAdapter, config: ShipbenchConfig, slug: string, toStatus: string): Promise<Task>;
|
|
246
|
+
declare function deleteTask(adapter: StorageAdapter, config: ShipbenchConfig, slug: string): Promise<void>;
|
|
247
|
+
declare function archiveTask(adapter: StorageAdapter, config: ShipbenchConfig, slug: string, options?: {
|
|
248
|
+
force?: boolean;
|
|
249
|
+
}): Promise<Task>;
|
|
250
|
+
declare function unarchiveTask(adapter: StorageAdapter, config: ShipbenchConfig, slug: string): Promise<Task>;
|
|
251
|
+
|
|
252
|
+
export { ArchiveBlockedError, BoardLayout, DEFAULT_CONFIG, FsAdapter, type GetTaskOptions, GitHubAdapter, GitHubApiError, type GithubRepositoryParts, type InitProjectOptions, type InitProjectResult, type ListTasksOptions, LoadConfigOptions, ProjectInitializationError, type ProjectInitializationState, ProjectWarning, ReadableStorageAdapter, ShipbenchConfig, StorageAdapter, Task, type TaskAvailabilityOptions, type TaskDependencyGraph, type TaskDependencyGraphNode, type TaskDependencyGraphOptions, type TaskDependencyIndex, type TaskDependencyResolution, TaskFrontmatter, TaskReadResult, type TaskSearchField, type TaskSearchMatch, addComment, archiveTask, buildTaskDependencyGraph, createTask, createTaskDependencyIndex, deleteComment, deleteTask, dependencyStatus, editComment, getTask, initProject, inspectProjectInitialization, listArchivedTasks, listAvailableTasks, listBlockedTasks, listTasks, loadConfig, moveTask, normalizeGithubRemoteUrl, normalizeGithubUrl, parseGithubRemoteUrl, parseGithubUrl, reorderTask, resolveSlugCollision, resolveTaskDependency, searchTasks, slugify, taskDependenciesAreSatisfied, taskFileSlugs, unarchiveTask, updateTask, validateConfig };
|