thufir 2.1.0 → 2.1.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.
@@ -1,7 +1,7 @@
1
1
  import * as p from '@clack/prompts';
2
2
  import pc from 'picocolors';
3
3
  import { execSync } from 'node:child_process';
4
- import { writeFileSync } from 'node:fs';
4
+ import { existsSync, writeFileSync } from 'node:fs';
5
5
  import { join } from 'node:path';
6
6
  import { showVersionCheck } from '../version-check.js';
7
7
  import { analyzeRepo } from '../analyze.js';
@@ -162,6 +162,57 @@ export async function runInit() {
162
162
  const result = scaffold(cwd, answers, analysis);
163
163
  createInitialCommit(cwd, result.filesCreated, answers.colleagueName);
164
164
  s.stop(`${answers.colleagueName} is ready.`);
165
+ // Install skill packages
166
+ if (answers.skillPackages.length > 0) {
167
+ const skillSpinner = p.spinner();
168
+ for (const pkg of answers.skillPackages) {
169
+ if (pkg === 'gstack') {
170
+ const gstackPath = join(process.env.HOME || '~', '.claude', 'skills', 'gstack');
171
+ if (existsSync(gstackPath)) {
172
+ p.log.info('gstack already installed at ~/.claude/skills/gstack');
173
+ }
174
+ else {
175
+ skillSpinner.start('Installing gstack (headless browser, QA, shipping)...');
176
+ try {
177
+ execSync('git clone https://github.com/garrytan/gstack.git ~/.claude/skills/gstack', { stdio: 'ignore', timeout: 60000 });
178
+ // Run setup if bun is available
179
+ try {
180
+ execSync('cd ~/.claude/skills/gstack && ./setup', { stdio: 'ignore', timeout: 30000 });
181
+ }
182
+ catch { /* setup is optional — browse binary may not build without bun */ }
183
+ skillSpinner.stop('gstack installed.');
184
+ }
185
+ catch (err) {
186
+ skillSpinner.stop('gstack installation failed.');
187
+ p.log.warn('Install manually: git clone https://github.com/garrytan/gstack.git ~/.claude/skills/gstack');
188
+ }
189
+ }
190
+ }
191
+ if (pkg === 'superpowers') {
192
+ const skillsDir = join(process.env.HOME || '~', '.claude', 'skills');
193
+ const spSkills = ['superpowers-brainstorming', 'superpowers-systematic-debugging', 'superpowers-test-driven-development'];
194
+ const installed = spSkills.filter(s => existsSync(join(skillsDir, s)));
195
+ if (installed.length >= spSkills.length) {
196
+ p.log.info(`Superpowers already installed (${installed.length} skills detected)`);
197
+ }
198
+ else if (installed.length > 0) {
199
+ p.log.info(`Superpowers partially installed (${installed.length}/${spSkills.length} core skills)`);
200
+ p.log.warn('Run `claude /install-skills superpowers` to install missing skills');
201
+ }
202
+ else {
203
+ // Try installing via superpowers-extended-cc if available
204
+ const extPath = join(skillsDir, 'superpowers-extended-cc');
205
+ if (existsSync(extPath)) {
206
+ p.log.info('Superpowers Extended (CC) detected — skills available');
207
+ }
208
+ else {
209
+ p.log.warn('Superpowers not found. Install via Claude Code:');
210
+ console.log(pc.dim(' Open Claude Code and run: /install superpowers'));
211
+ }
212
+ }
213
+ }
214
+ }
215
+ }
165
216
  // GitHub setup
166
217
  if (answers.setupGitHub !== 'skip' && analysis.hasGhCli) {
167
218
  setupGitHub(cwd, answers.setupGitHub, answers.repoName);
package/dist/scaffold.js CHANGED
@@ -156,7 +156,55 @@ const THUFIR_DOCS = [
156
156
  'plans/_meta.md',
157
157
  'workflows/_meta.md',
158
158
  'workflows/getting-started.md',
159
+ 'entities/_meta.md',
159
160
  ];
161
+ /**
162
+ * Merge existing AGENTS.md with new template.
163
+ * Strategy: Use the new template as the base, then append any sections
164
+ * from the existing file that don't exist in the new template.
165
+ * This preserves user-added sections (like "Data Querying") while
166
+ * updating Thufir-managed sections (personality, triggers, etc.)
167
+ */
168
+ function mergeAgentsMd(existing, fresh) {
169
+ // Extract H2 sections from both
170
+ const sectionPattern = /^## .+$/gm;
171
+ const extractSections = (content) => {
172
+ const sections = new Map();
173
+ const lines = content.split('\n');
174
+ let currentHeading = '__preamble__';
175
+ let currentContent = [];
176
+ for (const line of lines) {
177
+ if (line.match(/^## /)) {
178
+ if (currentContent.length > 0) {
179
+ sections.set(currentHeading, currentContent.join('\n').trim());
180
+ }
181
+ currentHeading = line.trim();
182
+ currentContent = [line];
183
+ }
184
+ else {
185
+ currentContent.push(line);
186
+ }
187
+ }
188
+ if (currentContent.length > 0) {
189
+ sections.set(currentHeading, currentContent.join('\n').trim());
190
+ }
191
+ return sections;
192
+ };
193
+ const existingSections = extractSections(existing);
194
+ const freshSections = extractSections(fresh);
195
+ // Start with the fresh template
196
+ let result = fresh.trimEnd();
197
+ // Append any sections from existing that aren't in fresh
198
+ for (const [heading, content] of existingSections) {
199
+ if (heading === '__preamble__')
200
+ continue; // Skip preamble — use fresh version
201
+ if (!freshSections.has(heading)) {
202
+ // This is a user-added section — preserve it
203
+ result += '\n\n' + content;
204
+ }
205
+ }
206
+ return result + '\n';
207
+ }
160
208
  export function scaffold(cwd, answers, analysis, isRealign = false) {
161
209
  const files = [];
162
210
  let gitInitialized = false;
@@ -166,7 +214,22 @@ export function scaffold(cwd, answers, analysis, isRealign = false) {
166
214
  }
167
215
  // AGENTS.md
168
216
  const agentsTemplate = readTemplate('agents/base.md');
169
- writeFile(cwd, 'AGENTS.md', substitute(agentsTemplate, answers));
217
+ const newAgents = substitute(agentsTemplate, answers);
218
+ if (isRealign) {
219
+ // Merge: keep user-added sections from existing AGENTS.md
220
+ const existingPath = join(cwd, 'AGENTS.md');
221
+ if (existsSync(existingPath)) {
222
+ const existing = readFileSync(existingPath, 'utf-8');
223
+ const merged = mergeAgentsMd(existing, newAgents);
224
+ writeFile(cwd, 'AGENTS.md', merged);
225
+ }
226
+ else {
227
+ writeFile(cwd, 'AGENTS.md', newAgents);
228
+ }
229
+ }
230
+ else {
231
+ writeFile(cwd, 'AGENTS.md', newAgents);
232
+ }
170
233
  files.push('AGENTS.md');
171
234
  // CLAUDE.md
172
235
  writeFile(cwd, 'CLAUDE.md', readTemplate('claude-md.md'));
@@ -220,7 +283,7 @@ export function scaffold(cwd, answers, analysis, isRealign = false) {
220
283
  // .thufir marker — full settings
221
284
  const date = new Date().toISOString().split('T')[0];
222
285
  const marker = JSON.stringify({
223
- version: '1.3.0',
286
+ version: '2.1.0',
224
287
  initialized: isRealign ? answers._originalInitialized || date : date,
225
288
  last_realigned: date,
226
289
  colleague_name: answers.colleagueName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thufir",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "AI Colleague — give your repository a brain",
5
5
  "type": "module",
6
6
  "bin": {