learnship 2.1.2 → 2.2.1

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 (49) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.cursor-plugin/plugin.json +1 -1
  3. package/README.md +172 -155
  4. package/SKILL.md +23 -2
  5. package/bin/install.js +316 -3
  6. package/commands/learnship/diagnose-issues.md +1 -0
  7. package/commands/learnship/discuss-phase.md +1 -0
  8. package/commands/learnship/ideate.md +3 -0
  9. package/commands/learnship/list-phase-assumptions.md +1 -0
  10. package/commands/learnship/new-project.md +2 -0
  11. package/commands/learnship/plan-phase.md +2 -0
  12. package/commands/learnship/quick.md +1 -0
  13. package/commands/learnship/research-phase.md +3 -0
  14. package/commands/learnship/secure-phase.md +1 -0
  15. package/commands/learnship/validate-phase.md +2 -0
  16. package/commands/learnship/verify-work.md +1 -0
  17. package/cursor-rules/learnship.mdc +14 -4
  18. package/gemini-extension.json +1 -1
  19. package/hooks/learnship-context-monitor.js +120 -0
  20. package/hooks/learnship-prompt-guard.js +75 -0
  21. package/hooks/learnship-session-state.js +136 -0
  22. package/hooks/learnship-statusline.js +179 -0
  23. package/learnship/agents/researcher.md +43 -2
  24. package/learnship/contexts/dev.md +21 -0
  25. package/learnship/contexts/research.md +22 -0
  26. package/learnship/contexts/review.md +22 -0
  27. package/learnship/templates/research-project/ARCHITECTURE.md +140 -0
  28. package/learnship/templates/research-project/FEATURES.md +130 -0
  29. package/learnship/templates/research-project/PITFALLS.md +102 -0
  30. package/learnship/templates/research-project/STACK.md +105 -0
  31. package/learnship/templates/research-project/SUMMARY.md +111 -0
  32. package/learnship/workflows/challenge.md +16 -4
  33. package/learnship/workflows/debug.md +30 -6
  34. package/learnship/workflows/diagnose-issues.md +14 -1
  35. package/learnship/workflows/discuss-milestone.md +15 -1
  36. package/learnship/workflows/discuss-phase.md +83 -10
  37. package/learnship/workflows/ideate.md +25 -5
  38. package/learnship/workflows/list-phase-assumptions.md +12 -5
  39. package/learnship/workflows/new-milestone.md +12 -6
  40. package/learnship/workflows/new-project.md +232 -75
  41. package/learnship/workflows/plan-phase.md +17 -3
  42. package/learnship/workflows/quick.md +18 -4
  43. package/learnship/workflows/research-phase.md +62 -9
  44. package/learnship/workflows/secure-phase.md +57 -15
  45. package/learnship/workflows/settings.md +142 -142
  46. package/learnship/workflows/validate-phase.md +39 -12
  47. package/learnship/workflows/verify-work.md +27 -0
  48. package/package.json +1 -1
  49. package/templates/config.json +1 -0
package/bin/install.js CHANGED
@@ -67,6 +67,8 @@ const hasGlobal = args.includes('--global') || args.includes('-g');
67
67
  const hasLocal = args.includes('--local') || args.includes('-l');
68
68
  const hasUninstall = args.includes('--uninstall') || args.includes('-u');
69
69
  const hasHelp = args.includes('--help') || args.includes('-h');
70
+ const targetIdx = args.indexOf('--target');
71
+ const targetOverride = targetIdx !== -1 && args[targetIdx + 1] ? path.resolve(args[targetIdx + 1]) : null;
70
72
 
71
73
  let selectedPlatforms = [];
72
74
  if (hasAll) {
@@ -111,6 +113,7 @@ const helpText = `
111
113
  ${cyan}-l, --local${reset} Install to current project directory
112
114
 
113
115
  ${yellow}Options:${reset}
116
+ ${cyan}--target <dir>${reset} Install to a custom directory instead of the platform default
114
117
  ${cyan}-u, --uninstall${reset} Remove learnship files
115
118
  ${cyan}-h, --help${reset} Show this help
116
119
 
@@ -260,6 +263,8 @@ function convertToOpencode(content) {
260
263
  .replace(/~\/\.claude\//g, '~/.config/opencode/')
261
264
  .replace(/\$HOME\/\.claude\//g, '$HOME/.config/opencode/')
262
265
  .replace(/\bAskUserQuestion\b/g, 'question')
266
+ .replace(/\bWebSearch\b/g, 'websearch')
267
+ .replace(/\bWebFetch\b/g, 'webfetch')
263
268
  .replace(/\bSlashCommand\b/g, 'skill')
264
269
  .replace(/\bTodoWrite\b/g, 'todowrite')
265
270
  .replace(/subagent_type="general-purpose"/g, 'subagent_type="general"');
@@ -533,6 +538,25 @@ function replacePaths(content, pathPrefix, platform) {
533
538
  } else if (platform === 'codex') {
534
539
  c = c.replace(/~\/\.codex\//g, pathPrefix);
535
540
  }
541
+ // Rewrite AskUserQuestion to platform-native interactive question tool name.
542
+ // Source files use AskUserQuestion (Claude Code syntax). Each platform has its own tool name.
543
+ // OpenCode is handled separately in convertToOpencode(). Claude keeps AskUserQuestion as-is.
544
+ if (platform === 'windsurf') {
545
+ c = c.replace(/\bAskUserQuestion\b/g, 'ask_user_question');
546
+ } else if (platform === 'gemini') {
547
+ c = c.replace(/\bAskUserQuestion\b/g, 'ask_user');
548
+ } else if (platform === 'codex') {
549
+ c = c.replace(/\bAskUserQuestion\b/g, 'request_user_input');
550
+ }
551
+ // Rewrite WebSearch/WebFetch to platform-native tool names.
552
+ // Claude and Codex use WebSearch/WebFetch as-is. OpenCode handled in convertToOpencode().
553
+ if (platform === 'windsurf') {
554
+ c = c.replace(/\bWebSearch\b/g, 'search_web');
555
+ c = c.replace(/\bWebFetch\b/g, 'read_url_content');
556
+ } else if (platform === 'gemini') {
557
+ c = c.replace(/\bWebSearch\b/g, 'google_web_search');
558
+ c = c.replace(/\bWebFetch\b/g, 'web_fetch');
559
+ }
536
560
  // Replace @mention skill syntax — @mention dispatch is Windsurf-native only
537
561
  if (platform === 'claude') {
538
562
  c = c.replace(/@agentic-learning\b/g, '/agentic-learning');
@@ -1084,6 +1108,266 @@ function scanForLeakedPaths(targetDir, platform) {
1084
1108
  }
1085
1109
  }
1086
1110
 
1111
+ // ─── Hook installation ────────────────────────────────────────────────────
1112
+
1113
+ /** List of learnship hook files managed by the installer */
1114
+ const LEARNSHIP_MANAGED_HOOKS = [
1115
+ 'learnship-statusline.js',
1116
+ 'learnship-context-monitor.js',
1117
+ 'learnship-prompt-guard.js',
1118
+ 'learnship-session-state.js',
1119
+ ];
1120
+
1121
+ /**
1122
+ * Install Claude Code / Gemini native hooks into settings.json.
1123
+ * Copies hook .js files to target/hooks/ and registers them in settings.json.
1124
+ * Preserves existing non-learnship entries (read-modify-write).
1125
+ */
1126
+ function installClaudeHooks(targetDir, isGlobal, platform) {
1127
+ const hooksSrc = path.join(__dirname, '..', 'hooks');
1128
+ const hooksDest = path.join(targetDir, 'hooks');
1129
+ fs.mkdirSync(hooksDest, { recursive: true });
1130
+
1131
+ // Copy hook .js files (skip session-start bash script — replaced by learnship-session-state.js)
1132
+ let copied = 0;
1133
+ for (const file of LEARNSHIP_MANAGED_HOOKS) {
1134
+ const src = path.join(hooksSrc, file);
1135
+ if (fs.existsSync(src)) {
1136
+ // Stamp version header
1137
+ let content = fs.readFileSync(src, 'utf8');
1138
+ content = content.replace(/learnship-hook-version:\s*[\d.]+/, `learnship-hook-version: ${pkg.version}`);
1139
+ fs.writeFileSync(path.join(hooksDest, file), content);
1140
+ copied++;
1141
+ }
1142
+ }
1143
+
1144
+ if (copied === 0) return 0;
1145
+
1146
+ // Write package.json for CJS require() support in hooks
1147
+ const pkgJsonPath = path.join(targetDir, 'package.json');
1148
+ if (!fs.existsSync(pkgJsonPath)) {
1149
+ fs.writeFileSync(pkgJsonPath, '{"type":"commonjs"}\n');
1150
+ }
1151
+
1152
+ // Build hook commands — use $CLAUDE_PROJECT_DIR for local installs
1153
+ const dirName = getDirName(platform);
1154
+ const localPrefix = '"$CLAUDE_PROJECT_DIR"/' + dirName;
1155
+ const buildCmd = (file) => {
1156
+ if (isGlobal) {
1157
+ const resolved = path.resolve(targetDir).replace(/\\/g, '/');
1158
+ return `node "${resolved}/hooks/${file}"`;
1159
+ }
1160
+ return `node ${localPrefix}/hooks/${file}`;
1161
+ };
1162
+
1163
+ // Read-modify-write settings.json
1164
+ const settingsPath = path.join(targetDir, 'settings.json');
1165
+ const settings = readSettings(settingsPath);
1166
+ if (!settings.hooks) settings.hooks = {};
1167
+
1168
+ // Gemini uses AfterTool/BeforeTool instead of PostToolUse/PreToolUse
1169
+ const isGemini = platform === 'gemini';
1170
+ const postToolEvent = isGemini ? 'AfterTool' : 'PostToolUse';
1171
+ const preToolEvent = isGemini ? 'BeforeTool' : 'PreToolUse';
1172
+
1173
+ // --- SessionStart: learnship-session-state.js ---
1174
+ if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
1175
+ const hasSessionHook = settings.hooks.SessionStart.some(entry =>
1176
+ entry.hooks && entry.hooks.some(h => h.command && h.command.includes('learnship-session-state'))
1177
+ );
1178
+ if (!hasSessionHook && fs.existsSync(path.join(hooksDest, 'learnship-session-state.js'))) {
1179
+ settings.hooks.SessionStart.push({
1180
+ hooks: [{ type: 'command', command: buildCmd('learnship-session-state.js') }]
1181
+ });
1182
+ }
1183
+
1184
+ // --- PostToolUse: learnship-context-monitor.js ---
1185
+ if (!settings.hooks[postToolEvent]) settings.hooks[postToolEvent] = [];
1186
+ const hasContextHook = settings.hooks[postToolEvent].some(entry =>
1187
+ entry.hooks && entry.hooks.some(h => h.command && h.command.includes('learnship-context-monitor'))
1188
+ );
1189
+ if (!hasContextHook && fs.existsSync(path.join(hooksDest, 'learnship-context-monitor.js'))) {
1190
+ settings.hooks[postToolEvent].push({
1191
+ matcher: 'Bash|Edit|Write|MultiEdit',
1192
+ hooks: [{ type: 'command', command: buildCmd('learnship-context-monitor.js'), timeout: 10 }]
1193
+ });
1194
+ }
1195
+
1196
+ // --- PreToolUse: learnship-prompt-guard.js ---
1197
+ if (!settings.hooks[preToolEvent]) settings.hooks[preToolEvent] = [];
1198
+ const hasPromptGuard = settings.hooks[preToolEvent].some(entry =>
1199
+ entry.hooks && entry.hooks.some(h => h.command && h.command.includes('learnship-prompt-guard'))
1200
+ );
1201
+ if (!hasPromptGuard && fs.existsSync(path.join(hooksDest, 'learnship-prompt-guard.js'))) {
1202
+ settings.hooks[preToolEvent].push({
1203
+ matcher: 'Write|Edit',
1204
+ hooks: [{ type: 'command', command: buildCmd('learnship-prompt-guard.js'), timeout: 5 }]
1205
+ });
1206
+ }
1207
+
1208
+ // --- statusLine: learnship-statusline.js ---
1209
+ if (!settings.statusLine && fs.existsSync(path.join(hooksDest, 'learnship-statusline.js'))) {
1210
+ settings.statusLine = {
1211
+ type: 'command',
1212
+ command: buildCmd('learnship-statusline.js')
1213
+ };
1214
+ }
1215
+
1216
+ writeSettings(settingsPath, settings);
1217
+ return copied;
1218
+ }
1219
+
1220
+ /**
1221
+ * Remove learnship hooks from settings.json and delete hook files.
1222
+ */
1223
+ function uninstallClaudeHooks(targetDir) {
1224
+ const settingsPath = path.join(targetDir, 'settings.json');
1225
+ if (fs.existsSync(settingsPath)) {
1226
+ try {
1227
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
1228
+ let modified = false;
1229
+
1230
+ // Remove learnship entries from hook arrays
1231
+ for (const event of ['SessionStart', 'PostToolUse', 'AfterTool', 'PreToolUse', 'BeforeTool']) {
1232
+ if (Array.isArray(settings.hooks?.[event])) {
1233
+ const before = settings.hooks[event].length;
1234
+ settings.hooks[event] = settings.hooks[event].filter(entry =>
1235
+ !entry.hooks || !entry.hooks.some(h => h.command && h.command.includes('learnship-'))
1236
+ );
1237
+ if (settings.hooks[event].length !== before) modified = true;
1238
+ if (settings.hooks[event].length === 0) delete settings.hooks[event];
1239
+ }
1240
+ }
1241
+
1242
+ // Remove statusLine if it's ours
1243
+ if (settings.statusLine?.command?.includes('learnship-')) {
1244
+ delete settings.statusLine;
1245
+ modified = true;
1246
+ }
1247
+
1248
+ // Clean empty hooks object
1249
+ if (settings.hooks && Object.keys(settings.hooks).length === 0) delete settings.hooks;
1250
+
1251
+ if (modified) {
1252
+ writeSettings(settingsPath, settings);
1253
+ console.log(` ${green}✓${reset} Removed learnship hooks from settings.json`);
1254
+ }
1255
+ } catch (e) { /* ignore parse errors */ }
1256
+ }
1257
+
1258
+ // Remove hook files
1259
+ const hooksDir = path.join(targetDir, 'hooks');
1260
+ if (fs.existsSync(hooksDir)) {
1261
+ let n = 0;
1262
+ for (const file of LEARNSHIP_MANAGED_HOOKS) {
1263
+ const fp = path.join(hooksDir, file);
1264
+ if (fs.existsSync(fp)) { fs.unlinkSync(fp); n++; }
1265
+ }
1266
+ if (n > 0) console.log(` ${green}✓${reset} Removed ${n} learnship hook files`);
1267
+ }
1268
+
1269
+ // Remove package.json if it's our minimal one
1270
+ const pkgJsonPath = path.join(targetDir, 'package.json');
1271
+ if (fs.existsSync(pkgJsonPath)) {
1272
+ try {
1273
+ const content = fs.readFileSync(pkgJsonPath, 'utf8').trim();
1274
+ if (content === '{"type":"commonjs"}') {
1275
+ fs.unlinkSync(pkgJsonPath);
1276
+ }
1277
+ } catch (e) { /* ignore */ }
1278
+ }
1279
+ }
1280
+
1281
+ // ─── File manifest ────────────────────────────────────────────────────────
1282
+
1283
+ const crypto = require('crypto');
1284
+
1285
+ function fileHash(filePath) {
1286
+ const content = fs.readFileSync(filePath);
1287
+ return crypto.createHash('sha256').update(content).digest('hex');
1288
+ }
1289
+
1290
+ /**
1291
+ * Generate install manifest with SHA-256 hashes for all installed files.
1292
+ */
1293
+ function generateManifest(targetDir) {
1294
+ const manifest = {
1295
+ version: pkg.version,
1296
+ timestamp: new Date().toISOString(),
1297
+ files: {}
1298
+ };
1299
+
1300
+ function scanDir(dir, prefix) {
1301
+ if (!fs.existsSync(dir)) return;
1302
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
1303
+ const full = path.join(dir, entry.name);
1304
+ const rel = prefix ? prefix + '/' + entry.name : entry.name;
1305
+ if (entry.isDirectory()) {
1306
+ scanDir(full, rel);
1307
+ } else {
1308
+ manifest.files[rel] = fileHash(full);
1309
+ }
1310
+ }
1311
+ }
1312
+
1313
+ // Scan learnship/ payload
1314
+ const learnshipDir = path.join(targetDir, 'learnship');
1315
+ if (fs.existsSync(learnshipDir)) scanDir(learnshipDir, 'learnship');
1316
+
1317
+ // Scan hooks/
1318
+ const hooksDir = path.join(targetDir, 'hooks');
1319
+ if (fs.existsSync(hooksDir)) {
1320
+ for (const file of fs.readdirSync(hooksDir)) {
1321
+ if (file.startsWith('learnship-')) {
1322
+ manifest.files['hooks/' + file] = fileHash(path.join(hooksDir, file));
1323
+ }
1324
+ }
1325
+ }
1326
+
1327
+ fs.writeFileSync(path.join(targetDir, 'learnship-file-manifest.json'), JSON.stringify(manifest, null, 2));
1328
+ return manifest;
1329
+ }
1330
+
1331
+ /**
1332
+ * Detect user-modified files by comparing against install manifest.
1333
+ * Backs up modified files to learnship-local-patches/.
1334
+ */
1335
+ function saveLocalPatches(targetDir) {
1336
+ const manifestPath = path.join(targetDir, 'learnship-file-manifest.json');
1337
+ if (!fs.existsSync(manifestPath)) return [];
1338
+
1339
+ let manifest;
1340
+ try { manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); } catch { return []; }
1341
+
1342
+ const patchesDir = path.join(targetDir, 'learnship-local-patches');
1343
+ const modified = [];
1344
+
1345
+ for (const [relPath, originalHash] of Object.entries(manifest.files || {})) {
1346
+ const fullPath = path.join(targetDir, relPath);
1347
+ if (!fs.existsSync(fullPath)) continue;
1348
+ const currentHash = fileHash(fullPath);
1349
+ if (currentHash !== originalHash) {
1350
+ const backupPath = path.join(patchesDir, relPath);
1351
+ fs.mkdirSync(path.dirname(backupPath), { recursive: true });
1352
+ fs.copyFileSync(fullPath, backupPath);
1353
+ modified.push(relPath);
1354
+ }
1355
+ }
1356
+
1357
+ if (modified.length > 0) {
1358
+ const meta = {
1359
+ backed_up_at: new Date().toISOString(),
1360
+ from_version: manifest.version,
1361
+ files: modified
1362
+ };
1363
+ fs.writeFileSync(path.join(patchesDir, 'backup-meta.json'), JSON.stringify(meta, null, 2));
1364
+ console.log(`\n ${yellow}i${reset} Found ${modified.length} locally modified learnship file(s) — backed up to learnship-local-patches/`);
1365
+ for (const f of modified.slice(0, 5)) console.log(` ${dim}${f}${reset}`);
1366
+ if (modified.length > 5) console.log(` ${dim}... and ${modified.length - 5} more${reset}`);
1367
+ }
1368
+ return modified;
1369
+ }
1370
+
1087
1371
  // ─── Main install function ─────────────────────────────────────────────────
1088
1372
  function install(platform, isGlobal) {
1089
1373
  // Cursor installs via the marketplace plugin, not this CLI.
@@ -1100,7 +1384,7 @@ function install(platform, isGlobal) {
1100
1384
  }
1101
1385
 
1102
1386
  const src = path.join(__dirname, '..');
1103
- const targetDir = isGlobal ? getGlobalDir(platform) : path.join(process.cwd(), getDirName(platform));
1387
+ const targetDir = targetOverride || (isGlobal ? getGlobalDir(platform) : path.join(process.cwd(), getDirName(platform)));
1104
1388
  const pathPrefix = `${targetDir.replace(/\\/g, '/')}/learnship/`;
1105
1389
  const label = getPlatformLabel(platform);
1106
1390
  const locationLabel = targetDir.replace(os.homedir(), '~');
@@ -1109,6 +1393,9 @@ function install(platform, isGlobal) {
1109
1393
 
1110
1394
  fs.mkdirSync(targetDir, { recursive: true });
1111
1395
 
1396
+ // Save locally modified files before overwriting
1397
+ saveLocalPatches(targetDir);
1398
+
1112
1399
  const learnshipSrc = path.join(src, 'learnship');
1113
1400
  const commandsSrc = path.join(src, 'commands', 'learnship');
1114
1401
  const agentsSrc = path.join(src, 'agents');
@@ -1200,6 +1487,9 @@ function install(platform, isGlobal) {
1200
1487
  } else {
1201
1488
  failures.push('skills/');
1202
1489
  }
1490
+ // Install native Claude Code hooks (settings.json + hook files)
1491
+ const hCount = installClaudeHooks(targetDir, isGlobal, 'claude');
1492
+ if (hCount > 0) console.log(` ${green}✓${reset} Installed ${hCount} hooks + settings.json (statusline, context monitor, prompt guard, session state)`);
1203
1493
  } else if (platform === 'opencode') {
1204
1494
  const count = installOpencodeCommands(commandsSrc, targetDir, pathPrefix);
1205
1495
  console.log(` ${green}✓${reset} Installed ${count} commands to command/ (flat)`);
@@ -1222,6 +1512,9 @@ function install(platform, isGlobal) {
1222
1512
  writeSettings(settingsPath, settings);
1223
1513
  console.log(` ${green}✓${reset} Enabled experimental.enableAgents in settings.json`);
1224
1514
  }
1515
+ // Install native Gemini hooks (settings.json + hook files)
1516
+ const hCount = installClaudeHooks(targetDir, isGlobal, 'gemini');
1517
+ if (hCount > 0) console.log(` ${green}✓${reset} Installed ${hCount} hooks + settings.json (statusline, context monitor, prompt guard, session state)`);
1225
1518
  } else if (platform === 'codex') {
1226
1519
  const count = installCodexSkills(commandsSrc, targetDir, pathPrefix);
1227
1520
  console.log(` ${green}✓${reset} Installed ${count} skills to skills/`);
@@ -1237,7 +1530,11 @@ function install(platform, isGlobal) {
1237
1530
  // 4. Scan for leaked .claude paths
1238
1531
  scanForLeakedPaths(targetDir, platform);
1239
1532
 
1240
- // 5. Post-install tips
1533
+ // 5. Generate file manifest for upgrade safety
1534
+ generateManifest(targetDir);
1535
+ console.log(` ${green}✓${reset} Generated learnship-file-manifest.json`);
1536
+
1537
+ // 6. Post-install tips
1241
1538
  const firstCmd = platform === 'windsurf' ? '/ls' :
1242
1539
  platform === 'claude' ? '/learnship:ls' :
1243
1540
  platform === 'opencode' ? '/learnship-ls' :
@@ -1251,7 +1548,7 @@ function install(platform, isGlobal) {
1251
1548
 
1252
1549
  // ─── Uninstall function ────────────────────────────────────────────────────
1253
1550
  function uninstall(platform, isGlobal) {
1254
- const targetDir = isGlobal ? getGlobalDir(platform) : path.join(process.cwd(), getDirName(platform));
1551
+ const targetDir = targetOverride || (isGlobal ? getGlobalDir(platform) : path.join(process.cwd(), getDirName(platform)));
1255
1552
  const label = getPlatformLabel(platform);
1256
1553
  const locationLabel = targetDir.replace(os.homedir(), '~');
1257
1554
  console.log(`\n Uninstalling learnship from ${cyan}${label}${reset} at ${cyan}${locationLabel}${reset}\n`);
@@ -1374,6 +1671,18 @@ function uninstall(platform, isGlobal) {
1374
1671
  if (n > 0) { removed++; console.log(` ${green}✓${reset} Removed ${n} learnship agent files`); }
1375
1672
  }
1376
1673
 
1674
+ // 4. Remove hooks and settings.json entries (Claude Code / Gemini)
1675
+ if (platform === 'claude' || platform === 'gemini') {
1676
+ uninstallClaudeHooks(targetDir);
1677
+ removed++;
1678
+ }
1679
+
1680
+ // 5. Remove file manifest and local patches
1681
+ const manifestPath = path.join(targetDir, 'learnship-file-manifest.json');
1682
+ if (fs.existsSync(manifestPath)) { fs.unlinkSync(manifestPath); removed++; console.log(` ${green}✓${reset} Removed learnship-file-manifest.json`); }
1683
+ const patchesDir = path.join(targetDir, 'learnship-local-patches');
1684
+ if (fs.existsSync(patchesDir)) { fs.rmSync(patchesDir, { recursive: true }); removed++; console.log(` ${green}✓${reset} Removed learnship-local-patches/`); }
1685
+
1377
1686
  if (removed === 0) console.log(` ${yellow}⚠${reset} No learnship files found.`);
1378
1687
  else console.log(`\n ${green}Done!${reset} learnship uninstalled from ${label}. Your other files and settings were preserved.`);
1379
1688
  }
@@ -1455,6 +1764,10 @@ if (process.env.LEARNSHIP_TEST_MODE) {
1455
1764
  rewriteNewProject,
1456
1765
  rewriteAgentsMd,
1457
1766
  installClaudeSkills,
1767
+ installClaudeHooks,
1768
+ uninstallClaudeHooks,
1769
+ generateManifest,
1770
+ saveLocalPatches,
1458
1771
  toHomePrefix,
1459
1772
  LEARNSHIP_CODEX_MARKER,
1460
1773
  CODEX_AGENT_SANDBOX,
@@ -7,6 +7,7 @@ allowed-tools:
7
7
  - Bash
8
8
  - Write
9
9
  - Task
10
+ - AskUserQuestion
10
11
  ---
11
12
 
12
13
  <execution_context>
@@ -7,6 +7,7 @@ allowed-tools:
7
7
  - Bash
8
8
  - Write
9
9
  - Task
10
+ - AskUserQuestion
10
11
  ---
11
12
 
12
13
  <execution_context>
@@ -7,6 +7,9 @@ allowed-tools:
7
7
  - Bash
8
8
  - Write
9
9
  - Task
10
+ - AskUserQuestion
11
+ - WebSearch
12
+ - WebFetch
10
13
  ---
11
14
 
12
15
  <execution_context>
@@ -5,6 +5,7 @@ argument-hint: "[N]"
5
5
  allowed-tools:
6
6
  - Read
7
7
  - Bash
8
+ - AskUserQuestion
8
9
  ---
9
10
 
10
11
  <execution_context>
@@ -8,6 +8,8 @@ allowed-tools:
8
8
  - Write
9
9
  - Task
10
10
  - AskUserQuestion
11
+ - WebSearch
12
+ - WebFetch
11
13
  ---
12
14
 
13
15
  <execution_context>
@@ -7,6 +7,8 @@ allowed-tools:
7
7
  - Bash
8
8
  - Write
9
9
  - Task
10
+ - WebSearch
11
+ - WebFetch
10
12
  ---
11
13
 
12
14
  <execution_context>
@@ -7,6 +7,7 @@ allowed-tools:
7
7
  - Bash
8
8
  - Write
9
9
  - Task
10
+ - AskUserQuestion
10
11
  ---
11
12
 
12
13
  <execution_context>
@@ -7,6 +7,9 @@ allowed-tools:
7
7
  - Bash
8
8
  - Write
9
9
  - Task
10
+ - AskUserQuestion
11
+ - WebSearch
12
+ - WebFetch
10
13
  ---
11
14
 
12
15
  <execution_context>
@@ -7,6 +7,7 @@ allowed-tools:
7
7
  - Bash
8
8
  - Write
9
9
  - Task
10
+ - AskUserQuestion
10
11
  ---
11
12
 
12
13
  <execution_context>
@@ -6,6 +6,8 @@ allowed-tools:
6
6
  - Read
7
7
  - Bash
8
8
  - Write
9
+ - Task
10
+ - AskUserQuestion
9
11
  ---
10
12
 
11
13
  <execution_context>
@@ -7,6 +7,7 @@ allowed-tools:
7
7
  - Bash
8
8
  - Write
9
9
  - Task
10
+ - AskUserQuestion
10
11
  ---
11
12
 
12
13
  <execution_context>
@@ -47,7 +47,7 @@ When the user runs `/new-project`, execute these **9 mandatory steps in order**.
47
47
 
48
48
  2. **Research decision = always ask the user.** After PROJECT.md is confirmed, ask: "Do you want me to research the domain ecosystem first?" and WAIT for the user's reply. You are FORBIDDEN from deciding this yourself — even if the tech stack is defined in PROJECT.md, the domain seems trivial, or the user gave detailed answers. Never say "no research needed" or "skipping research" on your own.
49
49
 
50
- 3. **Research = WRITE 5 FILES TO DISK.** "Research" means creating files on the filesystem, not thinking or browsing. If the user chooses research, write exactly 5 files to `.planning/research/`: `STACK.md`, `FEATURES.md`, `ARCHITECTURE.md`, `PITFALLS.md`, `SUMMARY.md`. Do NOT do web searches or domain analysis and then say "I have enough research data" without writing the files that is a workflow failure. Run the `node -e` verification command — it must print `RESEARCH VERIFIED OK` before proceeding to requirements.
50
+ 3. **Research = WEB SEARCH then WRITE 5 FILES TO DISK.** "Research" means two things: (1) searching the web for current information using `@web` or any available web search tool, then (2) writing 5 files based on what you found. Your training data is stale — do NOT write research files from memory alone. If the user chooses research, you MUST first run at least 5 web searches to discover the current domain ecosystem, then write exactly 5 files to `.planning/research/`: `STACK.md`, `FEATURES.md`, `ARCHITECTURE.md`, `PITFALLS.md`, `SUMMARY.md`. Include confidence levels (HIGH/MEDIUM/LOW) and cite sources. Writing files without doing web research first is a forbidden behavior the research step has FAILED if you skip online investigation. Run the `node -e` verification command — it must print `RESEARCH VERIFIED OK` before proceeding to requirements.
51
51
 
52
52
  4. **After Step 7 (roadmap approved):** Do NOT display the done banner or suggest next steps. Generate AGENTS.md (Step 8) first.
53
53
 
@@ -141,13 +141,23 @@ Available actions: `adapt`, `animate`, `arrange`, `audit`, `bolder`, `clarify`,
141
141
 
142
142
  ## Parallel Execution
143
143
 
144
- Cursor supports real parallel subagents. During `/new-project` setup (Group D), ask:
144
+ Cursor supports real parallel subagents. During `/new-project` Step 2 configuration, one of the questions asks about parallel execution:
145
145
 
146
- "Do you want to enable parallel subagent execution?"
147
146
  - **No** (recommended default) — Plans execute sequentially, one at a time. Safer, easier to follow.
148
147
  - **Yes** — Each independent plan in a wave gets its own dedicated subagent with a fresh context budget. Faster, but uses more tokens.
149
148
 
150
- Set `"parallelization": true|false` in `.planning/config.json` based on the user's choice.
149
+ Set `"parallelization": { "enabled": true|false }` in `.planning/config.json` based on the user's choice.
150
+
151
+ ## Structured Questions
152
+
153
+ When workflows include `AskUserQuestion()` blocks, **Cursor has no native structured question tool**. Present each question as a numbered text list with descriptions and ask the user to reply with their choice number or label. Example:
154
+
155
+ ```
156
+ **Working Style**
157
+ How do you want to work?
158
+ 1. YOLO (Recommended) — Auto-approve steps, just execute
159
+ 2. Interactive — Confirm at each step
160
+ ```
151
161
 
152
162
  ## Learning Mode
153
163
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "learnship",
3
- "version": "2.1.2",
3
+ "version": "2.2.1",
4
4
  "description": "Agentic engineering done right — 57 structured workflows, persistent memory across sessions, integrated learning partner, and impeccable UI design system.",
5
5
  "author": "Favio Vazquez",
6
6
  "homepage": "https://faviovazquez.github.io/learnship/",