@projitive/mcp 2.1.0 → 2.1.2
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/output/package.json +5 -1
- package/output/source/prompts/quickStart.js +58 -22
- package/output/source/prompts/taskDiscovery.js +13 -4
- package/output/source/prompts/taskExecution.js +57 -13
- package/output/source/prompts/taskExecution.test.js +2 -1
- package/output/source/tools/task.js +195 -25
- package/output/source/tools/task.test.js +6 -6
- package/package.json +5 -1
package/output/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@projitive/mcp",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "Projitive MCP Server for project and task discovery/update",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "",
|
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
"publishConfig": {
|
|
13
13
|
"access": "public"
|
|
14
14
|
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/yinxulai/projitive"
|
|
18
|
+
},
|
|
15
19
|
"scripts": {
|
|
16
20
|
"test": "vitest run",
|
|
17
21
|
"test:coverage": "vitest run --coverage",
|
|
@@ -78,34 +78,69 @@ export function registerQuickStartPrompt(server) {
|
|
|
78
78
|
'',
|
|
79
79
|
'## Autonomous Operating Loop',
|
|
80
80
|
'',
|
|
81
|
-
'Keep this loop
|
|
82
|
-
'1. Discover: `taskNext()`',
|
|
83
|
-
'2. Execute: update governance store + docs
|
|
84
|
-
'3. Verify: `taskContext()`',
|
|
85
|
-
'4. Re-prioritize: `taskNext()`',
|
|
86
|
-
'',
|
|
87
|
-
'
|
|
88
|
-
'
|
|
89
|
-
'
|
|
90
|
-
'-
|
|
81
|
+
'Keep this loop while high-value actionable tasks exist:',
|
|
82
|
+
'1. Discover: `taskNext()` - Select highest-priority task',
|
|
83
|
+
'2. Execute: perform work + update governance store (.projitive) + docs',
|
|
84
|
+
'3. Verify: `taskContext()` - Confirm changes and consistency',
|
|
85
|
+
'4. Re-prioritize: `taskNext()` - Update priority and select next',
|
|
86
|
+
'',
|
|
87
|
+
'**When to Stop or Pause:**',
|
|
88
|
+
'',
|
|
89
|
+
'**Stop Reason 1: Task is BLOCKED and needs external action**',
|
|
90
|
+
'- Blocker must have: type, description, blockingEntity (if applicable), unblockCondition (if applicable)',
|
|
91
|
+
'- Cannot continue work until blocker is actually resolved (not just "in progress")',
|
|
92
|
+
'- Examples:',
|
|
93
|
+
' - internal_dependency: dependent TASK must be DONE first',
|
|
94
|
+
' - external_dependency: external party must deliver (tracked via escalationPath)',
|
|
95
|
+
' - resource: tool/access/budget must be allocated (tracked via escalationPath)',
|
|
96
|
+
' - approval: decision-maker must sign off (tracked via blockingEntity)',
|
|
97
|
+
'- **What to do next:** Follow BLOCKED guidance to unblock, then re-run `taskNext()`',
|
|
98
|
+
'',
|
|
99
|
+
'**Stop Reason 2: Task is DONE and all acceptance criteria are met**',
|
|
100
|
+
'- Verify in `taskContext()` that all links/references are consistent',
|
|
101
|
+
'- Check that report/design/roadmap evidence is properly documented',
|
|
102
|
+
'- **What to do next:** Run `taskNext()` to pick the next task',
|
|
103
|
+
'',
|
|
104
|
+
'**Stop Reason 3: No actionable tasks (all TODO blocked or no tasks exist)**',
|
|
105
|
+
'- Means: all unblocked work is complete, only blocked tasks remain OR no tasks at all',
|
|
106
|
+
'- **What to do next:** Analyze blockers and roadmap, create 1-3 new TODO tasks via `taskCreate()`',
|
|
91
107
|
'',
|
|
92
108
|
'## Special Cases',
|
|
93
109
|
'',
|
|
94
110
|
'### Case 1: No .projitive directory',
|
|
95
111
|
'Call `projectInit(projectPath="<project-dir>")` to initialize governance structure.',
|
|
96
112
|
'',
|
|
97
|
-
'### Case 2: No actionable tasks',
|
|
98
|
-
'
|
|
99
|
-
'
|
|
100
|
-
'
|
|
101
|
-
'
|
|
102
|
-
' -
|
|
103
|
-
' -
|
|
104
|
-
' -
|
|
105
|
-
' -
|
|
106
|
-
'
|
|
107
|
-
'
|
|
108
|
-
'
|
|
113
|
+
'### Case 2: No actionable tasks (all TODO/IN_PROGRESS are blocked OR task count is zero)',
|
|
114
|
+
'',
|
|
115
|
+
'**If some tasks exist but all are BLOCKED:**',
|
|
116
|
+
'1. Run `taskList()` and filter by status = BLOCKED',
|
|
117
|
+
'2. For each BLOCKED task, check its blocker type:',
|
|
118
|
+
' - **internal_dependency**: Is the blocking task listed? If no, create it',
|
|
119
|
+
' - **external_dependency**: Is escalationPath defined? If no, add it',
|
|
120
|
+
' - **resource**: Is escalationPath defined for requestor? If no, add it',
|
|
121
|
+
' - **approval**: Is blockingEntity defined? If no, add it',
|
|
122
|
+
'3. For each blocker with missing metadata: call `taskUpdate()` to complete it',
|
|
123
|
+
'4. Select 1 BLOCKED task: follow its unblock guidance to take concrete action',
|
|
124
|
+
'5. Once blocker is resolved: call `taskUpdate(..., {status: "TODO"})` to unblock',
|
|
125
|
+
'6. Re-run `taskNext()` to see newly unblocked tasks',
|
|
126
|
+
'',
|
|
127
|
+
'**If no tasks exist at all (or only DONE tasks):**',
|
|
128
|
+
'1. Review `projectContext()` to confirm task count is 0',
|
|
129
|
+
'2. Read active roadmap milestones via `roadmapContext()`',
|
|
130
|
+
'3. For each active milestone, derive 1-3 executable TODO tasks:',
|
|
131
|
+
' - **Clear outcome**: one-sentence done condition',
|
|
132
|
+
' - **Evidence target**: at least one artifact (report, design, readme update)',
|
|
133
|
+
' - **Small slice**: completable in one focused execution cycle',
|
|
134
|
+
' - **Roadmap link**: link to roadmap via roadmapRefs',
|
|
135
|
+
' - **Minimal dependencies**: avoid complex task chains at start',
|
|
136
|
+
'4. Call `taskCreate()` for each task with complete metadata',
|
|
137
|
+
'5. Re-run `taskNext()` to start executing the new tasks',
|
|
138
|
+
'',
|
|
139
|
+
'**If roadmap is also empty:**',
|
|
140
|
+
'1. Read design documents under `projitive://designs/`',
|
|
141
|
+
'2. Identify next architectural milestone or feature slice',
|
|
142
|
+
'3. Document in ROADMAP.md via `roadmapCreate()`',
|
|
143
|
+
'4. Create corresponding TODO tasks via `taskCreate()`',
|
|
109
144
|
'',
|
|
110
145
|
'## Hard Rules',
|
|
111
146
|
'',
|
|
@@ -114,6 +149,7 @@ export function registerQuickStartPrompt(server) {
|
|
|
114
149
|
'- **.projitive governance store is source of truth** - tasks.md/roadmap.md are generated views and may be overwritten',
|
|
115
150
|
'- **Prefer tool writes over manual table/view edits** - Use taskCreate/taskUpdate/roadmapCreate/roadmapUpdate',
|
|
116
151
|
'- **Always verify after updates** - Re-run taskContext() to confirm reference consistency',
|
|
152
|
+
'- **BLOCKED tasks require full blocker metadata** - type, description required; blockingEntity, unblockCondition, escalationPath as needed',
|
|
117
153
|
].join('\n');
|
|
118
154
|
return asUserPrompt(text);
|
|
119
155
|
});
|
|
@@ -73,21 +73,30 @@ export function registerTaskDiscoveryPrompt(server) {
|
|
|
73
73
|
' - Tasks with roadmapRefs first',
|
|
74
74
|
' - Tasks with explicit owner first',
|
|
75
75
|
'',
|
|
76
|
-
'3. **BLOCKED** - Last
|
|
77
|
-
' -
|
|
78
|
-
' -
|
|
76
|
+
'3. **BLOCKED** - Last: unblock first before starting new work',
|
|
77
|
+
' - Each BLOCKED task has blocker metadata with clear action steps',
|
|
78
|
+
' - Check blocker type (internal_dependency, external_dependency, resource, approval)',
|
|
79
|
+
' - Read taskContext to see blocker details and required action path',
|
|
80
|
+
' - Follow the action path to unblock (may involve creating unblock task or reaching out)',
|
|
81
|
+
' - Only move to TODO after unblock condition is actually met + documented',
|
|
79
82
|
'',
|
|
80
83
|
'### Discovery Methods',
|
|
81
84
|
'',
|
|
82
85
|
'#### Method A: Auto-select with taskNext() (Recommended)',
|
|
83
86
|
'',
|
|
84
87
|
'Call `taskNext()`, the tool will automatically:',
|
|
85
|
-
'- Sort all tasks by priority',
|
|
88
|
+
'- Sort all tasks by priority (IN_PROGRESS > TODO, filter out pure BLOCKED)',
|
|
86
89
|
'- Select highest-priority actionable task',
|
|
87
90
|
'- Return task ID and summary',
|
|
88
91
|
'',
|
|
89
92
|
'Then call `taskContext(projectPath="...", taskId="<task-id>")` for task details.',
|
|
90
93
|
'',
|
|
94
|
+
'**If taskNext returns a BLOCKED task:**',
|
|
95
|
+
'- This means all TODO/IN_PROGRESS options have been exhausted',
|
|
96
|
+
'- You MUST resolve the blocker before starting other work',
|
|
97
|
+
'- Read blocker metadata and follow action path',
|
|
98
|
+
'- Once blocker is resolved, re-run taskNext() to get next actionable task',
|
|
99
|
+
'',
|
|
91
100
|
'### Discovery Quality Gate (before creating new tasks)',
|
|
92
101
|
'Only create a TODO when all conditions are true:',
|
|
93
102
|
'- It can be finished in one focused execution cycle',
|
|
@@ -47,7 +47,8 @@ export function registerTaskExecutionPrompt(server) {
|
|
|
47
47
|
'- If status is MISSING/INCOMPLETE, complete it before any implementation',
|
|
48
48
|
'',
|
|
49
49
|
'### Pre-Execution Research Brief (Mandatory Gate)',
|
|
50
|
-
'- Fixed file name: `designs/research/<TASK-ID>.implementation-research.md`',
|
|
50
|
+
'- Fixed file name (relative to governanceDir): `designs/research/<TASK-ID>.implementation-research.md`',
|
|
51
|
+
'- Absolute location: `<governanceDir>/designs/research/<TASK-ID>.implementation-research.md`',
|
|
51
52
|
'- Required sections:',
|
|
52
53
|
' - `## Design Guidelines and Specs`',
|
|
53
54
|
' - `## Code Architecture and Implementation Findings`',
|
|
@@ -69,8 +70,9 @@ export function registerTaskExecutionPrompt(server) {
|
|
|
69
70
|
'|----------------|-----------|-------------|',
|
|
70
71
|
'| TODO | \u2192 IN_PROGRESS | When starting execution |',
|
|
71
72
|
'| IN_PROGRESS | \u2192 DONE | When task is complete |',
|
|
72
|
-
'| IN_PROGRESS | \u2192 BLOCKED | When
|
|
73
|
-
'| BLOCKED | \u2192 TODO | When blocker is resolved |',
|
|
73
|
+
'| IN_PROGRESS | \u2192 BLOCKED | When production is halted by external factor |',
|
|
74
|
+
'| BLOCKED | \u2192 TODO | When blocker condition is resolved + evidence is documented |',
|
|
75
|
+
'| BLOCKED | \u2192 UNBLOCK TASK (new) | Optional: create a new TODO task if blocker requires work (e.g., someone else must do something) |',
|
|
74
76
|
'',
|
|
75
77
|
'### Execution Steps',
|
|
76
78
|
'',
|
|
@@ -129,17 +131,59 @@ export function registerTaskExecutionPrompt(server) {
|
|
|
129
131
|
'',
|
|
130
132
|
'## Special Cases',
|
|
131
133
|
'',
|
|
132
|
-
'### Case 1: Encountered a blocker',
|
|
134
|
+
'### Case 1: Encountered a blocker during IN_PROGRESS',
|
|
133
135
|
'',
|
|
134
|
-
'If unable to continue task execution:',
|
|
135
|
-
'1. Call `taskUpdate()` to change status to BLOCKED',
|
|
136
|
-
'2. Fill blocker field (Spec v1.1.0):',
|
|
137
|
-
' - type: Blocker type (dependency/missing-info/technical-debt/other)',
|
|
138
|
-
' - description: Blocker description',
|
|
139
|
-
' - relatedLinks: Related links (optional)',
|
|
140
|
-
'3. Create a new TODO task via `taskCreate()` to resolve blocker',
|
|
136
|
+
'If unable to continue task execution due to external/blocking factors:',
|
|
141
137
|
'',
|
|
142
|
-
'
|
|
138
|
+
'**Always do these steps:**',
|
|
139
|
+
'1. Call `taskUpdate()` to change status to BLOCKED',
|
|
140
|
+
'2. Fill blocker metadata (REQUIRED - Spec v1.1.0):',
|
|
141
|
+
' - **type**: One of: `internal_dependency`, `external_dependency`, `resource`, `approval`',
|
|
142
|
+
' - **description**: Clear description of what is blocking and why',
|
|
143
|
+
' - **blockingEntity** (optional): Who/what is causing block (person, team, external service)',
|
|
144
|
+
' - **unblockCondition** (optional): Specific condition needed to unblock (e.g., "TASK-42 must be completed")',
|
|
145
|
+
' - **escalationPath** (optional): Path to escalate if blocker persists',
|
|
146
|
+
'',
|
|
147
|
+
'**Then choose ONE of these paths based on blocker type:**',
|
|
148
|
+
'',
|
|
149
|
+
'#### If `internal_dependency` (another task must complete first)',
|
|
150
|
+
'- Check if the blocking task exists and has owner assigned.',
|
|
151
|
+
'- If exists: coordinate with owner and recheck after their task completes.',
|
|
152
|
+
'- If missing: create new TASK via taskCreate() with explicit unblock condition.',
|
|
153
|
+
'',
|
|
154
|
+
'#### If `external_dependency` (external party/service/approval needed)',
|
|
155
|
+
'- Document blockingEntity clearly.',
|
|
156
|
+
'- If unclear how to reach them, add escalationPath.',
|
|
157
|
+
'- Wait for external delivery or take action per escalationPath.',
|
|
158
|
+
'',
|
|
159
|
+
'#### If `resource` (missing tool, access, budget, personnel)',
|
|
160
|
+
'- Document what resource is needed (in description).',
|
|
161
|
+
'- If owner unknown, set escalationPath.',
|
|
162
|
+
'- Take action to request/allocate resource.',
|
|
163
|
+
'',
|
|
164
|
+
'#### If `approval` (needs approver sign-off)',
|
|
165
|
+
'- Document who must approve (blockingEntity).',
|
|
166
|
+
'- Prepare approval request with clear criteria.',
|
|
167
|
+
'- Track approval status and escalate if delayed.',
|
|
168
|
+
'',
|
|
169
|
+
'**What NOT to do:**',
|
|
170
|
+
'- Do NOT leave BLOCKED task without blocker metadata.',
|
|
171
|
+
'- Do NOT mark BLOCKED if it\'s actually just "waiting for me to finish" (keep IN_PROGRESS).',
|
|
172
|
+
'- Do NOT flip BLOCKED → TODO unless actual unblock condition is met + documented.',
|
|
173
|
+
'',
|
|
174
|
+
'### Case 2: Found a BLOCKED task in taskNext()',
|
|
175
|
+
'',
|
|
176
|
+
'If taskNext() returns a BLOCKED task instead of TODO/IN_PROGRESS:',
|
|
177
|
+
'1. Read blocker metadata carefully (shown in taskContext output).',
|
|
178
|
+
'2. Follow blocker-specific action path in taskStatusGuidance output.',
|
|
179
|
+
'3. Take concrete steps to unblock:',
|
|
180
|
+
' - Create/track dependency task',
|
|
181
|
+
' - Reach out to blocking entity',
|
|
182
|
+
' - Request missing resource',
|
|
183
|
+
' - Follow escalation path if applicable',
|
|
184
|
+
'4. Once unblock condition is met: call `taskUpdate(taskId, {status: \'TODO\'})` and re-run taskNext().',
|
|
185
|
+
'',
|
|
186
|
+
'### Case 3: No actionable tasks (all TODO are blocked)',
|
|
143
187
|
'',
|
|
144
188
|
'If taskNext() returns empty:',
|
|
145
189
|
'1. Call `projectContext()` to recheck project state',
|
|
@@ -181,7 +225,7 @@ export function registerTaskExecutionPrompt(server) {
|
|
|
181
225
|
'',
|
|
182
226
|
'6. **Pre-execution research brief is mandatory**',
|
|
183
227
|
' - `TODO -> IN_PROGRESS` is not allowed until research brief is READY',
|
|
184
|
-
' - Always read
|
|
228
|
+
' - Always read `<governanceDir>/designs/research/<TASK-ID>.implementation-research.md` before implementation',
|
|
185
229
|
].join('\n');
|
|
186
230
|
return asUserPrompt(text);
|
|
187
231
|
});
|
|
@@ -11,7 +11,8 @@ describe('taskExecution prompt', () => {
|
|
|
11
11
|
expect(text).toContain('1) Run taskContext(projectPath="/workspace/app", taskId="TASK-0007").');
|
|
12
12
|
expect(text).toContain('Every status transition must have report evidence');
|
|
13
13
|
expect(text).toContain('Pre-Execution Research Brief (Mandatory Gate)');
|
|
14
|
-
expect(text).toContain('designs/research/<TASK-ID>.implementation-research.md');
|
|
14
|
+
expect(text).toContain('Fixed file name (relative to governanceDir): `designs/research/<TASK-ID>.implementation-research.md`');
|
|
15
|
+
expect(text).toContain('Always read `<governanceDir>/designs/research/<TASK-ID>.implementation-research.md` before implementation');
|
|
15
16
|
});
|
|
16
17
|
it('falls back to taskNext when task is not provided', async () => {
|
|
17
18
|
const server = { registerPrompt: vi.fn() };
|
|
@@ -27,10 +27,180 @@ function taskStatusGuidance(task) {
|
|
|
27
27
|
];
|
|
28
28
|
}
|
|
29
29
|
if (task.status === 'BLOCKED') {
|
|
30
|
-
|
|
31
|
-
'
|
|
32
|
-
'
|
|
30
|
+
const guidance = [
|
|
31
|
+
'## BLOCKED Task - Structured Unblocking Path',
|
|
32
|
+
'',
|
|
33
33
|
];
|
|
34
|
+
// ===== LAYER 1: CRITICAL VALIDATION =====
|
|
35
|
+
guidance.push('### 🔴 CRITICAL - Validate Blocker Metadata First');
|
|
36
|
+
guidance.push('');
|
|
37
|
+
if (!task.blocker) {
|
|
38
|
+
guidance.push('⚠️ **BLOCKER MISSING** - Task cannot be truly BLOCKED without blocker metadata.');
|
|
39
|
+
guidance.push('');
|
|
40
|
+
guidance.push('**Required Action:**');
|
|
41
|
+
guidance.push('```');
|
|
42
|
+
guidance.push('taskUpdate(projectPath="...", taskId="' + task.id + '", {');
|
|
43
|
+
guidance.push(' blocker: {');
|
|
44
|
+
guidance.push(' type: "internal_dependency|external_dependency|resource|approval",');
|
|
45
|
+
guidance.push(' description: "Specific reason for block",');
|
|
46
|
+
guidance.push(' blockingEntity: "Optional: who/what is blocking",');
|
|
47
|
+
guidance.push(' unblockCondition: "Optional: exact condition to unblock"');
|
|
48
|
+
guidance.push(' }');
|
|
49
|
+
guidance.push('})');
|
|
50
|
+
guidance.push('```');
|
|
51
|
+
guidance.push('');
|
|
52
|
+
guidance.push('**Then re-run taskContext() to see type-specific guidance.**');
|
|
53
|
+
return guidance;
|
|
54
|
+
}
|
|
55
|
+
const { type, description, blockingEntity, unblockCondition, escalationPath } = task.blocker;
|
|
56
|
+
guidance.push('**Blocker Summary:**');
|
|
57
|
+
guidance.push(`- Type: **${type}**`);
|
|
58
|
+
guidance.push(`- Issue: ${description}`);
|
|
59
|
+
if (blockingEntity)
|
|
60
|
+
guidance.push(`- Blocking Entity: ${blockingEntity}`);
|
|
61
|
+
if (unblockCondition)
|
|
62
|
+
guidance.push(`- Unblock Condition: ${unblockCondition}`);
|
|
63
|
+
guidance.push('');
|
|
64
|
+
// ===== LAYER 2: HOW TO UNBLOCK =====
|
|
65
|
+
guidance.push('### 🟠 HOW TO UNBLOCK - Type-Specific Steps');
|
|
66
|
+
guidance.push('');
|
|
67
|
+
if (type === 'internal_dependency') {
|
|
68
|
+
guidance.push('**This task is blocked by another task (internal dependency).**');
|
|
69
|
+
guidance.push('');
|
|
70
|
+
guidance.push('**Step 1: Identify the Blocking Task**');
|
|
71
|
+
guidance.push('- Call `taskList()` and search for a task matching this description:');
|
|
72
|
+
guidance.push(` "${unblockCondition || description}"`);
|
|
73
|
+
guidance.push('- Or ask: who owns completing this blocker?');
|
|
74
|
+
guidance.push('');
|
|
75
|
+
guidance.push('**Step 2: Check Blocking Task Status**');
|
|
76
|
+
guidance.push('- If DONE: Proceed to Step 4');
|
|
77
|
+
guidance.push('- If TODO/IN_PROGRESS: Coordinate with owner → proceed to Step 4 when complete');
|
|
78
|
+
guidance.push('- If NOT FOUND: Proceed to Step 3');
|
|
79
|
+
guidance.push('');
|
|
80
|
+
guidance.push('**Step 3: Create the Missing Blocking Task (if needed)**');
|
|
81
|
+
guidance.push('```');
|
|
82
|
+
guidance.push('taskCreate(projectPath="...", {');
|
|
83
|
+
guidance.push(' title: "Unblock ' + task.id + ': [specific outcome]",');
|
|
84
|
+
guidance.push(' status: "TODO",');
|
|
85
|
+
guidance.push(' summary: "Required to unblock ' + task.id + '"');
|
|
86
|
+
guidance.push('})');
|
|
87
|
+
guidance.push('```');
|
|
88
|
+
guidance.push('');
|
|
89
|
+
guidance.push('**Step 4: After Blocker is Resolved - Unblock This Task**');
|
|
90
|
+
guidance.push('- Verify the blocker is actually DONE (not just in progress)');
|
|
91
|
+
guidance.push('- Call `taskUpdate()` to move back to TODO:');
|
|
92
|
+
guidance.push('```');
|
|
93
|
+
guidance.push('taskUpdate(projectPath="...", taskId="' + task.id + '", {status: "TODO"})');
|
|
94
|
+
guidance.push('```');
|
|
95
|
+
guidance.push('- This removes the BLOCKED state and allows execution to continue');
|
|
96
|
+
}
|
|
97
|
+
else if (type === 'external_dependency') {
|
|
98
|
+
guidance.push('**This task is blocked by an external party/service/event.**');
|
|
99
|
+
guidance.push('');
|
|
100
|
+
guidance.push('**Step 1: Understand What is Needed**');
|
|
101
|
+
guidance.push(`- Blocking Entity: ${blockingEntity || '(not documented)'}`);
|
|
102
|
+
guidance.push(`- Description: ${description}`);
|
|
103
|
+
guidance.push('- Contact method: (verify in your knowledge base)');
|
|
104
|
+
guidance.push('');
|
|
105
|
+
guidance.push('**Step 2: Reach Out or Escalate**');
|
|
106
|
+
if (escalationPath) {
|
|
107
|
+
guidance.push(`- Use escalation path: ${escalationPath}`);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
guidance.push('- If escalation path missing: call `taskUpdate()` to add it');
|
|
111
|
+
}
|
|
112
|
+
guidance.push('- Send request specifying: what, by when, and why (link this TASK ID)');
|
|
113
|
+
guidance.push('');
|
|
114
|
+
guidance.push('**Step 3: Track External Progress**');
|
|
115
|
+
guidance.push('- Check status periodically');
|
|
116
|
+
guidance.push('- Update task with any new information via `taskUpdate()`');
|
|
117
|
+
guidance.push('- If blocked for too long: follow escalation path');
|
|
118
|
+
guidance.push('');
|
|
119
|
+
guidance.push('**Step 4: After External Delivery - Unblock This Task**');
|
|
120
|
+
guidance.push('- Verify delivery is complete and acceptable');
|
|
121
|
+
guidance.push('- Call `taskUpdate()` to move back to TODO:');
|
|
122
|
+
guidance.push('```');
|
|
123
|
+
guidance.push('taskUpdate(projectPath="...", taskId="' + task.id + '", {status: "TODO"})');
|
|
124
|
+
guidance.push('```');
|
|
125
|
+
}
|
|
126
|
+
else if (type === 'resource') {
|
|
127
|
+
guidance.push('**This task is blocked by missing resource (tools, access, personnel, budget, etc.).**');
|
|
128
|
+
guidance.push('');
|
|
129
|
+
guidance.push('**Step 1: Clarify the Missing Resource**');
|
|
130
|
+
guidance.push(`- Need: ${description}`);
|
|
131
|
+
guidance.push('- Who can allocate? (should be in escalationPath)');
|
|
132
|
+
guidance.push('');
|
|
133
|
+
guidance.push('**Step 2: Request or Allocate**');
|
|
134
|
+
if (escalationPath) {
|
|
135
|
+
guidance.push(`- Contact: ${escalationPath}`);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
guidance.push('- FIX: Call `taskUpdate()` to add escalationPath for resource owner');
|
|
139
|
+
}
|
|
140
|
+
guidance.push('- Provide justification (link this TASK ID and explain why needed)');
|
|
141
|
+
guidance.push('');
|
|
142
|
+
guidance.push('**Step 3: Wait for Approval and Setup**');
|
|
143
|
+
guidance.push('- Track approval and allocation status');
|
|
144
|
+
guidance.push('- Once allocated and available: proceed to Step 4');
|
|
145
|
+
guidance.push('');
|
|
146
|
+
guidance.push('**Step 4: After Resource Secured - Unblock This Task**');
|
|
147
|
+
guidance.push('- Confirm resource is ready to use');
|
|
148
|
+
guidance.push('- Call `taskUpdate()` to move back to TODO:');
|
|
149
|
+
guidance.push('```');
|
|
150
|
+
guidance.push('taskUpdate(projectPath="...", taskId="' + task.id + '", {status: "TODO"})');
|
|
151
|
+
guidance.push('```');
|
|
152
|
+
}
|
|
153
|
+
else if (type === 'approval') {
|
|
154
|
+
guidance.push('**This task is blocked by pending approval from decision maker.**');
|
|
155
|
+
guidance.push('');
|
|
156
|
+
guidance.push('**Step 1: Identify Approver**');
|
|
157
|
+
guidance.push(`- Approver: ${blockingEntity || '(not documented)'}`);
|
|
158
|
+
guidance.push('- Approval criteria: (ensure clear in description)');
|
|
159
|
+
guidance.push('');
|
|
160
|
+
guidance.push('**Step 2: Prepare and Submit Approval Request**');
|
|
161
|
+
guidance.push('- What are you asking approval for? (clear one-sentence request)');
|
|
162
|
+
guidance.push('- Why? (link this TASK ID and provide context)');
|
|
163
|
+
guidance.push('- By when? (deadline)');
|
|
164
|
+
if (blockingEntity) {
|
|
165
|
+
guidance.push(`- Send to: ${blockingEntity}`);
|
|
166
|
+
}
|
|
167
|
+
guidance.push('');
|
|
168
|
+
guidance.push('**Step 3: Track Approval Process**');
|
|
169
|
+
guidance.push('- Follow up if no response by deadline');
|
|
170
|
+
if (escalationPath) {
|
|
171
|
+
guidance.push(`- If denied or stalled: use escalation path: ${escalationPath}`);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
guidance.push('- If escalation needed: call `taskUpdate()` to add escalationPath');
|
|
175
|
+
}
|
|
176
|
+
guidance.push('');
|
|
177
|
+
guidance.push('**Step 4: After Approval Granted - Unblock This Task**');
|
|
178
|
+
guidance.push('- Confirm approval is in writing (link to approval evidence)');
|
|
179
|
+
guidance.push('- Call `taskUpdate()` to move back to TODO:');
|
|
180
|
+
guidance.push('```');
|
|
181
|
+
guidance.push('taskUpdate(projectPath="...", taskId="' + task.id + '", {status: "TODO"})');
|
|
182
|
+
guidance.push('```');
|
|
183
|
+
}
|
|
184
|
+
guidance.push('');
|
|
185
|
+
// ===== LAYER 3: REFERENCE INFORMATION =====
|
|
186
|
+
guidance.push('### ℹ️ REFERENCE - System-Wide Information');
|
|
187
|
+
guidance.push('');
|
|
188
|
+
guidance.push('**All Blocker Types:**');
|
|
189
|
+
guidance.push('- `internal_dependency` — Blocked by another task that must complete first');
|
|
190
|
+
guidance.push('- `external_dependency` — Blocked by external party/service/event');
|
|
191
|
+
guidance.push('- `resource` — Blocked by missing resource (tool, access, personnel, budget)');
|
|
192
|
+
guidance.push('- `approval` — Blocked by pending decision/sign-off');
|
|
193
|
+
guidance.push('');
|
|
194
|
+
guidance.push('**Unblock Verification Checklist:**');
|
|
195
|
+
guidance.push('- ✓ Blocker condition is actually met (not just "almost done")');
|
|
196
|
+
guidance.push('- ✓ Evidence is documented (link to TASK/report/email/etc)');
|
|
197
|
+
guidance.push('- ✓ Task status can be safely moved back to TODO');
|
|
198
|
+
guidance.push('');
|
|
199
|
+
guidance.push('**After Unblocking - Next Steps:**');
|
|
200
|
+
guidance.push('1. Call `taskUpdate(..., {status: "TODO"})` to unblock');
|
|
201
|
+
guidance.push('2. Call `taskContext()` to see task in unblocked state');
|
|
202
|
+
guidance.push('3. Call `taskNext()` to resume execution flow');
|
|
203
|
+
return guidance;
|
|
34
204
|
}
|
|
35
205
|
return [
|
|
36
206
|
'- This task is DONE: only reopen when new requirement changes scope.',
|
|
@@ -39,11 +209,15 @@ function taskStatusGuidance(task) {
|
|
|
39
209
|
}
|
|
40
210
|
const DEFAULT_NO_TASK_DISCOVERY_GUIDANCE = [
|
|
41
211
|
'- Recheck project state first: run projectContext and confirm there is truly no TODO/IN_PROGRESS task to execute.',
|
|
42
|
-
'-
|
|
43
|
-
'-
|
|
44
|
-
'-
|
|
45
|
-
'-
|
|
212
|
+
'- Check BLOCKED tasks: if BLOCKED tasks exist, read their blocker metadata and take unblock action before creating new tasks.',
|
|
213
|
+
' - internal_dependency: create/track the blocking task, coordinate with owner',
|
|
214
|
+
' - external_dependency: reach out to blocking entity or escalate',
|
|
215
|
+
' - resource: request/allocate the missing resource',
|
|
216
|
+
' - approval: follow escalation path to expedite approval',
|
|
217
|
+
'- Only after all BLOCKED tasks are unblocked (moved back to TODO/IN_PROGRESS), then create new tasks via `taskCreate(...)`.',
|
|
46
218
|
'- Create TODO tasks only when evidence is clear: each new task must produce at least one report/designs/readme artifact update.',
|
|
219
|
+
'- Start from active roadmap milestones and split into smallest executable slices with single done condition each.',
|
|
220
|
+
'- Prefer slices that unlock multiple downstream tasks before isolated refactors or low-impact cleanups.',
|
|
47
221
|
'- Skip duplicate scope: do not create tasks that overlap existing TODO/IN_PROGRESS/BLOCKED task intent.',
|
|
48
222
|
'- Use quality gates for discovery candidates: user value, delivery risk reduction, or measurable throughput improvement.',
|
|
49
223
|
'- Review and update project architecture docs under designs/core/ (architecture.md, style-guide.md) if they are missing or outdated.',
|
|
@@ -111,9 +285,6 @@ function normalizeTaskLink(link) {
|
|
|
111
285
|
const withoutDotPrefix = slashNormalized.replace(/^\.\//, '');
|
|
112
286
|
return withoutDotPrefix.replace(/^\/+/, '');
|
|
113
287
|
}
|
|
114
|
-
function resolveTaskLinkPath(projectPath, link) {
|
|
115
|
-
return path.join(projectPath, link);
|
|
116
|
-
}
|
|
117
288
|
function taskResearchBriefRelativePath(taskId) {
|
|
118
289
|
return `${TASK_RESEARCH_DIR}/${taskId}${TASK_RESEARCH_FILE_SUFFIX}`;
|
|
119
290
|
}
|
|
@@ -143,9 +314,8 @@ function renderTaskResearchBriefTemplate(task) {
|
|
|
143
314
|
];
|
|
144
315
|
}
|
|
145
316
|
async function inspectTaskResearchBrief(governanceDir, task) {
|
|
146
|
-
const projectPath = toProjectPath(governanceDir);
|
|
147
317
|
const relativePath = taskResearchBriefRelativePath(task.id);
|
|
148
|
-
const absolutePath =
|
|
318
|
+
const absolutePath = path.join(governanceDir, relativePath);
|
|
149
319
|
const exists = await fs.access(absolutePath).then(() => true).catch(() => false);
|
|
150
320
|
return { relativePath, absolutePath, exists, ready: exists };
|
|
151
321
|
}
|
|
@@ -153,8 +323,8 @@ function collectTaskResearchBriefLintSuggestions(state) {
|
|
|
153
323
|
if (!state.exists) {
|
|
154
324
|
return [{
|
|
155
325
|
code: TASK_LINT_CODES.RESEARCH_BRIEF_MISSING,
|
|
156
|
-
message: `Pre-execution research brief missing: ${state.relativePath}.`,
|
|
157
|
-
fixHint: 'Create the file and fill required sections before implementation.',
|
|
326
|
+
message: `Pre-execution research brief missing under governanceDir: ${state.relativePath}.`,
|
|
327
|
+
fixHint: 'Create the file under governanceDir and fill required sections before implementation.',
|
|
158
328
|
}];
|
|
159
329
|
}
|
|
160
330
|
return [];
|
|
@@ -183,14 +353,14 @@ function collectProjectContextDocsLintSuggestions(state) {
|
|
|
183
353
|
suggestions.push({
|
|
184
354
|
code: PROJECT_LINT_CODES.ARCHITECTURE_DOC_MISSING,
|
|
185
355
|
message: 'Project context is missing architecture design documentation.',
|
|
186
|
-
fixHint: `Add required file: ${CORE_ARCHITECTURE_DOC_FILE}.`,
|
|
356
|
+
fixHint: `Add required file under governanceDir: ${CORE_ARCHITECTURE_DOC_FILE}.`,
|
|
187
357
|
});
|
|
188
358
|
}
|
|
189
359
|
if (state.missingStyleDocs) {
|
|
190
360
|
suggestions.push({
|
|
191
361
|
code: PROJECT_LINT_CODES.STYLE_DOC_MISSING,
|
|
192
362
|
message: 'Project context is missing design style documentation.',
|
|
193
|
-
fixHint: `Add required file: ${CORE_STYLE_DOC_FILE}.`,
|
|
363
|
+
fixHint: `Add required file under governanceDir: ${CORE_STYLE_DOC_FILE}.`,
|
|
194
364
|
});
|
|
195
365
|
}
|
|
196
366
|
return suggestions;
|
|
@@ -935,10 +1105,10 @@ export function registerTaskTools(server) {
|
|
|
935
1105
|
? [
|
|
936
1106
|
'- Project context docs are incomplete. Complete missing project architecture/style docs before deep implementation.',
|
|
937
1107
|
...(data.projectContextDocsState.missingArchitectureDocs
|
|
938
|
-
? [`- Missing architecture design doc: create required file ${CORE_ARCHITECTURE_DOC_FILE}.`]
|
|
1108
|
+
? [`- Missing architecture design doc: create required file under governanceDir: ${CORE_ARCHITECTURE_DOC_FILE}.`]
|
|
939
1109
|
: []),
|
|
940
1110
|
...(data.projectContextDocsState.missingStyleDocs
|
|
941
|
-
? [`- Missing design style doc: create required file ${CORE_STYLE_DOC_FILE}.`]
|
|
1111
|
+
? [`- Missing design style doc: create required file under governanceDir: ${CORE_STYLE_DOC_FILE}.`]
|
|
942
1112
|
: []),
|
|
943
1113
|
]
|
|
944
1114
|
: []),
|
|
@@ -1061,11 +1231,11 @@ export function registerTaskTools(server) {
|
|
|
1061
1231
|
`- architecture docs: ${projectContextDocsState.architectureDocs.length > 0 ? 'found' : 'missing'}`,
|
|
1062
1232
|
...(projectContextDocsState.architectureDocs.length > 0
|
|
1063
1233
|
? projectContextDocsState.architectureDocs.map((item) => `- architecture: ${item}`)
|
|
1064
|
-
: [`- architecture: add required file ${CORE_ARCHITECTURE_DOC_FILE}.`]),
|
|
1234
|
+
: [`- architecture: add required file under governanceDir: ${CORE_ARCHITECTURE_DOC_FILE}.`]),
|
|
1065
1235
|
`- design style docs: ${projectContextDocsState.styleDocs.length > 0 ? 'found' : 'missing'}`,
|
|
1066
1236
|
...(projectContextDocsState.styleDocs.length > 0
|
|
1067
1237
|
? projectContextDocsState.styleDocs.map((item) => `- style: ${item}`)
|
|
1068
|
-
: [`- style: add required file ${CORE_STYLE_DOC_FILE}.`]),
|
|
1238
|
+
: [`- style: add required file under governanceDir: ${CORE_STYLE_DOC_FILE}.`]),
|
|
1069
1239
|
'',
|
|
1070
1240
|
'### Related Artifacts',
|
|
1071
1241
|
...(relatedArtifacts.length > 0 ? relatedArtifacts.map((file) => `- ${file}`) : ['- (none)']),
|
|
@@ -1082,22 +1252,22 @@ export function registerTaskTools(server) {
|
|
|
1082
1252
|
...(!researchBriefState.ready
|
|
1083
1253
|
? [
|
|
1084
1254
|
'- Pre-execution gate is NOT satisfied. Complete research brief first, then proceed with implementation.',
|
|
1085
|
-
`- Create or update ${researchBriefState.relativePath} with design guidelines + code architecture findings before code changes.`,
|
|
1255
|
+
`- Create or update ${researchBriefState.relativePath} under governanceDir with design guidelines + code architecture findings before code changes.`,
|
|
1086
1256
|
'- Include exact file/line locations in the brief (for example path/to/file.ts#L120).',
|
|
1087
1257
|
'- Re-run taskContext after writing the brief and confirm researchBriefStatus becomes READY.',
|
|
1088
1258
|
]
|
|
1089
1259
|
: [
|
|
1090
1260
|
'- Pre-execution gate satisfied. Read the research brief first, then continue implementation.',
|
|
1091
|
-
`- Must read ${researchBriefState.relativePath} before any task execution changes.`,
|
|
1261
|
+
`- Must read ${researchBriefState.relativePath} under governanceDir before any task execution changes.`,
|
|
1092
1262
|
]),
|
|
1093
1263
|
...(!projectContextDocsState.ready
|
|
1094
1264
|
? [
|
|
1095
1265
|
'- Project context docs gate is NOT satisfied. Complete missing project architecture/style docs first.',
|
|
1096
1266
|
...(projectContextDocsState.missingArchitectureDocs
|
|
1097
|
-
? [`- Missing architecture design doc. Add required file ${CORE_ARCHITECTURE_DOC_FILE} and include architecture boundaries and module responsibilities.`]
|
|
1267
|
+
? [`- Missing architecture design doc. Add required file under governanceDir: ${CORE_ARCHITECTURE_DOC_FILE} and include architecture boundaries and module responsibilities.`]
|
|
1098
1268
|
: []),
|
|
1099
1269
|
...(projectContextDocsState.missingStyleDocs
|
|
1100
|
-
? [`- Missing design style doc. Add required file ${CORE_STYLE_DOC_FILE} and include style language, tokens/themes, and UI consistency rules.`]
|
|
1270
|
+
? [`- Missing design style doc. Add required file under governanceDir: ${CORE_STYLE_DOC_FILE} and include style language, tokens/themes, and UI consistency rules.`]
|
|
1101
1271
|
: []),
|
|
1102
1272
|
'- Re-run taskContext and confirm both architectureDocsStatus/styleDocsStatus are READY.',
|
|
1103
1273
|
]
|
|
@@ -1240,7 +1410,7 @@ export function registerTaskTools(server) {
|
|
|
1240
1410
|
guidance: ({ updates, originalStatus }) => [
|
|
1241
1411
|
'Task updated successfully and tasks.md has been synced. Run `taskContext` to verify the changes.',
|
|
1242
1412
|
...(updates.status === 'IN_PROGRESS' && originalStatus === 'TODO'
|
|
1243
|
-
? ['- Ensure pre-execution research brief exists before deep implementation.']
|
|
1413
|
+
? ['- Ensure pre-execution research brief exists under governanceDir before deep implementation.']
|
|
1244
1414
|
: []),
|
|
1245
1415
|
...(updates.status === 'DONE'
|
|
1246
1416
|
? ['- Verify evidence links are attached and reflect completed work.']
|
|
@@ -313,14 +313,14 @@ describe('tasks module', () => {
|
|
|
313
313
|
await fs.rm(projectRoot, { recursive: true, force: true });
|
|
314
314
|
});
|
|
315
315
|
it('taskUpdate allows TODO -> IN_PROGRESS when research brief is ready', async () => {
|
|
316
|
-
const { projectRoot, dbPath } = await createGovernanceWorkspace();
|
|
316
|
+
const { projectRoot, governanceDir, dbPath } = await createGovernanceWorkspace();
|
|
317
317
|
await replaceRoadmapsInStore(dbPath, [
|
|
318
318
|
{ id: 'ROADMAP-0001', title: 'Bootstrap', status: 'active', updatedAt: '2026-03-14T00:00:00.000Z' },
|
|
319
319
|
]);
|
|
320
320
|
await saveTasks(dbPath, [
|
|
321
321
|
normalizeTask({ id: 'TASK-0001', title: 'gate test pass', status: 'TODO', roadmapRefs: ['ROADMAP-0001'] }),
|
|
322
322
|
]);
|
|
323
|
-
const researchDir = path.join(
|
|
323
|
+
const researchDir = path.join(governanceDir, 'designs', 'research');
|
|
324
324
|
await fs.mkdir(researchDir, { recursive: true });
|
|
325
325
|
await fs.writeFile(path.join(researchDir, 'TASK-0001.implementation-research.md'), [
|
|
326
326
|
'# TASK-0001 Implementation Research Brief',
|
|
@@ -404,8 +404,8 @@ describe('tasks module', () => {
|
|
|
404
404
|
const contextResult = await taskContext({ projectPath: projectRoot, taskId: 'TASK-0001' });
|
|
405
405
|
expect(contextResult.content[0].text).toContain('architectureDocsStatus: MISSING');
|
|
406
406
|
expect(contextResult.content[0].text).toContain('styleDocsStatus: MISSING');
|
|
407
|
-
expect(contextResult.content[0].text).toContain('add required file designs/core/architecture.md');
|
|
408
|
-
expect(contextResult.content[0].text).toContain('add required file designs/core/style-guide.md');
|
|
407
|
+
expect(contextResult.content[0].text).toContain('add required file under governanceDir: designs/core/architecture.md');
|
|
408
|
+
expect(contextResult.content[0].text).toContain('add required file under governanceDir: designs/core/style-guide.md');
|
|
409
409
|
await fs.rm(projectRoot, { recursive: true, force: true });
|
|
410
410
|
});
|
|
411
411
|
it('taskUpdate allows IN_PROGRESS -> DONE with lint guidance when conformance fails', async () => {
|
|
@@ -423,7 +423,7 @@ describe('tasks module', () => {
|
|
|
423
423
|
links: [],
|
|
424
424
|
}),
|
|
425
425
|
]);
|
|
426
|
-
const researchDir = path.join(
|
|
426
|
+
const researchDir = path.join(governanceDir, 'designs', 'research');
|
|
427
427
|
await fs.mkdir(researchDir, { recursive: true });
|
|
428
428
|
await fs.writeFile(path.join(researchDir, 'TASK-0001.implementation-research.md'), [
|
|
429
429
|
'# TASK-0001 Implementation Research Brief',
|
|
@@ -467,7 +467,7 @@ describe('tasks module', () => {
|
|
|
467
467
|
links: ['README.md'],
|
|
468
468
|
}),
|
|
469
469
|
]);
|
|
470
|
-
const researchDir = path.join(
|
|
470
|
+
const researchDir = path.join(governanceDir, 'designs', 'research');
|
|
471
471
|
await fs.mkdir(researchDir, { recursive: true });
|
|
472
472
|
await fs.writeFile(path.join(researchDir, 'TASK-0001.implementation-research.md'), [
|
|
473
473
|
'# TASK-0001 Implementation Research Brief',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@projitive/mcp",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "Projitive MCP Server for project and task discovery/update",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "",
|
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
"publishConfig": {
|
|
13
13
|
"access": "public"
|
|
14
14
|
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/yinxulai/projitive"
|
|
18
|
+
},
|
|
15
19
|
"scripts": {
|
|
16
20
|
"test": "vitest run",
|
|
17
21
|
"test:coverage": "vitest run --coverage",
|