Skip to content

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

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

Add manifest-driven scaffolding with create and scaffold-cleanup commands#97
ericelliott wants to merge 40 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:

  • npx aidd create [type|URI] <folder> — Scaffolds new projects from named scaffolds, local file:// URIs, or remote HTTPS URIs
  • npx aidd 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

High Risk
Executes scaffold-defined commands and agent prompts, and adds remote download/extraction and config writing logic; despite validation and HTTPS/confirmation safeguards, this expands the security-sensitive surface area.

Overview
Adds a manifest-driven scaffolding workflow to the aidd CLI via new create, verify-scaffold, scaffold-cleanup, and set create-uri commands, including support for named scaffolds, file:// scaffolds, and HTTPS-only remote scaffolds with explicit user confirmation.

Introduces a YAML-based SCAFFOLD-MANIFEST.yml runner (run shell steps + prompt steps executed via a configurable agent) with stricter validation/safe YAML parsing, plus GitHub repo URL resolution to latest release tarballs and a .aidd/ working directory cleanup flow.

Updates installation flow and docs to include Claude Code support (--claude + .claude symlink), automatic CLAUDE.md creation, new agent skill /aidd-fix, scaffold authoring documentation, and adjusts the pre-commit hook to run unit tests only (E2E run manually).

Written by Cursor Bugbot for commit f355dc3. 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.

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.

…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.

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
}
if (existingContent.includes("AGENTS.md")) {
return {
action: "unchanged",
Copy link

Choose a reason for hiding this comment

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

CLAUDE.md modified on every second install run

Medium Severity

ensureClaudeMd creates CLAUDE.md with AGENTS_MD_CONTENT on first run, but AGENTS_MD_CONTENT never contains the literal string "AGENTS.md". On the next npx aidd install, the existingContent.includes("AGENTS.md") check on the tool's own content always fails, causing it to append an unnecessary "see AGENTS.md" reference to a file it just created. This contradicts the stated intent of "never overwrites an existing CLAUDE.md" — the file is silently modified on every second invocation.

Fix in Cursor Fix in Web

return {
...(paths.downloaded
? { cleanupTip: `npx aidd scaffold-cleanup ${folder}` }
: {}),
Copy link

Choose a reason for hiding this comment

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

Cleanup tip breaks for paths containing spaces

Low Severity

The cleanupTip is built via simple string interpolation: `npx aidd scaffold-cleanup ${folder}`. If folder is an absolute path containing spaces (e.g. /home/user/my project), the suggested command becomes npx aidd scaffold-cleanup /home/user/my project, which the shell would split into two separate arguments. The path needs to be quoted in the tip string so the user can copy-paste it safely.

Fix in Cursor Fix in Web

…g.md

docs/scaffold-authoring.md was missing the fourth validation bullet — a
step with both run and prompt keys is rejected as ambiguous. Already
documented in ai/scaffolds/SCAFFOLD-AUTHORING.md and implemented in
lib/scaffold-runner.js lines 92-97.

https://claude.ai/code/session_01VxNxs3Ly9UQh9TZpJpjG63
Copilot AI review requested due to automatic review settings February 23, 2026 23:31
ai/scaffolds/ is for functional scaffolds (SCAFFOLD-MANIFEST.yml, bin/,
etc.), not documentation. SCAFFOLD-AUTHORING.md was plain docs that had
no agent-skill structure (no command pattern, no SudoLang, no frontmatter
command). The canonical authoring guide already lives at
docs/scaffold-authoring.md; update the README link accordingly.

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 39 out of 43 changed files in this pull request and generated no new comments.


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

…missing folder

When a user runs `npx aidd create https://github.com/org/repo` without a
folder argument, the URL was silently treated as the folder name, causing
path.resolve() to produce a mangled path (/cwd/https:/github.com/org/repo)
and the default scaffold to run in the wrong place.

Return null (triggering the existing "missing required argument 'folder'"
error) when the sole argument starts with https:// or file://. http:// is
intentionally excluded — it is not a supported protocol. The two-arg form
(npx aidd create <url> <folder>) is unaffected.

https://claude.ai/code/session_01VxNxs3Ly9UQh9TZpJpjG63
Record the requirement that was implemented in the previous fix commit
but not captured in the epic first. Process should be: update epic →
write failing test → implement → watch it pass → e2e → review → commit.

https://claude.ai/code/session_01VxNxs3Ly9UQh9TZpJpjG63
Copilot AI review requested due to automatic review settings February 23, 2026 23:49
$ npx aidd create https://github.com/org/scaffold my-project
$ npx aidd create file:///path/to/scaffold my-project
`,
)
Copy link

Choose a reason for hiding this comment

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

create help omits user config fallback

Medium Severity

The create command help text says [type] defaults to AIDD_CUSTOM_CREATE_URI and then "next-shadcn", but the actual resolver also falls back to ~/.aidd/config.yml (create-uri). This mismatch makes npx aidd set create-uri ... look ineffective and can lead to incorrect CLI usage.

Fix in Cursor Fix in Web

const folderPath = path.resolve(process.cwd(), resolvedFolder);

return { folderPath, resolvedFolder, type };
};
Copy link

Choose a reason for hiding this comment

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

Single http:// arg treated as folder

Medium Severity

resolveCreateArgs returns null for a single https:///file:// arg (to avoid mangled folder creation) but intentionally allows a single http:// arg to fall through as a folder name. This can try to ensureDir on a path containing : and /, producing confusing filesystem behavior instead of a clear “missing folder / unsupported URI” error.

Fix in Cursor Fix in Web

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 39 out of 43 changed files in this pull request and generated no new comments.


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

Codifies the AIDD fix process as an AgentSkills.io-compliant skill at
ai/skills/fix/SKILL.md (name: aidd-fix). Six sequential steps enforce:
context validation before touching any file, epic-first requirement
documentation, TDD discipline (failing test before implementation),
e2e gate, self-review, then commit+push.

Adds /fix to please.mdc command list for discoverability via /help.

https://claude.ai/code/session_01VxNxs3Ly9UQh9TZpJpjG63
- Rename /fix → /aidd-fix everywhere (please.mdc, SKILL.md Commands block)
  to avoid collision with framework built-in commands
- Add ai/commands/aidd-fix.md — thin stub discoverable by Cursor via the
  .cursor/ → ai/ symlink as /aidd-fix (plain Markdown, no frontmatter)
- Add Skills section to AGENTS.md and AGENTS_MD_CONTENT listing
  "fix bug → /aidd-fix" with per-platform discovery paths
- Note: .claude/ is gitignored; Claude Code discovery via symlink TBD

https://claude.ai/code/session_01VxNxs3Ly9UQh9TZpJpjG63
Copilot AI review requested due to automatic review settings February 24, 2026 00:19
const folderPath = path.resolve(process.cwd(), resolvedFolder);

return { folderPath, resolvedFolder, type };
};
Copy link

Choose a reason for hiding this comment

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

Single http URL becomes folder name

Medium Severity

resolveCreateArgs() treats a single http://... argument as a folder name (while rejecting single https:///file://). This can silently create a directory literally named like a URL and then scaffold using the default type, which looks like “create from URL” succeeded but actually ignored the URL.

Fix in Cursor Fix in Web

}

return { errors: [], valid: true };
};
Copy link

Choose a reason for hiding this comment

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

Verifier rejects extension-only scaffolds

Low Severity

verifyScaffold() marks manifests with no steps as invalid ("scaffold would do nothing"), but a scaffold can still do meaningful work via bin/extension.js (which runManifest() runs even when steps is empty). This makes verify-scaffold report false negatives for extension-driven scaffolds.

Fix in Cursor Fix in Web

@cursor
Copy link

cursor bot commented Feb 24, 2026

Bugbot Autofix prepared fixes for 2 of the 2 bugs found in the latest run.

  • ✅ Fixed: Single http URL becomes folder name
    • Added http:// to the URI detection regex in resolveCreateArgs to prevent creating weirdly-named directories when folder argument is missing.
  • ✅ Fixed: Verifier rejects extension-only scaffolds
    • Modified verifyScaffold to check for bin/extension.js existence before rejecting empty manifests, allowing valid extension-only scaffolds.

Create PR

Or push these changes by commenting:

@cursor push bfc9138408
Preview (bfc9138408)
diff --git a/lib/scaffold-create.js b/lib/scaffold-create.js
--- a/lib/scaffold-create.js
+++ b/lib/scaffold-create.js
@@ -22,9 +22,8 @@
   // If no folder was given and the sole argument looks like a URI, the user
   // most likely forgot the folder argument. Return null so the caller emits
   // "missing required argument 'folder'" rather than creating a directory
-  // with a mangled URL path. http:// is intentionally excluded — it is not
-  // a supported protocol.
-  if (folder === undefined && /^(https|file):\/\//i.test(typeOrFolder)) {
+  // with a mangled URL path.
+  if (folder === undefined && /^(https?|file):\/\//i.test(typeOrFolder)) {
     return null;
   }
 

diff --git a/lib/scaffold-create.test.js b/lib/scaffold-create.test.js
--- a/lib/scaffold-create.test.js
+++ b/lib/scaffold-create.test.js
@@ -32,13 +32,13 @@
     });
   });
 
-  test("does not reject http:// as a single arg (unsupported — falls through to folder)", () => {
+  test("returns null when single arg is an http:// URI (folder omitted)", () => {
     const result = resolveCreateArgs("http://example.com/scaffold", undefined);
     assert({
-      given: "an http:// URL (unsupported protocol) as the sole arg",
-      should: "not return null — http is not a recognised URI scheme",
-      actual: result !== null,
-      expected: true,
+      given: "only an http:// URL with no folder",
+      should: "return null to signal missing folder",
+      actual: result,
+      expected: null,
     });
   });
 

diff --git a/lib/scaffold-verifier.js b/lib/scaffold-verifier.js
--- a/lib/scaffold-verifier.js
+++ b/lib/scaffold-verifier.js
@@ -1,3 +1,4 @@
+import path from "path";
 import fs from "fs-extra";
 
 import { parseManifest } from "./scaffold-runner.js";
@@ -24,8 +25,14 @@
   }
 
   if (steps.length === 0) {
-    errors.push("Manifest contains no steps — scaffold would do nothing");
-    return { errors, valid: false };
+    const scaffoldDir = path.dirname(manifestPath);
+    const extensionJsPath = path.join(scaffoldDir, "bin/extension.js");
+    const extensionExists = await fs.pathExists(extensionJsPath);
+
+    if (!extensionExists) {
+      errors.push("Manifest contains no steps — scaffold would do nothing");
+      return { errors, valid: false };
+    }
   }
 
   return { errors: [], valid: true };

diff --git a/lib/scaffold-verifier.test.js b/lib/scaffold-verifier.test.js
--- a/lib/scaffold-verifier.test.js
+++ b/lib/scaffold-verifier.test.js
@@ -87,20 +87,38 @@
     });
   });
 
-  test("returns valid=false when manifest has no steps", async () => {
+  test("returns valid=false when manifest has no steps and no extension", async () => {
     const manifestPath = path.join(tempDir, "SCAFFOLD-MANIFEST.yml");
     await fs.writeFile(manifestPath, "steps: []\n");
 
     const result = await verifyScaffold({ manifestPath });
 
     assert({
-      given: "a manifest with an empty steps array",
+      given: "a manifest with an empty steps array and no extension.js",
       should: "return valid false (scaffold would do nothing)",
       actual: result.valid,
       expected: false,
     });
   });
 
+  test("returns valid=true when manifest has no steps but has extension.js", async () => {
+    const manifestPath = path.join(tempDir, "SCAFFOLD-MANIFEST.yml");
+    const extensionPath = path.join(tempDir, "bin/extension.js");
+
+    await fs.writeFile(manifestPath, "steps: []\n");
+    await fs.ensureDir(path.dirname(extensionPath));
+    await fs.writeFile(extensionPath, "console.log('extension');\n");
+
+    const result = await verifyScaffold({ manifestPath });
+
+    assert({
+      given: "a manifest with no steps but with bin/extension.js",
+      should: "return valid true (extension can still do work)",
+      actual: result.valid,
+      expected: true,
+    });
+  });
+
   test("returns valid=true for manifest with prompt steps", async () => {
     const manifestPath = path.join(tempDir, "SCAFFOLD-MANIFEST.yml");
     await fs.writeFile(
This Bugbot Autofix run was free. To enable Autofix for future PRs, go to the Cursor dashboard.

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 45 out of 49 changed files in this pull request and generated no new comments.


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

Generalise symlink creation so both --cursor and --claude share one
parameterised function instead of duplicating the logic.

- lib/symlinks.js: new createSymlink({ name, targetBase, force }) that
  handles any editor symlink (.cursor, .claude, or future additions)
- lib/cli-core.js: remove createCursorSymlink, import createSymlink,
  add claude param to executeClone
- bin/aidd.js: add --claude option, pass claude through, update Quick
  Start help text to show Cursor / Claude Code / both examples
- lib/symlinks.test.js: rename from cursor-symlink.test.js; add 6 new
  claude tests + 1 combined test (11 total); all cursor tests preserved
- bin/cli-help-e2e.test.js: update Quick Start assertion for new text
- tasks/claude-symlink-epic.md: new epic documenting requirements

293 unit tests, 41 e2e tests — all green.

https://claude.ai/code/session_01VxNxs3Ly9UQh9TZpJpjG63
action: "created",
message: "Created CLAUDE.md with AI agent guidelines",
};
}
Copy link

Choose a reason for hiding this comment

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

Non-idempotent CLAUDE.md creation on repeated installs

Medium Severity

ensureClaudeMd creates CLAUDE.md with AGENTS_MD_CONTENT, which does not contain the literal string "AGENTS.md". On a subsequent npx aidd run, the existingContent.includes("AGENTS.md") check returns false, so the function appends a redundant AGENTS.md reference to a file it just created. This makes the installer non-idempotent — the second run always modifies CLAUDE.md unnecessarily. A different detection mechanism (e.g., checking for the template's heading) would avoid modifying the tool's own output on re-run.

Additional Locations (1)

Fix in Cursor Fix in Web

readConfigFn: noConfig,
});

process.env.AIDD_CUSTOM_CREATE_URI = originalEnv;
Copy link

Choose a reason for hiding this comment

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

Env var restore sets "undefined" string instead of deleting

Low Severity

Restoring process.env.AIDD_CUSTOM_CREATE_URI by assigning a saved value that was undefined coerces it to the string "undefined" instead of removing the variable. process.env stringifies all assigned values. This pollutes the environment for subsequent tests, potentially causing resolveExtension to treat "undefined" as a scaffold type. The cleanup needs a conditional delete when the original value was not set.

Additional Locations (1)

Fix in Cursor Fix in Web

const FileSystemError = {
code: "FILESYSTEM_ERROR",
message: "File system operation failed",
};
Copy link

Choose a reason for hiding this comment

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

Duplicate error type definitions across modules

Low Severity

ValidationError and FileSystemError are redefined as standalone objects in symlinks.js rather than reusing the identical definitions from the errorCauses call in cli-core.js. If the error codes or messages are updated in one location, the other can easily drift out of sync, causing handleCliErrors to fail to dispatch correctly for symlink errors.

Fix in Cursor Fix in Web

- Drop "*No description available*" for files without frontmatter description; silently omit
- Shorten SKILL.md description to ≤50 chars: "Fix a bug or apply review feedback."
- Simplify AGENTS.md and AGENTS_MD_CONTENT Skills section to a plain index mapping (fix bug → /aidd-fix) — no duplicated docs

https://claude.ai/code/session_01VxNxs3Ly9UQh9TZpJpjG63
Copilot AI review requested due to automatic review settings February 24, 2026 01:04
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 1 potential issue.

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

const readConfig = async ({ configFile = CONFIG_FILE } = {}) => {
try {
const content = await fs.readFile(configFile, "utf-8");
return 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.

Unsafe YAML parsing allows code execution

Medium Severity

The yaml.load() call in readConfig doesn't specify a schema, defaulting to DEFAULT_SCHEMA which enables YAML type tags like !!js/function and !!js/regexp that execute arbitrary JavaScript. While ~/.aidd/config.yml is user-controlled, an attacker who can influence this file (through compromised dependencies or social engineering) could inject malicious YAML that executes when the user runs npx aidd create. The same codebase correctly uses yaml.JSON_SCHEMA in scaffold-runner.js line 55 to prevent this.

Fix in Cursor Fix in Web

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 53 out of 58 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 +13 to +17
const readConfig = async ({ configFile = CONFIG_FILE } = {}) => {
try {
const content = await fs.readFile(configFile, "utf-8");
return yaml.load(content) ?? {};
} catch {
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

readConfig uses yaml.load(content) with the default schema, which allows YAML tags that can deserialize into non-plain JS types (e.g. RegExp/functions). Since the returned value is used as a string (config["create-uri"]), a tagged value can cause runtime type errors or unexpected behavior. Parse with a restricted schema (e.g. yaml.JSON_SCHEMA) and/or validate that the loaded config is a plain object with string values before returning it.

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +89
const knownKeys = Object.keys(step).filter((k) => KNOWN_STEP_KEYS.has(k));

if (knownKeys.length === 0) {
const found = Object.keys(step).join(", ") || "(empty)";
throw createError({
...ScaffoldValidationError,
message: `Manifest step ${i + 1} has no recognized keys (run, prompt). Found: ${found}`,
});
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

parseManifest validates the presence of run/prompt keys but does not validate that their values are strings. A manifest like - run: 42 will pass validation, then runManifest/spawn will throw a raw TypeError instead of a typed ScaffoldValidationError/ScaffoldStepError. Add a validation that step.run / step.prompt are strings (and include the step number/type in the error).

Copilot uses AI. Check for mistakes.
ericelliott pushed a commit that referenced this pull request Feb 25, 2026
Breaks the 58-file manifest-driven scaffolding PR into 5 self-contained,
independently deployable chunks with no incomplete CLI workflows exposed.

https://claude.ai/code/session_01T1QH62uHcb8uFWK4apfn8N
my-scaffold/
├── SCAFFOLD-MANIFEST.yml # required — list of steps to execute
├── README.md # optional — displayed to the user before steps run
├── bin/
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We are not going to have a standard extension.js because you can just run any arbitrary command during install.

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.

4 participants