Skip to content

feat(sdk): scratchDir/scratchFile — organized temp file management (#790)#793

Merged
tamirdresher merged 11 commits intodevfrom
squad/790-scratch-dir
Apr 5, 2026
Merged

feat(sdk): scratchDir/scratchFile — organized temp file management (#790)#793
tamirdresher merged 11 commits intodevfrom
squad/790-scratch-dir

Conversation

@tamirdresher
Copy link
Copy Markdown
Collaborator

@tamirdresher tamirdresher commented Apr 3, 2026

What

Adds a dedicated scratch directory (.squad/.scratch/) with scratchDir() and scratchFile() utilities for organized temp file management.

Why

Squad and agents create temporary files (prompt files, commit drafts, processing artifacts) in the repo root, causing git status noise and accidental commit risk. Closes #790

How

  • scratchDir(squadRoot, create?) — resolve/create .squad/.scratch/
  • scratchFile(squadRoot, prefix, ext?, content?) — create named temp files with monotonic counter for uniqueness
  • Init scaffolds .squad/.scratch/ and adds it to .gitignore
  • Exported from SDK barrel

Testing

  • npm run build passes
  • npm test passes (8 new tests — dir creation, idempotency, file content, uniqueness, auto-create)

Docs

  • Changeset entry (scratch-dir-utility.md)
  • Feature doc page (will be added in follow-up commit)

Exports

  • scratchDir and scratchFile added to SDK barrel exports

Breaking Changes

None

Waivers

None

…les (#790)

- Add scratchDir() and scratchFile() to resolution.ts
- Export from SDK barrel
- Create .squad/.scratch/ during init, add to .gitignore
- 8 new tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@tamirdresher tamirdresher requested review from Copilot and diberry April 3, 2026 18:43
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

🛫 PR Readiness Check

ℹ️ This comment updates on each push. Last checked: commit 8863fc6

⚠️ 3 item(s) to address before review

Status Check Details
Single commit 11 commits — consider squashing before review
Not in draft Ready for review
Branch up to date Up to date with dev
Copilot review No Copilot review yet — it may still be processing
Changeset present Changeset file found
Scope clean No .squad/ or docs/proposals/ files
No merge conflicts No merge conflicts
Copilot threads resolved 0 active Copilot thread(s) resolved (5 outdated skipped)
CI passing 15 check(s) still running

This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-class “scratch” utilities to the Squad SDK so temporary artifacts are written under .squad/.scratch/ (and gitignored) instead of cluttering the repo root.

Changes:

  • Introduces scratchDir() and scratchFile() utilities in the SDK (and exports them publicly).
  • Updates initSquad() scaffolding to create .squad/.scratch/ and append it to .gitignore.
  • Adds Vitest coverage for scratch directory/file behavior and a changeset entry.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
test/scratch-dir.test.ts Adds tests validating scratch dir creation and scratch file naming/content behavior.
packages/squad-sdk/src/resolution.ts Implements scratchDir() / scratchFile() in the SDK resolution module.
packages/squad-sdk/src/index.ts Exposes scratchDir / scratchFile from the public SDK barrel export.
packages/squad-sdk/src/config/init.ts Ensures .squad/.scratch/ is scaffolded and added to .gitignore during init.
.changeset/scratch-dir-utility.md Declares a minor SDK release for the new scratch utilities.

Comment on lines +508 to +514
export function scratchFile(squadRoot: string, prefix: string, ext: string = '.tmp', content?: string): string {
const dir = scratchDir(squadRoot);
const filename = `${prefix}-${Date.now()}${ext}`;
const filePath = path.join(dir, filename);
if (content !== undefined) {
storage.writeSync(filePath, content);
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scratchFile() builds the target path using path.join(dir, filename) where filename includes user-controlled prefix/ext. If prefix is absolute (starts with / or a drive letter) or contains ../, path.join can escape the scratch directory and write outside .squad/. Please constrain prefix/ext to safe basename-like values and/or run the resulting filePath through the existing ensureSquadPath(filePath, squadRoot) guard before writing.

Copilot uses AI. Check for mistakes.
Comment on lines +509 to +514
const dir = scratchDir(squadRoot);
const filename = `${prefix}-${Date.now()}${ext}`;
const filePath = path.join(dir, filename);
if (content !== undefined) {
storage.writeSync(filePath, content);
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scratchFile() uses only Date.now() for uniqueness. Two calls in the same millisecond with the same prefix/ext will generate identical paths and storage.writeSync() will overwrite the first file. Please incorporate a stronger uniquifier (e.g., randomUUID() or a monotonic counter) so collisions are not possible under fast successive calls.

Copilot uses AI. Check for mistakes.
Comment thread packages/squad-sdk/src/resolution.ts Outdated
Comment on lines +497 to +506
* Create a temporary file inside the scratch directory.
*
* Returns the absolute path to the file. The caller is responsible for
* deleting the file when done (or relying on the cleanup capability).
*
* @param squadRoot - Absolute path to the `.squad/` directory.
* @param prefix - Filename prefix (e.g. `"fleet-prompt"`).
* @param ext - File extension including dot (e.g. `".txt"`). Defaults to `".tmp"`.
* @param content - Optional content to write immediately.
* @returns Absolute path to the created temp file.
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The scratchFile() JSDoc says it "Create[s] a temporary file" and "returns ... the created temp file", but the implementation only writes a file when content is provided; otherwise it just returns a path. Please make the behavior and documentation consistent (either always create the file, or clarify that it only returns a path unless content is provided).

Suggested change
* Create a temporary file inside the scratch directory.
*
* Returns the absolute path to the file. The caller is responsible for
* deleting the file when done (or relying on the cleanup capability).
*
* @param squadRoot - Absolute path to the `.squad/` directory.
* @param prefix - Filename prefix (e.g. `"fleet-prompt"`).
* @param ext - File extension including dot (e.g. `".txt"`). Defaults to `".tmp"`.
* @param content - Optional content to write immediately.
* @returns Absolute path to the created temp file.
* Generate a temporary file path inside the scratch directory.
*
* Returns the absolute path to the file. If `content` is provided, the file is
* created immediately with that content. Otherwise, this function only returns
* a unique path and does not create the file.
*
* The caller is responsible for deleting the file when done (or relying on the
* cleanup capability).
*
* @param squadRoot - Absolute path to the `.squad/` directory.
* @param prefix - Filename prefix (e.g. `"fleet-prompt"`).
* @param ext - File extension including dot (e.g. `".txt"`). Defaults to `".tmp"`.
* @param content - Optional content; when provided, the file is written immediately.
* @returns Absolute path to the temp file, whether or not it has been created yet.

Copilot uses AI. Check for mistakes.
Comment thread test/scratch-dir.test.ts
Comment on lines +59 to +64
it('generates unique filenames on successive calls', () => {
const a = scratchFile(SQUAD_ROOT, 'dup', '.txt', 'a');
// tiny delay to ensure different timestamp
const b = scratchFile(SQUAD_ROOT, 'dup', '.txt', 'b');
expect(a).not.toBe(b);
});
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test asserts successive calls return different filenames, but there is no delay despite the comment and scratchFile() currently keys uniqueness to Date.now(). This can be flaky if both calls occur in the same millisecond. Consider making the test deterministic (e.g., by mocking time) and/or relying on a non-time-based uniquifier in scratchFile().

Copilot uses AI. Check for mistakes.
Comment thread test/scratch-dir.test.ts Outdated
@@ -0,0 +1,71 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { scratchDir, scratchFile } from '@bradygaster/squad-sdk';
import { mkdirSync, rmSync, existsSync, readFileSync, readdirSync } from 'node:fs';
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

readdirSync is imported but never used in this test file. Please remove the unused import to keep the test code clean and avoid unnecessary lint noise.

Suggested change
import { mkdirSync, rmSync, existsSync, readFileSync, readdirSync } from 'node:fs';
import { mkdirSync, rmSync, existsSync, readFileSync } from 'node:fs';

Copilot uses AI. Check for mistakes.
Copilot and others added 3 commits April 3, 2026 22:06
Date.now() can return identical values on rapid successive calls.
Added monotonic counter suffix to guarantee unique filenames.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Sanitize prefix/ext in scratchFile to prevent path traversal
- Proper monotonic counter for sub-millisecond uniqueness
- JSDoc clarifies caller writes content (not always auto-written)
- Remove unused readdirSync import from test
- Add comment explaining monotonic counter in uniqueness test

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add ensureSquadPath guard after path construction to prevent traversal
- Add crypto.randomUUID() for stronger uniqueness alongside monotonic counter
- Update JSDoc to clarify file-creation vs path-only behavior per Copilot suggestion
- Update test regex to match new filename format with UUID segment

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 4, 2026

🟡 Impact Analysis — PR #793

Risk tier: 🟡 MEDIUM

📊 Summary

Metric Count
Files changed 3
Files added 0
Files modified 3
Files deleted 0
Modules touched 3

🎯 Risk Factors

  • 3 files changed (≤5 → LOW)
  • 3 modules touched (2-4 → MEDIUM)

📦 Modules Affected

root (1 file)
  • .changeset/scratch-dir-utility.md
squad-sdk (1 file)
  • packages/squad-sdk/src/resolution.ts
tests (1 file)
  • test/scratch-dir.test.ts

This report is generated automatically for every PR. See #733 for details.

@tamirdresher tamirdresher requested a review from bradygaster April 4, 2026 21:39
# Conflicts:
#	.changeset/scratch-dir-utility.md
#	packages/squad-sdk/src/resolution.ts
#	test/scratch-dir.test.ts
@diberry
Copy link
Copy Markdown
Collaborator

diberry commented Apr 4, 2026

🏗️ Dina Review: APPROVE WITH FOLLOW-UP

scratchDir/scratchFile — temp file management

All 5 Copilot review comments have been addressed in the current code (path traversal sanitized, uniqueness via counter+UUID, JSDoc updated, unused imports removed). Implementation is secure and well-tested (8 new tests).

Follow-up items (non-blocking):

  • Add a \clearScratchDir(squadRoot, olderThanDays?)\ cleanup utility for housekeeping
  • Document scratch file lifecycle in JSDoc (callers should delete when done)

✅ Ready to merge.

… JSDoc + test

- Use path.basename(prefix) to prevent path traversal via ../ or absolute paths
- Replace monotonic counter with crypto.randomBytes(4) hex suffix for collision-free filenames
- Update JSDoc to accurately state: writes content if provided, otherwise returns path only
- Update test regex patterns and comments to reflect new hex-based uniqueness mechanism

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@tamirdresher tamirdresher merged commit fb5be27 into dev Apr 5, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Squad creates temp files in repo root — should use dedicated temp directory

4 participants