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.
- package/.claude-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/README.md +172 -155
- package/SKILL.md +23 -2
- package/bin/install.js +316 -3
- package/commands/learnship/diagnose-issues.md +1 -0
- package/commands/learnship/discuss-phase.md +1 -0
- package/commands/learnship/ideate.md +3 -0
- package/commands/learnship/list-phase-assumptions.md +1 -0
- package/commands/learnship/new-project.md +2 -0
- package/commands/learnship/plan-phase.md +2 -0
- package/commands/learnship/quick.md +1 -0
- package/commands/learnship/research-phase.md +3 -0
- package/commands/learnship/secure-phase.md +1 -0
- package/commands/learnship/validate-phase.md +2 -0
- package/commands/learnship/verify-work.md +1 -0
- package/cursor-rules/learnship.mdc +14 -4
- package/gemini-extension.json +1 -1
- package/hooks/learnship-context-monitor.js +120 -0
- package/hooks/learnship-prompt-guard.js +75 -0
- package/hooks/learnship-session-state.js +136 -0
- package/hooks/learnship-statusline.js +179 -0
- package/learnship/agents/researcher.md +43 -2
- package/learnship/contexts/dev.md +21 -0
- package/learnship/contexts/research.md +22 -0
- package/learnship/contexts/review.md +22 -0
- package/learnship/templates/research-project/ARCHITECTURE.md +140 -0
- package/learnship/templates/research-project/FEATURES.md +130 -0
- package/learnship/templates/research-project/PITFALLS.md +102 -0
- package/learnship/templates/research-project/STACK.md +105 -0
- package/learnship/templates/research-project/SUMMARY.md +111 -0
- package/learnship/workflows/challenge.md +16 -4
- package/learnship/workflows/debug.md +30 -6
- package/learnship/workflows/diagnose-issues.md +14 -1
- package/learnship/workflows/discuss-milestone.md +15 -1
- package/learnship/workflows/discuss-phase.md +83 -10
- package/learnship/workflows/ideate.md +25 -5
- package/learnship/workflows/list-phase-assumptions.md +12 -5
- package/learnship/workflows/new-milestone.md +12 -6
- package/learnship/workflows/new-project.md +232 -75
- package/learnship/workflows/plan-phase.md +17 -3
- package/learnship/workflows/quick.md +18 -4
- package/learnship/workflows/research-phase.md +62 -9
- package/learnship/workflows/secure-phase.md +57 -15
- package/learnship/workflows/settings.md +142 -142
- package/learnship/workflows/validate-phase.md +39 -12
- package/learnship/workflows/verify-work.md +27 -0
- package/package.json +1 -1
- 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.
|
|
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,
|
|
@@ -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
|
|
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`
|
|
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
|
|
package/gemini-extension.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "learnship",
|
|
3
|
-
"version": "2.1
|
|
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/",
|