internaltool-mcp 1.6.41 → 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.
Files changed (2) hide show
  1. package/index.js +532 -126
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -463,76 +463,126 @@ Returns tasks with key, title, column, assignees, priority, taskType, and branch
463
463
  }
464
464
  )
465
465
 
466
- // ── plan_task_from_codebase ───────────────────────────────────────────────────
466
+ // ── suggest_skill ─────────────────────────────────────────────────────────────
467
467
  server.tool(
468
- 'plan_task_from_codebase',
469
- `Specialized task-creation agent analyzes the codebase and creates a fully structured, kickoff-ready task.
468
+ 'suggest_skill',
469
+ `Propose a reusable skill, rule, prompt, subagent, or hook to the project's AgentKit — pending human approval.
470
470
 
471
- Use this instead of create_task when you need to implement something and want the task to
472
- contain a real implementation plan based on the actual codebase, not a generic description.
471
+ ## When to call this
473
472
 
474
- ## MANDATORY protocol follow every step in order
473
+ Call this when you discover something during a coding session that would help future sessions in this codebase:
475
474
 
476
- ### Step 1 Duplicate check (always first)
477
- Call search_tasks(projectId, query="<keywords from the request>").
478
- If a similar open task already exists return it with kickoff instructions instead of creating a duplicate.
475
+ - **skill**: A step-by-step methodology you used (e.g. "how to add a new API endpoint in this Express app", "how to run and interpret the test suite")
476
+ - **rule**: A constraint that should always apply (e.g. "always validate with zod before touching the DB", "never modify migrations directly")
477
+ - **prompt**: A prompt template that reliably produces good results for this project (e.g. "security sweep prompt", "PR review checklist")
478
+ - **subagent**: A specialized agent persona (e.g. "database migration specialist with access to prisma tools only")
479
+ - **hook**: A shell command that should run automatically (e.g. "check .internaltool-active-task before any Edit", "run lint after Write")
479
480
 
480
- ### Step 2 Codebase analysis (READ the code, do not guess)
481
- Using your native Read / Grep / Glob tools:
481
+ ## When NOT to call this
482
482
 
483
- a) **Stack detection** read package.json / go.mod / requirements.txt / Cargo.toml.
484
- Identify: language, framework, major libraries, test runner.
483
+ - Do not suggest things that are obvious from the README or existing docs
484
+ - Do not suggest generic skills that apply to any codebase (e.g. "how to write clean code")
485
+ - Do not suggest more than 1-2 skills per session — be selective and high-signal
485
486
 
486
- b) **Entry point mapping** find where the relevant feature area lives:
487
- - For backend: grep for existing route patterns (router.post, app.get, @Controller, etc.)
488
- - For frontend: grep for existing component patterns, hooks, state management
489
- - For DB: find schema/model files
487
+ ## The human reviews and decides
488
+
489
+ Your suggestion goes into a "Pending" queue visible in the project's AgentKit UI.
490
+ The developer can edit the body, approve (which promotes it to active config), or reject.
491
+ After approval, the skill/rule is injected at every future kickoff_task.`,
492
+ {
493
+ projectId: z.string().describe("Project's MongoDB ObjectId"),
494
+ type: z.enum(['skill', 'rule', 'prompt', 'subagent', 'hook']).describe('What kind of suggestion this is'),
495
+ name: z.string().describe('Slug name — lowercase, hyphens, no spaces (e.g. "add-express-route", "require-zod-validation")'),
496
+ description: z.string().describe('One-line description of what this does — shown in the AgentKit UI'),
497
+ body: z.string().describe('Full markdown content — for skills: step-by-step instructions; for rules: constraint language; for hooks: shell command + explanation'),
498
+ rationale: z.string().describe('Why this would help future sessions — what problem it solves, what mistake it prevents, or what pattern it captures'),
499
+ sourceTaskId: z.string().optional().describe('Task ID this was discovered during (for traceability)'),
500
+ sourceTaskKey: z.string().optional().describe('Task key (e.g. PROJ-42) for display'),
501
+ // Hook-specific
502
+ hookTrigger: z.string().optional().describe('For type=hook: when it fires (PreToolUse | PostToolUse | Stop)'),
503
+ hookMatcher: z.string().optional().describe('For type=hook: tool name regex matcher (e.g. "Edit|Write")'),
504
+ hookCommand: z.string().optional().describe('For type=hook: the shell command to execute'),
505
+ },
506
+ async ({ projectId, type, name, description, body, rationale, sourceTaskId, sourceTaskKey, hookTrigger, hookMatcher, hookCommand }) => {
507
+ try { assertProjectScope(projectId) } catch (e) { return errorText(e.message) }
508
+ return call(() => api.post(`/api/projects/${projectId}/skill-suggestions`, {
509
+ type, name, description, body, rationale,
510
+ sourceTaskId: sourceTaskId || null,
511
+ sourceTaskKey: sourceTaskKey || '',
512
+ suggestedBy: 'Claude Code',
513
+ hookTrigger: hookTrigger || '',
514
+ hookMatcher: hookMatcher || '',
515
+ hookCommand: hookCommand || '',
516
+ }))
517
+ }
518
+ )
519
+
520
+ // ── plan_task_from_codebase ───────────────────────────────────────────────────
521
+ server.tool(
522
+ 'plan_task_from_codebase',
523
+ `Specialized task-creation agent — scouts the codebase, sets up the agent config, and creates a fully-planned, kickoff-ready task.
490
524
 
491
- c) **Pattern extraction**read 2-3 existing files similar to what you'll build.
492
- Note: naming conventions, folder structure, how services/routes/components are wired.
525
+ ## MANDATORY protocolfollow every step in order
493
526
 
494
- d) **Impact analysis**identify every file that needs to change:
495
- - Files to CREATE (new route, new component, new model, new test)
496
- - Files to MODIFY (existing router index, existing schema, existing types)
527
+ ### Step 1Duplicate check
528
+ Call search_tasks(projectId, query="<keywords from the request>").
529
+ If a similar open task exists return it with kickoff instructions. Do NOT create a duplicate.
497
530
 
498
- e) **Dependency order**which files must be built first (schema before service, service before route, etc.)
531
+ ### Step 2Codebase scout (READ the code do not guess)
532
+ Using your native Read / Grep / Glob tools:
499
533
 
500
- ### Step 3Write the implementation plan
501
- Using what you found in Step 2, build:
534
+ a) **Stack detection**read package.json / go.mod / requirements.txt / Cargo.toml.
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:
502
559
  - ## Goal — one sentence
503
- - ## Stack — language/framework detected
504
- - ## Technical approach — how it fits into the existing code (name actual files and functions)
505
- - ## Files to create — path + what it does
506
- - ## Files to modify — path + what changes
507
- - ## Subtasks — ordered implementation steps (schema → service → route → test → UI)
508
- - ## Acceptance criteria — what done looks like
509
-
510
- ### Step 4 — Determine task metadata
511
- - taskType: feature / bugfix / migration / integration / ui / backend / security / refactor
512
- - priority: low / medium / high / critical (use "high" if the request sounds important)
513
- - 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
514
566
 
515
567
  ### Step 5 — Create the task
516
568
  Call create_task with:
517
- - projectId (from this call)
518
- - title: action-oriented, concise (verb + noun, e.g. "Add rate limiting to /api/auth/login")
519
- - description: one paragraph summary
520
- - readmeMarkdown: the full plan from Step 3
521
- - taskType, priority, column="todo"
522
- - subtasks: the ordered list from Step 3 (each step = one subtask)
523
- - suggestedFiles: from Step 2d
524
-
525
- After create_task succeeds, immediately call:
526
- kickoff_task(taskId=<returned id>, confirmed=true, agentRole="builder", files=<suggestedFiles>)
527
-
528
- ## What makes a high-quality task
529
- - readmeMarkdown references REAL file paths found by grepping the codebase
530
- - subtasks are ordered (schema first, then service, then route, then test, then UI)
531
- - suggestedFiles lists every file that will be touched — no omissions
532
- - title is specific ("Add email verification to /api/auth/register") not generic ("Add email feature")
533
-
534
- Do NOT skip the codebase analysis. Do NOT create the task before reading the code.
535
- 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.`,
536
586
  {
537
587
  projectId: z.string().describe("InternalTool project's MongoDB ObjectId — from the project's task board URL or CLAUDE.md"),
538
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")'),
@@ -550,8 +600,9 @@ Do NOT ask the developer to describe the codebase — read it yourself.`,
550
600
  const dirTree = getDirTree(cwd, 2)
551
601
  const entryPoints = findEntryPoints(cwd, stack)
552
602
 
553
- // ── 2. Fetch project context ──────────────────────────────────────────────
603
+ // ── 2. Fetch project context + existing agentConfig ─────────────────────
554
604
  let projectContext = null
605
+ let existingAgentConfig = { skills: [], rules: [], subagents: [], prompts: [] }
555
606
  try {
556
607
  const projRes = await api.get(`/api/projects/${projectId}`)
557
608
  if (projRes?.success) {
@@ -561,6 +612,13 @@ Do NOT ask the developer to describe the codebase — read it yourself.`,
561
612
  taskCount: (projRes.data.tasks || []).length,
562
613
  githubRepo: p.github?.repoUrl || null,
563
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
+ }
564
622
  }
565
623
  } catch { /* non-fatal */ }
566
624
 
@@ -578,7 +636,102 @@ Do NOT ask the developer to describe the codebase — read it yourself.`,
578
636
  }
579
637
  } catch { /* non-fatal */ }
580
638
 
581
- // ── 4. Return codebase intelligence + tight instructions ──────────────────
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 ──────────────────
582
735
  const hasEntryPoints = Object.keys(entryPoints).length > 0
583
736
  const stackSummary = [
584
737
  stack.language, stack.framework, stack.testRunner, ...(stack.extra || [])
@@ -595,6 +748,26 @@ Do NOT ask the developer to describe the codebase — read it yourself.`,
595
748
  : { note: 'No entry points found at cwd — check that MCP server is running from the project root' },
596
749
  },
597
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
+
598
771
  // Duplicate guard
599
772
  ...(similarTasks?.length > 0 && {
600
773
  duplicateWarning: true,
@@ -602,16 +775,21 @@ Do NOT ask the developer to describe the codebase — read it yourself.`,
602
775
  duplicateInstruction: 'Check the list above. If one matches → call kickoff_task on it. If none match → proceed.',
603
776
  }),
604
777
 
605
- // What to do now — use codebaseIntelligence above to skip re-reading the filesystem
778
+ // What to do now
606
779
  nextSteps: [
607
- 'Use dirTree + entryPoints above to identify which files need changing — you already have the map.',
608
- `Read 2-3 files from entryPoints.routes / entryPoints.models / entryPoints.components to extract naming conventions and wiring patterns.`,
609
- 'Draft readmeMarkdown with: ## Goal, ## Stack, ## Technical approach (name real files), ## Files to create, ## Files to modify, ## Subtasks (ordered), ## Acceptance criteria.',
610
- `Call create_task(projectId="${projectId}", title="<verb + noun>", readmeMarkdown="<plan>", taskType="<feature|bugfix|...>", priority="${priority}", column="todo", subtasks=[...], suggestedFiles=[...])`,
611
- 'Immediately after: call kickoff_task(taskId=<returned id>, confirmed=true, agentRole="builder", files=[...suggestedFiles])',
612
- ],
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),
613
792
 
614
- // Context
615
793
  request,
616
794
  projectId,
617
795
  priority,
@@ -661,8 +839,13 @@ Always prefer column="todo" so the task is visibly ready to start.`,
661
839
  .describe('Ordered implementation checklist — shown in the task UI and read by agents at kickoff. Order matters: schema → model → service → route → test → frontend.'),
662
840
  suggestedFiles: z.array(z.string()).optional()
663
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.'),
664
847
  },
665
- async ({ projectId, suggestedFiles, ...taskData }) => {
848
+ async ({ projectId, suggestedFiles, agentKitOverrides, ...taskData }) => {
666
849
  try { assertProjectScope(projectId) } catch (e) { return errorText(e.message) }
667
850
 
668
851
  // Append suggested files section to the README so the builder sees them at kickoff
@@ -682,18 +865,37 @@ Always prefer column="todo" so the task is visibly ready to start.`,
682
865
  if (!res?.success) return errorText(res?.message || 'Failed to create task')
683
866
 
684
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
+
685
880
  return text({
686
- created: true,
687
- taskId: task?._id,
688
- taskKey: task?.key,
689
- title: task?.title,
690
- column: task?.column,
691
- taskType: task?.taskType || null,
692
- subtasks: (task?.subtasks || []).length,
693
- kickoff: task?._id
694
- ? `kickoff_task(taskId="${task._id}", confirmed=false) ← read plan first\nkickoff_task(taskId="${task._id}", confirmed=true, agentRole="builder", files=[...]) ← start building`
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')
695
897
  : null,
696
- message: `Task ${task?.key} created and ready to kick off.`,
898
+ message: `Task ${task?.key} created${kitApplied?.applied ? ' with kit overrides applied' : ''}. Follow nextStep — do NOT branch until the plan is approved.`,
697
899
  })
698
900
  }
699
901
  )
@@ -1858,16 +2060,45 @@ Returns systemPrompt ready to use as a Claude system prompt.`,
1858
2060
  } catch { /* non-fatal */ }
1859
2061
  }
1860
2062
 
1861
- // Suggest relevant skills based on role and available project skills
1862
- // Respects project-level enabled + task-level agentKitOverrides
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.
1863
2065
  const allProjectSkills = ctx.project?.agentConfig?.skills || []
1864
2066
  const taskSkillOverrides = ctx.task?.agentKitOverrides?.skills || {}
1865
- const availableSkills = allProjectSkills.filter(s => {
2067
+ const enabledSkills = allProjectSkills.filter(s => {
1866
2068
  const taskOverride = taskSkillOverrides[s.name]
1867
2069
  return taskOverride !== undefined ? taskOverride : s.enabled !== false
1868
2070
  })
1869
- const suggestedSkills = availableSkills.length > 0
1870
- ? availableSkills.map(s => ({
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 => ({
1871
2102
  name: s.name,
1872
2103
  description: s.description,
1873
2104
  path: `.cursor/skills/${s.name}.md`,
@@ -1889,7 +2120,12 @@ Returns systemPrompt ready to use as a Claude system prompt.`,
1889
2120
  suggestedSkills,
1890
2121
  task: ctx.task,
1891
2122
  project: ctx.project,
1892
- usage: 'Use systemPrompt as your Claude system prompt. If allowedTools is set, restrict your MCP tool calls to that list. Read and follow each skill in suggestedSkills before starting work.',
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(' '),
1893
2129
  })
1894
2130
  }
1895
2131
  )
@@ -3615,6 +3851,43 @@ Use this when a developer says "start task", "brief me on", or "what do I need t
3615
3851
  const subtasksDone = subtasks.filter(s => s.done).length
3616
3852
  const subtasksTotal = subtasks.length
3617
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
+
3618
3891
  // ── Preview: show the full plan before touching anything ──
3619
3892
  const pendingApv = (task.approvals || []).find(a => a.state === 'pending') || null
3620
3893
  const hasApprovedApv = (task.approvals || []).some(a => a.state === 'approved')
@@ -3833,10 +4106,24 @@ Use this when a developer says "start task", "brief me on", or "what do I need t
3833
4106
  claimedFiles: task.claimedFiles || [],
3834
4107
  agentRole: task.agentRole || null,
3835
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,
3836
4123
  requiresConfirmation: true,
3837
4124
  message: approvalBlocks
3838
4125
  ? `Read the plan above, then follow workflowRoadmap — approval is required before you can branch and start coding.`
3839
- : `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.`,
3840
4127
  })
3841
4128
  }
3842
4129
 
@@ -3894,28 +4181,39 @@ Use this when a developer says "start task", "brief me on", or "what do I need t
3894
4181
  }
3895
4182
 
3896
4183
  // ── #1 Preflight: check dirty tree before writing cursor rules / moving task ──
3897
- {
3898
- const pCwd = repoPath || process.cwd()
3899
- const pRoot = findRepoRoot(pCwd)
3900
- if (pRoot) {
3901
- try {
3902
- const porcelain = runGit('status --porcelain=v1', pRoot)
3903
- const { localState, modified } = parseGitStatus(porcelain)
3904
- const currentBranchNow = runGit('branch --show-current', pRoot).trim()
3905
- if (localState === 'modified' && task.github?.headBranch && currentBranchNow !== task.github.headBranch) {
3906
- return text({
3907
- blocked: true,
3908
- reason: `You are on "${currentBranchNow}" with ${modified.length} modified file(s), but this task's branch is "${task.github.headBranch}".`,
3909
- modifiedFiles: modified,
3910
- choices: {
3911
- stash: `git stash push -m "wip: before kickoff ${task.key} on ${currentBranchNow}"`,
3912
- autoSwitch: `Switch to ${task.github.headBranch}: git stash && git checkout ${task.github.headBranch} && git stash pop`,
3913
- cancel: 'Review your changes first, then retry kickoff_task',
3914
- },
3915
- message: 'Resolve the working tree before kicking off this task to avoid cross-task pollution.',
3916
- })
3917
- }
3918
- } catch { /* non-fatal */ }
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
+ })
3919
4217
  }
3920
4218
  }
3921
4219
 
@@ -4091,6 +4389,26 @@ After \`request_human_input\`: STOP, show the question in chat, wait for reply,
4091
4389
  }
4092
4390
  api.patch(`/api/tasks/${taskId}`, workspacePatch).catch(() => {/* non-fatal */})
4093
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
+
4094
4412
  // Write .internaltool-active-task so the Claude Code hook knows a task is active
4095
4413
  try {
4096
4414
  writeFileSync(
@@ -4345,18 +4663,40 @@ How to determine status:
4345
4663
 
4346
4664
  server.tool(
4347
4665
  'submit_task_for_approval',
4348
- 'Create and submit a new approval request on a task. Each request has its own title, plan/readme, and reviewer. Only one request can be pending at a time.',
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()."`,
4349
4675
  {
4350
4676
  taskId: z.string().describe("Task's MongoDB ObjectId"),
4351
- title: z.string().describe('Short title for this approval request, e.g. "Experiment: Add caching layer"'),
4352
- readme: z.string().describe('The plan/markdown describing what you want to do and why (min 80 chars)'),
4353
- 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)'),
4354
4680
  },
4355
- async ({ taskId, title, readme, reviewerId }) => {
4356
- // Gate: block if latest test run is failing
4681
+ async ({ taskId, title, summary, reviewerId }) => {
4682
+ // Gate: block if another approval is already pending
4357
4683
  const taskRes = await api.get(`/api/tasks/${taskId}`)
4358
4684
  if (taskRes?.success) {
4359
- const latestRun = taskRes.data?.task?.testRuns?.[0]
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]
4360
4700
  if (latestRun && latestRun.status === 'failing') {
4361
4701
  return text({
4362
4702
  blocked: true,
@@ -4372,7 +4712,33 @@ How to determine status:
4372
4712
  })
4373
4713
  }
4374
4714
  }
4375
- return call(() => api.post(`/api/tasks/${taskId}/approvals`, { title, readme, reviewerId }))
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
+ })
4376
4742
  }
4377
4743
  )
4378
4744
 
@@ -5075,6 +5441,29 @@ function writeCursorWorkspace(task, projectAgentConfig, startPath) {
5075
5441
  mkdirSync(commandsDir, { recursive: true })
5076
5442
  mkdirSync(rulesDir, { recursive: true })
5077
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
+
5078
5467
  const written = []
5079
5468
 
5080
5469
  // ── 1. Project-level rules from DB → .cursor/rules/<name>.mdc ─────────────
@@ -5340,23 +5729,41 @@ log_session_event(type="info", name="conflict-resolved", summary="<what you merg
5340
5729
 
5341
5730
  /**
5342
5731
  * Delete task-specific workspace files written at kickoff.
5343
- * Project-level rules (.cursor/rules/<project-rule>.mdc) and skills are kept.
5344
- * Only the active-agent and start-task command are removed (they are task-scoped).
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.
5345
5734
  */
5346
5735
  function deleteCursorWorkspace(role, startPath) {
5347
5736
  const deleted = []
5348
5737
  try {
5349
5738
  const repoRoot = findRepoRoot(startPath)
5350
5739
  if (!repoRoot) return deleted
5351
- const toDelete = [
5740
+
5741
+ // Always-delete: task-scoped agent and command files
5742
+ const alwaysDelete = [
5352
5743
  join(repoRoot, '.cursor', 'agents', 'active-agent.md'),
5353
5744
  join(repoRoot, '.cursor', 'commands', 'start-task.md'),
5354
5745
  ]
5355
- for (const f of toDelete) {
5746
+ for (const f of alwaysDelete) {
5356
5747
  try {
5357
5748
  if (existsSync(f)) { unlinkSync(f); deleted.push(f) }
5358
5749
  } catch { /* non-fatal */ }
5359
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
+ }
5360
5767
  } catch { /* non-fatal */ }
5361
5768
  return deleted
5362
5769
  }
@@ -6011,14 +6418,13 @@ If you have uncommitted tracked changes, it will tell you exactly what to do bef
6011
6418
  const task = taskRes.data.task
6012
6419
 
6013
6420
  // ── Approval gate check ───────────────────────────────────────────────────
6014
- // The server blocks non-admins from moving todo in_progress without approval.
6015
- // 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.
6016
6422
  const pendingApv2 = (task.approvals || []).find(a => a.state === 'pending') || null
6017
6423
  const hasApprovedApv2 = (task.approvals || []).some(a => a.state === 'approved')
6018
6424
  const approvalState = pendingApv2 ? 'pending' : hasApprovedApv2 ? 'approved' : 'none'
6019
6425
  const PLANNING_COLS = ['backlog', 'todo']
6020
6426
  const needsApproval = PLANNING_COLS.includes(task.column) && !hasApprovedApv2
6021
- if (needsApproval && !confirmed) {
6427
+ if (needsApproval) {
6022
6428
  const isFix2 = /\b(fix|bug|hotfix|patch)\b/i.test(task.title + ' ' + (task.description || ''))
6023
6429
  const slug2 = task.title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 35)
6024
6430
  const previewBranch = `${isFix2 ? 'fix' : 'feature'}/${task.key.toLowerCase()}-${slug2}`
@@ -6026,16 +6432,16 @@ If you have uncommitted tracked changes, it will tell you exactly what to do bef
6026
6432
  blocked: true,
6027
6433
  reason: 'approval_required',
6028
6434
  task: { key: task.key, title: task.title, column: task.column, approvalState },
6029
- message: `The task's implementation plan (README) must be approved before creating a branch and moving to In progress.`,
6435
+ message: `⛔ The plan must be approved before creating a branch. Do NOT code until the reviewer approves.`,
6030
6436
  approvalStatus: approvalState === 'pending'
6031
- ? `Plan is already submitted and waiting for reviewer approval. Once approved, call create_branch again.`
6437
+ ? `Plan is submitted and waiting for reviewer approval. Once approved, call create_branch again.`
6032
6438
  : `Plan has not been submitted for review yet.`,
6033
6439
  nextSteps: approvalState === 'pending'
6034
- ? [`Wait for the reviewer to approve, then call create_branch with taskId="${taskId}"`]
6440
+ ? [`Wait for decide_task_approval to return decision="approve", then call create_branch with taskId="${taskId}"`]
6035
6441
  : [
6036
- `1. Make sure the README / implementation plan is written on the task (use update_task with readmeMarkdown if needed)`,
6037
- `2. Call submit_task_for_approval with taskId="${taskId}" and the reviewerId of your project lead`,
6038
- `3. Once approved, call create_branch again — it will create "${previewBranch}" and move the task to In progress automatically`,
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`,
6039
6445
  ],
6040
6446
  })
6041
6447
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "internaltool-mcp",
3
- "version": "1.6.41",
3
+ "version": "1.6.45",
4
4
  "description": "MCP server for InternalTool — connect AI assistants (Claude Code, Cursor) to your project and task management platform",
5
5
  "type": "module",
6
6
  "main": "index.js",