@vheins/local-memory-mcp 0.14.9 → 0.15.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/dist/{chunk-UQZNTQJP.js → chunk-SWYPZXG3.js} +75 -28
- package/dist/dashboard/server.js +1 -1
- package/dist/mcp/server.js +89 -55
- package/dist/prompts/create-task.md +2 -2
- package/dist/prompts/export-task-to-github.md +3 -3
- package/dist/prompts/import-github-issues.md +3 -2
- package/dist/prompts/review-and-audit.md +2 -2
- package/dist/prompts/review-and-post-issue.md +2 -2
- package/dist/prompts/sentinel-issue-resolver.md +2 -1
- package/dist/prompts/session-planner.md +2 -2
- package/dist/prompts/task-memory-executor.md +1 -1
- package/package.json +1 -1
|
@@ -81,8 +81,8 @@ function loadServerInstructions() {
|
|
|
81
81
|
// src/mcp/capabilities.ts
|
|
82
82
|
var __dirname2 = path2.dirname(fileURLToPath2(import.meta.url));
|
|
83
83
|
var pkgVersion = "0.1.0";
|
|
84
|
-
if ("0.
|
|
85
|
-
pkgVersion = "0.
|
|
84
|
+
if ("0.15.0") {
|
|
85
|
+
pkgVersion = "0.15.0";
|
|
86
86
|
} else {
|
|
87
87
|
let searchDir = __dirname2;
|
|
88
88
|
for (let i = 0; i < 5; i++) {
|
|
@@ -585,6 +585,11 @@ var MigrationManager = class {
|
|
|
585
585
|
name: "changed_files",
|
|
586
586
|
table: "tasks",
|
|
587
587
|
definition: "ALTER TABLE tasks ADD COLUMN changed_files TEXT"
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
name: "suggested_skills",
|
|
591
|
+
table: "tasks",
|
|
592
|
+
definition: "ALTER TABLE tasks ADD COLUMN suggested_skills TEXT"
|
|
588
593
|
}
|
|
589
594
|
];
|
|
590
595
|
for (const col of columnsToAdd) {
|
|
@@ -1074,6 +1079,7 @@ var BaseEntity = class {
|
|
|
1074
1079
|
commit_id: r.commit_id || null,
|
|
1075
1080
|
changed_files: this.safeJSONParse(r.changed_files, []),
|
|
1076
1081
|
tags: this.safeJSONParse(r.tags, []),
|
|
1082
|
+
suggested_skills: this.safeJSONParse(r.suggested_skills, []),
|
|
1077
1083
|
metadata: this.safeJSONParse(r.metadata, {}),
|
|
1078
1084
|
parent_id: r.parent_id || null,
|
|
1079
1085
|
depends_on: r.depends_on || null,
|
|
@@ -1615,9 +1621,9 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1615
1621
|
this.run(
|
|
1616
1622
|
`INSERT INTO tasks (
|
|
1617
1623
|
id, repo, task_code, phase, title, description, status, priority,
|
|
1618
|
-
agent, role, doc_path, created_at, updated_at, finished_at, canceled_at, tags, metadata, parent_id, depends_on, est_tokens, in_progress_at,
|
|
1624
|
+
agent, role, doc_path, created_at, updated_at, finished_at, canceled_at, tags, suggested_skills, metadata, parent_id, depends_on, est_tokens, in_progress_at,
|
|
1619
1625
|
commit_id, changed_files
|
|
1620
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1626
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1621
1627
|
[
|
|
1622
1628
|
task.id,
|
|
1623
1629
|
task.repo,
|
|
@@ -1635,6 +1641,7 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1635
1641
|
task.finished_at || null,
|
|
1636
1642
|
task.canceled_at || null,
|
|
1637
1643
|
task.tags ? JSON.stringify(task.tags) : null,
|
|
1644
|
+
task.suggested_skills ? JSON.stringify(task.suggested_skills) : null,
|
|
1638
1645
|
task.metadata ? JSON.stringify(task.metadata) : null,
|
|
1639
1646
|
task.parent_id || null,
|
|
1640
1647
|
task.depends_on || null,
|
|
@@ -1663,6 +1670,7 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1663
1670
|
"finished_at",
|
|
1664
1671
|
"canceled_at",
|
|
1665
1672
|
"tags",
|
|
1673
|
+
"suggested_skills",
|
|
1666
1674
|
"metadata",
|
|
1667
1675
|
"parent_id",
|
|
1668
1676
|
"depends_on",
|
|
@@ -1673,7 +1681,7 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1673
1681
|
]);
|
|
1674
1682
|
Object.keys(updates).forEach((key) => {
|
|
1675
1683
|
if (VALID_COLUMNS2.has(key) && anyUpdates[key] !== void 0) {
|
|
1676
|
-
if (key === "tags" || key === "metadata" || key === "changed_files") {
|
|
1684
|
+
if (key === "tags" || key === "metadata" || key === "changed_files" || key === "suggested_skills") {
|
|
1677
1685
|
fields.push(`${key} = ?`);
|
|
1678
1686
|
values.push(JSON.stringify(anyUpdates[key]));
|
|
1679
1687
|
} else {
|
|
@@ -1702,7 +1710,13 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1702
1710
|
WHERE t.id = ?`,
|
|
1703
1711
|
[id]
|
|
1704
1712
|
);
|
|
1705
|
-
return row ? {
|
|
1713
|
+
return row ? {
|
|
1714
|
+
...this.rowToTask(row),
|
|
1715
|
+
comments: this.all(
|
|
1716
|
+
"SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at DESC, id DESC",
|
|
1717
|
+
[id]
|
|
1718
|
+
)
|
|
1719
|
+
} : null;
|
|
1706
1720
|
}
|
|
1707
1721
|
getTasksByIds(ids) {
|
|
1708
1722
|
if (ids.length === 0) return [];
|
|
@@ -1743,7 +1757,13 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1743
1757
|
WHERE t.repo = ? AND t.task_code = ?`,
|
|
1744
1758
|
[repo, taskCode]
|
|
1745
1759
|
);
|
|
1746
|
-
return row ? {
|
|
1760
|
+
return row ? {
|
|
1761
|
+
...this.rowToTask(row),
|
|
1762
|
+
comments: this.all(
|
|
1763
|
+
"SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at DESC, id DESC",
|
|
1764
|
+
[row.id]
|
|
1765
|
+
)
|
|
1766
|
+
} : null;
|
|
1747
1767
|
}
|
|
1748
1768
|
getTasksByRepo(repo, status, limit, offset, search) {
|
|
1749
1769
|
let query = `
|
|
@@ -1904,9 +1924,9 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1904
1924
|
this.run(
|
|
1905
1925
|
`INSERT INTO tasks (
|
|
1906
1926
|
id, repo, task_code, phase, title, description, status, priority,
|
|
1907
|
-
agent, role, doc_path, created_at, updated_at, finished_at, canceled_at, tags, metadata, parent_id, depends_on, est_tokens, in_progress_at,
|
|
1927
|
+
agent, role, doc_path, created_at, updated_at, finished_at, canceled_at, tags, suggested_skills, metadata, parent_id, depends_on, est_tokens, in_progress_at,
|
|
1908
1928
|
commit_id, changed_files
|
|
1909
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1929
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1910
1930
|
[
|
|
1911
1931
|
task.id,
|
|
1912
1932
|
task.repo,
|
|
@@ -1924,6 +1944,7 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1924
1944
|
task.finished_at || null,
|
|
1925
1945
|
task.canceled_at || null,
|
|
1926
1946
|
task.tags ? JSON.stringify(task.tags) : null,
|
|
1947
|
+
task.suggested_skills ? JSON.stringify(task.suggested_skills) : null,
|
|
1927
1948
|
task.metadata ? JSON.stringify(task.metadata) : null,
|
|
1928
1949
|
task.parent_id || null,
|
|
1929
1950
|
task.depends_on || null,
|
|
@@ -3350,10 +3371,15 @@ var MemoryStoreSchema = z.object({
|
|
|
3350
3371
|
is_global: z.boolean().default(false),
|
|
3351
3372
|
structured: z.boolean().default(false),
|
|
3352
3373
|
memories: z.array(SingleMemorySchema).min(1).optional()
|
|
3353
|
-
}).refine(
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3374
|
+
}).refine(
|
|
3375
|
+
(data) => {
|
|
3376
|
+
if (data.memories) return true;
|
|
3377
|
+
return !!(data.type && data.title && data.content && data.importance && data.agent && data.model && data.scope);
|
|
3378
|
+
},
|
|
3379
|
+
{
|
|
3380
|
+
message: "Either 'memories' array or single memory fields (type, title, content, importance, agent, model, scope) must be provided"
|
|
3381
|
+
}
|
|
3382
|
+
);
|
|
3357
3383
|
var MemoryUpdateSchema = z.object({
|
|
3358
3384
|
id: z.string().uuid().optional(),
|
|
3359
3385
|
code: z.string().max(20).optional(),
|
|
@@ -3438,7 +3464,7 @@ var MemorySynthesizeSchema = z.object({
|
|
|
3438
3464
|
var TaskStatusSchema = z.enum(["backlog", "pending", "in_progress", "completed", "canceled", "blocked"]);
|
|
3439
3465
|
var TaskPrioritySchema = z.number().min(1).max(5);
|
|
3440
3466
|
var SingleTaskCreateSchema = z.object({
|
|
3441
|
-
task_code: z.string().min(1),
|
|
3467
|
+
task_code: z.string().min(1).optional(),
|
|
3442
3468
|
phase: z.string().min(1),
|
|
3443
3469
|
title: z.string().min(3).max(100),
|
|
3444
3470
|
description: z.string().min(1),
|
|
@@ -3448,6 +3474,7 @@ var SingleTaskCreateSchema = z.object({
|
|
|
3448
3474
|
role: z.string().optional(),
|
|
3449
3475
|
doc_path: z.string().optional(),
|
|
3450
3476
|
tags: z.array(z.string()).optional(),
|
|
3477
|
+
suggested_skills: z.array(z.string()).optional(),
|
|
3451
3478
|
metadata: z.record(z.string(), z.any()).optional(),
|
|
3452
3479
|
parent_id: z.string().optional(),
|
|
3453
3480
|
depends_on: z.string().optional(),
|
|
@@ -3466,6 +3493,7 @@ var TaskCreateSchema = z.object({
|
|
|
3466
3493
|
role: z.string().optional(),
|
|
3467
3494
|
doc_path: z.string().optional(),
|
|
3468
3495
|
tags: z.array(z.string()).optional(),
|
|
3496
|
+
suggested_skills: z.array(z.string()).optional(),
|
|
3469
3497
|
metadata: z.record(z.string(), z.any()).optional(),
|
|
3470
3498
|
parent_id: z.string().optional(),
|
|
3471
3499
|
depends_on: z.string().optional(),
|
|
@@ -3476,9 +3504,9 @@ var TaskCreateSchema = z.object({
|
|
|
3476
3504
|
}).refine(
|
|
3477
3505
|
(data) => {
|
|
3478
3506
|
if (data.tasks) return true;
|
|
3479
|
-
return !!(data.
|
|
3507
|
+
return !!(data.phase && data.title && data.description);
|
|
3480
3508
|
},
|
|
3481
|
-
{ message: "Either 'tasks' array or single task fields (
|
|
3509
|
+
{ message: "Either 'tasks' array or single task fields (phase, title, description) must be provided" }
|
|
3482
3510
|
);
|
|
3483
3511
|
var TaskCreateInteractiveSchema = SingleTaskCreateSchema.partial().extend({
|
|
3484
3512
|
repo: z.string().min(1).transform(normalizeRepo).optional(),
|
|
@@ -3501,6 +3529,7 @@ var TaskUpdateSchema = z.object({
|
|
|
3501
3529
|
doc_path: z.string().optional(),
|
|
3502
3530
|
tags: z.array(z.string()).optional(),
|
|
3503
3531
|
metadata: z.record(z.string(), z.any()).optional(),
|
|
3532
|
+
suggested_skills: z.array(z.string()).optional(),
|
|
3504
3533
|
parent_id: z.string().optional(),
|
|
3505
3534
|
depends_on: z.string().optional(),
|
|
3506
3535
|
est_tokens: z.number().int().min(0).optional(),
|
|
@@ -3657,13 +3686,15 @@ var StandardStoreSchema = z.object({
|
|
|
3657
3686
|
model: z.string().optional(),
|
|
3658
3687
|
structured: z.boolean().default(false),
|
|
3659
3688
|
standards: z.array(SingleStandardSchema).min(1).optional()
|
|
3660
|
-
}).refine(
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
{ message: "
|
|
3666
|
-
)
|
|
3689
|
+
}).refine(
|
|
3690
|
+
(data) => {
|
|
3691
|
+
if (data.standards) return true;
|
|
3692
|
+
return !!(data.name && data.content && data.tags && data.metadata);
|
|
3693
|
+
},
|
|
3694
|
+
{ message: "Either 'standards' array or single standard fields (name, content, tags, metadata) must be provided" }
|
|
3695
|
+
).refine((data) => data.is_global !== false || !!data.repo, {
|
|
3696
|
+
message: "repo is required for repo-specific standards"
|
|
3697
|
+
});
|
|
3667
3698
|
var StandardUpdateSchema = z.object({
|
|
3668
3699
|
id: z.string().uuid().optional(),
|
|
3669
3700
|
code: z.string().max(20).optional(),
|
|
@@ -3919,7 +3950,10 @@ var TOOL_DEFINITIONS = [
|
|
|
3919
3950
|
description: "If true, this memory is shared across all repositories"
|
|
3920
3951
|
},
|
|
3921
3952
|
ttlDays: { type: "number", minimum: 1 },
|
|
3922
|
-
supersedes: {
|
|
3953
|
+
supersedes: {
|
|
3954
|
+
type: "string",
|
|
3955
|
+
description: "Optional memory ID (UUID) or memory code to supersede. Resolved before storing."
|
|
3956
|
+
},
|
|
3923
3957
|
memories: {
|
|
3924
3958
|
type: "array",
|
|
3925
3959
|
items: {
|
|
@@ -4384,7 +4418,10 @@ var TOOL_DEFINITIONS = [
|
|
|
4384
4418
|
type: "string",
|
|
4385
4419
|
description: "Optional parent task ID (UUID) or parent task code (e.g. TASK-001). Resolved to UUID before storing."
|
|
4386
4420
|
},
|
|
4387
|
-
depends_on: {
|
|
4421
|
+
depends_on: {
|
|
4422
|
+
type: "string",
|
|
4423
|
+
description: "Optional task ID (UUID) or task code (e.g. TASK-001). Resolved to UUID before storing."
|
|
4424
|
+
},
|
|
4388
4425
|
est_tokens: { type: "number", minimum: 0, description: "Estimated tokens budget for this task" },
|
|
4389
4426
|
tasks: {
|
|
4390
4427
|
type: "array",
|
|
@@ -4415,7 +4452,10 @@ var TOOL_DEFINITIONS = [
|
|
|
4415
4452
|
type: "string",
|
|
4416
4453
|
description: "Optional parent task ID (UUID) or parent task code (e.g. TASK-001). Resolved to UUID before storing."
|
|
4417
4454
|
},
|
|
4418
|
-
depends_on: {
|
|
4455
|
+
depends_on: {
|
|
4456
|
+
type: "string",
|
|
4457
|
+
description: "Optional task ID (UUID) or task code (e.g. TASK-001). Resolved to UUID before storing."
|
|
4458
|
+
},
|
|
4419
4459
|
est_tokens: { type: "number", minimum: 0 }
|
|
4420
4460
|
},
|
|
4421
4461
|
required: ["task_code", "phase", "title", "description"]
|
|
@@ -4490,7 +4530,10 @@ var TOOL_DEFINITIONS = [
|
|
|
4490
4530
|
type: "string",
|
|
4491
4531
|
description: "Optional parent task ID (UUID) or parent task code (e.g. TASK-001). Resolved to UUID before storing."
|
|
4492
4532
|
},
|
|
4493
|
-
depends_on: {
|
|
4533
|
+
depends_on: {
|
|
4534
|
+
type: "string",
|
|
4535
|
+
description: "Optional task ID (UUID) or task code (e.g. TASK-001). Resolved to UUID before storing."
|
|
4536
|
+
},
|
|
4494
4537
|
est_tokens: {
|
|
4495
4538
|
type: "number",
|
|
4496
4539
|
minimum: 0,
|
|
@@ -4658,7 +4701,11 @@ var TOOL_DEFINITIONS = [
|
|
|
4658
4701
|
type: "object",
|
|
4659
4702
|
properties: {
|
|
4660
4703
|
repo: { type: "string", description: "Repository name" },
|
|
4661
|
-
query: {
|
|
4704
|
+
query: {
|
|
4705
|
+
type: "string",
|
|
4706
|
+
minLength: 1,
|
|
4707
|
+
description: "Search keyword matching task code, title, or description"
|
|
4708
|
+
},
|
|
4662
4709
|
status: { type: "string", description: "Optional status filter (single or comma-separated)" },
|
|
4663
4710
|
phase: { type: "string", description: "Filter by phase (e.g., 'research', 'implementation')" },
|
|
4664
4711
|
priority: { type: "number", minimum: 1, maximum: 5, description: "Filter by priority (1-5)" },
|
package/dist/dashboard/server.js
CHANGED
package/dist/mcp/server.js
CHANGED
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
toContextSlug,
|
|
61
61
|
updateSessionFromInitialize,
|
|
62
62
|
updateSessionRoots
|
|
63
|
-
} from "../chunk-
|
|
63
|
+
} from "../chunk-SWYPZXG3.js";
|
|
64
64
|
|
|
65
65
|
// src/mcp/server.ts
|
|
66
66
|
import readline from "readline";
|
|
@@ -189,19 +189,44 @@ function invalidCompletionParams(message) {
|
|
|
189
189
|
|
|
190
190
|
// src/mcp/tools/memory.store.ts
|
|
191
191
|
import { randomUUID } from "crypto";
|
|
192
|
+
|
|
193
|
+
// src/mcp/utils/code-generator.ts
|
|
194
|
+
var ENTITY_CONFIG = {
|
|
195
|
+
task: { prefix: "TASK", table: "tasks", column: "task_code" },
|
|
196
|
+
memory: { prefix: "MEM", table: "memories", column: "code" },
|
|
197
|
+
standard: { prefix: "STD", table: "coding_standards", column: "code" }
|
|
198
|
+
};
|
|
199
|
+
function generateNextCode(repo, entityType, storage, batchCodes) {
|
|
200
|
+
const config = ENTITY_CONFIG[entityType];
|
|
201
|
+
const pattern = `${config.prefix}-%`;
|
|
202
|
+
const offset = config.prefix.length + 2;
|
|
203
|
+
const row = storage.db.prepare(
|
|
204
|
+
`
|
|
205
|
+
SELECT MAX(CAST(SUBSTR(${config.column}, ?) AS INTEGER)) as max_seq
|
|
206
|
+
FROM ${config.table}
|
|
207
|
+
WHERE repo = ? AND ${config.column} LIKE ?
|
|
208
|
+
`
|
|
209
|
+
).get(offset, repo, pattern);
|
|
210
|
+
let nextSeq = (row?.max_seq ?? 0) + 1;
|
|
211
|
+
if (batchCodes) {
|
|
212
|
+
for (const code of batchCodes) {
|
|
213
|
+
if (code.startsWith(`${config.prefix}-`)) {
|
|
214
|
+
const num = parseInt(code.slice(config.prefix.length + 1), 10);
|
|
215
|
+
if (!isNaN(num) && num >= nextSeq) {
|
|
216
|
+
nextSeq = num + 1;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return `${config.prefix}-${String(nextSeq).padStart(3, "0")}`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/mcp/tools/memory.store.ts
|
|
192
225
|
var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
193
226
|
function hasMetadataLikeTitle(title) {
|
|
194
227
|
const normalized = title.trim();
|
|
195
228
|
return /^\[[^\]]{0,200}(agent:|role:|model:|\d{4}-\d{2}-\d{2}|source_)[^\]]*\]/i.test(normalized);
|
|
196
229
|
}
|
|
197
|
-
function generateShortCode() {
|
|
198
|
-
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
199
|
-
let code = "";
|
|
200
|
-
for (let i = 0; i < 6; i++) {
|
|
201
|
-
code += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
202
|
-
}
|
|
203
|
-
return code;
|
|
204
|
-
}
|
|
205
230
|
function resolveMemorySupersedes(value, db2) {
|
|
206
231
|
if (!value) return null;
|
|
207
232
|
if (UUID_REGEX.test(value)) return value;
|
|
@@ -252,7 +277,7 @@ async function storeSingleMemory(params, db2, vectors2) {
|
|
|
252
277
|
}
|
|
253
278
|
const entry = {
|
|
254
279
|
id: randomUUID(),
|
|
255
|
-
code: params.code ||
|
|
280
|
+
code: params.code || generateNextCode(params.scope.repo, "memory", db2),
|
|
256
281
|
type: params.type,
|
|
257
282
|
title: params.title,
|
|
258
283
|
content: params.content,
|
|
@@ -303,6 +328,7 @@ async function handleMemoryStore(params, db2, vectors2) {
|
|
|
303
328
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
304
329
|
const entries = [];
|
|
305
330
|
const storedCodes = [];
|
|
331
|
+
const batchCodes = /* @__PURE__ */ new Set();
|
|
306
332
|
for (const mem of validated.memories) {
|
|
307
333
|
if (hasMetadataLikeTitle(mem.title)) {
|
|
308
334
|
throw new Error(
|
|
@@ -313,13 +339,7 @@ async function handleMemoryStore(params, db2, vectors2) {
|
|
|
313
339
|
const expires_at = mem.ttlDays != null ? new Date(createdAtTime + mem.ttlDays * 864e5).toISOString() : null;
|
|
314
340
|
const resolvedSupersedes = resolveMemorySupersedes(mem.supersedes, db2);
|
|
315
341
|
if (!resolvedSupersedes && mem.type !== "task_archive") {
|
|
316
|
-
const conflict = await db2.memoryVectors.checkConflicts(
|
|
317
|
-
mem.content,
|
|
318
|
-
mem.scope.repo,
|
|
319
|
-
mem.type,
|
|
320
|
-
vectors2,
|
|
321
|
-
0.55
|
|
322
|
-
);
|
|
342
|
+
const conflict = await db2.memoryVectors.checkConflicts(mem.content, mem.scope.repo, mem.type, vectors2, 0.55);
|
|
323
343
|
if (conflict) {
|
|
324
344
|
return createMcpResponse(
|
|
325
345
|
{
|
|
@@ -343,7 +363,8 @@ async function handleMemoryStore(params, db2, vectors2) {
|
|
|
343
363
|
if (mem.scope.language && !tags.includes(mem.scope.language.toLowerCase())) {
|
|
344
364
|
tags.push(mem.scope.language.toLowerCase());
|
|
345
365
|
}
|
|
346
|
-
const code = mem.code ||
|
|
366
|
+
const code = mem.code || generateNextCode(mem.scope.repo, "memory", db2, batchCodes);
|
|
367
|
+
batchCodes.add(code);
|
|
347
368
|
entries.push({
|
|
348
369
|
id: randomUUID(),
|
|
349
370
|
code,
|
|
@@ -380,7 +401,12 @@ async function handleMemoryStore(params, db2, vectors2) {
|
|
|
380
401
|
}
|
|
381
402
|
const codesStr = storedCodes.length > 0 ? `: ${storedCodes.join(", ")}` : "";
|
|
382
403
|
return createMcpResponse(
|
|
383
|
-
{
|
|
404
|
+
{
|
|
405
|
+
success: true,
|
|
406
|
+
repo: validated.memories[0]?.scope.repo,
|
|
407
|
+
createdCount: validated.memories.length,
|
|
408
|
+
codes: storedCodes
|
|
409
|
+
},
|
|
384
410
|
`Stored ${validated.memories.length} memories${codesStr}.`,
|
|
385
411
|
{ includeSerializedStructuredContent: validated.structured }
|
|
386
412
|
);
|
|
@@ -756,16 +782,18 @@ function capitalize2(str) {
|
|
|
756
782
|
// src/mcp/tools/task.manage.ts
|
|
757
783
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
758
784
|
var UUID_REGEX3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
759
|
-
function resolveParentId(value, repo, storage) {
|
|
785
|
+
function resolveParentId(value, repo, storage, localCodeMap) {
|
|
760
786
|
if (!value) return null;
|
|
761
787
|
if (UUID_REGEX3.test(value)) return value;
|
|
788
|
+
if (localCodeMap?.has(value)) return localCodeMap.get(value);
|
|
762
789
|
const parent = storage.tasks.getTaskByCode(repo, value);
|
|
763
790
|
if (!parent) throw new Error(`parent_id: task with code '${value}' not found in repo '${repo}'`);
|
|
764
791
|
return parent.id;
|
|
765
792
|
}
|
|
766
|
-
function resolveDependsOn(value, repo, storage) {
|
|
793
|
+
function resolveDependsOn(value, repo, storage, localCodeMap) {
|
|
767
794
|
if (!value) return null;
|
|
768
795
|
if (UUID_REGEX3.test(value)) return value;
|
|
796
|
+
if (localCodeMap?.has(value)) return localCodeMap.get(value);
|
|
769
797
|
const task = storage.tasks.getTaskByCode(repo, value);
|
|
770
798
|
if (!task) throw new Error(`depends_on: task with code '${value}' not found in repo '${repo}'`);
|
|
771
799
|
return task.id;
|
|
@@ -955,28 +983,38 @@ async function handleTaskCreate(args, storage) {
|
|
|
955
983
|
const tasksToInsert = [];
|
|
956
984
|
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
957
985
|
const codesInRequest = /* @__PURE__ */ new Set();
|
|
986
|
+
const batchCodes = /* @__PURE__ */ new Set();
|
|
987
|
+
for (const taskData of bulkTasks) {
|
|
988
|
+
if (!taskData.task_code) {
|
|
989
|
+
taskData.task_code = generateNextCode(repo, "task", storage, batchCodes);
|
|
990
|
+
}
|
|
991
|
+
batchCodes.add(taskData.task_code);
|
|
992
|
+
}
|
|
958
993
|
const allCodes = bulkTasks.map((t) => t.task_code);
|
|
959
994
|
const existingCodes = storage.tasks.getExistingTaskCodes(repo, allCodes);
|
|
960
995
|
const initialStats = storage.taskStats.getTaskStats(repo);
|
|
961
996
|
let pendingInRequestCount = 0;
|
|
997
|
+
const localCodeMap = /* @__PURE__ */ new Map();
|
|
962
998
|
for (const taskData of bulkTasks) {
|
|
963
|
-
|
|
964
|
-
|
|
999
|
+
localCodeMap.set(taskData.task_code, randomUUID2());
|
|
1000
|
+
}
|
|
1001
|
+
for (const taskData of bulkTasks) {
|
|
1002
|
+
const code = taskData.task_code;
|
|
1003
|
+
if (codesInRequest.has(code)) {
|
|
1004
|
+
throw new Error(`Duplicate task_code in request: '${code}'`);
|
|
965
1005
|
}
|
|
966
|
-
if (existingCodes.has(
|
|
967
|
-
throw new Error(`Duplicate task_code: '${
|
|
1006
|
+
if (existingCodes.has(code)) {
|
|
1007
|
+
throw new Error(`Duplicate task_code: '${code}' already exists in repository '${repo}'`);
|
|
968
1008
|
}
|
|
969
|
-
codesInRequest.add(
|
|
1009
|
+
codesInRequest.add(code);
|
|
970
1010
|
const normalizedStatus = taskData.status || "backlog";
|
|
971
1011
|
if (normalizedStatus !== "backlog" && normalizedStatus !== "pending") {
|
|
972
|
-
throw new Error(
|
|
973
|
-
`New tasks must be 'backlog' or 'pending'. Task '${taskData.task_code}' has status '${normalizedStatus}'.`
|
|
974
|
-
);
|
|
1012
|
+
throw new Error(`New tasks must be 'backlog' or 'pending'. Task '${code}' has status '${normalizedStatus}'.`);
|
|
975
1013
|
}
|
|
976
1014
|
if (normalizedStatus === "pending") {
|
|
977
1015
|
if (initialStats.todo + pendingInRequestCount >= 10) {
|
|
978
1016
|
throw new Error(
|
|
979
|
-
`Cannot create task '${
|
|
1017
|
+
`Cannot create task '${code}' as 'pending'. Maximum of 10 pending tasks reached. Please use status 'backlog' for new tasks instead.`
|
|
980
1018
|
);
|
|
981
1019
|
}
|
|
982
1020
|
}
|
|
@@ -986,10 +1024,11 @@ async function handleTaskCreate(args, storage) {
|
|
|
986
1024
|
if (!tags.includes(phaseTag2)) {
|
|
987
1025
|
tags.push(phaseTag2);
|
|
988
1026
|
}
|
|
1027
|
+
const taskId2 = localCodeMap.get(code);
|
|
989
1028
|
const task2 = {
|
|
990
|
-
id:
|
|
1029
|
+
id: taskId2,
|
|
991
1030
|
repo,
|
|
992
|
-
task_code:
|
|
1031
|
+
task_code: code,
|
|
993
1032
|
phase: taskData.phase,
|
|
994
1033
|
title: taskData.title,
|
|
995
1034
|
description: taskData.description,
|
|
@@ -1005,14 +1044,15 @@ async function handleTaskCreate(args, storage) {
|
|
|
1005
1044
|
canceled_at: statusTimestamps2.canceled_at,
|
|
1006
1045
|
est_tokens: taskData.est_tokens ?? 0,
|
|
1007
1046
|
tags,
|
|
1047
|
+
suggested_skills: taskData.suggested_skills || [],
|
|
1008
1048
|
commit_id: null,
|
|
1009
1049
|
changed_files: [],
|
|
1010
1050
|
metadata: taskData.metadata || {},
|
|
1011
|
-
parent_id: resolveParentId(taskData.parent_id, repo, storage),
|
|
1012
|
-
depends_on: resolveDependsOn(taskData.depends_on, repo, storage)
|
|
1051
|
+
parent_id: resolveParentId(taskData.parent_id, repo, storage, localCodeMap),
|
|
1052
|
+
depends_on: resolveDependsOn(taskData.depends_on, repo, storage, localCodeMap)
|
|
1013
1053
|
};
|
|
1014
1054
|
tasksToInsert.push(task2);
|
|
1015
|
-
createdTasks.push(
|
|
1055
|
+
createdTasks.push(code);
|
|
1016
1056
|
if (normalizedStatus === "pending") {
|
|
1017
1057
|
pendingInRequestCount++;
|
|
1018
1058
|
}
|
|
@@ -1040,11 +1080,12 @@ async function handleTaskCreate(args, storage) {
|
|
|
1040
1080
|
depends_on,
|
|
1041
1081
|
est_tokens
|
|
1042
1082
|
} = singleTask;
|
|
1043
|
-
if (!
|
|
1044
|
-
throw new Error("Missing required fields for single task creation (
|
|
1083
|
+
if (!phase || !title || !description) {
|
|
1084
|
+
throw new Error("Missing required fields for single task creation (phase, title, description)");
|
|
1045
1085
|
}
|
|
1046
|
-
|
|
1047
|
-
|
|
1086
|
+
const resolvedCode = task_code || generateNextCode(repo, "task", storage);
|
|
1087
|
+
if (storage.tasks.isTaskCodeDuplicate(repo, resolvedCode)) {
|
|
1088
|
+
throw new Error(`Duplicate task_code: '${resolvedCode}' already exists in repository '${repo}'`);
|
|
1048
1089
|
}
|
|
1049
1090
|
if (status !== "backlog" && status !== "pending" && status !== void 0) {
|
|
1050
1091
|
throw new Error("New tasks must be created with status 'backlog' or 'pending'.");
|
|
@@ -1068,7 +1109,7 @@ async function handleTaskCreate(args, storage) {
|
|
|
1068
1109
|
const task = {
|
|
1069
1110
|
id: taskId,
|
|
1070
1111
|
repo,
|
|
1071
|
-
task_code,
|
|
1112
|
+
task_code: resolvedCode,
|
|
1072
1113
|
phase,
|
|
1073
1114
|
title,
|
|
1074
1115
|
description,
|
|
@@ -1084,6 +1125,7 @@ async function handleTaskCreate(args, storage) {
|
|
|
1084
1125
|
canceled_at: statusTimestamps.canceled_at,
|
|
1085
1126
|
est_tokens: est_tokens ?? 0,
|
|
1086
1127
|
tags: finalTags,
|
|
1128
|
+
suggested_skills: singleTask.suggested_skills || [],
|
|
1087
1129
|
commit_id: null,
|
|
1088
1130
|
changed_files: [],
|
|
1089
1131
|
metadata: metadata || {},
|
|
@@ -1150,11 +1192,6 @@ function buildMissingTaskSchema(task) {
|
|
|
1150
1192
|
description: "Name of the repository for this task.",
|
|
1151
1193
|
minLength: 1
|
|
1152
1194
|
});
|
|
1153
|
-
addRequiredStringField(properties, required, task, "task_code", {
|
|
1154
|
-
title: "Task Code",
|
|
1155
|
-
description: "Unique task code in this repository.",
|
|
1156
|
-
minLength: 1
|
|
1157
|
-
});
|
|
1158
1195
|
addRequiredStringField(properties, required, task, "phase", {
|
|
1159
1196
|
title: "Phase",
|
|
1160
1197
|
description: "Project phase or milestone for this task.",
|
|
@@ -1759,14 +1796,6 @@ async function handleMemoryDetail(args, storage) {
|
|
|
1759
1796
|
// src/mcp/tools/standard.store.ts
|
|
1760
1797
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
1761
1798
|
var UUID_REGEX4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1762
|
-
function generateShortCode2() {
|
|
1763
|
-
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ0123456789";
|
|
1764
|
-
let code = "";
|
|
1765
|
-
for (let i = 0; i < 6; i++) {
|
|
1766
|
-
code += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
1767
|
-
}
|
|
1768
|
-
return code;
|
|
1769
|
-
}
|
|
1770
1799
|
function resolveStandardParentId(value, db2) {
|
|
1771
1800
|
if (!value) return null;
|
|
1772
1801
|
if (UUID_REGEX4.test(value)) return value;
|
|
@@ -1808,7 +1837,7 @@ async function storeSingleStandard(params, db2, vectors2) {
|
|
|
1808
1837
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1809
1838
|
const entry = {
|
|
1810
1839
|
id: randomUUID3(),
|
|
1811
|
-
code:
|
|
1840
|
+
code: generateNextCode(params.repo || "__global__", "standard", db2),
|
|
1812
1841
|
title: params.name,
|
|
1813
1842
|
content: params.content,
|
|
1814
1843
|
parent_id: resolveStandardParentId(params.parent_id, db2),
|
|
@@ -1851,6 +1880,8 @@ async function handleStandardStore(params, db2, vectors2) {
|
|
|
1851
1880
|
if (validated.standards) {
|
|
1852
1881
|
const entries = [];
|
|
1853
1882
|
const storedCodes = [];
|
|
1883
|
+
const batchCodes = /* @__PURE__ */ new Set();
|
|
1884
|
+
const standardRepo = validated.repo || "__global__";
|
|
1854
1885
|
for (const std of validated.standards) {
|
|
1855
1886
|
const incomingVersion = std.version || "1.0.0";
|
|
1856
1887
|
const incomingLanguage = std.language ?? null;
|
|
@@ -1883,7 +1914,8 @@ async function handleStandardStore(params, db2, vectors2) {
|
|
|
1883
1914
|
);
|
|
1884
1915
|
}
|
|
1885
1916
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1886
|
-
const code =
|
|
1917
|
+
const code = generateNextCode(standardRepo, "standard", db2, batchCodes);
|
|
1918
|
+
batchCodes.add(code);
|
|
1887
1919
|
entries.push({
|
|
1888
1920
|
id: randomUUID3(),
|
|
1889
1921
|
code,
|
|
@@ -2349,6 +2381,8 @@ async function handleTaskGet(args, storage) {
|
|
|
2349
2381
|
];
|
|
2350
2382
|
if (task.phase) lines.push(`Phase: ${task.phase}`);
|
|
2351
2383
|
if (task.description) lines.push(`Description: ${task.description}`);
|
|
2384
|
+
if (task.suggested_skills && task.suggested_skills.length > 0)
|
|
2385
|
+
lines.push(`Suggested Skills: ${task.suggested_skills.join(", ")}`);
|
|
2352
2386
|
if (task.metadata) lines.push(`Metadata: ${JSON.stringify(task.metadata)}`);
|
|
2353
2387
|
lines.push(`Created: ${task.created_at}`);
|
|
2354
2388
|
if (task.updated_at) lines.push(`Updated: ${task.updated_at}`);
|
|
@@ -13,12 +13,12 @@ tags: [workflow, task-creation, planning, mcp]
|
|
|
13
13
|
|
|
14
14
|
## FSM
|
|
15
15
|
|
|
16
|
-
Entry=S0 → S1 → S2 → S3
|
|
16
|
+
Entry=S0 → S1 → S2 → S3 Exit=created
|
|
17
17
|
Guard: S(N) req S(N-1)✅; NO code/edit/delete — MCP tools ONLY (allowed: task-create, task-list, task-detail, task-update, memory-store, memory-search, standard-search, standard-store, handoff-list, handoff-update, read)
|
|
18
18
|
|
|
19
19
|
S0 | pre_analysis: memory-search(architecture/history) + standard-search(if task leads to code/test/refactor/migrate decisions) + handoff-list(pending; close stale that describe completed work) + read code(verify paths+impl) + task-list dedup(DO NOT duplicate; link via parent_id/depends_on) | — | context | —
|
|
20
20
|
S1 | design tasks: atomic(1 logical change), layered(DB/Service/State/UI), context(paths+symbols+APIs), min 1 pos+1 neg test | S0✅ | task specs | —
|
|
21
|
-
S2 | assign attributes: task_code(
|
|
21
|
+
S2 | assign attributes: task_code(optional — auto-generated as TASK-xxx if omitted), phase(Discovery|Implementation|Testing), priority(1=Low..5=Critical), strict description format | S1✅ | task attrs | —
|
|
22
22
|
S3 | create via task-create(bulk max 500) + log decisions via memory-store(arch/feature changes; skip simple bugs) | S2✅ | MCP tasks created | —
|
|
23
23
|
G1 | blueprint? | src=idea-to-blueprint | → route blueprint flow | —
|
|
24
24
|
G2 | sprint? | src=.agents/documents/tasks/sprints/ | → route sprint flow | —
|
|
@@ -3,10 +3,10 @@ name: export-task-to-github
|
|
|
3
3
|
description: Export local tasks to GitHub Issues
|
|
4
4
|
arguments:
|
|
5
5
|
- name: owner
|
|
6
|
-
description: GitHub repo owner
|
|
6
|
+
description: GitHub repo owner (hint: run `git remote -v` to extract from origin URL)
|
|
7
7
|
required: true
|
|
8
8
|
- name: repo
|
|
9
|
-
description: GitHub repo name
|
|
9
|
+
description: GitHub repo name (hint: run `git remote -v` to extract from origin URL)
|
|
10
10
|
required: true
|
|
11
11
|
- name: task_id
|
|
12
12
|
description: Local task ID
|
|
@@ -19,7 +19,7 @@ tags: [workflow, github, task-sync, mcp]
|
|
|
19
19
|
|
|
20
20
|
## FSM
|
|
21
21
|
|
|
22
|
-
Entry=S0 → S1 → G1 → S2 → S3 → S4
|
|
22
|
+
Entry=S0 → S1 → G1 → S2 → S3 → S4 Exit=exported|skipped
|
|
23
23
|
Guard: S(N) req S(N-1)✅; MCP + GitHub tools ONLY
|
|
24
24
|
|
|
25
25
|
S0 | fetch task via task-detail | task_id exists? | task data | —
|
|
@@ -10,11 +10,12 @@ tags: [workflow, github, issue-import, mcp]
|
|
|
10
10
|
|
|
11
11
|
## FSM
|
|
12
12
|
|
|
13
|
-
Entry=S0 → S1 → S2 → S3 → S4
|
|
13
|
+
Entry=S0 → S1 → S2 → S3 → S4 Exit=imported
|
|
14
14
|
Guard: S(N) req S(N-1)✅
|
|
15
|
+
Hint: If repo not auto-detected, run `git remote -v` to get owner/repo from origin URL.
|
|
15
16
|
|
|
16
17
|
S0 | fetch open issues: primary=github-mcp-server; fallback=`gh issue list --json number,title,body,labels,url` | — | issue list | —
|
|
17
18
|
S1 | dedup via task-list (skip if GH-{number} exists) | S0✅ | filtered issues | —
|
|
18
|
-
S2 | create MCP tasks: task_code=GH-{number}, EXACT title/body (DO NOT summarize), tags=labels, phase=backlog|triage, metadata=URL | S1✅ | tasks created | —
|
|
19
|
+
S2 | create MCP tasks: task_code=GH-{number} (auto-generated as TASK-xxx if omitted), EXACT title/body (DO NOT summarize), tags=labels, phase=backlog|triage, metadata=URL | S1✅ | tasks created | —
|
|
19
20
|
S3 | import comments via issue_read → task-update | S2✅ | comments linked | —
|
|
20
21
|
S4 | report created count | S3✅ | summary | —
|
|
@@ -13,12 +13,12 @@ tags: [workflow, audit, ux, gap-analysis, mcp]
|
|
|
13
13
|
|
|
14
14
|
## FSM
|
|
15
15
|
|
|
16
|
-
Entry=S0 → S1 → S2 → S3
|
|
16
|
+
Entry=S0 → S1 → S2 → S3 Exit=done
|
|
17
17
|
Guard: S(N) req S(N-1)✅; NO code/edit/delete — read+MCP tools ONLY
|
|
18
18
|
|
|
19
19
|
S0 | sequential discovery: docs → code → UI (chrome-dev-tools) | — | findings | —
|
|
20
20
|
S1 | pre-task analysis: memory-search (0.55 threshold) + standard-search + handoff-list + task-list dedup | S0✅ | context | —
|
|
21
|
-
S2 | design tasks: atomic, attributes (task_code, phase, priority, agent, model), strict description format | S1✅ | task specs | —
|
|
21
|
+
S2 | design tasks: atomic, attributes (task_code optional — auto-generated as TASK-xxx, phase, priority, agent, model), strict description format | S1✅ | task specs | —
|
|
22
22
|
S3 | create via task-create + log decisions via memory-store + standard-store for coding rules | S2✅ | MCP tasks | —
|
|
23
23
|
|
|
24
24
|
## Description Format (STRICT — used in S2)
|
|
@@ -3,10 +3,10 @@ name: review-and-post-issue
|
|
|
3
3
|
description: Audit documentation against implementation; generate GitHub issues for gaps.
|
|
4
4
|
arguments:
|
|
5
5
|
- name: owner
|
|
6
|
-
description: GitHub repo owner.
|
|
6
|
+
description: GitHub repo owner. (hint: run `git remote -v` to extract from origin URL)
|
|
7
7
|
required: true
|
|
8
8
|
- name: repo
|
|
9
|
-
description: GitHub repo name.
|
|
9
|
+
description: GitHub repo name. (hint: run `git remote -v` to extract from origin URL)
|
|
10
10
|
required: true
|
|
11
11
|
- name: target
|
|
12
12
|
description: Module, feature, or component to audit.
|
|
@@ -13,8 +13,9 @@ tags: [workflow, github, issue-resolution, sentinel]
|
|
|
13
13
|
|
|
14
14
|
## FSM
|
|
15
15
|
|
|
16
|
-
Entry=S0 → S1 → S2 → S3 → S4 → S5 → S6 → S7 → S8
|
|
16
|
+
Entry=S0 → S1 → S2 → S3 → S4 → S5 → S6 → S7 → S8 Exit=resolved
|
|
17
17
|
Guard: S(N) req S(N-1)✅; autonomous — no permission per step
|
|
18
|
+
Hint: If repo not auto-detected from issue_url, run `git remote -v` to get owner/repo from origin URL.
|
|
18
19
|
|
|
19
20
|
S0 | fetch: issue_read body + ALL comments (get_comments) | issue_url provided? | raw issue + all comments | —
|
|
20
21
|
S1 | analyze comments: extract requirements, hints, root cause clues, reproduction steps, error details | S0✅ | comment analysis | —
|
|
@@ -13,14 +13,14 @@ tags: [workflow, planning, task-breakdown]
|
|
|
13
13
|
|
|
14
14
|
## FSM
|
|
15
15
|
|
|
16
|
-
Entry=S0 → S1 → S2 → S3 → S4 → S5
|
|
16
|
+
Entry=S0 → S1 → S2 → S3 → S4 → S5 Exit=planned
|
|
17
17
|
Guard: S(N) req S(N-1)✅
|
|
18
18
|
|
|
19
19
|
S0 | orient: task-list (avoid dupes) + standard-search (if code edits) + handoff-list (close stale) | objective provided? | existing state | —
|
|
20
20
|
S1 | analyze: break into 3-7 atomic verifiable tasks | S0✅ | task breakdown | —
|
|
21
21
|
S2 | phase: group into research / implementation / validation | S1✅ | phased tasks | —
|
|
22
22
|
S3 | hierarchy: parent_id + depends_on for sequencing; priority 1-5 scale | S2✅ | structured plan | —
|
|
23
|
-
S4 | create via task-create (stable task_code, tags, acceptance criteria) | S3✅ | MCP tasks | —
|
|
23
|
+
S4 | create via task-create (stable task_code or omit for auto-generated TASK-xxx, tags, suggested_skills if relevant, acceptance criteria) | S3✅ | MCP tasks | —
|
|
24
24
|
S5 | display final plan to user | S4✅ | user output | —
|
|
25
25
|
|
|
26
26
|
Objective: {{objective}}
|
|
@@ -20,7 +20,7 @@ S0 | sync: resolve identity (arg→auto `<runner>-<randomName>`, 1x reuse all lo
|
|
|
20
20
|
S1 | hydrate: task-detail ONCE per task — MUST cache, MUST reuse all steps, NO re-fetch | S0✅ | full task | —
|
|
21
21
|
G0 | readiness: depends_on✅ AND parent_id✅? if all blocked → report blockers + pause | S1✅ | → S2 / skip+pick next | —
|
|
22
22
|
S2 | claim: task-claim(with identity metadata) + task-update→in_progress(agent, role, identity) | G0✅ | ownership | —
|
|
23
|
-
S3 | research: memory-search + standard-search(MANDATORY per task — even sub-agents/decomposed) + hydrate relevant | S2✅ | context | —
|
|
23
|
+
S3 | research: check task-detail for suggested_skills — load each via skill() tool; then memory-search + standard-search(MANDATORY per task — even sub-agents/decomposed) + hydrate relevant | S2✅ | context | —
|
|
24
24
|
S4 | execute: trace logic+callsites+docs — DO NOT infer from file presence; decompose if too broad | S3✅ | changes | —
|
|
25
25
|
S5 | validate: tests + linters + type-check + browser(if UI — MANDATORY: console errors, overflow, responsive, core interactions) + logic audit all paths | S4✅ | verification | —
|
|
26
26
|
S6 | finalize: task-update→completed(evidence: inspected files, verified logic, test results) + memory-store(insights) + standard-store(rules) + handoff(if work remains — with identity) + retrospective + report | S5✅ | completion | —
|