@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.
- package/README.md +15 -0
- package/package.json +51 -0
- package/src/db/client.ts +34 -0
- package/src/db/index.ts +1 -0
- package/src/db/migrate.ts +51 -0
- package/src/db/migrations/001_initial_schema.sql +160 -0
- package/src/domain/index.ts +1 -0
- package/src/domain/types.ts +225 -0
- package/src/index.ts +7 -0
- package/src/repos/base.ts +151 -0
- package/src/repos/dependencies.ts +356 -0
- package/src/repos/features.ts +507 -0
- package/src/repos/index.ts +4 -0
- package/src/repos/projects.ts +350 -0
- package/src/repos/sections.ts +505 -0
- package/src/repos/tags.example.ts +125 -0
- package/src/repos/tags.ts +175 -0
- package/src/repos/tasks.ts +581 -0
- package/src/repos/templates.ts +649 -0
- package/src/server.ts +121 -0
- package/src/services/index.ts +2 -0
- package/src/services/status-validator.ts +100 -0
- package/src/services/workflow.ts +104 -0
- package/src/tools/apply-template.ts +129 -0
- package/src/tools/get-blocked-tasks.ts +63 -0
- package/src/tools/get-next-status.ts +183 -0
- package/src/tools/get-next-task.ts +75 -0
- package/src/tools/get-tag-usage.ts +54 -0
- package/src/tools/index.ts +30 -0
- package/src/tools/list-tags.ts +56 -0
- package/src/tools/manage-container.ts +333 -0
- package/src/tools/manage-dependency.ts +198 -0
- package/src/tools/manage-sections.ts +388 -0
- package/src/tools/manage-template.ts +313 -0
- package/src/tools/query-container.ts +296 -0
- package/src/tools/query-dependencies.ts +68 -0
- package/src/tools/query-sections.ts +70 -0
- package/src/tools/query-templates.ts +137 -0
- package/src/tools/query-workflow-state.ts +198 -0
- package/src/tools/registry.ts +180 -0
- package/src/tools/rename-tag.ts +64 -0
- 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
|
+
}
|