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.
Files changed (2) hide show
  1. package/index.js +478 -126
  2. 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 — analyzes the codebase and creates a fully structured, kickoff-ready task.
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 (always first)
527
+ ### Step 1 — Duplicate check
531
528
  Call search_tasks(projectId, query="<keywords from the request>").
532
- If a similar open task already exists → return it with kickoff instructions instead of creating a duplicate.
529
+ If a similar open task exists → return it with kickoff instructions. Do NOT create a duplicate.
533
530
 
534
- ### Step 2 — Codebase analysis (READ the code, do not guess)
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
- Identify: language, framework, major libraries, test runner.
539
-
540
- b) **Entry point mapping** — find where the relevant feature area lives:
541
- - For backend: grep for existing route patterns (router.post, app.get, @Controller, etc.)
542
- - For frontend: grep for existing component patterns, hooks, state management
543
- - For DB: find schema/model files
544
-
545
- c) **Pattern extraction** read 2-3 existing files similar to what you'll build.
546
- Note: naming conventions, folder structure, how services/routes/components are wired.
547
-
548
- d) **Impact analysis** identify every file that needs to change:
549
- - Files to CREATE (new route, new component, new model, new test)
550
- - Files to MODIFY (existing router index, existing schema, existing types)
551
-
552
- e) **Dependency order** which files must be built first (schema before service, service before route, etc.)
553
-
554
- ### Step 3 Write the implementation plan
555
- Using what you found in Step 2, build:
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 — language/framework detected
558
- - ## Technical approach — how it fits into the existing code (name actual files and functions)
559
- - ## Files to create — path + what it does
560
- - ## Files to modify — path + what changes
561
- - ## Subtasks — ordered implementation steps (schema → service → route → test → UI)
562
- - ## Acceptance criteria — what done looks like
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 (from this call)
572
- - title: action-oriented, concise (verb + noun, e.g. "Add rate limiting to /api/auth/login")
573
- - description: one paragraph summary
574
- - readmeMarkdown: the full plan from Step 3
575
- - taskType, priority, column="todo"
576
- - subtasks: the ordered list from Step 3 (each step = one subtask)
577
- - suggestedFiles: from Step 2d
578
-
579
- After create_task succeeds, immediately call:
580
- kickoff_task(taskId=<returned id>, confirmed=true, agentRole="builder", files=<suggestedFiles>)
581
-
582
- ## What makes a high-quality task
583
- - readmeMarkdown references REAL file paths found by grepping the codebase
584
- - subtasks are ordered (schema first, then service, then route, then test, then UI)
585
- - suggestedFiles lists every file that will be touched — no omissions
586
- - title is specific ("Add email verification to /api/auth/register") not generic ("Add email feature")
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. 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 ──────────────────
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 — use codebaseIntelligence above to skip re-reading the filesystem
778
+ // What to do now
660
779
  nextSteps: [
661
- 'Use dirTree + entryPoints above to identify which files need changing — you already have the map.',
662
- `Read 2-3 files from entryPoints.routes / entryPoints.models / entryPoints.components to extract naming conventions and wiring patterns.`,
663
- 'Draft readmeMarkdown with: ## Goal, ## Stack, ## Technical approach (name real files), ## Files to create, ## Files to modify, ## Subtasks (ordered), ## Acceptance criteria.',
664
- `Call create_task(projectId="${projectId}", title="<verb + noun>", readmeMarkdown="<plan>", taskType="<feature|bugfix|...>", priority="${priority}", column="todo", subtasks=[...], suggestedFiles=[...])`,
665
- 'Immediately after: call kickoff_task(taskId=<returned id>, confirmed=true, agentRole="builder", files=[...suggestedFiles])',
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: true,
741
- taskId: task?._id,
742
- taskKey: task?.key,
743
- title: task?.title,
744
- column: task?.column,
745
- taskType: task?.taskType || null,
746
- subtasks: (task?.subtasks || []).length,
747
- kickoff: task?._id
748
- ? `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')
749
897
  : null,
750
- 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.`,
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
- // Suggest relevant skills based on role and available project skills
1916
- // 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.
1917
2065
  const allProjectSkills = ctx.project?.agentConfig?.skills || []
1918
2066
  const taskSkillOverrides = ctx.task?.agentKitOverrides?.skills || {}
1919
- const availableSkills = allProjectSkills.filter(s => {
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
- const suggestedSkills = availableSkills.length > 0
1924
- ? 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 => ({
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: '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(' '),
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
- const pCwd = repoPath || process.cwd()
3953
- const pRoot = findRepoRoot(pCwd)
3954
- if (pRoot) {
3955
- try {
3956
- const porcelain = runGit('status --porcelain=v1', pRoot)
3957
- const { localState, modified } = parseGitStatus(porcelain)
3958
- const currentBranchNow = runGit('branch --show-current', pRoot).trim()
3959
- if (localState === 'modified' && task.github?.headBranch && currentBranchNow !== task.github.headBranch) {
3960
- return text({
3961
- blocked: true,
3962
- reason: `You are on "${currentBranchNow}" with ${modified.length} modified file(s), but this task's branch is "${task.github.headBranch}".`,
3963
- modifiedFiles: modified,
3964
- choices: {
3965
- stash: `git stash push -m "wip: before kickoff ${task.key} on ${currentBranchNow}"`,
3966
- autoSwitch: `Switch to ${task.github.headBranch}: git stash && git checkout ${task.github.headBranch} && git stash pop`,
3967
- cancel: 'Review your changes first, then retry kickoff_task',
3968
- },
3969
- message: 'Resolve the working tree before kicking off this task to avoid cross-task pollution.',
3970
- })
3971
- }
3972
- } 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
+ })
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
- '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()."`,
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. "Experiment: Add caching layer"'),
4406
- readme: z.string().describe('The plan/markdown describing what you want to do and why (min 80 chars)'),
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, readme, reviewerId }) => {
4410
- // Gate: block if latest test run is failing
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 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]
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
- 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
+ })
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
- * Project-level rules (.cursor/rules/<project-rule>.mdc) and skills are kept.
5398
- * 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.
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
- const toDelete = [
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 toDelete) {
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
- // The server blocks non-admins from moving todo in_progress without approval.
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 && !confirmed) {
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: `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.`,
6084
6436
  approvalStatus: approvalState === 'pending'
6085
- ? `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.`
6086
6438
  : `Plan has not been submitted for review yet.`,
6087
6439
  nextSteps: approvalState === 'pending'
6088
- ? [`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}"`]
6089
6441
  : [
6090
- `1. Make sure the README / implementation plan is written on the task (use update_task with readmeMarkdown if needed)`,
6091
- `2. Call submit_task_for_approval with taskId="${taskId}" and the reviewerId of your project lead`,
6092
- `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`,
6093
6445
  ],
6094
6446
  })
6095
6447
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "internaltool-mcp",
3
- "version": "1.6.42",
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",