Skip to content

Add manifest-driven scaffolding with create and scaffold-cleanup commands#97

Open
ericelliott wants to merge 15 commits intomainfrom
claude/execute-aidd-create-epic-orEHx
Open

Add manifest-driven scaffolding with create and scaffold-cleanup commands#97
ericelliott wants to merge 15 commits intomainfrom
claude/execute-aidd-create-epic-orEHx

Conversation

@ericelliott
Copy link
Collaborator

@ericelliott ericelliott commented Feb 18, 2026

Overview

This PR implements the npx aidd create epic by adding two new CLI subcommands that enable manifest-driven project scaffolding:

  • create [type|URI] <folder> — Scaffolds new projects from named scaffolds, local file:// URIs, or remote HTTP(S) URIs
  • scaffold-cleanup [folder] — Removes temporary .aidd/ working directories after scaffolding

New Requirements

  • For custom URL - assume those point to GitHub repos. Download the latest release into the scaffold folder
  • Add documentation explaining how to stage files to be included in the scaffold: use the standard files array in package.json as if you were packaging an npm module

Key Changes

New Modules

lib/scaffold-resolver.js

  • Resolves scaffold sources (named, file://, or HTTP/HTTPS URIs) to local paths
  • Handles remote code warnings and user confirmation before downloading
  • Fetches and caches remote scaffolds in .aidd/scaffold/
  • Displays README contents to users
  • Supports AIDD_CUSTOM_EXTENSION_URI environment variable for custom defaults

lib/scaffold-runner.js

  • Parses YAML manifest files into executable steps
  • Executes run steps as shell commands
  • Executes prompt steps via agent CLI (default: claude) with proper argument escaping
  • Runs bin/extension.js after all manifest steps if present
  • Halts on first failure with clear error messages

lib/scaffold-cleanup.js

  • Removes .aidd/ directory from scaffolded projects
  • Reports whether cleanup was performed or nothing was found

CLI Integration

Updated bin/aidd.js to add:

  • create command with --agent flag for specifying the AI agent
  • scaffold-cleanup command for post-scaffold cleanup
  • Proper error handling and user feedback with chalk colors

Scaffolds

Added two scaffold fixtures:

ai/scaffolds/scaffold-example/ — E2E test fixture

  • Initializes npm project with npm init -y
  • Installs testing dependencies (riteway, vitest, @playwright/test, error-causes, @paralleldrive/cuid2)
  • Configures test script

ai/scaffolds/next-shadcn/ — Default scaffold (placeholder)

  • Stub implementation with prompt step
  • Full implementation planned for future release

Tests

Added comprehensive test suites:

  • lib/scaffold-resolver.test.js (378 lines) — Tests named scaffolds, file:// URIs, HTTP/HTTPS URIs, remote code warnings, and environment variable handling
  • lib/scaffold-runner.test.js (329 lines) — Tests manifest parsing, step execution, error handling, and extension.js invocation
  • lib/scaffold-cleanup.test.js (70 lines) — Tests directory removal and not-found cases
  • bin/create-e2e.test.js (292 lines) — End-to-end tests for create and scaffold-cleanup commands with real file I/O

Security Considerations

  • Remote HTTP/HTTPS scaffolds require explicit user confirmation before download
  • Warning message includes the full URI for transparency
  • Prompt step arguments passed as array elements (no shell injection risk)
  • User can cancel remote downloads at confirmation prompt

Test Plan

All changes are covered by automated tests:

  • Unit tests verify resolver, runner, and cleanup logic with mocked I/O
  • E2E tests verify CLI integration with real file system operations
  • Tests use temporary directories and proper cleanup
  • CI will run full test suite to validate functionality

https://claude.ai/code/session_01VxNxs3Ly9UQh9TZpJpjG63


Note

Medium Risk
Adds new CLI commands that download and execute scaffold-defined commands (including optional remote tarballs), so failures or validation gaps could impact developer machines despite confirmation prompts and improved validation.

Overview
Adds a manifest-driven scaffolding workflow to the CLI: aidd create now resolves a scaffold from a named bundle, file:// URI, or downloaded HTTP(S) tarball (with remote-code confirmation), prints the scaffold README, runs SCAFFOLD-MANIFEST.yml steps (run shell commands and prompt agent invocations), optionally runs bin/extension.js, and suggests scaffold-cleanup with an absolute path.

Introduces aidd verify-scaffold (preflight manifest validation) and aidd scaffold-cleanup (removes .aidd/ working dir), adds new typed scaffold error handling, and hardens manifest parsing/validation (rejects malformed/ambiguous steps; fails fast when the manifest is missing). Bundles ai/scaffolds (next-shadcn stub and scaffold-example fixture), adds scaffold authoring docs, creates CLAUDE.md during install alongside updated AGENTS.md, and updates the pre-commit hook to run unit tests only (E2E run manually).

Written by Cursor Bugbot for commit dd1fdaf. This will update automatically on new commits. Configure here.

Implements the `npx aidd create` epic with manifest-driven scaffolding:

- `aidd create [type|URI] <folder>` resolves named scaffolds, HTTP/HTTPS
  URIs (with remote-code warning + confirmation), and file:// URIs.
  Reads SCAFFOLD-MANIFEST.yml and executes run/prompt steps sequentially;
  supports `--agent <name>` (default: claude) for prompt steps; warns about
  remote code and halts on step failure; leaves .aidd/scaffold/ in place
  and suggests scaffold-cleanup on completion.
- `aidd scaffold-cleanup [folder]` removes the .aidd/ working directory
  and reports gracefully when nothing exists to clean up.
- `lib/scaffold-resolver.js` with full unit test coverage.
- `lib/scaffold-runner.js` with parseManifest (gray-matter YAML parsing)
  and runManifest with injectable execStep for isolation.
- `lib/scaffold-cleanup.js` with unit tests.
- `ai/scaffolds/scaffold-example` — minimal E2E test fixture that inits
  npm, sets vitest test script, and installs riteway/vitest/playwright/
  error-causes/@paralleldrive/cuid2 at @latest.
- `ai/scaffolds/next-shadcn` — default named scaffold stub with README
  describing the intended Next.js + shadcn/ui setup.
- `bin/create-e2e.test.js` — E2E tests covering scaffold creation,
  AIDD_CUSTOM_EXTENSION_URI file:// override, scaffold-cleanup, and
  --agent flag passthrough.

https://claude.ai/code/session_01VxNxs3Ly9UQh9TZpJpjG63
- Fix command injection in scaffold-runner: prompt steps now use
  spawn([agent, promptText]) array form instead of shell interpolation,
  so prompt content cannot inject shell commands
- Fix extension.js execution to also use array spawn (node, path)
- Add 30s network timeout to defaultFetchText to prevent indefinite hangs
- Extract isHttpUrl/isFileUrl predicates and DEFAULT_SCAFFOLD_TYPE constant
  in scaffold-resolver for readability and to eliminate repeated string checks
- Remove dead _noFetch variable from scaffold-resolver test
- Fix fragile cwd-default test in scaffold-cleanup using vi.spyOn(process, cwd)
  instead of depending on ambient filesystem state
- Mark npx-aidd-create-epic as DONE and log activity

https://claude.ai/code/session_01VxNxs3Ly9UQh9TZpJpjG63
Copilot AI review requested due to automatic review settings February 18, 2026 04:22
@ericelliott
Copy link
Collaborator Author

@cursor please /review

@cursor
Copy link

cursor bot commented Feb 18, 2026

✅ No technical debt markers (TODO/FIXME) in new code

9. Specific Issues Summary

Critical:
None

High:

  1. Missing error-causes usage - Should use project's error handling standard
  2. SSRF vulnerability - No validation on HTTP/HTTPS URLs to prevent SSRF attacks

Medium:
3. No integrity checks - Remote scaffolds lack checksum/signature verification
4. Missing JSDoc - Public API functions lack minimal docblocks

Low:
5. Security logging - Consider adding audit trail for scaffold operations
6. Error context - Errors should include more structured metadata

10. Actionable Recommendations

Must Fix (Before Merge):

  1. Add error-causes support:
// In lib/scaffold-resolver.js
import { createError } from "error-causes";

// Replace all throw new Error() with:
throw createError({
  name: 'ScaffoldError',
  message: 'Remote extension download cancelled by user.',
  code: 'USER_CANCELLED',
  uri: effectiveType
});
  1. Add SSRF protection:
import { URL } from 'url';

const validateHttpUrl = (urlString) => {
  try {
    const url = new URL(urlString);
    const hostname = url.hostname;
    
    // Block private IP ranges
    if (
      hostname === 'localhost' ||
      hostname.startsWith('127.') ||
      hostname.startsWith('10.') ||
      hostname.startsWith('192.168.') ||
      hostname.startsWith('169.254.') ||
      hostname.match(/^172\.(1[6-9]|2[0-9]|3[0-1])\./)
    ) {
      throw createError({
        name: 'InvalidScaffoldUri',
        message: 'Private IP addresses are not allowed',
        code: 'PRIVATE_IP_BLOCKED',
        uri: urlString
      });
    }
    
    return true;
  } catch (err) {
    throw createError({
      name: 'InvalidScaffoldUri',
      message: 'Invalid URL format',
      code: 'INVALID_URL',
      uri: urlString,
      cause: err
    });
  }
};

// Use before fetching
if (isHttpUrl(effectiveType)) {
  validateHttpUrl(effectiveType);
  // ... rest of logic
}

Should Fix (Post-Merge):

  1. Add JSDoc to public APIs:
/**
 * Resolves a scaffold source to local file paths.
 * Supports named scaffolds, file:// URIs, and HTTP(S) URIs.
 * @param {Object} options - Configuration options
 * @param {string} [options.type] - Scaffold type: name, file://, or http(s):// URI
 * @param {string} options.folder - Target folder path
 * @param {string} [options.packageRoot] - Package root directory
 * @returns {Promise<{manifestPath: string, extensionJsPath: string, readmePath: string}>}
 */
  1. Add integrity verification for remote scaffolds:

    • Consider supporting a .sha256 file alongside remote scaffolds
    • Verify checksum before executing
  2. Add security logging:

    • Log scaffold downloads with timestamps
    • Log executed commands for audit trail

11. Vision Alignment

✅ The PR aligns well with the vision document:

  • Enhances developer experience with clear, intuitive commands
  • Follows the "universal compatibility" principle
  • Open-source first approach
  • Professional quality implementation

Final Verdict

Status: ⚠️ Conditionally Approve - High quality implementation with excellent test coverage, but requires two security fixes before merge:

  1. Must add error-causes for consistency with project standards
  2. Must add SSRF protection for HTTP/HTTPS URLs

Overall Quality: 8.5/10

  • Excellent code organization and testing
  • Good architectural patterns
  • Strong adherence to functional programming principles
  • Security considerations present but incomplete

Test Coverage: 10/10 - Comprehensive and well-written

Code Quality: 9/10 - Clean, functional, follows project standards (minus error-causes)

Security: 7/10 - Good injection prevention, but SSRF risk and missing integrity checks

The implementation is solid and the test coverage is exemplary. Once the two security issues are addressed, this will be an excellent addition to the codebase.

Open in Cursor Open in Web

Define ScaffoldCancelledError, ScaffoldNetworkError, and ScaffoldStepError
in lib/scaffold-errors.js using the errorCauses() factory so every call-site
that builds a handler is exhaustiveness-checked at setup time.

- scaffold-resolver: throw createError({...ScaffoldCancelledError}) on user
  cancellation; wrap fetchAndSaveExtension failures in ScaffoldNetworkError
- scaffold-runner: defaultExecStep now rejects with createError({...ScaffoldStepError})
  so spawn failures carry a typed cause
- bin/aidd.js create action: replace generic catch with handleScaffoldErrors()
  for typed branching (cancelled → info, network → retry hint, step → manifest hint)

https://claude.ai/code/session_01VxNxs3Ly9UQh9TZpJpjG63
Copy link
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

Implements manifest-driven project scaffolding for the aidd CLI by adding create and scaffold-cleanup subcommands, along with the supporting resolver/runner/cleanup libraries, scaffold fixtures, and test coverage.

Changes:

  • Added scaffold source resolution (named scaffolds, file://, http(s):// with confirmation) and a manifest runner for run/prompt steps.
  • Integrated new create and scaffold-cleanup subcommands into the CLI.
  • Added scaffold fixtures (scaffold-example, next-shadcn) plus unit + E2E test suites.

Reviewed changes

Copilot reviewed 17 out of 20 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tasks/npx-aidd-create-epic.md Marks the epic status as DONE.
lib/scaffold-runner.js New manifest parser + step runner (run/prompt + optional extension.js).
lib/scaffold-runner.test.js Unit tests for manifest parsing and execution ordering/failure behavior.
lib/scaffold-resolver.js New resolver for named/file/http scaffolds with caching under .aidd/scaffold.
lib/scaffold-resolver.test.js Unit tests for named/file/http resolution, confirmation, and env var defaulting.
lib/scaffold-cleanup.js New cleanup helper to remove .aidd/ working directory.
lib/scaffold-cleanup.test.js Unit tests for cleanup behavior and default folder handling.
bin/aidd.js Adds create (with --agent) and scaffold-cleanup subcommands.
bin/create-e2e.test.js E2E tests for create, env defaulting, --agent, and cleanup.
ai/scaffolds/index.md Adds index entry for scaffold directory.
ai/index.md Links to ai/scaffolds/ from the top-level AI index.
ai/scaffolds/scaffold-example/README.md Documents the scaffold-example fixture scaffold.
ai/scaffolds/scaffold-example/SCAFFOLD-MANIFEST.yml Defines manifest steps for the E2E fixture (npm init/pkg/test/install).
ai/scaffolds/scaffold-example/index.md Generated directory index for scaffold-example.
ai/scaffolds/scaffold-example/bin/index.md Generated directory index for bin/.
ai/scaffolds/next-shadcn/README.md Documents default scaffold (stub).
ai/scaffolds/next-shadcn/SCAFFOLD-MANIFEST.yml Placeholder manifest with a prompt step.
ai/scaffolds/next-shadcn/index.md Generated directory index for next-shadcn.
activity-log.md Adds an entry describing the new commands and modules.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +30 to +33
} else {
resolve();
}
});
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

parseManifest returns data.steps || [] without validating the type. If steps is accidentally a string/object, runManifest will silently iterate unexpected values (e.g., characters) and skip all work, potentially reporting a successful scaffold with no steps executed. Validate that steps is an array of objects (and consider throwing on unknown step shapes) so manifest mistakes fail fast with a clear error.

Copilot uses AI. Check for mistakes.
Comment on lines 122 to 140
const confirmed = await confirm(
`\n⚠️ Warning: You are about to download and execute code from a remote URI:\n ${effectiveType}\n\nThis code will run on your machine. Only proceed if you trust the source.\nContinue? (y/N): `,
);
if (!confirmed) {
throw createError({
...ScaffoldCancelledError,
message: "Remote extension download cancelled by user.",
});
}
await fs.ensureDir(folder);
try {
paths = await fetchAndSaveExtension({
fetchText,
folder,
uri: effectiveType,
});
} catch (originalError) {
throw createError({
...ScaffoldNetworkError,
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

resolveExtension returns manifestPath even when the manifest file doesn't exist (e.g., remote fetch returned null / 404 and nothing was written). This will later fail in runManifest with a low-level ENOENT. Consider verifying that paths.manifestPath exists before returning (and for HTTP sources, throw a clear error mentioning the URI and required files) so users get an actionable message.

Copilot uses AI. Check for mistakes.
The previous implementation fetched only README.md, SCAFFOLD-MANIFEST.yml,
and bin/extension.js over HTTP(S). This prevented scaffolds from carrying
arbitrary file trees, templates, package.json dependencies, config files,
bin scripts, etc.

Replace fetchAndSaveExtension + manual HTTP(S) fetch with a git clone
(defaultGitClone) that clones the full repo into <folder>/.aidd/scaffold/.
The injectable `clone` parameter keeps tests fast and network-free.

Also drop the now-unused http/https node built-ins and FETCH_TIMEOUT_MS.

https://claude.ai/code/session_01VxNxs3Ly9UQh9TZpJpjG63
1. YAML document-start marker silently drops all steps (medium severity)
   The gray-matter wrapper trick ("---\n" + content + "\n---") caused
   gray-matter to see two consecutive --- lines and parse empty frontmatter
   when a manifest author included the standard --- marker. Replace with
   matter.engines.yaml.parse(content), a direct call to js-yaml's load()
   which handles --- markers correctly.

   Test added: "parses steps when manifest begins with YAML document-start marker"

2. npx aidd create (no args) crashes with TypeError (high severity)
   Both positionals were optional, so Commander allowed zero arguments.
   folderArg became undefined and path.resolve(cwd, undefined) threw.
   Flip the arg order to <folder> [type] — folder is now required and
   Commander rejects the call before the action runs.

   Also note: the defaultFetchText res.resume() socket-leak issue reported
   separately is moot — defaultFetchText was removed in the previous commit
   when HTTP fetching was replaced with git clone.

https://claude.ai/code/session_01VxNxs3Ly9UQh9TZpJpjG63
Copilot AI review requested due to automatic review settings February 18, 2026 19:43
Copy link
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

Copilot reviewed 17 out of 20 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 24 to 26
const defaultGitClone = (repoUrl, destPath) =>
new Promise((resolve, reject) => {
const child = spawn("git", ["clone", repoUrl, destPath], {
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

The PR description states "For custom URL - assume those point to GitHub repos. Download the latest release into the scaffold folder", but the implementation uses git clone to clone the entire repository rather than downloading a specific release. This is a discrepancy between the documented requirement and the implementation. The current implementation clones the default branch, not the "latest release". Consider whether the implementation should fetch release artifacts instead, or if the documentation should be updated to reflect that it clones the repository.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +14
# scaffolds

This index provides an overview of the contents in this directory.

## Subdirectories

### 📁 next-shadcn/

See [`next-shadcn/index.md`](./next-shadcn/index.md) for contents.

### 📁 scaffold-example/

See [`scaffold-example/index.md`](./scaffold-example/index.md) for contents.

Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

The PR description requires "Add documentation explaining how to stage files to be included in the scaffold: use the standard files array in package.json as if you were packaging an npm module". However, this documentation does not appear to exist in the PR. Consider adding documentation in ai/scaffolds/index.md or a dedicated ai/scaffolds/README.md file that explains how developers can create their own scaffolds and use the package.json files array to control which files are included.

Copilot uses AI. Check for mistakes.
…ent GitHub release workflow

TDD: added failing tests first, then implemented to pass.

- scaffold-errors.js: add ScaffoldValidationError (code SCAFFOLD_VALIDATION_ERROR)
- scaffold-runner.js: parseManifest now validates that steps is an array of plain
  objects with at least one recognized key (run/prompt); throws ScaffoldValidationError
  with a descriptive message on any violation instead of silently iterating wrong values
- scaffold-runner.test.js: 5 new tests covering string steps, object steps, bare-string
  items, unrecognized-key items, and the error message content
- scaffold-verifier.js + scaffold-verifier.test.js: new verifyScaffold() function
  (8 tests) that checks manifest existence, valid YAML, valid step shapes, and
  non-empty steps list — returns { valid, errors } for clean reporting
- bin/aidd.js: add `verify-scaffold [type]` subcommand; update `create` error handler
  to cover ScaffoldValidationError
- scaffold-example/SCAFFOLD-MANIFEST.yml: add release-it install + scripts.release step
  so generated projects have a release command out of the box
- scaffold-example/package.json: new file so scaffold AUTHORS can release their scaffold
  as a GitHub release with `npm run release`
- tasks/npx-aidd-create-epic.md: update requirements — GitHub releases instead of git
  clone for remote scaffolds; add verify-scaffold, steps validation, and scaffold
  author release workflow sections
- docs/scaffold-authoring.md: new guide covering manifest format, validation, the
  distinction between npm's files array (npm publish only) and GitHub release assets,
  and how to publish a scaffold as a GitHub release

https://claude.ai/code/session_01VxNxs3Ly9UQh9TZpJpjG63
…rst, folder second

The previous fix made <folder> required (correct) but accidentally swapped the
positional order to `create <folder> [type]`. Every E2E test, both scaffold
READMEs, the epic requirements, the activity log, and the docs all call the
command as `create [type] <folder>` (e.g. `create scaffold-example my-project`).

With the wrong order, `create scaffold-example test-project` would bind
folder="scaffold-example" and type="test-project", creating a directory named
after the scaffold and trying to resolve a nonexistent scaffold named after the
folder — completely backwards.

Restores `.command("create [type] <folder>")` with action `(type, folder, opts)`.

https://claude.ai/code/session_01VxNxs3Ly9UQh9TZpJpjG63
Copilot AI review requested due to automatic review settings February 19, 2026 00:20
…full e2e in pre-commit

Argument parsing:
  The previous [type] <folder> definition caused Commander to assign the
  single-argument form (create my-folder) to `type`, leaving folder missing.
  Restore [typeOrFolder] [folder] with manual validation so all three calling
  patterns work correctly:
    create scaffold-example my-project  →  type=scaffold-example, folder=my-project
    create my-project                   →  type=undefined (env/default), folder=my-project
    create                              →  explicit 'missing required argument' error

Pre-commit hook:
  Changed from `npm run test:unit` to `npm test` so e2e tests run on every
  commit. The arg-order regression slipped through exactly because e2e was
  excluded from the hook. Full suite is ~50s but catches integration bugs.

https://claude.ai/code/session_01VxNxs3Ly9UQh9TZpJpjG63
Copy link
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

Copilot reviewed 22 out of 25 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 77 to 81
const cloneExtension = async ({ uri, folder, clone }) => {
const scaffoldDir = path.join(folder, ".aidd/scaffold");
// Remove any prior clone so we start clean
await fs.remove(scaffoldDir);
await clone(uri, scaffoldDir);
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

If the git clone operation fails partway through (line 81), the .aidd/scaffold directory may be left in a partially cloned state. While line 80 removes any prior clone, if the user retries the scaffold after a partial failure, the directory will be cleaned up. However, if the clone fails due to a transient network error and succeeds on retry, this behavior is acceptable. Consider documenting this behavior or adding more robust cleanup on failure.

Copilot uses AI. Check for mistakes.
Comment on lines 24 to 35
const defaultGitClone = (repoUrl, destPath) =>
new Promise((resolve, reject) => {
const child = spawn("git", ["clone", repoUrl, destPath], {
stdio: "inherit",
});
child.on("close", (code) => {
if (code !== 0)
reject(new Error(`git clone failed with exit code ${code}`));
else resolve();
});
child.on("error", reject);
});
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

The implementation uses git clone to fetch remote scaffolds, but the PR description and task requirements specify downloading GitHub release tarballs instead. The task document at line 33 states: "Given an HTTP/HTTPS URI pointing to a GitHub repository, should download the latest GitHub release tarball (rather than git clone)". This discrepancy means the implementation doesn't match the specified requirements - it will clone the entire git history instead of downloading a versioned release tarball.

Copilot uses AI. Check for mistakes.
// correctly handles the optional YAML document-start marker (---).
// The previous gray-matter wrapper trick ("---\n" + content + "\n---")
// would silently drop all steps if the content itself started with ---.
const data = matter.engines.yaml.parse(content);
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

The code uses matter.engines.yaml.parse() directly to parse pure YAML content, which is atypical. The gray-matter library is designed to parse frontmatter (YAML between --- delimiters) from Markdown/text files. For parsing pure YAML files without frontmatter, it would be more conventional and clearer to use js-yaml directly (which is what gray-matter uses internally). This would eliminate the unusual API usage and make dependencies more explicit. Consider replacing matter.engines.yaml.parse(content) with a direct yaml.load(content) import from js-yaml.

Copilot uses AI. Check for mistakes.
**Requirements**:
- Given the scaffold runs, should initialize a new npm project in `<folder>`
- Given the scaffold runs, should install `riteway`, `vitest`, `@playwright/test`, `error-causes`, and `cuid2` at `@latest`
- Given the scaffold runs, should install `riteway`, `vitest`, `@playwright/test`, `error-causes`, `cuid2`, and `release-it` at `@latest`
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

The task requirements at line 94 specify installing cuid2, but the manifest and E2E tests install @paralleldrive/cuid2. While the actual package name is correct, there's an inconsistency between the requirement specification and implementation. The requirement should be updated to use the full package name @paralleldrive/cuid2 for clarity, or verify that cuid2 is not a shorthand that was intended.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Use @paralleldrive/cuid2

The internal [typeOrFolder] [folder] signature (needed to work around
Commander's left-to-right arg assignment) showed both as optional in --help.
Add a .usage() override and .addHelpText() so the displayed usage reads
`[options] [type] <folder>` with an Arguments section that explicitly marks
<folder> as required and shows four calling-convention examples.

https://claude.ai/code/session_01VxNxs3Ly9UQh9TZpJpjG63
Copy link
Contributor

Copilot AI commented Feb 19, 2026

@ericelliott I've opened a new pull request, #99, to work on those changes. Once the pull request is ready, I'll request review from you.

* Initial plan

* fix: use @paralleldrive/cuid2 in task requirements for consistency

Co-authored-by: ericelliott <364727+ericelliott@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: ericelliott <364727+ericelliott@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 19, 2026 23:30
Copy link
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

Copilot reviewed 22 out of 25 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

git add README.md 2>/dev/null || true

npm run test:unit
npm test
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

The pre-commit hook changed from npm run test:unit to npm test, which now runs all tests including E2E tests. E2E tests can be slow (the scaffold-example test has a 180-second timeout and runs npm install), which could significantly slow down the commit process. Consider whether this change is intentional, or if pre-commit should continue running only unit tests to keep the feedback loop fast.

Suggested change
npm test
npm run test:unit

Copilot uses AI. Check for mistakes.
Comment on lines +33 to 36
- Given an HTTP/HTTPS URI pointing to a GitHub repository, should download the latest GitHub release tarball (rather than git clone) and extract it to `<folder>/.aidd/scaffold/` — this gives versioned, reproducible scaffolds without fetching the full git history
- Given a `file://` URI, should read extension files from the local path it points to without copying them
- Given any extension, should display README contents to the user before proceeding
- Given a remote HTTP/HTTPS URI, should warn the user they are about to execute remote code and prompt for confirmation before downloading or running anything
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

These extension resolver requirements allow remote scaffolds to be fetched over both http:// and https://, even though scaffold code will later run locally via shell run steps and optional bin/extension.js. Downloading executable scaffolds over plain HTTP lets an on-path attacker tamper with the tarball contents in transit and gain arbitrary code execution on the developer’s machine, even when the user thinks they are using a trusted GitHub release. Tighten this contract so scaffold URIs must use https:// (and reject or explicitly gated http://), and ensure the implementation enforces this before any download or execution occurs.

Copilot uses AI. Check for mistakes.
resolvedFolder +
"` to remove the downloaded extension files.",
),
);
Copy link

Choose a reason for hiding this comment

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

Cleanup tip shown when no downloaded files exist

Low Severity

The "Tip: Run scaffold-cleanup" message is unconditionally printed after every successful scaffold, including named scaffolds and file:// URIs. Only HTTP/HTTPS scaffolds create a .aidd/scaffold/ directory. For named and file-based scaffolds there are no downloaded extension files, making the tip misleading — the subsequent cleanup would report "nothing to clean up."

Fix in Cursor Fix in Web

…step, manifest existence

- pre-commit hook: npm test → npm run test:unit so slow E2E tests don't block
  commits or time out the release pipeline; comment instructs agents to run
  npm run test:e2e manually before committing
- cleanup tip: use folderPath (absolute) not resolvedFolder (relative) so the
  suggested scaffold-cleanup command works regardless of cwd
- parseManifest: throw ScaffoldValidationError when a step has both run and
  prompt keys simultaneously, naming both conflicting keys in the message
- resolveExtension: verify SCAFFOLD-MANIFEST.yml exists on disk before
  returning paths; named, file://, and HTTP scaffolds all get a clear
  ScaffoldValidationError instead of a silent ENOENT later in runManifest
- remediation epic + plan.md entry added

Tests: +5 unit tests (222 total, 0 failures)

https://claude.ai/code/session_01VxNxs3Ly9UQh9TZpJpjG63
…md on install

AGENTS.md / agents-md.js:
- Add "test:e2e" to REQUIRED_DIRECTIVES and to AGENTS_MD_CONTENT so every
  installed project instructs agents to run `npm run test:e2e` manually
  before committing (pre-commit hook only runs unit tests)
- Update appendDirectives template with the same Testing section
- Update the repo's own AGENTS.md to include the Testing section

CLAUDE.md / ensureClaudeMd:
- New ensureClaudeMd() creates CLAUDE.md (read by Claude Code) with full
  AGENTS.md content when no CLAUDE.md exists
- If CLAUDE.md exists but doesn't reference AGENTS.md, appends a pointer line
- If CLAUDE.md already references AGENTS.md, leaves it unchanged
- Wire ensureClaudeMd into cli-core.js executeClone so it runs on every install
- Bootstrap CLAUDE.md for this repo

Tests: +6 unit tests (228 total, 0 failures)

https://claude.ai/code/session_01VxNxs3Ly9UQh9TZpJpjG63
…ng docs

scaffold-runner.js:
- Replace matter.engines.yaml.parse() (undocumented gray-matter internal) with
  yaml.load() from js-yaml, added as an explicit direct dependency

scaffold-resolver.js:
- Replace git clone with fetch + system tar extraction (--strip-components=1)
  to correctly implement the epic requirement: download the release tarball,
  not the full repo history
- The `clone` parameter is renamed to `download` throughout; tests updated to
  match; default implementation uses built-in Node 22 fetch + Unix tar
- Remove the spawn('git') path and defaultGitClone entirely
- Clean destPath before extracting so retries start fresh

scaffold-authoring docs:
- New ai/scaffolds/SCAFFOLD-AUTHORING.md explains scaffold directory layout,
  SCAFFOLD-MANIFEST.yml step types, bin/extension.js, the package.json files
  array, and how to distribute via named, file://, or GitHub release tarball

https://claude.ai/code/session_01VxNxs3Ly9UQh9TZpJpjG63
…ed modules

- Extract resolveCreateArgs + runCreate into lib/scaffold-create.js
- Extract runVerifyScaffold into lib/scaffold-verify-cmd.js
- Delegate from bin/aidd.js to the new modules (handlers no longer inline)
- Add 9 unit tests for scaffold-create and 4 for scaffold-verify-cmd
- Archive completed remediation epic and remove from plan.md

Closes remediation epic (all 10 tasks complete, 241 tests passing).

https://claude.ai/code/session_01VxNxs3Ly9UQh9TZpJpjG63
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 7 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.


const parseManifest = (content) => {
// yaml.load() handles the optional YAML document-start marker (---).
const data = yaml.load(content);
Copy link

Choose a reason for hiding this comment

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

YAML parser accepts arbitrary code execution

High Severity

yaml.load() can execute arbitrary code from untrusted YAML manifests. Since scaffolds can come from remote HTTP/HTTPS URIs, manifest content is untrusted. The parser accepts executable JavaScript through specially crafted YAML (regexps, functions, undefined). Use yaml.load() with { schema: yaml.FAILSAFE_SCHEMA } or similar safe schema option to prevent code execution during parsing.

Fix in Cursor Fix in Web

}

// File exists — check if it already references AGENTS.md
const existingContent = await fs.readFile(claudePath, "utf-8");
Copy link

Choose a reason for hiding this comment

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

Missing error handling when reading CLAUDE.md

Medium Severity

fs.readFile(claudePath, "utf-8") reads CLAUDE.md without try-catch. If the file exists but can't be read (permission errors, I/O errors, or filesystem issues), an unhandled error is thrown instead of being wrapped in AgentsFileError like other file operations in the same function. This breaks error handling consistency and produces less helpful error messages.

Fix in Cursor Fix in Web

"SCAFFOLD-MANIFEST.yml",
),
readmePath: path.resolve(packageRoot, "../ai/scaffolds", type, "README.md"),
});
Copy link

Choose a reason for hiding this comment

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

Named scaffold type lacks path traversal validation

Low Severity

The type parameter in resolveNamed is used in path.resolve() without validation. A malicious type like "../../etc" could resolve paths outside the ai/scaffolds/ directory. While the manifest existence check (line 148) provides defense-in-depth, input validation should happen at the boundary. Other modules in the codebase (like lib/index-generator.js) validate paths to prevent traversal. This function lacks that defensive check.

Fix in Cursor Fix in Web

child.stdin.write(buffer);
child.stdin.end();
});
};
Copy link

Choose a reason for hiding this comment

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

HTTP scaffold requires tarball URL, not repo URL

High Severity

The epic requirement specifies that HTTP/HTTPS URIs should point to GitHub repositories and automatically download the latest release tarball, but defaultDownloadAndExtract simply fetches whatever URL is provided without resolving repo URLs to release tarballs. Users providing https://github.com/org/repo will fetch HTML instead of a tarball, causing tar extraction to fail. The implementation expects direct tarball URLs like https://github.com/org/repo/archive/refs/tags/v1.0.0.tar.gz, contradicting the documented requirement.

Additional Locations (1)

Fix in Cursor Fix in Web

resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
});
});
};
Copy link

Choose a reason for hiding this comment

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

Readline interface missing error handlers causes hang

High Severity

The readline.createInterface in defaultConfirm creates a promise that only resolves via the question callback, with no error or close event handlers. If process.stdin is closed, not a TTY, or encounters an error, the readline interface emits error/close events that aren't handled, causing the promise to hang indefinitely and never resolve or reject. This blocks the entire scaffold operation and requires killing the process.

Fix in Cursor Fix in Web

});
child.on("error", reject);
child.stdin.write(buffer);
child.stdin.end();
Copy link

Choose a reason for hiding this comment

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

Missing stdin error handler causes crash on pipe break

Medium Severity

child.stdin.write(buffer) and child.stdin.end() write to the tar process stdin pipe without an error handler. If the tar process crashes or closes before consuming all input, the pipe breaks and child.stdin emits an error event. Since no listener is attached, this becomes an uncaught error that crashes Node.js with an unhandled error event exception.

Fix in Cursor Fix in Web

} catch {
console.error(chalk.red(`\n❌ Scaffold failed: ${err.message}`));
}
process.exit(1);
Copy link

Choose a reason for hiding this comment

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

User cancellation incorrectly exits with error code

Medium Severity

When a user declines the remote code confirmation prompt, ScaffoldCancelledError is thrown and handled by logging a yellow info message, but then process.exit(1) is called unconditionally at line 295 (and 341 for verify-scaffold). Exit code 1 signals failure to shells and CI systems, but user cancellation is a graceful abort, not an error. This breaks scripts and pipelines that check exit codes, making them treat cancellation as failure.

Additional Locations (1)

Fix in Cursor Fix in Web

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.

3 participants

Comments