@allpepper/task-orchestrator 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.
Files changed (42) hide show
  1. package/README.md +15 -0
  2. package/package.json +51 -0
  3. package/src/db/client.ts +34 -0
  4. package/src/db/index.ts +1 -0
  5. package/src/db/migrate.ts +51 -0
  6. package/src/db/migrations/001_initial_schema.sql +160 -0
  7. package/src/domain/index.ts +1 -0
  8. package/src/domain/types.ts +225 -0
  9. package/src/index.ts +7 -0
  10. package/src/repos/base.ts +151 -0
  11. package/src/repos/dependencies.ts +356 -0
  12. package/src/repos/features.ts +507 -0
  13. package/src/repos/index.ts +4 -0
  14. package/src/repos/projects.ts +350 -0
  15. package/src/repos/sections.ts +505 -0
  16. package/src/repos/tags.example.ts +125 -0
  17. package/src/repos/tags.ts +175 -0
  18. package/src/repos/tasks.ts +581 -0
  19. package/src/repos/templates.ts +649 -0
  20. package/src/server.ts +121 -0
  21. package/src/services/index.ts +2 -0
  22. package/src/services/status-validator.ts +100 -0
  23. package/src/services/workflow.ts +104 -0
  24. package/src/tools/apply-template.ts +129 -0
  25. package/src/tools/get-blocked-tasks.ts +63 -0
  26. package/src/tools/get-next-status.ts +183 -0
  27. package/src/tools/get-next-task.ts +75 -0
  28. package/src/tools/get-tag-usage.ts +54 -0
  29. package/src/tools/index.ts +30 -0
  30. package/src/tools/list-tags.ts +56 -0
  31. package/src/tools/manage-container.ts +333 -0
  32. package/src/tools/manage-dependency.ts +198 -0
  33. package/src/tools/manage-sections.ts +388 -0
  34. package/src/tools/manage-template.ts +313 -0
  35. package/src/tools/query-container.ts +296 -0
  36. package/src/tools/query-dependencies.ts +68 -0
  37. package/src/tools/query-sections.ts +70 -0
  38. package/src/tools/query-templates.ts +137 -0
  39. package/src/tools/query-workflow-state.ts +198 -0
  40. package/src/tools/registry.ts +180 -0
  41. package/src/tools/rename-tag.ts +64 -0
  42. package/src/tools/setup-project.ts +189 -0
@@ -0,0 +1,175 @@
1
+ import { db, queryAll, queryOne, execute, ok, err, generateId, now } from './base';
2
+ import type { Result } from '../domain/types';
3
+ import { transaction } from '../db/client';
4
+
5
+ // ============================================================================
6
+ // Types
7
+ // ============================================================================
8
+
9
+ export interface TagCount {
10
+ tag: string;
11
+ count: number;
12
+ }
13
+
14
+ export interface TagUsage {
15
+ entityId: string;
16
+ entityType: string;
17
+ }
18
+
19
+ export interface RenameTagParams {
20
+ dryRun?: boolean;
21
+ }
22
+
23
+ export interface RenameTagResult {
24
+ affected: number;
25
+ entities: TagUsage[];
26
+ }
27
+
28
+ // ============================================================================
29
+ // Repository Functions
30
+ // ============================================================================
31
+
32
+ /**
33
+ * List all tags with their usage counts
34
+ * @param params Optional filter by entity type
35
+ * @returns Array of tags with counts, ordered by count DESC, tag ASC
36
+ */
37
+ export function listTags(params?: { entityType?: string }): Result<TagCount[]> {
38
+ try {
39
+ let sql = `
40
+ SELECT tag, COUNT(*) as count
41
+ FROM entity_tags
42
+ `;
43
+ const sqlParams: string[] = [];
44
+
45
+ if (params?.entityType) {
46
+ sql += ' WHERE entity_type = ?';
47
+ sqlParams.push(params.entityType);
48
+ }
49
+
50
+ sql += ' GROUP BY tag ORDER BY count DESC, tag ASC';
51
+
52
+ const rows = queryAll<TagCount>(sql, sqlParams);
53
+ return ok(rows);
54
+ } catch (error) {
55
+ return err(`Failed to list tags: ${error instanceof Error ? error.message : String(error)}`);
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Get all entities using a specific tag
61
+ * @param tag The tag to search for (case-insensitive)
62
+ * @returns Array of entity IDs and types
63
+ */
64
+ export function getTagUsage(tag: string): Result<TagUsage[]> {
65
+ try {
66
+ const normalizedTag = tag.trim().toLowerCase();
67
+ if (!normalizedTag) {
68
+ return err('Tag cannot be empty', 'VALIDATION_ERROR');
69
+ }
70
+
71
+ const rows = queryAll<TagUsage>(
72
+ `SELECT entity_id as entityId, entity_type as entityType
73
+ FROM entity_tags
74
+ WHERE LOWER(tag) = ?
75
+ ORDER BY entity_type, entity_id`,
76
+ [normalizedTag]
77
+ );
78
+
79
+ return ok(rows);
80
+ } catch (error) {
81
+ return err(`Failed to get tag usage: ${error instanceof Error ? error.message : String(error)}`);
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Rename a tag across all entities
87
+ * @param oldTag The current tag name
88
+ * @param newTag The new tag name
89
+ * @param params Optional dry run mode
90
+ * @returns Number of affected rows and list of entities
91
+ */
92
+ export function renameTag(
93
+ oldTag: string,
94
+ newTag: string,
95
+ params?: RenameTagParams
96
+ ): Result<RenameTagResult> {
97
+ try {
98
+ const normalizedOldTag = oldTag.trim().toLowerCase();
99
+ const normalizedNewTag = newTag.trim().toLowerCase();
100
+
101
+ // Validation
102
+ if (!normalizedOldTag) {
103
+ return err('Old tag cannot be empty', 'VALIDATION_ERROR');
104
+ }
105
+ if (!normalizedNewTag) {
106
+ return err('New tag cannot be empty', 'VALIDATION_ERROR');
107
+ }
108
+ if (normalizedOldTag === normalizedNewTag) {
109
+ return err('Old and new tags are the same', 'VALIDATION_ERROR');
110
+ }
111
+
112
+ // Get all entities using the old tag
113
+ const entities = queryAll<{ entity_id: string; entity_type: string }>(
114
+ 'SELECT entity_id, entity_type FROM entity_tags WHERE LOWER(tag) = ?',
115
+ [normalizedOldTag]
116
+ );
117
+
118
+ if (entities.length === 0) {
119
+ return ok({ affected: 0, entities: [] });
120
+ }
121
+
122
+ // If dry run, just return the count
123
+ if (params?.dryRun) {
124
+ return ok({
125
+ affected: entities.length,
126
+ entities: entities.map(e => ({
127
+ entityId: e.entity_id,
128
+ entityType: e.entity_type
129
+ }))
130
+ });
131
+ }
132
+
133
+ // Execute the rename in a transaction
134
+ const result = transaction(() => {
135
+ let updated = 0;
136
+ let deleted = 0;
137
+
138
+ for (const entity of entities) {
139
+ // Check if the entity already has the new tag
140
+ const existing = queryOne<{ count: number }>(
141
+ 'SELECT COUNT(*) as count FROM entity_tags WHERE entity_id = ? AND entity_type = ? AND LOWER(tag) = ?',
142
+ [entity.entity_id, entity.entity_type, normalizedNewTag]
143
+ );
144
+
145
+ if (existing && existing.count > 0) {
146
+ // Conflict: entity already has the new tag, delete the old tag row
147
+ execute(
148
+ 'DELETE FROM entity_tags WHERE entity_id = ? AND entity_type = ? AND LOWER(tag) = ?',
149
+ [entity.entity_id, entity.entity_type, normalizedOldTag]
150
+ );
151
+ deleted++;
152
+ } else {
153
+ // No conflict: update the old tag to the new tag
154
+ execute(
155
+ 'UPDATE entity_tags SET tag = ? WHERE entity_id = ? AND entity_type = ? AND LOWER(tag) = ?',
156
+ [normalizedNewTag, entity.entity_id, entity.entity_type, normalizedOldTag]
157
+ );
158
+ updated++;
159
+ }
160
+ }
161
+
162
+ return updated + deleted;
163
+ });
164
+
165
+ return ok({
166
+ affected: result,
167
+ entities: entities.map(e => ({
168
+ entityId: e.entity_id,
169
+ entityType: e.entity_type
170
+ }))
171
+ });
172
+ } catch (error) {
173
+ return err(`Failed to rename tag: ${error instanceof Error ? error.message : String(error)}`);
174
+ }
175
+ }