pragma-so 0.1.3 → 0.1.5

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/dist/cli/index.js CHANGED
File without changes
@@ -0,0 +1,535 @@
1
+ "use strict";
2
+ /**
3
+ * Bundled skill definitions that ship with every new workspace database.
4
+ * Sourced from https://github.com/anthropics/skills
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.BUNDLED_SKILLS = void 0;
8
+ // Helper constants for embedding backticks inside template literals
9
+ const BT = "`";
10
+ const BT3 = "```";
11
+ exports.BUNDLED_SKILLS = [
12
+ {
13
+ id: "skill_bundled_docx",
14
+ name: "docx",
15
+ description: "Use this skill whenever the user wants to create, read, edit, or manipulate Word documents (.docx files). Triggers include: any mention of 'Word doc', 'word document', '.docx', or requests to produce professional documents with formatting like tables of contents, headings, page numbers, or letterheads.",
16
+ content: `---
17
+ name: docx
18
+ description: "Use this skill whenever the user wants to create, read, edit, or manipulate Word documents (.docx files). Triggers include: any mention of 'Word doc', 'word document', '.docx', or requests to produce professional documents with formatting like tables of contents, headings, page numbers, or letterheads. Also use when extracting or reorganizing content from .docx files, inserting or replacing images in documents, performing find-and-replace in Word files, working with tracked changes or comments, or converting content into a polished Word document. If the user asks for a 'report', 'memo', 'letter', 'template', or similar deliverable as a Word or .docx file, use this skill. Do NOT use for PDFs, spreadsheets, Google Docs, or general coding tasks unrelated to document generation."
19
+ license: Proprietary. LICENSE.txt has complete terms
20
+ ---
21
+
22
+ # DOCX creation, editing, and analysis
23
+
24
+ ## Overview
25
+
26
+ A .docx file is a ZIP archive containing XML files.
27
+
28
+ ## Quick Reference
29
+
30
+ | Task | Approach |
31
+ |------|----------|
32
+ | Read/analyze content | ${BT}pandoc${BT} or unpack for raw XML |
33
+ | Create new document | Use ${BT}docx-js${BT} - see Creating New Documents below |
34
+ | Edit existing document | Unpack → edit XML → repack - see Editing Existing Documents below |
35
+
36
+ ### Converting .doc to .docx
37
+
38
+ Legacy ${BT}.doc${BT} files must be converted before editing:
39
+
40
+ ${BT3}bash
41
+ python scripts/office/soffice.py --headless --convert-to docx document.doc
42
+ ${BT3}
43
+
44
+ ### Reading Content
45
+
46
+ ${BT3}bash
47
+ # Text extraction with tracked changes
48
+ pandoc --track-changes=all document.docx -o output.md
49
+
50
+ # Raw XML access
51
+ python scripts/office/unpack.py document.docx unpacked/
52
+ ${BT3}
53
+
54
+ ### Converting to Images
55
+
56
+ ${BT3}bash
57
+ python scripts/office/soffice.py --headless --convert-to pdf document.docx
58
+ pdftoppm -jpeg -r 150 document.pdf page
59
+ ${BT3}
60
+
61
+ ## Creating New Documents
62
+
63
+ Generate .docx files with JavaScript, then validate. Install: ${BT}npm install -g docx${BT}
64
+
65
+ ### Setup
66
+ ${BT3}javascript
67
+ const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell, ImageRun,
68
+ Header, Footer, AlignmentType, PageOrientation, LevelFormat, ExternalHyperlink,
69
+ InternalHyperlink, Bookmark, FootnoteReferenceRun, PositionalTab,
70
+ PositionalTabAlignment, PositionalTabRelativeTo, PositionalTabLeader,
71
+ TabStopType, TabStopPosition, Column, SectionType,
72
+ TableOfContents, HeadingLevel, BorderStyle, WidthType, ShadingType,
73
+ VerticalAlign, PageNumber, PageBreak } = require('docx');
74
+
75
+ const doc = new Document({ sections: [{ children: [/* content */] }] });
76
+ Packer.toBuffer(doc).then(buffer => fs.writeFileSync("doc.docx", buffer));
77
+ ${BT3}
78
+
79
+ ### Validation
80
+ After creating the file, validate it. If validation fails, unpack, fix the XML, and repack.
81
+ ${BT3}bash
82
+ python scripts/office/validate.py doc.docx
83
+ ${BT3}
84
+
85
+ ## Dependencies
86
+
87
+ - **pandoc**: Text extraction
88
+ - **docx**: ${BT}npm install -g docx${BT} (new documents)
89
+ - **LibreOffice**: PDF conversion
90
+ - **Poppler**: ${BT}pdftoppm${BT} for images`,
91
+ },
92
+ {
93
+ id: "skill_bundled_fdes",
94
+ name: "frontend-design",
95
+ description: "Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications. Generates creative, polished code and UI design that avoids generic AI aesthetics.",
96
+ content: `---
97
+ name: frontend-design
98
+ description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.
99
+ license: Complete terms in LICENSE.txt
100
+ ---
101
+
102
+ This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
103
+
104
+ The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.
105
+
106
+ ## Design Thinking
107
+
108
+ Before coding, understand the context and commit to a BOLD aesthetic direction:
109
+ - **Purpose**: What problem does this interface solve? Who uses it?
110
+ - **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc.
111
+ - **Constraints**: Technical requirements (framework, performance, accessibility).
112
+ - **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?
113
+
114
+ **CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.
115
+
116
+ Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
117
+ - Production-grade and functional
118
+ - Visually striking and memorable
119
+ - Cohesive with a clear aesthetic point-of-view
120
+ - Meticulously refined in every detail
121
+
122
+ ## Frontend Aesthetics Guidelines
123
+
124
+ Focus on:
125
+ - **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics.
126
+ - **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.
127
+ - **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Focus on high-impact moments: one well-orchestrated page load with staggered reveals creates more delight than scattered micro-interactions.
128
+ - **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
129
+ - **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors.
130
+
131
+ NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes, predictable layouts, and cookie-cutter design that lacks context-specific character.
132
+
133
+ Remember: Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.`,
134
+ },
135
+ {
136
+ id: "skill_bundled_pdf0",
137
+ name: "pdf",
138
+ description: "Use this skill whenever the user wants to do anything with PDF files. This includes reading or extracting text/tables from PDFs, combining or merging multiple PDFs into one, splitting PDFs apart, rotating pages, adding watermarks, creating new PDFs, filling PDF forms, encrypting/decrypting PDFs, extracting images, and OCR on scanned PDFs.",
139
+ content: `---
140
+ name: pdf
141
+ description: Use this skill whenever the user wants to do anything with PDF files. This includes reading or extracting text/tables from PDFs, combining or merging multiple PDFs into one, splitting PDFs apart, rotating pages, adding watermarks, creating new PDFs, filling PDF forms, encrypting/decrypting PDFs, extracting images, and OCR on scanned PDFs to make them searchable. If the user mentions a .pdf file or asks to produce one, use this skill.
142
+ license: Proprietary. LICENSE.txt has complete terms
143
+ ---
144
+
145
+ # PDF Processing Guide
146
+
147
+ ## Overview
148
+
149
+ This guide covers essential PDF processing operations using Python libraries and command-line tools.
150
+
151
+ ## Quick Start
152
+
153
+ ${BT3}python
154
+ from pypdf import PdfReader, PdfWriter
155
+
156
+ # Read a PDF
157
+ reader = PdfReader("document.pdf")
158
+ print(f"Pages: {len(reader.pages)}")
159
+
160
+ # Extract text
161
+ text = ""
162
+ for page in reader.pages:
163
+ text += page.extract_text()
164
+ ${BT3}
165
+
166
+ ## Python Libraries
167
+
168
+ ### pypdf - Basic Operations
169
+
170
+ #### Merge PDFs
171
+ ${BT3}python
172
+ from pypdf import PdfWriter, PdfReader
173
+
174
+ writer = PdfWriter()
175
+ for pdf_file in ["doc1.pdf", "doc2.pdf", "doc3.pdf"]:
176
+ reader = PdfReader(pdf_file)
177
+ for page in reader.pages:
178
+ writer.add_page(page)
179
+
180
+ with open("merged.pdf", "wb") as output:
181
+ writer.write(output)
182
+ ${BT3}
183
+
184
+ ### pdfplumber - Text and Table Extraction
185
+
186
+ ${BT3}python
187
+ import pdfplumber
188
+
189
+ with pdfplumber.open("document.pdf") as pdf:
190
+ for page in pdf.pages:
191
+ text = page.extract_text()
192
+ print(text)
193
+ ${BT3}
194
+
195
+ ### reportlab - Create PDFs
196
+
197
+ ${BT3}python
198
+ from reportlab.lib.pagesizes import letter
199
+ from reportlab.pdfgen import canvas
200
+
201
+ c = canvas.Canvas("hello.pdf", pagesize=letter)
202
+ width, height = letter
203
+ c.drawString(100, height - 100, "Hello World!")
204
+ c.save()
205
+ ${BT3}
206
+
207
+ ## Command-Line Tools
208
+
209
+ ### pdftotext (poppler-utils)
210
+ ${BT3}bash
211
+ pdftotext input.pdf output.txt
212
+ pdftotext -layout input.pdf output.txt
213
+ ${BT3}
214
+
215
+ ### qpdf
216
+ ${BT3}bash
217
+ qpdf --empty --pages file1.pdf file2.pdf -- merged.pdf
218
+ qpdf input.pdf --pages . 1-5 -- pages1-5.pdf
219
+ ${BT3}
220
+
221
+ ## Quick Reference
222
+
223
+ | Task | Best Tool | Command/Code |
224
+ |------|-----------|--------------|
225
+ | Merge PDFs | pypdf | ${BT}writer.add_page(page)${BT} |
226
+ | Split PDFs | pypdf | One page per file |
227
+ | Extract text | pdfplumber | ${BT}page.extract_text()${BT} |
228
+ | Extract tables | pdfplumber | ${BT}page.extract_tables()${BT} |
229
+ | Create PDFs | reportlab | Canvas or Platypus |
230
+ | Command line merge | qpdf | ${BT}qpdf --empty --pages ...${BT} |
231
+ | OCR scanned PDFs | pytesseract | Convert to image first |
232
+ | Fill PDF forms | pdf-lib or pypdf | See FORMS.md |`,
233
+ },
234
+ {
235
+ id: "skill_bundled_pptx",
236
+ name: "pptx",
237
+ description: 'Use this skill any time a .pptx file is involved in any way — as input, output, or both. This includes: creating slide decks, pitch decks, or presentations; reading, parsing, or extracting text from any .pptx file; editing, modifying, or updating existing presentations.',
238
+ content: `---
239
+ name: pptx
240
+ description: "Use this skill any time a .pptx file is involved in any way — as input, output, or both. This includes: creating slide decks, pitch decks, or presentations; reading, parsing, or extracting text from any .pptx file (even if the extracted content will be used elsewhere, like in an email or summary); editing, modifying, or updating existing presentations; combining or splitting slide files; working with templates, layouts, speaker notes, or comments. Trigger whenever the user mentions \\"deck,\\" \\"slides,\\" \\"presentation,\\" or references a .pptx filename, regardless of what they plan to do with the content afterward. If a .pptx file needs to be opened, created, or touched, use this skill."
241
+ license: Proprietary. LICENSE.txt has complete terms
242
+ ---
243
+
244
+ # PPTX Skill
245
+
246
+ ## Quick Reference
247
+
248
+ | Task | Guide |
249
+ |------|-------|
250
+ | Read/analyze content | ${BT}python -m markitdown presentation.pptx${BT} |
251
+ | Edit or create from template | Read editing.md |
252
+ | Create from scratch | Read pptxgenjs.md |
253
+
254
+ ## Reading Content
255
+
256
+ ${BT3}bash
257
+ # Text extraction
258
+ python -m markitdown presentation.pptx
259
+
260
+ # Visual overview
261
+ python scripts/thumbnail.py presentation.pptx
262
+
263
+ # Raw XML
264
+ python scripts/office/unpack.py presentation.pptx unpacked/
265
+ ${BT3}
266
+
267
+ ## Design Ideas
268
+
269
+ **Don't create boring slides.** Plain bullets on a white background won't impress anyone.
270
+
271
+ ### Before Starting
272
+
273
+ - **Pick a bold, content-informed color palette**
274
+ - **Dominance over equality**: One color should dominate (60-70% visual weight)
275
+ - **Dark/light contrast**: Dark backgrounds for title + conclusion slides, light for content
276
+ - **Commit to a visual motif**: Pick ONE distinctive element and repeat it
277
+
278
+ ### Typography
279
+
280
+ | Element | Size |
281
+ |---------|------|
282
+ | Slide title | 36-44pt bold |
283
+ | Section header | 20-24pt bold |
284
+ | Body text | 14-16pt |
285
+ | Captions | 10-12pt muted |
286
+
287
+ ## Dependencies
288
+
289
+ - ${BT}pip install "markitdown[pptx]"${BT} - text extraction
290
+ - ${BT}pip install Pillow${BT} - thumbnail grids
291
+ - ${BT}npm install -g pptxgenjs${BT} - creating from scratch
292
+ - LibreOffice (${BT}soffice${BT}) - PDF conversion
293
+ - Poppler (${BT}pdftoppm${BT}) - PDF to images`,
294
+ },
295
+ {
296
+ id: "skill_bundled_xlsx",
297
+ name: "xlsx",
298
+ description: 'Use this skill any time a spreadsheet file is the primary input or output. This means any task where the user wants to: open, read, edit, or fix an existing .xlsx, .xlsm, .csv, or .tsv file; create a new spreadsheet from scratch or from other data sources; or convert between tabular file formats.',
299
+ content: `---
300
+ name: xlsx
301
+ description: "Use this skill any time a spreadsheet file is the primary input or output. This means any task where the user wants to: open, read, edit, or fix an existing .xlsx, .xlsm, .csv, or .tsv file (e.g., adding columns, computing formulas, formatting, charting, cleaning messy data); create a new spreadsheet from scratch or from other data sources; or convert between tabular file formats. Trigger especially when the user references a spreadsheet file by name or path. The deliverable must be a spreadsheet file. Do NOT trigger when the primary deliverable is a Word document, HTML report, standalone Python script, database pipeline, or Google Sheets API integration, even if tabular data is involved."
302
+ license: Proprietary. LICENSE.txt has complete terms
303
+ ---
304
+
305
+ # XLSX creation, editing, and analysis
306
+
307
+ ## Overview
308
+
309
+ A user may ask you to create, edit, or analyze the contents of an .xlsx file.
310
+
311
+ ## CRITICAL: Use Formulas, Not Hardcoded Values
312
+
313
+ **Always use Excel formulas instead of calculating values in Python and hardcoding them.**
314
+
315
+ ### Reading and analyzing data
316
+
317
+ ${BT3}python
318
+ import pandas as pd
319
+
320
+ df = pd.read_excel('file.xlsx')
321
+ all_sheets = pd.read_excel('file.xlsx', sheet_name=None)
322
+ ${BT3}
323
+
324
+ ### Creating new Excel files
325
+
326
+ ${BT3}python
327
+ from openpyxl import Workbook
328
+ from openpyxl.styles import Font, PatternFill, Alignment
329
+
330
+ wb = Workbook()
331
+ sheet = wb.active
332
+ sheet['A1'] = 'Hello'
333
+ sheet['B2'] = '=SUM(A1:A10)'
334
+ sheet['A1'].font = Font(bold=True, color='FF0000')
335
+ wb.save('output.xlsx')
336
+ ${BT3}
337
+
338
+ ### Editing existing Excel files
339
+
340
+ ${BT3}python
341
+ from openpyxl import load_workbook
342
+
343
+ wb = load_workbook('existing.xlsx')
344
+ sheet = wb.active
345
+ sheet['A1'] = 'New Value'
346
+ wb.save('modified.xlsx')
347
+ ${BT3}
348
+
349
+ ## Recalculating formulas
350
+
351
+ ${BT3}bash
352
+ python scripts/recalc.py <excel_file> [timeout_seconds]
353
+ ${BT3}
354
+
355
+ ## Best Practices
356
+
357
+ - **pandas**: Best for data analysis, bulk operations, and simple data export
358
+ - **openpyxl**: Best for complex formatting, formulas, and Excel-specific features
359
+ - Cell indices are 1-based
360
+ - Use ${BT}data_only=True${BT} to read calculated values
361
+ - Formulas are preserved but not evaluated - use scripts/recalc.py to update values`,
362
+ },
363
+ {
364
+ id: "skill_bundled_dcoa",
365
+ name: "doc-coauthoring",
366
+ description: "Guide users through a structured workflow for co-authoring documentation. Use when user wants to write documentation, proposals, technical specs, decision docs, or similar structured content.",
367
+ content: `---
368
+ name: doc-coauthoring
369
+ description: Guide users through a structured workflow for co-authoring documentation. Use when user wants to write documentation, proposals, technical specs, decision docs, or similar structured content. This workflow helps users efficiently transfer context, refine content through iteration, and verify the doc works for readers.
370
+ ---
371
+
372
+ # Doc Co-Authoring Workflow
373
+
374
+ This skill provides a structured workflow for guiding users through collaborative document creation. Act as an active guide, walking users through three stages: Context Gathering, Refinement & Structure, and Reader Testing.
375
+
376
+ ## Stage 1: Context Gathering
377
+
378
+ **Goal:** Close the gap between what the user knows and what Claude knows.
379
+
380
+ ### Initial Questions
381
+ 1. What type of document is this?
382
+ 2. Who's the primary audience?
383
+ 3. What's the desired impact when someone reads this?
384
+ 4. Is there a template or specific format to follow?
385
+ 5. Any other constraints or context to know?
386
+
387
+ Then encourage the user to dump all relevant context.
388
+
389
+ ## Stage 2: Refinement & Structure
390
+
391
+ **Goal:** Build the document section by section through brainstorming, curation, and iterative refinement.
392
+
393
+ For each section:
394
+ 1. Ask clarifying questions about what to include
395
+ 2. Brainstorm 5-20 options
396
+ 3. User indicates what to keep/remove/combine
397
+ 4. Draft the section
398
+ 5. Refine through surgical edits
399
+
400
+ ## Stage 3: Reader Testing
401
+
402
+ **Goal:** Test the document with a fresh Claude (no context) to verify it works for readers.
403
+
404
+ 1. Predict reader questions
405
+ 2. Test with sub-agent or fresh conversation
406
+ 3. Run additional checks for ambiguity and contradictions
407
+ 4. Fix any gaps found`,
408
+ },
409
+ {
410
+ id: "skill_bundled_jnb0",
411
+ name: "jupyter-notebook",
412
+ description: "Use this skill when the user wants to create, edit, read, or work with Jupyter notebooks (.ipynb files). Covers creating notebooks with code cells, markdown cells, and outputs, as well as running and managing notebook workflows.",
413
+ content: `---
414
+ name: jupyter-notebook
415
+ description: Use this skill when the user wants to create, edit, read, or work with Jupyter notebooks (.ipynb files). Covers creating notebooks with code cells, markdown cells, and outputs, as well as running and managing notebook workflows.
416
+ ---
417
+
418
+ # Jupyter Notebook Skill
419
+
420
+ ## Overview
421
+
422
+ Jupyter notebooks (.ipynb) are JSON documents containing an ordered list of cells (code, markdown, or raw) with optional outputs. They are widely used for data analysis, machine learning, scientific computing, and documentation.
423
+
424
+ ## Reading Notebooks
425
+
426
+ ${BT3}python
427
+ import json
428
+
429
+ with open("notebook.ipynb", "r") as f:
430
+ nb = json.load(f)
431
+
432
+ for cell in nb["cells"]:
433
+ print(f"Type: {cell['cell_type']}")
434
+ print("".join(cell["source"]))
435
+ print("---")
436
+ ${BT3}
437
+
438
+ Or use nbformat:
439
+
440
+ ${BT3}python
441
+ import nbformat
442
+
443
+ nb = nbformat.read("notebook.ipynb", as_version=4)
444
+ for cell in nb.cells:
445
+ print(cell.cell_type, ":", "".join(cell.source)[:80])
446
+ ${BT3}
447
+
448
+ ## Creating Notebooks
449
+
450
+ ${BT3}python
451
+ import nbformat
452
+
453
+ nb = nbformat.v4.new_notebook()
454
+ nb.cells = [
455
+ nbformat.v4.new_markdown_cell("# My Notebook\\n\\nThis is an example."),
456
+ nbformat.v4.new_code_cell("import pandas as pd\\nprint('Hello')"),
457
+ nbformat.v4.new_markdown_cell("## Results\\n\\nAnalysis below."),
458
+ nbformat.v4.new_code_cell("df = pd.DataFrame({'x': [1,2,3]})\\ndf"),
459
+ ]
460
+
461
+ with open("output.ipynb", "w") as f:
462
+ nbformat.write(nb, f)
463
+ ${BT3}
464
+
465
+ ## Running Notebooks
466
+
467
+ ${BT3}bash
468
+ # Execute notebook and save output in place
469
+ jupyter nbconvert --to notebook --execute notebook.ipynb --output notebook.ipynb
470
+
471
+ # Execute and convert to HTML
472
+ jupyter nbconvert --to html --execute notebook.ipynb
473
+
474
+ # Execute and convert to PDF
475
+ jupyter nbconvert --to pdf --execute notebook.ipynb
476
+
477
+ # Run with papermill (parameterized execution)
478
+ papermill input.ipynb output.ipynb -p param_name value
479
+ ${BT3}
480
+
481
+ ## Editing Notebooks
482
+
483
+ ${BT3}python
484
+ import nbformat
485
+
486
+ nb = nbformat.read("notebook.ipynb", as_version=4)
487
+
488
+ # Add a cell
489
+ nb.cells.append(nbformat.v4.new_code_cell("print('new cell')"))
490
+
491
+ # Modify a cell
492
+ nb.cells[0].source = "# Updated Title"
493
+
494
+ # Delete a cell
495
+ del nb.cells[2]
496
+
497
+ # Insert at position
498
+ nb.cells.insert(1, nbformat.v4.new_markdown_cell("## Inserted Section"))
499
+
500
+ nbformat.write(nb, open("notebook.ipynb", "w"))
501
+ ${BT3}
502
+
503
+ ## Converting Formats
504
+
505
+ ${BT3}bash
506
+ # To Python script
507
+ jupyter nbconvert --to script notebook.ipynb
508
+
509
+ # To Markdown
510
+ jupyter nbconvert --to markdown notebook.ipynb
511
+
512
+ # To HTML (no execution)
513
+ jupyter nbconvert --to html notebook.ipynb
514
+
515
+ # From Python script to notebook
516
+ jupytext --to notebook script.py
517
+ ${BT3}
518
+
519
+ ## Best Practices
520
+
521
+ - Use markdown cells to document your analysis flow
522
+ - Keep code cells focused on one logical step
523
+ - Clear outputs before committing to version control
524
+ - Use requirements.txt or environment.yml for dependencies
525
+ - Consider using jupytext for version-control-friendly notebook formats
526
+
527
+ ## Dependencies
528
+
529
+ - **nbformat**: Reading/writing notebooks programmatically
530
+ - **jupyter**: Core notebook infrastructure
531
+ - **nbconvert**: Converting between formats
532
+ - **papermill**: Parameterized notebook execution
533
+ - **jupytext**: Notebook/script synchronization`,
534
+ },
535
+ ];
@@ -296,7 +296,7 @@ async function createFreshTaskWorktrees(input) {
296
296
  branchName,
297
297
  startPoint: baseCommit,
298
298
  });
299
- await symlinkOrCopyGitignoredFiles({
299
+ await copyGitignoredEntriesToWorktree({
300
300
  sourceRepoPath,
301
301
  worktreePath: taskRepoPath,
302
302
  excludedEntries: getManagedIgnoredEntryExcludes(relativePath),
@@ -328,7 +328,7 @@ async function createFollowupTaskWorktrees(input) {
328
328
  branchName,
329
329
  startPoint: predecessorHead,
330
330
  });
331
- await symlinkOrCopyGitignoredFiles({
331
+ await copyGitignoredEntriesToWorktree({
332
332
  sourceRepoPath,
333
333
  worktreePath: taskRepoPath,
334
334
  excludedEntries: getManagedIgnoredEntryExcludes(relativePath),
@@ -359,7 +359,7 @@ async function ensureExistingTaskWorktrees(input) {
359
359
  branchName: input.gitState.branch_name,
360
360
  startPoint: repo.base_commit,
361
361
  });
362
- await symlinkOrCopyGitignoredFiles({
362
+ await copyGitignoredEntriesToWorktree({
363
363
  sourceRepoPath,
364
364
  worktreePath: taskRepoPath,
365
365
  excludedEntries: getManagedIgnoredEntryExcludes(relativePath),
@@ -419,11 +419,16 @@ async function getTopLevelGitignoredEntries(repoPath, excludedEntries = new Set(
419
419
  }
420
420
  return entries;
421
421
  }
422
- async function symlinkOrCopyGitignoredFiles(input) {
422
+ async function copyGitignoredEntriesToWorktree(input) {
423
+ const sourceRepoPath = (0, node_path_1.resolve)(input.sourceRepoPath);
424
+ const worktreePath = (0, node_path_1.resolve)(input.worktreePath);
425
+ if (sourceRepoPath === worktreePath) {
426
+ return;
427
+ }
423
428
  const entries = await getTopLevelGitignoredEntries(input.sourceRepoPath, input.excludedEntries);
424
429
  for (const entry of entries) {
425
- const sourcePath = (0, node_path_1.join)(input.sourceRepoPath, entry);
426
- const targetPath = (0, node_path_1.join)(input.worktreePath, entry);
430
+ const sourcePath = (0, node_path_1.resolve)(sourceRepoPath, entry);
431
+ const targetPath = (0, node_path_1.resolve)(worktreePath, entry);
427
432
  try {
428
433
  if (sourcePath === targetPath)
429
434
  continue;
@@ -434,14 +439,7 @@ async function symlinkOrCopyGitignoredFiles(input) {
434
439
  const sourceStat = await (0, promises_1.lstat)(sourcePath).catch(() => null);
435
440
  if (!sourceStat)
436
441
  continue;
437
- if (sourceStat.isDirectory()) {
438
- // Large directories → symlink
439
- await (0, promises_1.symlink)(sourcePath, targetPath);
440
- }
441
- else if (sourceStat.isFile()) {
442
- // Small files → copy (avoids shared-mutation for .env etc.)
443
- await (0, promises_1.copyFile)(sourcePath, targetPath);
444
- }
442
+ await copyIgnoredEntry(sourcePath, targetPath);
445
443
  }
446
444
  catch {
447
445
  // Silently continue on individual failures (race conditions, permissions, etc.)
@@ -449,27 +447,33 @@ async function symlinkOrCopyGitignoredFiles(input) {
449
447
  }
450
448
  }
451
449
  async function syncGitignoredFilesBackToSource(input) {
452
- const entries = await getTopLevelGitignoredEntries(input.sourceRepoPath, input.excludedEntries);
450
+ const sourceRepoPath = (0, node_path_1.resolve)(input.sourceRepoPath);
451
+ const worktreePath = (0, node_path_1.resolve)(input.worktreePath);
452
+ if (sourceRepoPath === worktreePath) {
453
+ return;
454
+ }
455
+ const [sourceEntries, worktreeEntries] = await Promise.all([
456
+ getTopLevelGitignoredEntries(input.sourceRepoPath, input.excludedEntries),
457
+ getTopLevelGitignoredEntries(input.worktreePath, input.excludedEntries),
458
+ ]);
459
+ const entries = [...new Set([...sourceEntries, ...worktreeEntries])];
453
460
  for (const entry of entries) {
454
- const worktreeEntryPath = (0, node_path_1.join)(input.worktreePath, entry);
455
- const sourceEntryPath = (0, node_path_1.join)(input.sourceRepoPath, entry);
461
+ const worktreeEntryPath = (0, node_path_1.resolve)(worktreePath, entry);
462
+ const sourceEntryPath = (0, node_path_1.resolve)(sourceRepoPath, entry);
456
463
  try {
457
464
  if (worktreeEntryPath === sourceEntryPath)
458
465
  continue;
459
466
  const worktreeStat = await (0, promises_1.lstat)(worktreeEntryPath).catch(() => null);
460
- if (!worktreeStat)
461
- continue;
462
- // If it's still a symlink, source already has the content — nothing to do
463
- if (worktreeStat.isSymbolicLink())
467
+ const sourceStat = await (0, promises_1.lstat)(sourceEntryPath).catch(() => null);
468
+ if (!worktreeStat) {
469
+ if (sourceStat) {
470
+ await (0, promises_1.rm)(sourceEntryPath, { recursive: true, force: true });
471
+ }
464
472
  continue;
465
- if (worktreeStat.isFile()) {
466
- // Agent replaced copied file — copy it back to source
467
- await (0, promises_1.copyFile)(worktreeEntryPath, sourceEntryPath);
468
473
  }
469
- else if (worktreeStat.isDirectory()) {
470
- // Agent replaced symlink with real directory — copy back to source
474
+ if (worktreeStat.isFile() || worktreeStat.isDirectory() || worktreeStat.isSymbolicLink()) {
471
475
  await (0, promises_1.rm)(sourceEntryPath, { recursive: true, force: true });
472
- await (0, promises_1.cp)(worktreeEntryPath, sourceEntryPath, { recursive: true });
476
+ await copyIgnoredEntry(worktreeEntryPath, sourceEntryPath);
473
477
  }
474
478
  }
475
479
  catch {
@@ -477,6 +481,14 @@ async function syncGitignoredFilesBackToSource(input) {
477
481
  }
478
482
  }
479
483
  }
484
+ async function copyIgnoredEntry(sourcePath, targetPath) {
485
+ await (0, promises_1.mkdir)((0, node_path_1.dirname)(targetPath), { recursive: true });
486
+ await (0, promises_1.cp)(sourcePath, targetPath, {
487
+ recursive: true,
488
+ force: true,
489
+ dereference: false,
490
+ });
491
+ }
480
492
  async function discoverFlatRepoPaths(paths) {
481
493
  const rootRepo = await isGitRepo(paths.workspaceDir);
482
494
  if (!rootRepo) {
@@ -184,8 +184,8 @@ function buildWorkerPrompt(input) {
184
184
  "If you changed code, submit at least one runnable validation command for the task window.",
185
185
  `For richer testing UIs with multiple processes and panels, use \`submit-testing-config\`:`,
186
186
  submitTestingConfigCommand,
187
- `The config JSON has: \`processes\` (array of {name, command, cwd?, port?, ready_pattern?}) and \`panels\` (array of panel objects). Panel types: \`web-preview\` ({type, title, process, path?, devices?}), \`api-tester\` ({type, title, process, endpoints: [{method, path, description?, body?, headers?}]}), \`terminal\` ({type, title, command, cwd?}), \`log-viewer\` ({type, title, process}). Optional: \`setup\` (array of setup commands), \`layout\` ("tabs"|"grid").`,
188
- `Example: \`--config '{"processes":[{"name":"server","command":"npm run dev","cwd":"code/my-app","port":3000,"ready_pattern":"ready on"}],"panels":[{"type":"web-preview","title":"App","process":"server"}]}'\``,
187
+ `The config JSON has: \`processes\` (array of {name, command, cwd?, ready_pattern?}) and \`panels\` (array of panel objects). Panel types: \`web-preview\` ({type, title, process, path?, devices?}), \`api-tester\` ({type, title, process, endpoints: [{method, path, description?, body?, headers?}]}), \`terminal\` ({type, title, command, cwd?}), \`log-viewer\` ({type, title, process}). Optional: \`setup\` (array of setup commands), \`layout\` ("tabs"|"grid").`,
188
+ `Example: \`--config '{"processes":[{"name":"server","command":"npm run dev","cwd":"code/my-app","ready_pattern":"ready on"}],"panels":[{"type":"web-preview","title":"App","process":"server"}]}'\``,
189
189
  `Fallback: for simple single-command cases, use:`,
190
190
  "Include the exact run directory for each command (for example: `--cwd \"code/default/my-app\"`):",
191
191
  submitTestsCommand,