flowcat 1.3.0 → 1.5.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.
Files changed (51) hide show
  1. package/core/addTask.ts +121 -0
  2. package/core/aliasReconcile.ts +41 -0
  3. package/core/aliasRepo.ts +92 -0
  4. package/core/autoCommit.ts +84 -0
  5. package/core/commitMessage.ts +95 -0
  6. package/core/config.ts +110 -0
  7. package/core/constants.ts +8 -0
  8. package/core/dayAssignments.ts +198 -0
  9. package/core/edit.ts +59 -0
  10. package/core/format.ts +63 -0
  11. package/core/fsm.ts +28 -0
  12. package/core/git.ts +117 -0
  13. package/core/gitignore.ts +30 -0
  14. package/core/id.ts +41 -0
  15. package/core/initFlow.ts +122 -0
  16. package/core/json.ts +13 -0
  17. package/core/lock.ts +31 -0
  18. package/core/plugin.ts +99 -0
  19. package/core/pluginErrors.ts +26 -0
  20. package/core/pluginLoader.ts +222 -0
  21. package/core/pluginManager.ts +75 -0
  22. package/core/pluginRunner.ts +217 -0
  23. package/core/pr.ts +89 -0
  24. package/core/prWorkflow.ts +185 -0
  25. package/core/schemas/dayAssignment.ts +11 -0
  26. package/core/schemas/logEntry.ts +11 -0
  27. package/core/schemas/pr.ts +29 -0
  28. package/core/schemas/task.ts +41 -0
  29. package/core/search.ts +25 -0
  30. package/core/taskFactory.ts +29 -0
  31. package/core/taskOperations.ts +104 -0
  32. package/core/taskRepo.ts +109 -0
  33. package/core/taskResolver.ts +44 -0
  34. package/core/taskStore.ts +14 -0
  35. package/core/time.ts +63 -0
  36. package/core/types.ts +7 -0
  37. package/core/workspace.ts +133 -0
  38. package/dist/fc.mjs +87532 -0
  39. package/dist/highlights-eq9cgrbb.scm +604 -0
  40. package/dist/highlights-ghv9g403.scm +205 -0
  41. package/dist/highlights-hk7bwhj4.scm +284 -0
  42. package/dist/highlights-r812a2qc.scm +150 -0
  43. package/dist/highlights-x6tmsnaa.scm +115 -0
  44. package/dist/index.mjs +5221 -2724
  45. package/dist/injections-73j83es3.scm +27 -0
  46. package/dist/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
  47. package/dist/tree-sitter-markdown-411r6y9b.wasm +0 -0
  48. package/dist/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
  49. package/dist/tree-sitter-typescript-zxjzwt75.wasm +0 -0
  50. package/dist/tree-sitter-zig-e78zbjpm.wasm +0 -0
  51. package/package.json +25 -9
package/core/time.ts ADDED
@@ -0,0 +1,63 @@
1
+ import { DateTime } from "luxon";
2
+
3
+ const DEFAULT_ZONE = "Europe/Paris";
4
+
5
+ type ListDateScope = "today" | "yesterday" | "this_week" | "last_week";
6
+
7
+ const toIsoDate = (value: DateTime, label: string): string => {
8
+ const date = value.toISODate();
9
+ if (!date) {
10
+ throw new Error(`Failed to generate ${label} date`);
11
+ }
12
+ return date;
13
+ };
14
+
15
+ const buildDateRange = (start: DateTime, end: DateTime): string[] => {
16
+ const dates: string[] = [];
17
+ let cursor = start.startOf("day");
18
+ const final = end.startOf("day");
19
+
20
+ while (cursor <= final) {
21
+ dates.push(toIsoDate(cursor, "range"));
22
+ cursor = cursor.plus({ days: 1 });
23
+ }
24
+
25
+ return dates;
26
+ };
27
+
28
+ export const nowIso = (): string => {
29
+ const value = DateTime.now().setZone(DEFAULT_ZONE).toISO();
30
+ if (!value) {
31
+ throw new Error("Failed to generate timestamp");
32
+ }
33
+ return value;
34
+ };
35
+
36
+ export const todayDate = (date?: string): string => {
37
+ const value = date
38
+ ? DateTime.fromISO(date, { zone: DEFAULT_ZONE }).toISODate()
39
+ : DateTime.now().setZone(DEFAULT_ZONE).toISODate();
40
+
41
+ if (!value) {
42
+ throw new Error("Failed to generate date");
43
+ }
44
+
45
+ return value;
46
+ };
47
+
48
+ export const listDatesForScope = (scope: ListDateScope): string[] => {
49
+ const now = DateTime.now().setZone(DEFAULT_ZONE);
50
+
51
+ switch (scope) {
52
+ case "today":
53
+ return [toIsoDate(now, "today")];
54
+ case "yesterday":
55
+ return [toIsoDate(now.minus({ days: 1 }), "yesterday")];
56
+ case "this_week":
57
+ return buildDateRange(now.startOf("week"), now.endOf("week"));
58
+ case "last_week": {
59
+ const lastWeek = now.minus({ weeks: 1 });
60
+ return buildDateRange(lastWeek.startOf("week"), lastWeek.endOf("week"));
61
+ }
62
+ }
63
+ };
package/core/types.ts ADDED
@@ -0,0 +1,7 @@
1
+ export const taskStatuses = ["backlog", "active", "paused", "completed", "cancelled"] as const;
2
+ export const taskTypes = ["regular", "review"] as const;
3
+ export const taskActions = ["start", "pause", "complete", "cancel"] as const;
4
+
5
+ export type TaskStatus = (typeof taskStatuses)[number];
6
+ export type TaskType = (typeof taskTypes)[number];
7
+ export type TaskAction = (typeof taskActions)[number];
@@ -0,0 +1,133 @@
1
+ import { access, mkdir } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import path from "node:path";
4
+
5
+ import { configFileExists, resolveGlobalConfigPath, resolveWorkspaceConfigPath } from "./config";
6
+ import { APP_DIR, APP_NAME, LOCK_DIR, PLUGINS_DIR, STATUS_ORDER, TASKS_DIR } from "./constants";
7
+ import { writeJsonAtomic } from "./json";
8
+
9
+ export type WorkspaceKind = "explicit" | "global" | "repo";
10
+
11
+ export type Workspace = {
12
+ root: string;
13
+ kind: WorkspaceKind;
14
+ };
15
+
16
+ const exists = async (filePath: string): Promise<boolean> => {
17
+ try {
18
+ await access(filePath);
19
+ return true;
20
+ } catch {
21
+ return false;
22
+ }
23
+ };
24
+
25
+ export const findGitRoot = async (startDir: string): Promise<string | null> => {
26
+ let current = path.resolve(startDir);
27
+
28
+ while (true) {
29
+ if (await exists(path.join(current, ".git"))) {
30
+ return current;
31
+ }
32
+
33
+ const parent = path.dirname(current);
34
+ if (parent === current) {
35
+ return null;
36
+ }
37
+
38
+ current = parent;
39
+ }
40
+ };
41
+
42
+ export const resolveGlobalWorkspaceRoot = (): string => {
43
+ const dataHome = process.env.XDG_DATA_HOME ?? path.join(homedir(), ".local", "share");
44
+ return path.join(dataHome, APP_NAME);
45
+ };
46
+
47
+ export type WorkspaceOptions = {
48
+ store?: string;
49
+ global?: boolean;
50
+ repo?: boolean;
51
+ cwd?: string;
52
+ };
53
+
54
+ export const resolveWorkspace = async (options: WorkspaceOptions): Promise<Workspace> => {
55
+ if (options.store) {
56
+ return { root: path.resolve(options.store), kind: "explicit" };
57
+ }
58
+
59
+ const cwd = options.cwd ?? process.cwd();
60
+ const repoRoot = await findGitRoot(cwd);
61
+
62
+ if (options.repo) {
63
+ if (!repoRoot) {
64
+ throw new Error("Not inside a git repository");
65
+ }
66
+
67
+ return { root: path.join(repoRoot, APP_DIR), kind: "repo" };
68
+ }
69
+
70
+ if (options.global) {
71
+ return { root: resolveGlobalWorkspaceRoot(), kind: "global" };
72
+ }
73
+
74
+ if (repoRoot) {
75
+ const repoWorkspace = path.join(repoRoot, APP_DIR);
76
+ if (await exists(repoWorkspace)) {
77
+ return { root: repoWorkspace, kind: "repo" };
78
+ }
79
+ }
80
+
81
+ return { root: resolveGlobalWorkspaceRoot(), kind: "global" };
82
+ };
83
+
84
+ export const isWorkspaceInitialized = async (workspace: Workspace): Promise<boolean> => {
85
+ const tasksPath = path.join(workspace.root, TASKS_DIR);
86
+ const hasTasksDir = await exists(tasksPath);
87
+ if (!hasTasksDir) {
88
+ return false;
89
+ }
90
+
91
+ const configPath =
92
+ workspace.kind === "global"
93
+ ? resolveGlobalConfigPath()
94
+ : resolveWorkspaceConfigPath(workspace.root);
95
+ return configFileExists(configPath);
96
+ };
97
+
98
+ export const ensureWorkspaceLayout = async (workspaceRoot: string): Promise<void> => {
99
+ await mkdir(path.join(workspaceRoot, TASKS_DIR), { recursive: true });
100
+ await mkdir(path.join(workspaceRoot, LOCK_DIR), { recursive: true });
101
+ await mkdir(path.join(workspaceRoot, PLUGINS_DIR), { recursive: true });
102
+
103
+ await Promise.all(
104
+ STATUS_ORDER.map((status) =>
105
+ mkdir(path.join(workspaceRoot, TASKS_DIR, status), { recursive: true }),
106
+ ),
107
+ );
108
+
109
+ // Create plugins/package.json if it doesn't exist
110
+ await ensurePluginsPackageJson(workspaceRoot);
111
+ };
112
+
113
+ /**
114
+ * Ensure plugins directory has a package.json for TypeScript plugin development
115
+ */
116
+ export const ensurePluginsPackageJson = async (workspaceRoot: string): Promise<void> => {
117
+ const packageJsonPath = path.join(workspaceRoot, PLUGINS_DIR, "package.json");
118
+
119
+ if (await exists(packageJsonPath)) {
120
+ return;
121
+ }
122
+
123
+ const packageJson = {
124
+ name: "flowcat-plugins",
125
+ type: "module",
126
+ private: true,
127
+ dependencies: {
128
+ flowcat: "^1.4.0",
129
+ },
130
+ };
131
+
132
+ await writeJsonAtomic(packageJsonPath, packageJson);
133
+ };