internaltool-mcp 1.6.42 → 1.6.45
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/index.js +478 -126
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -520,73 +520,69 @@ After approval, the skill/rule is injected at every future kickoff_task.`,
|
|
|
520
520
|
// ── plan_task_from_codebase ───────────────────────────────────────────────────
|
|
521
521
|
server.tool(
|
|
522
522
|
'plan_task_from_codebase',
|
|
523
|
-
`Specialized task-creation agent —
|
|
524
|
-
|
|
525
|
-
Use this instead of create_task when you need to implement something and want the task to
|
|
526
|
-
contain a real implementation plan based on the actual codebase, not a generic description.
|
|
523
|
+
`Specialized task-creation agent — scouts the codebase, sets up the agent config, and creates a fully-planned, kickoff-ready task.
|
|
527
524
|
|
|
528
525
|
## MANDATORY protocol — follow every step in order
|
|
529
526
|
|
|
530
|
-
### Step 1 — Duplicate check
|
|
527
|
+
### Step 1 — Duplicate check
|
|
531
528
|
Call search_tasks(projectId, query="<keywords from the request>").
|
|
532
|
-
If a similar open task
|
|
529
|
+
If a similar open task exists → return it with kickoff instructions. Do NOT create a duplicate.
|
|
533
530
|
|
|
534
|
-
### Step 2 — Codebase
|
|
531
|
+
### Step 2 — Codebase scout (READ the code — do not guess)
|
|
535
532
|
Using your native Read / Grep / Glob tools:
|
|
536
533
|
|
|
537
534
|
a) **Stack detection** — read package.json / go.mod / requirements.txt / Cargo.toml.
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
535
|
+
b) **Entry point mapping** — grep for route/component/schema patterns relevant to the feature.
|
|
536
|
+
c) **Pattern extraction** — read 2–3 files similar to what you'll build. Note naming conventions, folder structure, wiring patterns.
|
|
537
|
+
d) **Impact analysis** — list every file to CREATE and every file to MODIFY.
|
|
538
|
+
e) **Dependency order** — schema → model → service → route → test → UI.
|
|
539
|
+
|
|
540
|
+
### Step 3 — Agent config analysis (use agentConfigAnalysis returned by this tool)
|
|
541
|
+
The tool returns agentConfigAnalysis showing what the project already has and what it needs.
|
|
542
|
+
Act on it BEFORE creating the task:
|
|
543
|
+
|
|
544
|
+
**a) suggest_skill for each item in agentConfigAnalysis.suggestForProject:**
|
|
545
|
+
Call suggest_skill(projectId, type, name, description, body, rationale, sourceTaskKey)
|
|
546
|
+
— one call per missing skill/rule. Use the body template from agentConfigAnalysis.suggestBodyTemplate.
|
|
547
|
+
These go to the admin approval queue and will be injected at every future kickoff.
|
|
548
|
+
|
|
549
|
+
**b) Set task-level kit via agentKitOverrides in create_task:**
|
|
550
|
+
Pass agentKitOverrides = agentConfigAnalysis.taskKitOverrides
|
|
551
|
+
— this pre-selects the right agents/skills/prompts for THIS task without touching project-level config.
|
|
552
|
+
|
|
553
|
+
**c) Use remember() for facts that cannot be expressed as a skill or rule:**
|
|
554
|
+
After create_task, call remember(taskId, key, value) for each fact in agentConfigAnalysis.rememberFacts.
|
|
555
|
+
Examples: "auth_module = server/middleware/auth.js", "test_runner = jest --runInBand"
|
|
556
|
+
|
|
557
|
+
### Step 4 — Write the implementation plan
|
|
558
|
+
Using findings from Step 2:
|
|
556
559
|
- ## Goal — one sentence
|
|
557
|
-
- ## Stack —
|
|
558
|
-
- ## Technical approach —
|
|
559
|
-
- ## Files to create
|
|
560
|
-
- ## Files to modify
|
|
561
|
-
- ## Subtasks — ordered
|
|
562
|
-
- ## Acceptance criteria
|
|
563
|
-
|
|
564
|
-
### Step 4 — Determine task metadata
|
|
565
|
-
- taskType: feature / bugfix / migration / integration / ui / backend / security / refactor
|
|
566
|
-
- priority: low / medium / high / critical (use "high" if the request sounds important)
|
|
567
|
-
- suggestedFiles: the exact file paths from your Step 2d impact analysis
|
|
560
|
+
- ## Stack — detected stack
|
|
561
|
+
- ## Technical approach — real file names and functions
|
|
562
|
+
- ## Files to create
|
|
563
|
+
- ## Files to modify
|
|
564
|
+
- ## Subtasks — ordered steps
|
|
565
|
+
- ## Acceptance criteria
|
|
568
566
|
|
|
569
567
|
### Step 5 — Create the task
|
|
570
568
|
Call create_task with:
|
|
571
|
-
- projectId (
|
|
572
|
-
-
|
|
573
|
-
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
-
|
|
584
|
-
-
|
|
585
|
-
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
Do NOT skip the codebase analysis. Do NOT create the task before reading the code.
|
|
589
|
-
Do NOT ask the developer to describe the codebase — read it yourself.`,
|
|
569
|
+
- projectId, title (verb+noun), description, readmeMarkdown, taskType, priority, column="todo"
|
|
570
|
+
- subtasks (ordered), suggestedFiles (from Step 2d)
|
|
571
|
+
- agentKitOverrides from Step 3b
|
|
572
|
+
|
|
573
|
+
### Step 6 — Post-creation setup
|
|
574
|
+
1. For each fact in agentConfigAnalysis.rememberFacts: call remember(taskId, key, value)
|
|
575
|
+
2. Log to session: call log_session_event(taskId, type="info", name="task-created", summary="Scouted codebase, configured kit, created task")
|
|
576
|
+
3. Present the approval summary to the developer and wait for submit_task_for_approval confirmation
|
|
577
|
+
|
|
578
|
+
## Quality checklist
|
|
579
|
+
- readmeMarkdown references REAL file paths
|
|
580
|
+
- subtasks are ordered (schema → model → service → route → test → UI)
|
|
581
|
+
- agentKitOverrides is set based on task type
|
|
582
|
+
- suggest_skill was called for any missing project-level skills
|
|
583
|
+
- remember() was called for codebase-specific facts
|
|
584
|
+
|
|
585
|
+
Do NOT skip the scout phase. Do NOT ask the developer to describe the codebase.`,
|
|
590
586
|
{
|
|
591
587
|
projectId: z.string().describe("InternalTool project's MongoDB ObjectId — from the project's task board URL or CLAUDE.md"),
|
|
592
588
|
request: z.string().describe('What the developer wants to build — the raw natural language request (e.g. "add rate limiting to the login endpoint", "fix the pagination bug on users list")'),
|
|
@@ -604,8 +600,9 @@ Do NOT ask the developer to describe the codebase — read it yourself.`,
|
|
|
604
600
|
const dirTree = getDirTree(cwd, 2)
|
|
605
601
|
const entryPoints = findEntryPoints(cwd, stack)
|
|
606
602
|
|
|
607
|
-
// ── 2. Fetch project context
|
|
603
|
+
// ── 2. Fetch project context + existing agentConfig ─────────────────────
|
|
608
604
|
let projectContext = null
|
|
605
|
+
let existingAgentConfig = { skills: [], rules: [], subagents: [], prompts: [] }
|
|
609
606
|
try {
|
|
610
607
|
const projRes = await api.get(`/api/projects/${projectId}`)
|
|
611
608
|
if (projRes?.success) {
|
|
@@ -615,6 +612,13 @@ Do NOT ask the developer to describe the codebase — read it yourself.`,
|
|
|
615
612
|
taskCount: (projRes.data.tasks || []).length,
|
|
616
613
|
githubRepo: p.github?.repoUrl || null,
|
|
617
614
|
}
|
|
615
|
+
const cfg = p.agentConfig || {}
|
|
616
|
+
existingAgentConfig = {
|
|
617
|
+
skills: (cfg.skills || []).map(s => s.name),
|
|
618
|
+
rules: (cfg.rules || []).map(r => r.name),
|
|
619
|
+
subagents: (cfg.subagents || []).map(a => a.name),
|
|
620
|
+
prompts: (cfg.prompts || []).map(p => p.name),
|
|
621
|
+
}
|
|
618
622
|
}
|
|
619
623
|
} catch { /* non-fatal */ }
|
|
620
624
|
|
|
@@ -632,7 +636,102 @@ Do NOT ask the developer to describe the codebase — read it yourself.`,
|
|
|
632
636
|
}
|
|
633
637
|
} catch { /* non-fatal */ }
|
|
634
638
|
|
|
635
|
-
// ── 4.
|
|
639
|
+
// ── 4. Agent config gap analysis ─────────────────────────────────────────
|
|
640
|
+
// Determine what kit the task needs based on stack + request keywords
|
|
641
|
+
const requestCorpus = `${request} ${stack.language || ''} ${stack.framework || ''} ${(stack.extra || []).join(' ')}`.toLowerCase()
|
|
642
|
+
|
|
643
|
+
// Map task type keywords → what kit category is relevant
|
|
644
|
+
const KIT_CATEGORY_HINTS = {
|
|
645
|
+
security: ['auth', 'security', 'jwt', 'password', 'permission', 'rbac', 'owasp', 'token', 'encrypt', 'rate limit', 'sanitize'],
|
|
646
|
+
scout: ['explore', 'understand', 'architecture', 'trace', 'how does', 'what is', 'map', 'audit', 'recon', 'investigate'],
|
|
647
|
+
merge: ['merge', 'rebase', 'conflict', 'ship', 'push', 'pr', 'branch'],
|
|
648
|
+
review: ['review', 'code review', 'diff', 'approve', 'feedback', 'pull request'],
|
|
649
|
+
coordinator: ['large', 'complex', 'parallel', 'coordinate', 'orchestrate', 'decompose', 'multi-step'],
|
|
650
|
+
builder: ['build', 'implement', 'create', 'add', 'fix', 'feature', 'endpoint', 'route', 'component', 'model', 'service', 'crud'],
|
|
651
|
+
}
|
|
652
|
+
let detectedKitCategory = 'builder'
|
|
653
|
+
let maxKitScore = 0
|
|
654
|
+
for (const [cat, keywords] of Object.entries(KIT_CATEGORY_HINTS)) {
|
|
655
|
+
const score = keywords.filter(kw => requestCorpus.includes(kw)).length
|
|
656
|
+
if (score > maxKitScore) { maxKitScore = score; detectedKitCategory = cat }
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Kit catalog — what skills/agents/prompts exist in ZopKit
|
|
660
|
+
const ZOPKIT_SKILLS_CATALOG = ['zop-blueprint', 'zop-recon', 'zop-guard-skill', 'zop-merge-skill', 'zop-review-skill', 'session-start']
|
|
661
|
+
const ZOPKIT_AGENTS_CATALOG = ['zop-shipper', 'zop-scout', 'zop-guard', 'zop-merger', 'zop-reviewer', 'zop-orchestrator']
|
|
662
|
+
const ZOPKIT_PROMPTS_CATALOG = ['ship-feature', 'codebase-recon', 'security-sweep', 'merge-guide', 'review-pr']
|
|
663
|
+
|
|
664
|
+
// Recommended kit for detected category
|
|
665
|
+
const KIT_RECOMMENDATIONS = {
|
|
666
|
+
builder: { skills: ['zop-blueprint', 'session-start'], agents: ['zop-shipper'], prompts: ['ship-feature'] },
|
|
667
|
+
scout: { skills: ['zop-recon', 'session-start'], agents: ['zop-scout'], prompts: ['codebase-recon'] },
|
|
668
|
+
security: { skills: ['zop-guard-skill', 'session-start'], agents: ['zop-guard'], prompts: ['security-sweep'] },
|
|
669
|
+
merge: { skills: ['zop-merge-skill', 'session-start'], agents: ['zop-merger'], prompts: ['merge-guide'] },
|
|
670
|
+
review: { skills: ['zop-review-skill', 'session-start'], agents: ['zop-reviewer'], prompts: ['review-pr'] },
|
|
671
|
+
coordinator: { skills: ['zop-blueprint', 'zop-recon', 'session-start'], agents: ['zop-orchestrator'], prompts: ['ship-feature'] },
|
|
672
|
+
}
|
|
673
|
+
const recommendedKit = KIT_RECOMMENDATIONS[detectedKitCategory] || KIT_RECOMMENDATIONS.builder
|
|
674
|
+
|
|
675
|
+
// What's already in the project vs what's missing
|
|
676
|
+
const missingSkills = recommendedKit.skills.filter(s => !existingAgentConfig.skills.includes(s))
|
|
677
|
+
const missingAgents = recommendedKit.agents.filter(a => !existingAgentConfig.subagents.includes(a))
|
|
678
|
+
const missingPrompts = recommendedKit.prompts.filter(p => !existingAgentConfig.prompts.includes(p))
|
|
679
|
+
|
|
680
|
+
// Task-level overrides: enable recommended, disable irrelevant
|
|
681
|
+
const taskKitOverrides = {
|
|
682
|
+
skills: Object.fromEntries([
|
|
683
|
+
...existingAgentConfig.skills.map(s => [s, recommendedKit.skills.includes(s)]),
|
|
684
|
+
...recommendedKit.skills.filter(s => !existingAgentConfig.skills.includes(s)).map(s => [s, false]), // not in project yet — pending suggest_skill
|
|
685
|
+
]),
|
|
686
|
+
subagents: Object.fromEntries(existingAgentConfig.subagents.map(a => [a, recommendedKit.agents.includes(a)])),
|
|
687
|
+
prompts: Object.fromEntries(existingAgentConfig.prompts.map(p => [p, recommendedKit.prompts.includes(p)])),
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Stack-derived codebase facts that should be persisted via remember()
|
|
691
|
+
const rememberFacts = [
|
|
692
|
+
stack.language && { key: 'stack_language', value: stack.language },
|
|
693
|
+
stack.framework && { key: 'stack_framework', value: stack.framework },
|
|
694
|
+
stack.testRunner && { key: 'stack_test_runner', value: stack.testRunner },
|
|
695
|
+
stack.packageManager && { key: 'stack_pkg_manager', value: stack.packageManager },
|
|
696
|
+
entryPoints.routes?.[0] && { key: 'entry_routes', value: entryPoints.routes[0] },
|
|
697
|
+
entryPoints.models?.[0] && { key: 'entry_models', value: entryPoints.models[0] },
|
|
698
|
+
entryPoints.components?.[0] && { key: 'entry_components', value: entryPoints.components[0] },
|
|
699
|
+
].filter(Boolean)
|
|
700
|
+
|
|
701
|
+
// Skill suggestion templates for each missing item
|
|
702
|
+
const SKILL_BODY_TEMPLATES = {
|
|
703
|
+
'zop-blueprint': `## Feature Blueprint\n1. Define the data model / schema changes\n2. Build the API layer (route → service → model)\n3. Build the frontend (component → hook → API call)\n4. Wire everything together\n5. Write tests (unit + integration)\n6. Update README / docs`,
|
|
704
|
+
'zop-recon': `## Codebase Recon\n1. Read entry points: routes, models, middleware, utils\n2. Map the data flow end-to-end\n3. Identify patterns: naming conventions, error handling, auth\n4. Find gaps: missing tests, dead code, undocumented APIs\n5. Write findings to scout_task`,
|
|
705
|
+
'zop-guard-skill': `## Security Audit\n1. Check authentication (JWT expiry, refresh, revocation)\n2. Check authorization (role checks on every sensitive route)\n3. Check input validation (Zod/Joi on all user inputs)\n4. Check for injection (SQL, NoSQL, command)\n5. Check error messages (no stack traces exposed)\n6. Run OWASP Top 10 checklist`,
|
|
706
|
+
'zop-merge-skill': `## Merge & Ship\n1. git fetch origin && git rebase origin/main\n2. Resolve any conflicts using resolve_conflict tool\n3. Run full test suite — must be green\n4. Push with --force-with-lease\n5. Verify PR CI passes\n6. Request final review`,
|
|
707
|
+
'zop-review-skill': `## Code Review\n1. Read the task README to understand intent\n2. Review each changed file: correctness, edge cases, security\n3. Check test coverage: are new paths tested?\n4. Check for breaking changes\n5. Post review with specific file+line comments\n6. Approve or request changes`,
|
|
708
|
+
'session-start': `## Session Start\n1. Call recall(taskId) to restore memory from previous sessions\n2. Call get_agent_context(taskId) for the full implementation plan\n3. Check local git state: \`git status\`\n4. Check branch: \`git branch --show-current\`\n5. Read any changed files since last session\n6. Resume work from where you left off`,
|
|
709
|
+
}
|
|
710
|
+
const AGENT_BODY_TEMPLATES = {
|
|
711
|
+
'zop-shipper': `You are a builder agent. Your job is to implement code end-to-end.\nConstraints:\n- Only modify files listed in claimedFiles\n- Every change needs a test\n- Run tests before marking done\n- Never modify auth middleware without explicit approval`,
|
|
712
|
+
'zop-scout': `You are a scout agent. Your job is to map the codebase — READ ONLY.\nConstraints:\n- Never write or modify any file\n- Read every source file systematically\n- Write findings via scout_task tool\n- Focus on: data flow, auth, API surface, test coverage, gaps`,
|
|
713
|
+
'zop-guard': `You are a security audit agent.\nConstraints:\n- Check auth, authorization, input validation, and OWASP Top 10\n- Report findings as structured issues, not inline comments\n- Never modify security-critical code without explicit admin approval`,
|
|
714
|
+
'zop-orchestrator': `You are a coordinator agent. Your job is to decompose and delegate — NO CODE.\nConstraints:\n- Call decompose_task to break work into parallel subtasks\n- Call get_parallel_kickoffs to generate builder prompts\n- Never implement anything yourself\n- Each subtask must have exclusive file ownership`,
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const suggestForProject = [
|
|
718
|
+
...missingSkills.map(name => ({
|
|
719
|
+
type: 'skill',
|
|
720
|
+
name,
|
|
721
|
+
description: `${name} — ${ZOPKIT_SKILLS_CATALOG.includes(name) ? 'ZopKit standard skill' : 'custom skill'}`,
|
|
722
|
+
body: SKILL_BODY_TEMPLATES[name] || `## ${name}\n[Add instructions here]`,
|
|
723
|
+
rationale: `Detected task type: ${detectedKitCategory}. This skill guides the builder through the ${name.replace('zop-', '').replace('-skill', '')} process in this codebase.`,
|
|
724
|
+
})),
|
|
725
|
+
...missingAgents.map(name => ({
|
|
726
|
+
type: 'subagent',
|
|
727
|
+
name,
|
|
728
|
+
description: `${name} — ${ZOPKIT_AGENTS_CATALOG.includes(name) ? 'ZopKit standard agent' : 'custom agent'}`,
|
|
729
|
+
body: AGENT_BODY_TEMPLATES[name] || `## ${name} Agent\n[Add role instructions here]`,
|
|
730
|
+
rationale: `Detected task type: ${detectedKitCategory}. This agent persona enforces the right constraints for ${name.replace('zop-', '')} sessions.`,
|
|
731
|
+
})),
|
|
732
|
+
]
|
|
733
|
+
|
|
734
|
+
// ── 5. Return codebase intelligence + tight instructions ──────────────────
|
|
636
735
|
const hasEntryPoints = Object.keys(entryPoints).length > 0
|
|
637
736
|
const stackSummary = [
|
|
638
737
|
stack.language, stack.framework, stack.testRunner, ...(stack.extra || [])
|
|
@@ -649,6 +748,26 @@ Do NOT ask the developer to describe the codebase — read it yourself.`,
|
|
|
649
748
|
: { note: 'No entry points found at cwd — check that MCP server is running from the project root' },
|
|
650
749
|
},
|
|
651
750
|
|
|
751
|
+
// Agent config analysis — act on this BEFORE calling create_task
|
|
752
|
+
agentConfigAnalysis: {
|
|
753
|
+
detectedKitCategory,
|
|
754
|
+
recommendedKit,
|
|
755
|
+
existingProjectConfig: existingAgentConfig,
|
|
756
|
+
missingFromProject: { skills: missingSkills, agents: missingAgents, prompts: missingPrompts },
|
|
757
|
+
suggestForProject, // call suggest_skill for each item here
|
|
758
|
+
taskKitOverrides, // pass as agentKitOverrides to create_task
|
|
759
|
+
rememberFacts, // call remember(taskId, key, value) for each after create_task
|
|
760
|
+
instruction: [
|
|
761
|
+
suggestForProject.length > 0
|
|
762
|
+
? `⚠️ ${suggestForProject.length} item(s) are missing from project config — call suggest_skill for each before creating the task.`
|
|
763
|
+
: `✅ Project config already has the recommended kit for ${detectedKitCategory} tasks.`,
|
|
764
|
+
`Pass taskKitOverrides as agentKitOverrides in create_task to pre-configure this task.`,
|
|
765
|
+
rememberFacts.length > 0
|
|
766
|
+
? `After create_task, call remember(taskId, key, value) for ${rememberFacts.length} codebase fact(s) so future sessions don't need to re-scout.`
|
|
767
|
+
: null,
|
|
768
|
+
].filter(Boolean).join('\n'),
|
|
769
|
+
},
|
|
770
|
+
|
|
652
771
|
// Duplicate guard
|
|
653
772
|
...(similarTasks?.length > 0 && {
|
|
654
773
|
duplicateWarning: true,
|
|
@@ -656,16 +775,21 @@ Do NOT ask the developer to describe the codebase — read it yourself.`,
|
|
|
656
775
|
duplicateInstruction: 'Check the list above. If one matches → call kickoff_task on it. If none match → proceed.',
|
|
657
776
|
}),
|
|
658
777
|
|
|
659
|
-
// What to do now
|
|
778
|
+
// What to do now
|
|
660
779
|
nextSteps: [
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
'
|
|
666
|
-
|
|
780
|
+
suggestForProject.length > 0
|
|
781
|
+
? `FIRST: call suggest_skill for each item in agentConfigAnalysis.suggestForProject (${suggestForProject.length} item(s)). Use the body and rationale from the array.`
|
|
782
|
+
: null,
|
|
783
|
+
'Read 2-3 files from entryPoints.routes / entryPoints.models / entryPoints.components to extract naming conventions.',
|
|
784
|
+
'Draft readmeMarkdown: ## Goal, ## Stack, ## Technical approach, ## Files to create, ## Files to modify, ## Subtasks, ## Acceptance criteria.',
|
|
785
|
+
`Call create_task(projectId="${projectId}", title="<verb+noun>", readmeMarkdown="<plan>", taskType="<type>", priority="${priority}", column="todo", subtasks=[...], suggestedFiles=[...], agentKitOverrides=agentConfigAnalysis.taskKitOverrides)`,
|
|
786
|
+
'After create_task: call remember(taskId, key, value) for each fact in agentConfigAnalysis.rememberFacts.',
|
|
787
|
+
'Then: call log_session_event(taskId, type="info", name="task-created", summary="Codebase scouted, kit configured, task created").',
|
|
788
|
+
'Then: call kickoff_task(taskId, confirmed=false) to preview the plan and check git state.',
|
|
789
|
+
'Then: call submit_task_for_approval(taskId, summary="<3-8 sentence summary>", reviewerId="<admin id>").',
|
|
790
|
+
'After approval: call kickoff_task(taskId, confirmed=true, agentRole="builder", files=[...suggestedFiles]).',
|
|
791
|
+
].filter(Boolean),
|
|
667
792
|
|
|
668
|
-
// Context
|
|
669
793
|
request,
|
|
670
794
|
projectId,
|
|
671
795
|
priority,
|
|
@@ -715,8 +839,13 @@ Always prefer column="todo" so the task is visibly ready to start.`,
|
|
|
715
839
|
.describe('Ordered implementation checklist — shown in the task UI and read by agents at kickoff. Order matters: schema → model → service → route → test → frontend.'),
|
|
716
840
|
suggestedFiles: z.array(z.string()).optional()
|
|
717
841
|
.describe('Files the builder will claim at kickoff (e.g. ["server/routes/tasks.js", "server/models/Task.js"]). Included in the task README automatically so the builder knows what to pass to kickoff_task files=[...].'),
|
|
842
|
+
agentKitOverrides: z.object({
|
|
843
|
+
skills: z.record(z.boolean()).optional(),
|
|
844
|
+
subagents: z.record(z.boolean()).optional(),
|
|
845
|
+
prompts: z.record(z.boolean()).optional(),
|
|
846
|
+
}).optional().describe('Task-level skill/agent/prompt overrides — pass agentConfigAnalysis.taskKitOverrides from plan_task_from_codebase to pre-configure the right kit for this task.'),
|
|
718
847
|
},
|
|
719
|
-
async ({ projectId, suggestedFiles, ...taskData }) => {
|
|
848
|
+
async ({ projectId, suggestedFiles, agentKitOverrides, ...taskData }) => {
|
|
720
849
|
try { assertProjectScope(projectId) } catch (e) { return errorText(e.message) }
|
|
721
850
|
|
|
722
851
|
// Append suggested files section to the README so the builder sees them at kickoff
|
|
@@ -736,18 +865,37 @@ Always prefer column="todo" so the task is visibly ready to start.`,
|
|
|
736
865
|
if (!res?.success) return errorText(res?.message || 'Failed to create task')
|
|
737
866
|
|
|
738
867
|
const task = res.data?.task
|
|
868
|
+
|
|
869
|
+
// Apply agentKitOverrides immediately if provided
|
|
870
|
+
let kitApplied = null
|
|
871
|
+
if (task?._id && agentKitOverrides && Object.keys(agentKitOverrides).length > 0) {
|
|
872
|
+
try {
|
|
873
|
+
const kitRes = await api.patch(`/api/tasks/${task._id}`, { agentKitOverrides })
|
|
874
|
+
kitApplied = kitRes?.success
|
|
875
|
+
? { applied: true, overrides: agentKitOverrides }
|
|
876
|
+
: { applied: false, reason: kitRes?.message }
|
|
877
|
+
} catch { kitApplied = { applied: false, reason: 'patch failed' } }
|
|
878
|
+
}
|
|
879
|
+
|
|
739
880
|
return text({
|
|
740
|
-
created:
|
|
741
|
-
taskId:
|
|
742
|
-
taskKey:
|
|
743
|
-
title:
|
|
744
|
-
column:
|
|
745
|
-
taskType:
|
|
746
|
-
subtasks:
|
|
747
|
-
|
|
748
|
-
|
|
881
|
+
created: true,
|
|
882
|
+
taskId: task?._id,
|
|
883
|
+
taskKey: task?.key,
|
|
884
|
+
title: task?.title,
|
|
885
|
+
column: task?.column,
|
|
886
|
+
taskType: task?.taskType || null,
|
|
887
|
+
subtasks: (task?.subtasks || []).length,
|
|
888
|
+
kitApplied,
|
|
889
|
+
nextStep: task?._id
|
|
890
|
+
? [
|
|
891
|
+
`1. Call remember(taskId="${task._id}", key, value) for each fact in agentConfigAnalysis.rememberFacts.`,
|
|
892
|
+
`2. Call log_session_event(taskId="${task._id}", type="info", name="task-created", summary="Codebase scouted, kit configured").`,
|
|
893
|
+
`3. Call kickoff_task(taskId="${task._id}", confirmed=false) — read the plan and check local git state.`,
|
|
894
|
+
`4. Call submit_task_for_approval(taskId="${task._id}", summary="<3-8 sentences>", reviewerId="<admin id>").`,
|
|
895
|
+
`5. After approval: call kickoff_task(taskId="${task._id}", confirmed=true, agentRole="builder", files=[...]).`,
|
|
896
|
+
].join('\n')
|
|
749
897
|
: null,
|
|
750
|
-
message: `Task ${task?.key} created
|
|
898
|
+
message: `Task ${task?.key} created${kitApplied?.applied ? ' with kit overrides applied' : ''}. Follow nextStep — do NOT branch until the plan is approved.`,
|
|
751
899
|
})
|
|
752
900
|
}
|
|
753
901
|
)
|
|
@@ -1912,16 +2060,45 @@ Returns systemPrompt ready to use as a Claude system prompt.`,
|
|
|
1912
2060
|
} catch { /* non-fatal */ }
|
|
1913
2061
|
}
|
|
1914
2062
|
|
|
1915
|
-
//
|
|
1916
|
-
//
|
|
2063
|
+
// Filter skills to only those relevant to this role and task type.
|
|
2064
|
+
// An agent should NOT load every project skill — only what it needs for this specific task.
|
|
1917
2065
|
const allProjectSkills = ctx.project?.agentConfig?.skills || []
|
|
1918
2066
|
const taskSkillOverrides = ctx.task?.agentKitOverrides?.skills || {}
|
|
1919
|
-
const
|
|
2067
|
+
const enabledSkills = allProjectSkills.filter(s => {
|
|
1920
2068
|
const taskOverride = taskSkillOverrides[s.name]
|
|
1921
2069
|
return taskOverride !== undefined ? taskOverride : s.enabled !== false
|
|
1922
2070
|
})
|
|
1923
|
-
|
|
1924
|
-
|
|
2071
|
+
|
|
2072
|
+
// Role-based relevance filter: each role only gets skills that match its job
|
|
2073
|
+
const ROLE_SKILL_KEYWORDS = {
|
|
2074
|
+
builder: ['test', 'run', 'build', 'lint', 'commit', 'style', 'deploy', 'migration', 'seed'],
|
|
2075
|
+
scout: ['scout', 'codebase', 'explore', 'map', 'analyze', 'read'],
|
|
2076
|
+
reviewer: ['review', 'security', 'audit', 'test', 'quality'],
|
|
2077
|
+
coordinator: ['decompose', 'plan', 'coordinate', 'parallel'],
|
|
2078
|
+
}
|
|
2079
|
+
const roleKeywords = effectiveRole ? (ROLE_SKILL_KEYWORDS[effectiveRole] || []) : []
|
|
2080
|
+
|
|
2081
|
+
// Also use the task type's suggestedSkills list for relevance
|
|
2082
|
+
const taskTypeSkills = (() => {
|
|
2083
|
+
try {
|
|
2084
|
+
const taskType = ctx.task?.taskType || 'feature'
|
|
2085
|
+
const cfg = TASK_TYPES?.[taskType]
|
|
2086
|
+
return cfg?.suggestedSkills || []
|
|
2087
|
+
} catch { return [] }
|
|
2088
|
+
})()
|
|
2089
|
+
|
|
2090
|
+
const relevantSkills = enabledSkills.filter(s => {
|
|
2091
|
+
// Always include explicitly overridden-true skills
|
|
2092
|
+
if (taskSkillOverrides[s.name] === true) return true
|
|
2093
|
+
// Include if skill name matches task type's suggested list
|
|
2094
|
+
if (taskTypeSkills.includes(s.name)) return true
|
|
2095
|
+
// Include if skill name/description matches role keywords
|
|
2096
|
+
const haystack = `${s.name} ${s.description || ''}`.toLowerCase()
|
|
2097
|
+
return roleKeywords.some(kw => haystack.includes(kw))
|
|
2098
|
+
})
|
|
2099
|
+
|
|
2100
|
+
const suggestedSkills = relevantSkills.length > 0
|
|
2101
|
+
? relevantSkills.map(s => ({
|
|
1925
2102
|
name: s.name,
|
|
1926
2103
|
description: s.description,
|
|
1927
2104
|
path: `.cursor/skills/${s.name}.md`,
|
|
@@ -1943,7 +2120,12 @@ Returns systemPrompt ready to use as a Claude system prompt.`,
|
|
|
1943
2120
|
suggestedSkills,
|
|
1944
2121
|
task: ctx.task,
|
|
1945
2122
|
project: ctx.project,
|
|
1946
|
-
usage:
|
|
2123
|
+
usage: [
|
|
2124
|
+
'Use systemPrompt as your Claude system prompt.',
|
|
2125
|
+
'If allowedTools is set, restrict your MCP tool calls to that list.',
|
|
2126
|
+
'Read ONLY the skills listed in suggestedSkills — they are filtered for your role and task type.',
|
|
2127
|
+
'After the task is done (park_task or raise_pr), the skill files are automatically deleted from .cursor/skills/.',
|
|
2128
|
+
].join(' '),
|
|
1947
2129
|
})
|
|
1948
2130
|
}
|
|
1949
2131
|
)
|
|
@@ -3669,6 +3851,43 @@ Use this when a developer says "start task", "brief me on", or "what do I need t
|
|
|
3669
3851
|
const subtasksDone = subtasks.filter(s => s.done).length
|
|
3670
3852
|
const subtasksTotal = subtasks.length
|
|
3671
3853
|
|
|
3854
|
+
// ── Gather local git state (used in both preview and confirmed path) ────────
|
|
3855
|
+
let localGitState = null
|
|
3856
|
+
{
|
|
3857
|
+
const pCwd = repoPath || process.cwd()
|
|
3858
|
+
const pRoot = findRepoRoot(pCwd)
|
|
3859
|
+
if (pRoot) {
|
|
3860
|
+
try {
|
|
3861
|
+
const porcelain = runGit('status --porcelain=v1', pRoot)
|
|
3862
|
+
const { localState, modified, staged, unstaged, untracked } = parseGitStatus(porcelain)
|
|
3863
|
+
const currentBranch = runGit('branch --show-current', pRoot).trim()
|
|
3864
|
+
|
|
3865
|
+
// Detect default branch (main / master)
|
|
3866
|
+
let defaultBranch = 'main'
|
|
3867
|
+
try {
|
|
3868
|
+
defaultBranch = runGit('symbolic-ref refs/remotes/origin/HEAD', pRoot).trim().replace('refs/remotes/origin/', '')
|
|
3869
|
+
} catch { /* no remote HEAD — stay with 'main' */ }
|
|
3870
|
+
|
|
3871
|
+
// How far behind is the current branch from origin/<default>?
|
|
3872
|
+
let mainBehindBy = 0
|
|
3873
|
+
try {
|
|
3874
|
+
runGit('fetch origin --quiet', pRoot)
|
|
3875
|
+
mainBehindBy = parseInt(runGit(`rev-list HEAD..origin/${defaultBranch} --count`, pRoot).trim(), 10) || 0
|
|
3876
|
+
} catch { /* no remote or network — non-fatal */ }
|
|
3877
|
+
|
|
3878
|
+
localGitState = {
|
|
3879
|
+
repoRoot: pRoot,
|
|
3880
|
+
currentBranch,
|
|
3881
|
+
defaultBranch,
|
|
3882
|
+
localState, // 'clean' | 'modified' | 'untracked'
|
|
3883
|
+
modifiedFiles: modified,
|
|
3884
|
+
untrackedFiles: untracked,
|
|
3885
|
+
mainBehindBy,
|
|
3886
|
+
}
|
|
3887
|
+
} catch { /* git not available or not a git repo — non-fatal */ }
|
|
3888
|
+
}
|
|
3889
|
+
}
|
|
3890
|
+
|
|
3672
3891
|
// ── Preview: show the full plan before touching anything ──
|
|
3673
3892
|
const pendingApv = (task.approvals || []).find(a => a.state === 'pending') || null
|
|
3674
3893
|
const hasApprovedApv = (task.approvals || []).some(a => a.state === 'approved')
|
|
@@ -3887,10 +4106,24 @@ Use this when a developer says "start task", "brief me on", or "what do I need t
|
|
|
3887
4106
|
claimedFiles: task.claimedFiles || [],
|
|
3888
4107
|
agentRole: task.agentRole || null,
|
|
3889
4108
|
},
|
|
4109
|
+
localGitState: localGitState ? {
|
|
4110
|
+
currentBranch: localGitState.currentBranch,
|
|
4111
|
+
status: localGitState.localState,
|
|
4112
|
+
modifiedFiles: localGitState.modifiedFiles,
|
|
4113
|
+
untrackedFiles: localGitState.untrackedFiles,
|
|
4114
|
+
mainBehindBy: localGitState.mainBehindBy,
|
|
4115
|
+
...(localGitState.localState === 'modified' && {
|
|
4116
|
+
warning: `⚠️ You have ${localGitState.modifiedFiles.length} uncommitted change(s) on "${localGitState.currentBranch}". Commit or stash before starting a new task to avoid cross-task pollution.`,
|
|
4117
|
+
suggestedFix: `git stash push -m "wip: before starting ${task.key}"`,
|
|
4118
|
+
}),
|
|
4119
|
+
...(localGitState.mainBehindBy > 0 && {
|
|
4120
|
+
mainWarning: `⚠️ Your workspace is ${localGitState.mainBehindBy} commit(s) behind origin/${localGitState.defaultBranch}. Run: git pull origin ${localGitState.defaultBranch} before creating a branch.`,
|
|
4121
|
+
}),
|
|
4122
|
+
} : null,
|
|
3890
4123
|
requiresConfirmation: true,
|
|
3891
4124
|
message: approvalBlocks
|
|
3892
4125
|
? `Read the plan above, then follow workflowRoadmap — approval is required before you can branch and start coding.`
|
|
3893
|
-
: `Read the implementation plan above carefully, then call kickoff_task again with confirmed=true.`,
|
|
4126
|
+
: `Read the implementation plan above carefully, resolve any localGitState warnings, then call kickoff_task again with confirmed=true.`,
|
|
3894
4127
|
})
|
|
3895
4128
|
}
|
|
3896
4129
|
|
|
@@ -3948,28 +4181,39 @@ Use this when a developer says "start task", "brief me on", or "what do I need t
|
|
|
3948
4181
|
}
|
|
3949
4182
|
|
|
3950
4183
|
// ── #1 Preflight: check dirty tree before writing cursor rules / moving task ──
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
const
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
4184
|
+
// Use the git state we already gathered above; re-check if not available.
|
|
4185
|
+
if (localGitState?.localState === 'modified') {
|
|
4186
|
+
const { currentBranch, modifiedFiles } = localGitState
|
|
4187
|
+
const taskBranch = task.github?.headBranch
|
|
4188
|
+
|
|
4189
|
+
if (taskBranch && currentBranch !== taskBranch) {
|
|
4190
|
+
// On the wrong branch — stash and switch, or cancel
|
|
4191
|
+
return text({
|
|
4192
|
+
blocked: true,
|
|
4193
|
+
reason: `You are on "${currentBranch}" with ${modifiedFiles.length} uncommitted change(s), but this task's branch is "${taskBranch}".`,
|
|
4194
|
+
modifiedFiles,
|
|
4195
|
+
choices: {
|
|
4196
|
+
stash: `git stash push -m "wip: before kickoff ${task.key} on ${currentBranch}"`,
|
|
4197
|
+
autoSwitch: `git stash && git checkout ${taskBranch} && git stash pop`,
|
|
4198
|
+
cancel: 'Review your changes first, then retry kickoff_task',
|
|
4199
|
+
},
|
|
4200
|
+
message: '⛔ Resolve the working tree before kicking off this task to avoid cross-task pollution.',
|
|
4201
|
+
})
|
|
4202
|
+
}
|
|
4203
|
+
|
|
4204
|
+
if (!taskBranch) {
|
|
4205
|
+
// First-time kickoff — uncommitted changes risk bleeding into the new branch
|
|
4206
|
+
return text({
|
|
4207
|
+
blocked: true,
|
|
4208
|
+
reason: `You have ${modifiedFiles.length} uncommitted change(s) on "${currentBranch}". Starting a new task from a dirty tree would carry these changes into the new branch.`,
|
|
4209
|
+
modifiedFiles,
|
|
4210
|
+
choices: {
|
|
4211
|
+
stash: `git stash push -m "wip: before starting ${task.key}"`,
|
|
4212
|
+
commit: 'git add -p && git commit -m "wip: save changes before new task"',
|
|
4213
|
+
cancel: 'Review your changes, then retry kickoff_task',
|
|
4214
|
+
},
|
|
4215
|
+
message: '⛔ Commit or stash your changes before creating a new task branch.',
|
|
4216
|
+
})
|
|
3973
4217
|
}
|
|
3974
4218
|
}
|
|
3975
4219
|
|
|
@@ -4145,6 +4389,26 @@ After \`request_human_input\`: STOP, show the question in chat, wait for reply,
|
|
|
4145
4389
|
}
|
|
4146
4390
|
api.patch(`/api/tasks/${taskId}`, workspacePatch).catch(() => {/* non-fatal */})
|
|
4147
4391
|
|
|
4392
|
+
// Auto-log every skill / rule / agent written to the session timeline
|
|
4393
|
+
// so the Session tab always shows what got injected at kickoff.
|
|
4394
|
+
if (workspaceResult?.written?.length) {
|
|
4395
|
+
const repoRoot = workspaceResult.repoRoot || ''
|
|
4396
|
+
for (const filePath of workspaceResult.written) {
|
|
4397
|
+
const rel = filePath.replace(repoRoot, '').replace(/^\//, '')
|
|
4398
|
+
const name = rel.replace(/^\.cursor\/(skills|rules|agents|commands)\//, '').replace(/\.(md|mdc)$/, '')
|
|
4399
|
+
const type = rel.includes('/skills/') ? 'skill'
|
|
4400
|
+
: rel.includes('/rules/') ? 'rule'
|
|
4401
|
+
: rel.includes('/agents/') ? 'subagent'
|
|
4402
|
+
: 'info'
|
|
4403
|
+
api.post(`/api/tasks/${taskId}/session/log`, {
|
|
4404
|
+
type,
|
|
4405
|
+
name,
|
|
4406
|
+
role: agentRole || null,
|
|
4407
|
+
summary: `${type === 'skill' ? '📖' : type === 'rule' ? '📏' : type === 'subagent' ? '🤖' : 'ℹ️'} Injected at kickoff: ${rel}`,
|
|
4408
|
+
}).catch(() => {/* non-fatal */})
|
|
4409
|
+
}
|
|
4410
|
+
}
|
|
4411
|
+
|
|
4148
4412
|
// Write .internaltool-active-task so the Claude Code hook knows a task is active
|
|
4149
4413
|
try {
|
|
4150
4414
|
writeFileSync(
|
|
@@ -4399,18 +4663,40 @@ How to determine status:
|
|
|
4399
4663
|
|
|
4400
4664
|
server.tool(
|
|
4401
4665
|
'submit_task_for_approval',
|
|
4402
|
-
|
|
4666
|
+
`Create and immediately submit an approval request so a human reviewer can approve the plan before any branch or code is created.
|
|
4667
|
+
|
|
4668
|
+
This is a single atomic operation — it creates the approval draft and submits it in one step.
|
|
4669
|
+
Only one approval can be pending at a time. Returns blocked=true if another is already pending.
|
|
4670
|
+
|
|
4671
|
+
IMPORTANT — what to write in "summary":
|
|
4672
|
+
- Write a SHORT, new approval request (3–8 sentences). Do NOT copy the task readmeMarkdown verbatim.
|
|
4673
|
+
- The reviewer already has access to the full plan. Your job is to write a concise summary: what will change, why, and what the key risks are.
|
|
4674
|
+
- Example: "This adds Redis caching to the product list endpoint. It will reduce DB load by ~60% during peak hours. Key risk: cache invalidation on product updates — handled by clearing the cache key on every product.save()."`,
|
|
4403
4675
|
{
|
|
4404
4676
|
taskId: z.string().describe("Task's MongoDB ObjectId"),
|
|
4405
|
-
title: z.string().describe('Short title for this approval request, e.g. "
|
|
4406
|
-
|
|
4407
|
-
reviewerId: z.string().describe('User ID of the reviewer'),
|
|
4677
|
+
title: z.string().describe('Short title for this approval request, e.g. "Plan: Add Redis caching to product list"'),
|
|
4678
|
+
summary: z.string().describe('A 3–8 sentence approval summary (NOT the full README). Describe: what changes, why, and key risks. The reviewer will read the full plan separately.'),
|
|
4679
|
+
reviewerId: z.string().describe('User ID of the reviewer (admin or project lead)'),
|
|
4408
4680
|
},
|
|
4409
|
-
async ({ taskId, title,
|
|
4410
|
-
// Gate: block if
|
|
4681
|
+
async ({ taskId, title, summary, reviewerId }) => {
|
|
4682
|
+
// Gate: block if another approval is already pending
|
|
4411
4683
|
const taskRes = await api.get(`/api/tasks/${taskId}`)
|
|
4412
4684
|
if (taskRes?.success) {
|
|
4413
|
-
const
|
|
4685
|
+
const task = taskRes.data?.task
|
|
4686
|
+
const alreadyPending = (task?.approvals || []).find(a => a.state === 'pending')
|
|
4687
|
+
if (alreadyPending) {
|
|
4688
|
+
return text({
|
|
4689
|
+
blocked: true,
|
|
4690
|
+
reason: 'An approval is already pending review.',
|
|
4691
|
+
pendingApproval: {
|
|
4692
|
+
title: alreadyPending.title,
|
|
4693
|
+
requestedAt: alreadyPending.requestedAt,
|
|
4694
|
+
},
|
|
4695
|
+
message: 'Wait for the reviewer to decide on the existing request, or ask them to reject it first.',
|
|
4696
|
+
})
|
|
4697
|
+
}
|
|
4698
|
+
// Gate: block if latest test run is failing
|
|
4699
|
+
const latestRun = task?.testRuns?.[0]
|
|
4414
4700
|
if (latestRun && latestRun.status === 'failing') {
|
|
4415
4701
|
return text({
|
|
4416
4702
|
blocked: true,
|
|
@@ -4426,7 +4712,33 @@ How to determine status:
|
|
|
4426
4712
|
})
|
|
4427
4713
|
}
|
|
4428
4714
|
}
|
|
4429
|
-
|
|
4715
|
+
|
|
4716
|
+
// Step 1: create draft
|
|
4717
|
+
const createRes = await api.post(`/api/tasks/${taskId}/approvals`, { title, readme: summary })
|
|
4718
|
+
if (!createRes?.success) return errorText(createRes?.message || 'Could not create approval draft')
|
|
4719
|
+
const freshTask = createRes.data?.task
|
|
4720
|
+
const newApproval = (freshTask?.approvals || []).find(a => a.state === 'none' && a.title === title)
|
|
4721
|
+
if (!newApproval?._id) return errorText('Approval created but could not find its ID — check the task approvals tab')
|
|
4722
|
+
|
|
4723
|
+
// Step 2: immediately submit for review
|
|
4724
|
+
const submitRes = await api.post(`/api/tasks/${taskId}/approvals/${newApproval._id}/submit`, { reviewerId })
|
|
4725
|
+
if (!submitRes?.success) {
|
|
4726
|
+
return text({
|
|
4727
|
+
draftCreated: true,
|
|
4728
|
+
approvalId: newApproval._id,
|
|
4729
|
+
submitFailed: true,
|
|
4730
|
+
reason: submitRes?.message || 'Draft created but submit step failed',
|
|
4731
|
+
message: 'The approval draft was created but could not be submitted automatically. Open the task → Approval tab → click "Submit for review" manually.',
|
|
4732
|
+
})
|
|
4733
|
+
}
|
|
4734
|
+
return text({
|
|
4735
|
+
submitted: true,
|
|
4736
|
+
approvalId: newApproval._id,
|
|
4737
|
+
title,
|
|
4738
|
+
state: 'pending',
|
|
4739
|
+
message: `Approval request submitted. The reviewer will be notified. Do NOT start coding or create a branch until they approve.`,
|
|
4740
|
+
nextStep: `Wait for decide_task_approval to return decision="approve". Then call create_branch.`,
|
|
4741
|
+
})
|
|
4430
4742
|
}
|
|
4431
4743
|
)
|
|
4432
4744
|
|
|
@@ -5129,6 +5441,29 @@ function writeCursorWorkspace(task, projectAgentConfig, startPath) {
|
|
|
5129
5441
|
mkdirSync(commandsDir, { recursive: true })
|
|
5130
5442
|
mkdirSync(rulesDir, { recursive: true })
|
|
5131
5443
|
|
|
5444
|
+
// ── Ensure generated AI files are NOT committed by the developer ────────────
|
|
5445
|
+
// These are runtime files — they change per task and per agent session.
|
|
5446
|
+
// They belong in the developer's local env, not in git history.
|
|
5447
|
+
const gitignorePath = join(repoRoot, '.gitignore')
|
|
5448
|
+
const AI_GITIGNORE_ENTRIES = [
|
|
5449
|
+
'# InternalTool AI workspace — auto-generated, do not commit',
|
|
5450
|
+
'.cursor/agents/',
|
|
5451
|
+
'.cursor/skills/',
|
|
5452
|
+
'.cursor/commands/',
|
|
5453
|
+
'.cursor/rules/',
|
|
5454
|
+
'.claude/',
|
|
5455
|
+
'.internaltool-active-task',
|
|
5456
|
+
]
|
|
5457
|
+
try {
|
|
5458
|
+
let existing = ''
|
|
5459
|
+
if (existsSync(gitignorePath)) existing = readFileSync(gitignorePath, 'utf8')
|
|
5460
|
+
const missing = AI_GITIGNORE_ENTRIES.filter(e => e.startsWith('#') ? false : !existing.includes(e))
|
|
5461
|
+
if (missing.length > 0) {
|
|
5462
|
+
const toAppend = '\n' + AI_GITIGNORE_ENTRIES[0] + '\n' + missing.join('\n') + '\n'
|
|
5463
|
+
writeFileSync(gitignorePath, existing + toAppend, 'utf8')
|
|
5464
|
+
}
|
|
5465
|
+
} catch { /* non-fatal — gitignore update is best-effort */ }
|
|
5466
|
+
|
|
5132
5467
|
const written = []
|
|
5133
5468
|
|
|
5134
5469
|
// ── 1. Project-level rules from DB → .cursor/rules/<name>.mdc ─────────────
|
|
@@ -5394,23 +5729,41 @@ log_session_event(type="info", name="conflict-resolved", summary="<what you merg
|
|
|
5394
5729
|
|
|
5395
5730
|
/**
|
|
5396
5731
|
* Delete task-specific workspace files written at kickoff.
|
|
5397
|
-
*
|
|
5398
|
-
*
|
|
5732
|
+
* Removes: active-agent.md, start-task.md command, and ALL skill files in .cursor/skills/.
|
|
5733
|
+
* Project-level rules (.cursor/rules/*.mdc) are kept — they are curated by the admin, not generated per task.
|
|
5399
5734
|
*/
|
|
5400
5735
|
function deleteCursorWorkspace(role, startPath) {
|
|
5401
5736
|
const deleted = []
|
|
5402
5737
|
try {
|
|
5403
5738
|
const repoRoot = findRepoRoot(startPath)
|
|
5404
5739
|
if (!repoRoot) return deleted
|
|
5405
|
-
|
|
5740
|
+
|
|
5741
|
+
// Always-delete: task-scoped agent and command files
|
|
5742
|
+
const alwaysDelete = [
|
|
5406
5743
|
join(repoRoot, '.cursor', 'agents', 'active-agent.md'),
|
|
5407
5744
|
join(repoRoot, '.cursor', 'commands', 'start-task.md'),
|
|
5408
5745
|
]
|
|
5409
|
-
for (const f of
|
|
5746
|
+
for (const f of alwaysDelete) {
|
|
5410
5747
|
try {
|
|
5411
5748
|
if (existsSync(f)) { unlinkSync(f); deleted.push(f) }
|
|
5412
5749
|
} catch { /* non-fatal */ }
|
|
5413
5750
|
}
|
|
5751
|
+
|
|
5752
|
+
// Delete all skill files — they are fetched fresh on every kickoff
|
|
5753
|
+
// based on role and task type. Keeping stale skills causes context bloat.
|
|
5754
|
+
const skillsDir = join(repoRoot, '.cursor', 'skills')
|
|
5755
|
+
if (existsSync(skillsDir)) {
|
|
5756
|
+
try {
|
|
5757
|
+
const skillFiles = readdirSync(skillsDir).filter(f => f.endsWith('.md'))
|
|
5758
|
+
for (const f of skillFiles) {
|
|
5759
|
+
try {
|
|
5760
|
+
const fullPath = join(skillsDir, f)
|
|
5761
|
+
unlinkSync(fullPath)
|
|
5762
|
+
deleted.push(fullPath)
|
|
5763
|
+
} catch { /* non-fatal */ }
|
|
5764
|
+
}
|
|
5765
|
+
} catch { /* non-fatal */ }
|
|
5766
|
+
}
|
|
5414
5767
|
} catch { /* non-fatal */ }
|
|
5415
5768
|
return deleted
|
|
5416
5769
|
}
|
|
@@ -6065,14 +6418,13 @@ If you have uncommitted tracked changes, it will tell you exactly what to do bef
|
|
|
6065
6418
|
const task = taskRes.data.task
|
|
6066
6419
|
|
|
6067
6420
|
// ── Approval gate check ───────────────────────────────────────────────────
|
|
6068
|
-
//
|
|
6069
|
-
// Detect this early and guide the developer instead of failing silently after branch creation.
|
|
6421
|
+
// Block branch creation (at both confirmed=false AND confirmed=true) until the plan is approved.
|
|
6070
6422
|
const pendingApv2 = (task.approvals || []).find(a => a.state === 'pending') || null
|
|
6071
6423
|
const hasApprovedApv2 = (task.approvals || []).some(a => a.state === 'approved')
|
|
6072
6424
|
const approvalState = pendingApv2 ? 'pending' : hasApprovedApv2 ? 'approved' : 'none'
|
|
6073
6425
|
const PLANNING_COLS = ['backlog', 'todo']
|
|
6074
6426
|
const needsApproval = PLANNING_COLS.includes(task.column) && !hasApprovedApv2
|
|
6075
|
-
if (needsApproval
|
|
6427
|
+
if (needsApproval) {
|
|
6076
6428
|
const isFix2 = /\b(fix|bug|hotfix|patch)\b/i.test(task.title + ' ' + (task.description || ''))
|
|
6077
6429
|
const slug2 = task.title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 35)
|
|
6078
6430
|
const previewBranch = `${isFix2 ? 'fix' : 'feature'}/${task.key.toLowerCase()}-${slug2}`
|
|
@@ -6080,16 +6432,16 @@ If you have uncommitted tracked changes, it will tell you exactly what to do bef
|
|
|
6080
6432
|
blocked: true,
|
|
6081
6433
|
reason: 'approval_required',
|
|
6082
6434
|
task: { key: task.key, title: task.title, column: task.column, approvalState },
|
|
6083
|
-
message:
|
|
6435
|
+
message: `⛔ The plan must be approved before creating a branch. Do NOT code until the reviewer approves.`,
|
|
6084
6436
|
approvalStatus: approvalState === 'pending'
|
|
6085
|
-
? `Plan is
|
|
6437
|
+
? `Plan is submitted and waiting for reviewer approval. Once approved, call create_branch again.`
|
|
6086
6438
|
: `Plan has not been submitted for review yet.`,
|
|
6087
6439
|
nextSteps: approvalState === 'pending'
|
|
6088
|
-
? [`Wait for
|
|
6440
|
+
? [`Wait for decide_task_approval to return decision="approve", then call create_branch with taskId="${taskId}"`]
|
|
6089
6441
|
: [
|
|
6090
|
-
`1.
|
|
6091
|
-
`2. Call submit_task_for_approval with taskId="${taskId}"
|
|
6092
|
-
`3. Once approved
|
|
6442
|
+
`1. Write the implementation plan: call update_task with readmeMarkdown if not already written`,
|
|
6443
|
+
`2. Call submit_task_for_approval with taskId="${taskId}", summary="<3-8 sentence summary>", reviewerId="<admin user id>"`,
|
|
6444
|
+
`3. Once approved: call create_branch — it will create "${previewBranch}" and move the task to In progress`,
|
|
6093
6445
|
],
|
|
6094
6446
|
})
|
|
6095
6447
|
}
|
package/package.json
CHANGED