feat(apps): create sidebar folders from a scanned project - #5890
feat(apps): create sidebar folders from a scanned project#5890billygerhard wants to merge 1 commit into
Conversation
6e87b61 to
6d70b77
Compare
6d70b77 to
7eb1c52
Compare
7eb1c52 to
47b9ead
Compare
47b9ead to
f8fee04
Compare
Dispositions — GPT 5.6 Review
|
Dispositions — UX Review
|
|
Disposition for the GPT 5.6 review of
Span note for recurrence tracking: this is the first blocking finding in the |
|
Disposition for the UX review of
|
|
Disposition for the First Principles review of
|
|
/ai-review override fable c05f449: Maintainer decision: project-scaffolder ships as a built-in by design. Folder scaffolding is a once-per-project setup action that must run against the gateway's own project tree and folder-create path, so it is off by default (defaultEnabled:false) and adds no permanent core-UI surface; the scan/create endpoints stay the repackaging boundary if it is later moved to the KiroCrewApps registry. Accepting the no-new-builtin-apps exception for this commit. |
|
/ai-review override design c05f449: Maintainer decision: project-scaffolder ships as a built-in by design. Folder scaffolding is a once-per-project setup action that must run against the gateway's own project tree and folder-create path, so it is off by default (defaultEnabled:false) and adds no permanent core-UI surface; the scan/create endpoints stay the repackaging boundary if it is later moved to the KiroCrewApps registry. Accepting the no-new-builtin-apps exception for this commit. |
|
/ai-review override first-principles c05f449: Maintainer decision: project-scaffolder ships as a built-in by design. Folder scaffolding is a once-per-project setup action that must run against the gateway's own project tree and folder-create path, so it is off by default (defaultEnabled:false) and adds no permanent core-UI surface; the scan/create endpoints stay the repackaging boundary if it is later moved to the KiroCrewApps registry. Accepting the no-new-builtin-apps exception for this commit. |
Human judgment recorded@CrysisDeu marked the fable AI finding as false positive, not applicable, or explicitly accepted for
This decision applies only to this commit. A new push requires a new judgment. |
|
Human judgment recorded, but the following reviewer lane(s) could not be re-run automatically: Fable 5. Re-run the lane's latest workflow run for |
Human judgment recorded@CrysisDeu marked the first-principles AI finding as false positive, not applicable, or explicitly accepted for
This decision applies only to this commit. A new push requires a new judgment. |
|
Human judgment recorded, but the following reviewer lane(s) could not be re-run automatically: First Principles Review. Re-run the lane's latest workflow run for |
|
/ai-review override design c05f449: Maintainer decision: project-scaffolder ships as a built-in by design. Folder scaffolding is a once-per-project setup action that must run against the gateway's own project tree and folder-create path, so it is off by default (defaultEnabled:false) and adds no permanent core-UI surface; the scan/create endpoints stay the repackaging boundary if it is later moved to the KiroCrewApps registry. Accepting the no-new-builtin-apps exception for this commit. |
Human judgment recorded@CrysisDeu marked the design AI finding as false positive, not applicable, or explicitly accepted for
This decision applies only to this commit. A new push requires a new judgment. |
|
Human judgment recorded, but the following reviewer lane(s) could not be re-run automatically: Design Review. Re-run the lane's latest workflow run for |
|
@billygerhard — maintainer update on the built-in-app question and one fix request before this can land. Delivery as a built-in app: accepted. The Please fix before the next push — the GPT 5.6 finding on project_dir, err = _validate_project_dir(requested_dir)
Fix: await it off the loop, matching the overlap check a few lines below (L631): project_dir, err = await asyncio.to_thread(_validate_project_dir, requested_dir)The two pre-existing synchronous calls in After you push, ping me here — the override is SHA-scoped, so I will re-issue it for the new head. |
Open PR relationship auditThis is a consolidated, point-in-time code-level audit note. It compares complete merge-base diffs and current/merged code; it does not treat a shared topic as duplication or partial coverage as completion. Relationship findings
No PR, Issue, label, branch, or review state was changed by the relationship-note portion of this audit. |
|
@billygerhard thanks — the rebase onto current main ( One thing still open: the |
A chat folder already carries a project_dir and nests via parent_id, but a monorepo or a directory of sibling repositories needs one folder per package before per-package steering loads at all — and assembling N sub-folders by hand is work nobody does. This adds a pure read-only scanner (project_scan.py) that detects packages via repository/manifest/deploy-root/workspace-member signals with two confidence tiers, honoring the project's own .gitignore with git semantics; two endpoints (scan = dry-run preview, scaffold = create the confirmed selection through the existing folder create path); and a manifest-only builtin app providing the preview/confirm UI. Detailed design notes live in docs/system-specs. The Store listing carries the fields the current app-listing contract requires: useCases and configuration key arrays on the APP_MANIFEST_KEY entry (with the strings in all 13 catalogs), and a screenshots entry pointing at a real UI capture of the scan preview — taken against a synthetic monorepo, so the image carries no real project names. One generated file outside the feature's own sources is updated: error-code-baseline.json is re-snapshotted because extracting create_folder_record moved two of chat_folders.py's refusals behind a FolderCreateError that carries a `code`, so its missing_code count IMPROVES 21 -> 19 and test_baseline_is_not_stale requires the snapshot be refreshed (python test/test_error_code_contract.py --update). Nothing is regenerated to silence a regression: the `except FolderCreateError` handler returns inline dict literals at each json_response site rather than a hoisted payload local, keeping opaque_body at 0 for that file, and the wire shape is unchanged — `code` appears only when the refusal carries one. Review findings from the automated reviewer are addressed in this revision: - The scaffold write now carries the same three guards as the folder create route: unattributable-caller refusal, one FOLDER_CREATE rate budget unit per internal scaffold call, and the caller's derived app identity threaded to every create_folder_record so scaffolded folders are stamped owner_app and app-ownership isolation applies. - Scanner warning reasons are redacted at construction (redact_exfiltration_urls + redact_credentials): a parse error quotes the offending source line, and a credential in a malformed declaration must not ride the warning into the scan response. - A .gitignore pattern the grammar refuses (a lone `!`) is converted to DeclarationError so it costs a warning and that file's layer instead of surfacing as an HTTP 500. - The nested-suggestions bulk controls move out of the disclosure legend into their own row (the toggle is itself a button, so the legend carried three actions in one horizontal group). - The shared ProjectPicker gets max-w-[calc(100vw-16px)] so its fixed 400px panel clamps on narrow viewports instead of extending off-screen; its left calc already floors at 8px. The `all` chunk budget in scripts/check-bundle-size.mjs is raised 9750 -> 9800 KB: this feature's 39 catalog keys add ~107 KB of eager translated strings, and upstream catalog growth had left the ceiling under 1 KB of headroom (measured 133 bytes over). The page itself is behind a lazy import(), so nothing but strings lands in that chunk. Review round 3 (AI reviewers on cee0f35): - Root resolution moves off the event loop at both handler call sites (asyncio.to_thread): _resolve_root does realpath/isdir syscalls, and a stalled network mount must not freeze the gateway (GPT 5.6 blocker). - The scan response drops the groups field: it shipped with zero consumers -- the preview page derives grouping from parent_path and documents why -- so _grouped(), the response key, the TS type, and the spec sentence are deleted; reintroduce with its first consumer (First Principles blocker). Spec updated to match, including the stale "two endpoints are the whole surface" claim. Closes kirodotdev#2516
|
@billygerhard — thank you for this contribution and for the many review rounds you carried it through. Since the one remaining reviewer request (the What #8924 contains, relative to this PR's head
Your authorship is preserved with a |
|
Closing as delivered by merged #8924 ( #8924 is a maintainer takeover of this exact commit (its body says so: "Takeover of PR #5890"), rebased and extended with the one open review finding this branch never landed ( Nothing further to carry over from this branch. Thank you for building the feature. |
Problem / Motivation
A chat folder already carries a
project_dirand nests viaparent_id, so a chat opened inside one is steering- and scope-correct for its package. What is missing for a monorepo, or a directory of sibling repositories, is population: assembling N sub-folders by hand is work nobody does, so per-package steering never loads. Filed as #2516; submitted as a PR per the contribution FAQ ("proposals are filed as pull requests and debated in the open") after the issue sat inneeds-humantriage for two weeks.Why it matters
Multi-package workspaces are the norm for real projects (three independent filers hit this within one week: #1637, #2164, #2516), and today the folder feature's per-package steering silently never engages for them — the user would have to hand-build the tree first, so the capability that exists goes unused exactly where it helps most.
What changed (motivation → approach → change)
Goal: populate the folder tree from the filesystem, without trusting the scan and without new persistence. Approach: a pure read-only scanner plus a preview-then-create endpoint pair, with the UI as a thin surface — chosen over teaching the folder API itself to scan (couples a slow filesystem walk into the create path) and over a client-side walker (the browser cannot see the disk; the server must re-derive anyway). What was built:
Scanner (
src/kiro_crew/project_scan.py): walks the pointed-at tree read-only and returns candidate packages. Signals: the directory's own.git/.kiro, a recognized manifest (npm, Python, Rust, Go, JVM, Flutter, PHP, Ruby, Elixir, Swift, Deno, Scala), a deploy-root marker (firebase.json,vercel.json,cdk.json,Procfile, ...), or being named by a workspace declaration (workspaces,pnpm-workspace.yaml, Cargo workspace,go.work). Two confidence tiers decide what arrives pre-ticked; deploy roots stay confident at any depth because a deploy config never names a build fixture. The project's own.gitignoreprunes with git semantics viapathspec(nested files stack,!negation, ignored directories are never entered — which defeats SwiftPM'scheckouts/store of vendored git clones), alongside a name-prune list (node_modules,venv,DerivedData, ...).Endpoints (
dashboard/chat_folder_scaffold.py):POST /api/project-scaffold/scan(dry-run preview, creates nothing) andPOST /api/project-scaffold/create(creates the confirmed selection through the existing folder create path — one writer, no new persistence, no schema change). The scaffold re-derives candidates server-side and refuses any selected path the fresh scan did not offer, so a stale preview and a forged path are the same 400. Additive only: re-scans offer additions, existing folders are reported and never touched, partial failure is reported rather than rolled back.Also in this change, called out for reviewers: this change adds NO config key. Both
scaffold.extra_manifest_signalsandscaffold.depth_capwere dropped per First Principles review — one reader, no setter, no filed need, and a shipped config key is permanent surface — so the scan's depth bound is now the scanner's ownDEFAULT_DEPTH_CAPand the built-in manifest set widens by code change; and the sharedProjectPickergainsmax-w-[calc(100vw-16px)]so its fixed 400px panel clamps on narrow viewports for every caller. Per First Principles review, the scan response'sgroupsfield was removed — it had zero consumers (the preview derives grouping from each candidate'sparent_path); it can return with its first consumer.Builtin app (
apps/builtins/project_scaffolder/): the UI surface — directory picker (reusing the coreProjectPicker), grouped preview with per-group select-all, confident-first ordering, a collapsed disclosure for speculative sub-folders, and result reporting. Manifest-only; it calls the two host endpoints and duplicates no logic.On shape: delivered as a builtin app to keep core UI untouched. The engine + endpoints are severable (branch
feature/create-folder-from-projecton the fork holds them as 16 standalone commits) — happy to split into two PRs or reshape the surface as a core folder-UI action if maintainers prefer; the endpoints were designed as the repackaging boundary so only the last-mile surface changes.Tests
603 Python tests: scanner fixtures over real temp directory layouts (symlinks, permissions, depth caps, gitignore negation/nesting/scope-boundary, the SwiftPM reproduction with its non-vacuity control), hypothesis property tests ("two scans of an unchanged tree compare equal", "no candidate is ever gitignore-matched", "ignoring a child prunes exactly that subtree and never invents candidates"), and endpoint tests including byte-identical 400-body parity with the manual folder flow and route-registration guards. 26 frontend tests over the app page: keyboard reach, selection isolation (main select-all does not reach the collapsed section), zero-fetch picker interaction, stale-selection rescan flow. i18n gates (37 keys × 13 locales), tsc, eslint, flake8, mypy, docs-lint all clean.
Manual verification
Exercised end to end against real project layouts: a directory of sibling repositories, an npm-workspaces monorepo with Firebase apps (two deploy roots + shared libs), and an iOS/SwiftPM project whose gitignored dependency store previously flooded the preview. Verified scan → tick → create → folders appear correctly nested, re-scan offers only additions, and chats opened in created folders load that package's steering.
Screenshots / video
Scan preview of a workspace monorepo — deploy roots arrive confident and pre-ticked, workspace-member libs are offered, speculative sub-folders sit in the collapsed disclosure:
The created tree — folders nest as they do on disk, each with its
project_dirbound:Related Issues
Closes #2516. Related: #2164 (workspace-first model this composes toward — its roots are manually managed, so this scanner remains the missing populate step under either model), #1637 (folded into #2164).
Checklist
feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)Contribution License Agreement
N/A — no CLA text is published in the template yet; will sign whatever OSPO supplies when it lands.
Generated file touched outside the feature's own sources
Called out in the commit body as well:
error-code-baseline.json— re-snapshotted, counts only go down. Extractingcreate_folder_recordmoved two ofchat_folders.py's refusals behind aFolderCreateErrorthat carries acode, so itsmissing_codecount improves 21 → 19 (totals 1370 → 1368) andtest_baseline_is_not_stalerequires the snapshot be refreshed via the documentedpython test/test_error_code_contract.py --update. Nothing was regenerated to silence a regression: theexcept FolderCreateErrorhandler returns inline dict literals at eachjson_responsesite rather than a hoistedpayloadlocal, keepingopaque_bodyat 0 for that file, and the wire shape is byte-identical to the pre-extraction handler (codepresent only when the refusal carries one).Bundle budget note
scripts/check-bundle-size.mjskeeps upstream'sall(eager i18n catalog) ceiling at 10490 KB — this branch does NOT move it. An earlier revision raised a lower ceiling; rebasing onto a main that had already raised it higher made that unnecessary, so only the budget entry's comment changes, to record that this feature's 39 catalog keys (~107 KB of eager strings per catalog) ride inside the headroom upstream's measurement already left. No new library or surface reaches that chunk — the app's page sits behind a lazyimport()insrc/apps/builtinRegistry.ts.