kushi-agents 3.14.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/.github/copilot-instructions.kushi.md +15 -11
  2. package/bin/cli.mjs +3 -0
  3. package/package.json +1 -1
  4. package/plugin/agents/kushi.agent.md +2 -1
  5. package/plugin/instructions/azure-auth-patterns.instructions.md +2 -2
  6. package/plugin/instructions/engagement-root-resolution.instructions.md +3 -3
  7. package/plugin/instructions/run-reports.instructions.md +1 -1
  8. package/plugin/instructions/side-by-side-config.instructions.md +1 -1
  9. package/plugin/instructions/workiq-only.instructions.md +2 -2
  10. package/plugin/learnings/misc.md +1 -1
  11. package/plugin/prompts/bootstrap.prompt.md +1 -1
  12. package/plugin/reference-packs/README.md +2 -2
  13. package/plugin/reference-packs/fde/README.md +3 -3
  14. package/plugin/skills/ask-project/SKILL.md +1 -1
  15. package/plugin/skills/bootstrap-project/SKILL.md +5 -4
  16. package/plugin/skills/fde-intake/SKILL.md +1 -1
  17. package/plugin/skills/intro/SKILL.md +2 -2
  18. package/plugin/skills/propose-ado-update/SKILL.md +1 -1
  19. package/plugin/skills/pull-misc/README.md +1 -1
  20. package/plugin/skills/pull-misc/SKILL.md +2 -2
  21. package/plugin/skills/pull-misc/runner.mjs +1 -1
  22. package/plugin/skills/pull-onenote/README.md +1 -1
  23. package/plugin/skills/pull-onenote/SKILL.md +3 -3
  24. package/plugin/skills/pull-onenote/runner.mjs +1 -1
  25. package/plugin/templates/init/integrations.template.yml +19 -0
  26. package/plugin/templates/init/project-evidence.template.yml +19 -13
  27. package/plugin/templates/init/project-user-settings.template.yml +1 -1
  28. package/src/constants.mjs +55 -0
  29. package/src/main.mjs +106 -8
  30. package/src/seed-config.mjs +44 -0
@@ -6,15 +6,18 @@ This workspace has [Kushi](https://gim-home.github.io/kushi/) installed under `.
6
6
 
7
7
  When the user types any of the following verbs followed by a project name, route to the corresponding Kushi prompt in `.kushi/prompts/` (no `@Kushi` needed):
8
8
 
9
- | Verb | Prompt to run |
10
- | -------------------------- | ------------------------------------------ |
11
- | `bootstrap <project>` | `/pull-all-bootstrap` |
12
- | `refresh <project>` | `/pull-all-refresh` |
13
- | `aggregate <project>` | `/pull-all-aggregate` |
14
- | `state <project>` | `/rebuild-state` |
15
- | `consolidate <project>` | `/consolidate-evidence` |
16
- | `status <project>` | `/show-run-log` |
17
- | `ask <project> <question>` | `/ask-project` (auto-routes to evidence Q&A) |
9
+ | Verb | Prompt to run |
10
+ | -------------------------- | ---------------- |
11
+ | `bootstrap <project>` | `/bootstrap` |
12
+ | `refresh <project>` | `/refresh` |
13
+ | `aggregate <project>` | `/aggregate` |
14
+ | `state <project>` | `/state` |
15
+ | `consolidate <project>` | `/consolidate` |
16
+ | `status <project>` | `/status` |
17
+ | `ask <project> <question>` | `/ask` |
18
+ | `fde-intake <project>` | `/fde-intake` |
19
+ | `fde-report <project>` | `/fde-report` |
20
+ | `fde-triage <project>` | `/fde-triage` |
18
21
 
19
22
  Project Q&A also auto-dispatches when the user names a known project under the engagement root **and** asks a question about it — no prefix needed.
20
23
 
@@ -29,10 +32,11 @@ Project Q&A also auto-dispatches when the user names a known project under the e
29
32
  ### Layout
30
33
 
31
34
  - `.kushi/agents/kushi.agent.md` — orchestrator (also addressable as `@Kushi`)
32
- - `.kushi/prompts/` — verb prompts (`/pull-*`, `/ask-project`, etc.)
35
+ - `.kushi/prompts/` — verb prompts (`/bootstrap`, `/refresh`, `/ask`, etc.)
33
36
  - `.kushi/skills/` — per-source pull + render skills
34
37
  - `.kushi/instructions/` — doctrine (citations, WorkIQ-first, freshness gates)
35
- - `.kushi/reference-packs/` — bundled domain doctrine (override at project / user / packaged layers)
38
+ - `.kushi/reference-packs/` — bundled domain doctrine (override at project / workspace / packaged layers)
39
+ - `.kushi/config/` — **user-editable** (project-evidence.yml, integrations.yml, reference-overrides). Seeded on first install; never overwritten on upgrade.
36
40
  - `.kushi/kushi-install.json` — profile manifest written by the installer
37
41
 
38
42
  Full docs: <https://gim-home.github.io/kushi/>
package/bin/cli.mjs CHANGED
@@ -25,6 +25,8 @@ if (args.includes('--help') || args.includes('-h')) {
25
25
  Options:
26
26
  --dest <path> Override destination (relative for vscode, absolute for clawpilot)
27
27
  --force Overwrite existing destination without asking
28
+ --yes, -y Accept the default destination and skip the project-root
29
+ check (useful for scripted or agent-driven installs)
28
30
  --no-settings Skip .vscode/settings.json update (vscode target only)
29
31
  --no-instructions Skip .github/copilot-instructions.md merge (vscode target only)
30
32
  --help, -h Show this help
@@ -55,6 +57,7 @@ if (args.includes('--clawpilot')) {
55
57
  const options = {
56
58
  dest: getFlag('--dest'),
57
59
  force: args.includes('--force'),
60
+ yes: args.includes('--yes') || args.includes('-y'),
58
61
  noSettings: args.includes('--no-settings'),
59
62
  noInstructions: args.includes('--no-instructions'),
60
63
  target,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kushi-agents",
3
- "version": "3.14.0",
3
+ "version": "4.0.0",
4
4
  "description": "Install Kushi — multi-source project evidence agent with snapshot+stream capture across Email, Teams, OneNote, SharePoint, Meetings, CRM, ADO. WorkIQ-only for M365 sources (Graph / m365_* FORBIDDEN as fallbacks; user-paste is first-class). Host-agnostic.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -95,7 +95,8 @@ When a user message arrives:
95
95
  ## Configuration layout
96
96
 
97
97
  ```
98
- <USER_HOME>/.copilot/project-evidence.yml ← personal: alias, engagement_root, active_projects
98
+ <workspace>/.kushi/config/project-evidence.yml ← personal: alias, engagement_root, active_projects (seeded by installer; never overwritten)
99
+ <workspace>/.kushi/config/integrations.yml ← optional global CRM/ADO defaults (seeded by installer; never overwritten)
99
100
  <engagement-root>/.project-evidence/ ← per-machine, per-user M365 + CRM + ADO config (OneDrive-synced)
100
101
  m365/m365-auth.json
101
102
  m365/m365-mutable.json
@@ -227,7 +227,7 @@ Maintained alongside `auth-and-retry §3` (canonical) — quick lookup:
227
227
  | Dynamics 365 / CRM | `<engagement-root>/.project-evidence/crm/config.yml` |
228
228
  | Azure DevOps | `<engagement-root>/.project-evidence/ado/config.yml` |
229
229
  | Microsoft Graph / M365 / OneNote | `<engagement-root>/.project-evidence/m365/m365-mutable.json` + `<engagement-root>/.project-evidence/m365/m365-auth.json` |
230
- | WorkIQ CLI path | `~/.copilot/project-evidence.yml` (user-scoped) |
231
- | Global integrations (optional) | `~/.copilot/integrations/{crm,ado}/` |
230
+ | WorkIQ CLI path | `<workspace>/.kushi/config/project-evidence.yml` (workspace-scoped) |
231
+ | Global integrations (optional) | `<workspace>/.kushi/config/integrations.yml` |
232
232
 
233
233
  Always read tenant IDs, resource URLs, org URLs, and allowed-tenant lists from these files at the start of each run. **Never hardcode them in skills or prompts.** The only acceptable literal in instructions/prompts is the public Microsoft tenant ID `72f988bf-86f1-41af-91ab-2d7cd011db47` in the user-facing `az login --tenant ...` suggestion text.
@@ -11,9 +11,9 @@ The **engagement-root** is the parent folder that contains all of the user's eng
11
11
 
12
12
  Resolve `<engagement-root>` in this order — first match wins:
13
13
 
14
- 1. **`<USER_HOME>/.copilot/project-evidence.yml`** — read `engagement_root:` field (preferred).
14
+ 1. **`<workspace>/.kushi/config/project-evidence.yml`** — read `engagement_root:` field (preferred).
15
15
  2. **`customer_workspace/FDEDocs/`** — if the user's workspace has this symlink, follow it. Common in FDE-style installs.
16
- 3. **Ask the user** once and persist the answer to `<USER_HOME>/.copilot/project-evidence.yml engagement_root`.
16
+ 3. **Ask the user** once and persist the answer to `<workspace>/.kushi/config/project-evidence.yml engagement_root`.
17
17
 
18
18
  ## Live config location
19
19
 
@@ -43,7 +43,7 @@ Once `<engagement-root>` is known, individual projects are subfolders:
43
43
  Project name resolution (always fuzzy):
44
44
 
45
45
  1. Match against keys in `<engagement-root>/.project-evidence/m365/m365-mutable.json m365Mutable.knownSections`.
46
- 2. Match against `active_projects:` in `<USER_HOME>/.copilot/project-evidence.yml`.
46
+ 2. Match against `active_projects:` in `<workspace>/.kushi/config/project-evidence.yml`.
47
47
  3. Match against actual subfolder names under `<engagement-root>`.
48
48
 
49
49
  Case-insensitive; ranking `exact > prefix > contains`. Multiple plausible candidates → ask user to pick. Zero candidates AND verb is `bootstrap` → create the folder. Zero candidates AND verb is anything else → ask user.
@@ -24,7 +24,7 @@ Critical for multi-user projects: each contributor's runs are independent, and e
24
24
  YYYY-MM-DD-HHmm_<mode>.md
25
25
  ```
26
26
 
27
- - `<alias>` — the contributor running the skill (from `~/.copilot/project-evidence.yml#alias`).
27
+ - `<alias>` — the contributor running the skill (from `<workspace>/.kushi/config/project-evidence.yml#alias`).
28
28
  - `<mode>` — one of `bootstrap`, `refresh`, `force-refresh`, `consolidate`.
29
29
  - Timestamp uses the **start time** of the run, in local time, format `YYYY-MM-DD-HHmm` (no seconds, no timezone — local-time prefix is enough for chronological sort).
30
30
 
@@ -52,5 +52,5 @@ End every bootstrap / fix-up turn by listing the live config files (path + size
52
52
  ## Why outside the kushi repo
53
53
 
54
54
  - **Multi-machine portability** — OneDrive-synced beside engagement folders, follows the user wherever they install kushi.
55
- - **Discoverability** — user can see/edit it next to their engagement work, not buried in `~/.copilot/`.
55
+ - **Discoverability** — user can see/edit it next to their engagement work and inside their workspace at `<workspace>/.kushi/config/`, not buried in an OS-hidden home directory.
56
56
  - **Repo-safe** — kushi is meant to be GitHub-published; live filled configs MUST never be committed.
@@ -47,7 +47,7 @@ For every M365 source in scope:
47
47
 
48
48
  ## Canonical WorkIQ commands (CODIFIED — do not re-discover)
49
49
 
50
- The CLI is at `C:\Users\ushak\.copilot\bin\workiq.cmd` (resolved from `<USER_HOME>/.copilot/project-evidence.yml workiq.cli_path`; fall back to PATH).
50
+ The CLI is at `<USER_HOME>\.kushi\bin\workiq.cmd` (resolved from `<workspace>/.kushi/config/project-evidence.yml workiq.cli_path`; fall back to PATH; fall back to `~/.kushi/bin/workiq.cmd`).
51
51
 
52
52
  Invocation shape:
53
53
 
@@ -152,7 +152,7 @@ List my Teams meetings between <YYYY-MM-DD> and <YYYY-MM-DD> where the subject c
152
152
 
153
153
  Before the first WorkIQ query in a run:
154
154
 
155
- 1. Resolve CLI path: `<USER_HOME>/.copilot/project-evidence.yml workiq.cli_path` OR `Get-Command workiq`. If missing → log `workiq-not-on-path`, write evidence file pointing at install docs, STOP this source.
155
+ 1. Resolve CLI path: `<workspace>/.kushi/config/project-evidence.yml workiq.cli_path` `Get-Command workiq` → `~/.kushi/bin/workiq.cmd`. If none resolves → log `workiq-not-on-path`, write evidence file pointing at install docs, STOP this source.
156
156
  2. Probe with `workiq ask -q "ping"`. If EULA prompt → `workiq accept-eula` once, retry.
157
157
  3. Capture `--version` once into run-log for audit.
158
158
 
@@ -17,7 +17,7 @@ The user pasting links into `external-links.txt` is the right boundary: auditabl
17
17
  **Routing model:**
18
18
 
19
19
  - `onenote / sharepoint / ado` → delegated to dedicated skills (recorded as `delegated` in registry, not double-pulled here).
20
- - `loop` → browser path (Playwright, reuses `~/.copilot/playwright-profile/onenote/` because same M365 cookie scope).
20
+ - `loop` → browser path (Playwright, reuses `~/.kushi/playwright-profile/onenote/` because same M365 cookie scope).
21
21
  - `web / learn / docs / github / confluence / pdf / unknown` → HTTP path (fetch + @mozilla/readability for HTML extraction).
22
22
  - `file` → local file read.
23
23
  - Anything matching `<PASTE_*_URL>` or `<TODO*>` → `placeholder`, surfaced in run report until filled.
@@ -12,7 +12,7 @@ Inputs the agent will resolve:
12
12
  - `<window>` — defaults to 30 days. Override with `last 60 days`, `since 2026-04-01`, or `2026-04-01..2026-05-01`.
13
13
 
14
14
  Reads:
15
- - `<USER_HOME>/.copilot/project-evidence.yml` for alias + engagement-root.
15
+ - `<workspace>/.kushi/config/project-evidence.yml` for alias + engagement-root.
16
16
 
17
17
  Produces:
18
18
  - `<engagement-root>/.project-evidence/m365/{m365-auth,m365-mutable}.json` (per-user filled)
@@ -11,7 +11,7 @@ When a skill (e.g. `fde-report`, `ask-project`) needs a pack's content, it loads
11
11
  | Priority | Location | Purpose | Lifecycle |
12
12
  |---:|---|---|---|
13
13
  | 1 (highest) | `<engagement-root>/<project>/.kushi-reference/<pack>/` | Per-engagement override | Versioned with the engagement; for project-specific tuning |
14
- | 2 | `~/.copilot/kushi-reference/<pack>/` | Per-user override | Per machine / per user; never overwritten by installer |
14
+ | 2 | `<workspace>/.kushi/config/reference-overrides/<pack>/` | Workspace override | Per workspace / per machine; never overwritten by installer |
15
15
  | 3 (canonical) | `<install-dest>/reference-packs/<pack>/` (i.e. this folder, installed) | Packaged default | Updated via `npm publish` + version bump |
16
16
 
17
17
  Skills must always:
@@ -33,7 +33,7 @@ Skills must always:
33
33
  What does **not** go in a pack:
34
34
 
35
35
  - Per-engagement data (lives under `<project>/Evidence/`)
36
- - Per-user identity / configs (lives in `~/.copilot/project-evidence.yml`)
36
+ - Per-user identity / configs (lives in `<workspace>/.kushi/config/project-evidence.yml`)
37
37
  - Skill behavior (lives in `plugin/skills/<skill>/SKILL.md`)
38
38
  - Cross-cutting agent doctrine (lives in `plugin/instructions/*.instructions.md`)
39
39
 
@@ -23,9 +23,9 @@ Use this pack when a report needs:
23
23
  2. **Enumerate and read all human-readable `.md` files** in the pack.
24
24
  3. Treat the pack as a **single reference set** — any file here may contribute to the FDE operating model used in reports.
25
25
  4. **Apply the 3-layer override order** (highest wins):
26
- - `<engagement-root>/<project>/.kushi-reference/fde/` ← per-engagement override
27
- - `~/.copilot/kushi-reference/fde/` per-user override
28
- - `<install-dest>/reference-packs/fde/` ← packaged default (this folder, installed)
26
+ - `<engagement-root>/<project>/.kushi-reference/fde/` ← per-engagement override
27
+ - `<workspace>/.kushi/config/reference-overrides/fde/` workspace override
28
+ - `<install-dest>/reference-packs/fde/` ← packaged default (this folder, installed)
29
29
  5. If two files at the same layer conflict, prefer the more **specific** file for that concept and raise the conflict explicitly in the skill's run-log / Open Questions — never silently average.
30
30
 
31
31
  ## File ownership
@@ -40,7 +40,7 @@ In order, stop on first hit:
40
40
  1. Exact `<engagement-root>/<project>/Evidence/` folder.
41
41
  2. Fuzzy match against `discovery.project_aliases` in any `<engagement-root>/<project>/Evidence/<alias>/.settings.yml`.
42
42
  3. `<engagement-root>/<project>/external-context/` (lighter-weight projects without full bootstrap).
43
- 4. `~/.copilot/project-evidence.yml` `active_projects` list.
43
+ 4. `<workspace>/.kushi/config/project-evidence.yml` `active_projects` list.
44
44
 
45
45
  If zero matches → ask user. If multiple → list candidates and ask user to pick. **Echo the resolved root path before answering** so the user can confirm.
46
46
 
@@ -44,13 +44,13 @@ After every run (success or coverage-gaps), write `<project>/bootstrap-status.md
44
44
  Verify in order. Stop on hard failures.
45
45
 
46
46
  1. **OS + host runtime** — display OS + Node/PowerShell version. Informational.
47
- 2. **WorkIQ install (REQUIRED, hard stop)** — read `<USER_HOME>/.copilot/project-evidence.yml workiq.cli_path`. If missing, probe known paths:
48
- - `$HOME\.copilot\bin\workiq.cmd`
47
+ 2. **WorkIQ install (REQUIRED, hard stop)** — read `<workspace>/.kushi/config/project-evidence.yml workiq.cli_path`. If missing, probe known paths:
48
+ - `$HOME\.kushi\bin\workiq.cmd`
49
49
  - `$env:LOCALAPPDATA\Programs\WorkIQ\workiq.cmd`
50
50
  - `$env:ProgramFiles\WorkIQ\workiq.cmd`
51
51
  If found, persist path. If not found, ask user for path (or to install). Test with `<workiq.cli_path> --help`. Without WorkIQ, M365 sources will all fail — STOP.
52
52
  3. **Conditional az login** — only if `<engagement-root>/.project-evidence/crm/config.yml` OR `<engagement-root>/.project-evidence/ado/config.yml` exists. Per `az-auth-conditional.instructions.md`. Soft warning on failure, never blocking.
53
- 4. **Engagement-root resolution** — per `engagement-root-resolution.instructions.md`. Persist to `<USER_HOME>/.copilot/project-evidence.yml engagement_root` if newly resolved.
53
+ 4. **Engagement-root resolution** — per `engagement-root-resolution.instructions.md`. Persist to `<workspace>/.kushi/config/project-evidence.yml engagement_root` if newly resolved.
54
54
 
55
55
  Display SETUP summary table with ✅ / ⚙️ / ❌ / ⚠️ / ➖ markers.
56
56
 
@@ -62,7 +62,8 @@ Required live files:
62
62
 
63
63
  | Live file | Template source |
64
64
  |---|---|
65
- | `<USER_HOME>/.copilot/project-evidence.yml` | `templates/init/project-evidence.template.yml` |
65
+ | `<workspace>/.kushi/config/project-evidence.yml` | `templates/init/project-evidence.template.yml` (seeded by installer) |
66
+ | `<workspace>/.kushi/config/integrations.yml` | `templates/init/integrations.template.yml` (seeded by installer) |
66
67
  | `<engagement-root>/.project-evidence/m365/m365-auth.json` | `templates/init/m365-auth.template.json` |
67
68
  | `<engagement-root>/.project-evidence/m365/m365-mutable.json` | `templates/init/m365-mutable.template.json` |
68
69
  | `<engagement-root>/<project>/integrations.yml` | `templates/init/project-integrations.template.yml` |
@@ -31,7 +31,7 @@ Same algorithm as `ask-project` Step 1. Echo the resolved root path before draft
31
31
  Build the pack's virtual filesystem with the **3-layer override order** (highest wins):
32
32
 
33
33
  1. **Project override** — `<engagement-root>/<project>/.kushi-reference/fde/` (if present)
34
- 2. **User override** — `~/.copilot/kushi-reference/fde/` (if present)
34
+ 2. **Workspace override** — `<workspace>/.kushi/config/reference-overrides/fde/` (if present)
35
35
  3. **Packaged default** — relative to this skill's install location:
36
36
  - vscode target: `<workspace>/.kushi/reference-packs/fde/`
37
37
  - clawpilot target: `~/.copilot/m-skills/kushi/reference-packs/fde/`
@@ -138,7 +138,7 @@ Present this verbatim:
138
138
 
139
139
  Before opening, check whether any project is already bootstrapped:
140
140
 
141
- 1. Read `~/.copilot/project-evidence.yml` (personal config). If `active_projects` has entries, pick the most recent as the demo target — substitute that name into `{{active_project}}` placeholders below.
141
+ 1. Read `<workspace>/.kushi/config/project-evidence.yml` (personal config). If `active_projects` has entries, pick the most recent as the demo target — substitute that name into `{{active_project}}` placeholders below.
142
142
  2. If none, propose a fictional project named **Contoso Discovery** and tell the user that Try-it prompts will use that name; they can substitute their own at any time.
143
143
 
144
144
  ### Navigation Keywords
@@ -444,6 +444,6 @@ Reply `next` to see the closing cheat sheet, or `done` to exit.
444
444
  - Rule 1.2: Mode selection must always present BOTH options — never auto-route to one mode.
445
445
  - Rule 1.3: In walkthrough mode, each moment must wait for `next` / `skip` / `done` / `try` before advancing — never auto-advance.
446
446
  - Rule 1.4: `done` must work at every moment — never trap the user.
447
- - Rule 1.5: Try-it prompts must use the active project name from `~/.copilot/project-evidence.yml`, OR the placeholder `Contoso Discovery` if no projects are configured. Never invent a real-sounding project name.
447
+ - Rule 1.5: Try-it prompts must use the active project name from `<workspace>/.kushi/config/project-evidence.yml`, OR the placeholder `Contoso Discovery` if no projects are configured. Never invent a real-sounding project name.
448
448
  - Rule 1.6: Skill descriptions in this file must match `kushi.agent.md` and the live `SKILL.md` files — `self-check` D-checks catch drift.
449
449
  - Rule 1.7: Demo moments must reflect the actual verb count in `kushi.agent.md`. If a verb is added or removed, this file must be updated in the same commit (`self-check` warns on drift).
@@ -27,7 +27,7 @@ This skill does **NOT** create a new top-level config file. It reads from the co
27
27
 
28
28
  **Resolution is fully deterministic** — never ask the user for paths the bootstrap layer already resolved:
29
29
 
30
- 1. `<engagement-root>` ← `~/.copilot/project-evidence.yml engagement_root` (per `engagement-root-resolution.instructions.md`).
30
+ 1. `<engagement-root>` ← `<workspace>/.kushi/config/project-evidence.yml engagement_root` (per `engagement-root-resolution.instructions.md`).
31
31
  2. `<project>` folder ← fuzzy-match per `engagement-root-resolution.instructions.md` (knownSections → active_projects → subfolders).
32
32
  3. `<project>/integrations.yml ado.engagement_id` — REQUIRED. If `0` or missing → see "Prerequisites" below; never invent an ID.
33
33
  4. `<project>/integrations.yml ado.writes` — if missing, append the block from `<KUSHI_ROOT>/plugin/templates/ado-update/integrations-ado-writes.example.yml` and stop for user confirmation of `fieldRefName`.
@@ -69,7 +69,7 @@ node runner.mjs --project ABN ... --types web,loop --titles "Architecture"
69
69
 
70
70
  ## Scheduled / unattended runs
71
71
 
72
- - Browser branch (loop) reuses `~/.copilot/playwright-profile/onenote/`. When MFA fires, runner marks loop links `auth-required` and exits cleanly. User does one interactive bootstrap; next scheduled run silent.
72
+ - Browser branch (loop) reuses `~/.kushi/playwright-profile/onenote/`. When MFA fires, runner marks loop links `auth-required` and exits cleanly. User does one interactive bootstrap; next scheduled run silent.
73
73
  - HTTP branch needs no auth for public links. For sites behind SSO (auth'd Confluence, internal dashboards) it returns `auth-required` and is currently NOT supported — paste the content into a `file` link as a workaround, OR add a per-site auth handler in a future version.
74
74
  - `placeholder` links surface in the run report every refresh until the user fills them in.
75
75
 
@@ -23,7 +23,7 @@ Pulls **misc** evidence in two shapes per `snapshot-vs-stream.instructions.md`:
23
23
 
24
24
  ## Tools (in order)
25
25
 
26
- 1. **Playwright (browser-scrape, persisted profile)** — for `loop` links. Reuses `~/.copilot/playwright-profile/onenote/` because it's the same M365 cookie scope. Implementation: `plugin/skills/pull-misc/runner.mjs` branch `loop`.
26
+ 1. **Playwright (browser-scrape, persisted profile)** — for `loop` links. Reuses `~/.kushi/playwright-profile/onenote/` because it's the same M365 cookie scope. Implementation: `plugin/skills/pull-misc/runner.mjs` branch `loop`.
27
27
  2. **HTTP fetch + Readability** — for `web` / `confluence` (anonymous) / `learn` / `docs` / `pdf` links. Implementation: `plugin/skills/pull-misc/runner.mjs` branch `web`.
28
28
  3. **Local file read** — for `file` links pointing at project-relative paths. Implementation: `plugin/skills/pull-misc/runner.mjs` branch `file`.
29
29
  4. **WorkIQ** — used only for stream/edit-event signals on `loop` links (Loop edit events surface in the M365 search index, same as OneNote stream events). NOT used for body retrieval — Loop bodies require the browser path.
@@ -72,7 +72,7 @@ These three facts are HARD-rule:
72
72
 
73
73
  ```pwsh
74
74
  # A. Playwright profile (only required if external-links.txt contains loop links)
75
- $prof = "$env:USERPROFILE\.copilot\playwright-profile\onenote"
75
+ $prof = "$env:USERPROFILE\.kushi\playwright-profile\onenote"
76
76
  if (-not (Test-Path $prof)) {
77
77
  Write-Host "[pull-misc] Playwright profile not yet seeded — loop links will be skipped with last_status: auth-required."
78
78
  Write-Host "[pull-misc] Run plugin/skills/pull-onenote/runner.mjs --bootstrap once to seed it."
@@ -36,7 +36,7 @@ const HEADLESS = !!args.headless;
36
36
  const TYPES_FILTER = args.types ? String(args.types).split(',').map(s => s.trim().toLowerCase()) : null;
37
37
  const TITLES_FILTER = args.titles ? String(args.titles).split(',').map(s => s.trim()) : null;
38
38
  const TIMEOUT_MS = parseInt(args.timeout || '45000', 10);
39
- const PROFILE_DIR = path.join(os.homedir(), '.copilot', 'playwright-profile', 'onenote');
39
+ const PROFILE_DIR = path.join(os.homedir(), '.kushi', 'playwright-profile', 'onenote');
40
40
 
41
41
  if (!PROJECT || !LINKS_FILE) {
42
42
  console.error('Usage: --project <name> --links-file <path> [--headless] [--types t1,t2] [--titles "t1,t2"]');
@@ -21,7 +21,7 @@ When the bootstrap command opens a Chromium window:
21
21
  - Wait for the OneNote home page to load.
22
22
  - Close the window.
23
23
 
24
- The profile is persisted at `~/.copilot/playwright-profile/onenote/`. Subsequent runs reuse it.
24
+ The profile is persisted at `~/.kushi/playwright-profile/onenote/`. Subsequent runs reuse it.
25
25
 
26
26
  ## Per-section runs
27
27
 
@@ -23,7 +23,7 @@ Pulls **onenote** evidence in two shapes per `snapshot-vs-stream.instructions.md
23
23
 
24
24
  ## Tools (in order)
25
25
 
26
- 1. **Playwright (browser-scrape, persisted profile)** — PRIMARY. Drives OneNote-for-Web to enumerate pages and read verbatim bodies via `#PageContentWrapper`. Profile lives at `~/.copilot/playwright-profile/onenote/` and is reused across runs. Implementation: `plugin/skills/pull-onenote/runner.mjs`.
26
+ 1. **Playwright (browser-scrape, persisted profile)** — PRIMARY. Drives OneNote-for-Web to enumerate pages and read verbatim bodies via `#PageContentWrapper`. Profile lives at `~/.kushi/playwright-profile/onenote/` and is reused across runs. Implementation: `plugin/skills/pull-onenote/runner.mjs`.
27
27
  2. **WorkIQ** — FALLBACK only. Used when the Playwright profile is auth-expired (`auth-required`) and for the stream/edit-event source. Always cite `m365-id-registry` doctrine when invoking; never use as primary for body retrieval (proven non-deterministic).
28
28
  3. **Host (m365_*)** — not used (Graph `Notes.Read.All` denied admin consent in this tenant).
29
29
 
@@ -145,7 +145,7 @@ Before any retrieval, validate the Playwright profile and (only if used as fallb
145
145
  ### A. Playwright profile (PRIMARY)
146
146
 
147
147
  ```pwsh
148
- $prof = "$env:USERPROFILE\.copilot\playwright-profile\onenote"
148
+ $prof = "$env:USERPROFILE\.kushi\playwright-profile\onenote"
149
149
  if (-not (Test-Path $prof)) {
150
150
  Write-Host "[pull-onenote] Playwright profile not yet seeded."
151
151
  Write-Host "[pull-onenote] Run plugin/skills/pull-onenote/runner.mjs --bootstrap once interactively to sign in."
@@ -170,7 +170,7 @@ Cookie domains do not share between `*.cloud.microsoft` and `*.sharepoint(-df).c
170
170
 
171
171
  These are the same selectors `preflightOneNoteWeb` uses. The bootstrap log MUST emit `Sign-in detected (OneNote chrome rendered).` between Step 1/2 and Step 2/2 — its absence is the defect signature of the v3.11.1-and-earlier bug (sign-in was silently skipped, and every subsequent scrape returned `auth-required` regardless of how thoroughly the user signed in). Same anti-pattern applies to any future bootstrap surface (SharePoint, Loop, M365 admin).
172
172
 
173
- **A.3 Profile reset on channel switch:** if migrating from a Chromium profile to Edge (or vice versa), delete `~/.copilot/playwright-profile/onenote/` first. Cookie/cache formats differ.
173
+ **A.3 Profile reset on channel switch:** if migrating from a Chromium profile to Edge (or vice versa), delete `~/.kushi/playwright-profile/onenote/` first. Cookie/cache formats differ.
174
174
 
175
175
  **A.4 OneNote-for-Web reachability pre-flight — HARD rule (kushi v3.11.0+):** before navigating to any section URL, the runner MUST probe `https://onenote.cloud.microsoft/` and classify the end-state into exactly one of three buckets:
176
176
 
@@ -40,7 +40,7 @@ const args = Object.fromEntries(
40
40
  }, [])
41
41
  );
42
42
 
43
- const PROFILE_DIR = path.join(os.homedir(), '.copilot', 'playwright-profile', 'onenote');
43
+ const PROFILE_DIR = path.join(os.homedir(), '.kushi', 'playwright-profile', 'onenote');
44
44
  const HEADLESS = !!args.headless;
45
45
  const BOOTSTRAP = !!args.bootstrap;
46
46
  const PREFLIGHT = !!args.preflight;
@@ -0,0 +1,19 @@
1
+ # Kushi — optional global integrations defaults.
2
+ #
3
+ # Seeded by the installer on first install. Never overwritten on upgrade.
4
+ # Per-project `integrations.yml` (inside the engagement folder) always
5
+ # takes precedence over this file.
6
+ #
7
+ # Most users can leave this file as-is. Fill it in only if you want a
8
+ # default CRM / ADO configuration that applies across all projects.
9
+
10
+ # Microsoft Dataverse (CRM) defaults.
11
+ # crm:
12
+ # environment_url: 'https://<org>.crm.dynamics.com'
13
+ # tenant_id: '72f988bf-86f1-41af-91ab-2d7cd011db47' # Microsoft tenant
14
+
15
+ # Azure DevOps defaults.
16
+ # ado:
17
+ # organization: 'https://dev.azure.com/<org>'
18
+ # project: '<default-project>'
19
+ # tenant_id: '72f988bf-86f1-41af-91ab-2d7cd011db47' # Microsoft tenant
@@ -1,32 +1,38 @@
1
- # Personal config for Kushi.
2
- # Copy to: ~/.copilot/project-evidence.yml (Linux/macOS)
3
- # %USERPROFILE%\.copilot\project-evidence.yml (Windows)
1
+ # Kushi personal config.
4
2
  #
5
- # Each team member maintains their own copy. Nothing in here is a secret,
6
- # but it IS personal never commit a filled copy to a shared repo.
3
+ # This file was seeded by the installer on first install. It is YOUR copy,
4
+ # never overwritten on reinstall / --force / upgrade. Each team member
5
+ # maintains their own. Nothing here is a secret, but it IS personal —
6
+ # add `.kushi/config/` to .gitignore before committing the rest of .kushi/.
7
+ #
8
+ # Edit the values below (lines marked FILL ME IN), then run `bootstrap <project>`.
7
9
 
8
- # Short id used as your evidence subfolder name (e.g. "alex", "jordan").
10
+ # FILL ME IN — short id used as your evidence subfolder name (e.g. "alex", "jordan").
9
11
  alias: <your-alias>
10
12
 
13
+ # FILL ME IN
11
14
  display_name: <Your Full Name>
12
15
  email: your.email@example.com
13
16
 
14
- # Where your engagement-root lives — i.e. the parent folder that contains
17
+ # FILL ME IN — where your engagement-root lives. The parent folder containing
15
18
  # one subfolder per project (typically synced from a team SharePoint library).
16
19
  # Examples:
17
20
  # Windows : 'C:\Users\<you>\OneDrive - <Tenant>\<Team>\Engagement Assets'
18
21
  # macOS : '/Users/<you>/OneDrive - <Tenant>/<Team>/Engagement Assets'
19
22
  projects_root: '<engagement-root>'
20
23
 
21
- # Default OneNote notebook to scan for project pages.
24
+ # FILL ME IN — default OneNote notebook to scan for project pages.
22
25
  default_onenote_notebook: <Your Notebook Name>
23
26
 
24
- # Projects you actively work on. Kushi scans only these unless you ask otherwise.
25
- # Match is fuzzy against subfolder names under projects_root.
27
+ # FILL ME IN — projects you actively work on. Kushi scans only these unless
28
+ # you ask otherwise. Match is fuzzy against subfolder names under projects_root.
26
29
  active_projects:
27
30
  - <ProjectA>
28
31
  # - <ProjectB>
29
- # - <add more here>
30
32
 
31
- # (Optional) where the global integrations live, if not the default.
32
- # integrations_root: '<USER_HOME>/.copilot/integrations'
33
+ # (Optional) Path to the WorkIQ CLI. If omitted, Kushi resolves in this order:
34
+ # 1. this `cli_path` value
35
+ # 2. `workiq` on PATH
36
+ # 3. ~/.kushi/bin/workiq.cmd (Windows) / ~/.kushi/bin/workiq (Linux/macOS)
37
+ # workiq:
38
+ # cli_path: 'C:\Users\<you>\.kushi\bin\workiq.cmd'
@@ -21,7 +21,7 @@ teams_chats:
21
21
  search_keywords: []
22
22
 
23
23
  onenote:
24
- notebook: '{{ONENOTE_NOTEBOOK}}' # default from ~\.copilot\project-evidence.yml
24
+ notebook: '{{ONENOTE_NOTEBOOK}}' # default from <workspace>\.kushi\config\project-evidence.yml
25
25
  section: '{{ONENOTE_SECTION}}' # e.g. 'Noridian.one'
26
26
  page_filter: '' # optional substring filter
27
27
 
package/src/constants.mjs CHANGED
@@ -22,6 +22,21 @@ export const PLUGIN_SOURCE_DIR = 'plugin';
22
22
  /** Asset subdirectories under plugin/ that get copied. */
23
23
  export const ASSET_DIRS = ['agents', 'instructions', 'prompts', 'skills', 'templates', 'reference-packs'];
24
24
 
25
+ /**
26
+ * User-editable config directory under the install destination.
27
+ * Seeded on first install only; never overwritten on reinstall / --force / upgrade.
28
+ */
29
+ export const CONFIG_DIR = 'config';
30
+
31
+ /**
32
+ * Files seeded into <dest>/config/ on first install only.
33
+ * Each entry: { template: <path under plugin/templates/init/>, target: <name in config/> }
34
+ */
35
+ export const CONFIG_SEED_FILES = [
36
+ { template: 'init/project-evidence.template.yml', target: 'project-evidence.yml' },
37
+ { template: 'init/integrations.template.yml', target: 'integrations.yml' },
38
+ ];
39
+
25
40
  /** Files to exclude from consumer installs (relative to plugin/). */
26
41
  export const EXCLUDED_FILES = [];
27
42
 
@@ -40,6 +55,46 @@ export const PROJECT_GITHUB_EXCLUDED_FILES = [
40
55
  'config/m365-mutable.json'
41
56
  ];
42
57
 
58
+ /**
59
+ * Files / directories that indicate the cwd is a sane install target.
60
+ *
61
+ * Two flavors:
62
+ * - 'code' — a software project root (any common ecosystem).
63
+ * - 'engagement' — a Kushi engagement folder (Evidence/State/integrations live here,
64
+ * or this dir already has a prior Kushi install / reference pack).
65
+ *
66
+ * Either flavor suppresses the "this directory does not look like a project root"
67
+ * warning. If neither is present and the directory is non-empty, the installer
68
+ * shows an actionable warning. If the directory is empty, it shows a friendly
69
+ * INFO message instead — empty dirs are the normal "starting fresh" case.
70
+ */
71
+ export const PROJECT_MARKERS = [
72
+ // ── code project roots ──────────────────────────────────────────────────
73
+ { name: 'package.json', kind: 'code', type: 'file' },
74
+ { name: '.git', kind: 'code', type: 'dir' },
75
+ { name: 'pyproject.toml', kind: 'code', type: 'file' },
76
+ { name: 'requirements.txt', kind: 'code', type: 'file' },
77
+ { name: 'Cargo.toml', kind: 'code', type: 'file' },
78
+ { name: 'go.mod', kind: 'code', type: 'file' },
79
+ { name: 'pom.xml', kind: 'code', type: 'file' },
80
+ { name: 'build.gradle', kind: 'code', type: 'file' },
81
+ { name: 'build.gradle.kts', kind: 'code', type: 'file' },
82
+ { name: 'composer.json', kind: 'code', type: 'file' },
83
+ { name: 'Gemfile', kind: 'code', type: 'file' },
84
+ { name: 'mix.exs', kind: 'code', type: 'file' },
85
+ // ── .NET (glob, not literal name) ───────────────────────────────────────
86
+ { glob: /\.csproj$/i, kind: 'code', type: 'file' },
87
+ { glob: /\.fsproj$/i, kind: 'code', type: 'file' },
88
+ { glob: /\.sln$/i, kind: 'code', type: 'file' },
89
+ // ── Kushi engagement folders ────────────────────────────────────────────
90
+ { name: 'Evidence', kind: 'engagement', type: 'dir' },
91
+ { name: 'State', kind: 'engagement', type: 'dir' },
92
+ { name: 'integrations.yml', kind: 'engagement', type: 'file' },
93
+ { name: 'integrations.yaml', kind: 'engagement', type: 'file' },
94
+ { name: '.kushi', kind: 'engagement', type: 'dir' },
95
+ { name: '.kushi-reference', kind: 'engagement', type: 'dir' },
96
+ ];
97
+
43
98
  /** VS Code settings keys → the .kushi subdirectory they point at. */
44
99
  export const SETTINGS_MAP = {
45
100
  'chat.agentFilesLocations': 'agents',
package/src/main.mjs CHANGED
@@ -10,11 +10,13 @@ import {
10
10
  CLAWPILOT_AGENT_SOURCE,
11
11
  CLAWPILOT_SKILL_DEST,
12
12
  PLUGIN_SOURCE_DIR,
13
+ PROJECT_MARKERS,
13
14
  } from './constants.mjs';
14
15
  import { promptForDestination } from './prompt.mjs';
15
16
  import { copyAssets, copyProjectFiles } from './copy-assets.mjs';
16
17
  import { mergeSettings } from './settings.mjs';
17
18
  import { mergeCopilotInstructions } from './copilot-instructions.mjs';
19
+ import { seedConfig } from './seed-config.mjs';
18
20
  import {
19
21
  resolveProfile,
20
22
  makeIncludeFilter,
@@ -82,14 +84,8 @@ export async function main(options = {}) {
82
84
  */
83
85
  async function installVscode(options, resolved, version) {
84
86
  const projectRoot = process.cwd();
85
- const hasProjectMarker =
86
- fs.existsSync(path.join(projectRoot, 'package.json')) ||
87
- fs.existsSync(path.join(projectRoot, '.git'));
88
-
89
- if (!hasProjectMarker) {
90
- console.warn(
91
- ' WARN: This directory does not look like a project root (no package.json or .git found). Proceeding anyway.\n',
92
- );
87
+ if (!options.yes) {
88
+ warnIfNotProjectRoot(projectRoot);
93
89
  }
94
90
 
95
91
  let dest;
@@ -112,6 +108,8 @@ async function installVscode(options, resolved, version) {
112
108
  console.error('\n Destination must be within the current project.\n');
113
109
  process.exit(1);
114
110
  }
111
+ } else if (options.yes) {
112
+ dest = DEFAULT_DEST;
115
113
  } else {
116
114
  dest = await promptForDestination(DEFAULT_DEST);
117
115
  }
@@ -132,6 +130,13 @@ async function installVscode(options, resolved, version) {
132
130
  const manifestPath = writeInstalledManifest(fullDest, resolved, version);
133
131
  console.log(` - kushi-install.json (profile manifest)`);
134
132
 
133
+ const { seeded, preserved } = seedConfig(PKG_ROOT, fullDest);
134
+ if (seeded.length > 0 || preserved.length > 0) {
135
+ console.log(`\n Config (.kushi/config/ — user-editable, preserved across upgrades):`);
136
+ for (const f of seeded) console.log(` - ${f} -> seeded from template`);
137
+ for (const f of preserved) console.log(` - ${f} -> preserved (already exists)`);
138
+ }
139
+
135
140
  if (!options.noSettings) {
136
141
  const { created, keysAdded, keysUnchanged } = mergeSettings(
137
142
  projectRoot,
@@ -224,6 +229,13 @@ async function installClawpilot(options, resolved, version) {
224
229
  writeInstalledManifest(fullDest, resolved, version);
225
230
  console.log(` - kushi-install.json (profile manifest)`);
226
231
 
232
+ const { seeded, preserved } = seedConfig(PKG_ROOT, fullDest);
233
+ if (seeded.length > 0 || preserved.length > 0) {
234
+ console.log(`\n Config (${displayDest}\\config\\ — user-editable, preserved across upgrades):`);
235
+ for (const f of seeded) console.log(` - ${f} -> seeded from template`);
236
+ for (const f of preserved) console.log(` - ${f} -> preserved (already exists)`);
237
+ }
238
+
227
239
  // Mirror agents/kushi.agent.md as top-level SKILL.md for Clawpilot discovery.
228
240
  const agentSrc = path.join(PKG_ROOT, PLUGIN_SOURCE_DIR, CLAWPILOT_AGENT_SOURCE);
229
241
  const skillDst = path.join(fullDest, CLAWPILOT_SKILL_DEST);
@@ -276,4 +288,90 @@ async function confirmOverwriteIfExists(fullDest, displayDest, force) {
276
288
  console.log('\n Cancelled.\n');
277
289
  process.exit(0);
278
290
  }
291
+ }
292
+
293
+ /**
294
+ * Detect whether the cwd looks like a sane install target and, if not, print
295
+ * an actionable message. Three cases:
296
+ *
297
+ * 1. A code marker (package.json, .git, pyproject.toml, *.csproj, ...) or an
298
+ * engagement marker (Evidence/, State/, integrations.yml, .kushi/, ...)
299
+ * is present → silent, proceed.
300
+ * 2. The directory is empty → friendly INFO ("starting fresh — Kushi will
301
+ * create a new install here"). Empty dirs are the normal first-run case
302
+ * and should not produce a WARN.
303
+ * 3. The directory has files but no recognized marker → WARN with the
304
+ * actual fix (run `git init`, cd to an engagement folder, or pass --yes
305
+ * to skip this check in scripted use).
306
+ *
307
+ * @param {string} projectRoot
308
+ */
309
+ function warnIfNotProjectRoot(projectRoot) {
310
+ const marker = findProjectMarker(projectRoot);
311
+ if (marker) return;
312
+
313
+ let entries;
314
+ try {
315
+ entries = fs
316
+ .readdirSync(projectRoot)
317
+ .filter((n) => n !== '.DS_Store' && n !== 'Thumbs.db');
318
+ } catch {
319
+ entries = [];
320
+ }
321
+
322
+ if (entries.length === 0) {
323
+ console.log(
324
+ ' INFO: Empty directory detected — Kushi will create a fresh install here.\n',
325
+ );
326
+ return;
327
+ }
328
+
329
+ console.warn(
330
+ ' WARN: This directory has files but no recognized project marker',
331
+ );
332
+ console.warn(
333
+ ' (e.g. package.json, .git, pyproject.toml, *.csproj, or a Kushi',
334
+ );
335
+ console.warn(
336
+ ' engagement folder containing Evidence/, State/, or integrations.yml).',
337
+ );
338
+ console.warn('');
339
+ console.warn(' If this is intentional, press Enter to accept the default path.');
340
+ console.warn(' Otherwise, Ctrl+C and either:');
341
+ console.warn(' • run `git init` first (for a new code project), or');
342
+ console.warn(' • cd into your engagement folder (where Evidence/ lives), or');
343
+ console.warn(' • re-run with `--yes` to suppress this check in scripted use.\n');
344
+ }
345
+
346
+ /**
347
+ * Return the first matching project/engagement marker in `dir`, or null.
348
+ * @param {string} dir
349
+ * @returns {{ name: string, kind: 'code'|'engagement', type: 'file'|'dir' } | null}
350
+ */
351
+ function findProjectMarker(dir) {
352
+ let entries;
353
+ try {
354
+ entries = fs.readdirSync(dir, { withFileTypes: true });
355
+ } catch {
356
+ return null;
357
+ }
358
+
359
+ for (const marker of PROJECT_MARKERS) {
360
+ if (marker.glob) {
361
+ const hit = entries.find(
362
+ (e) => e.isFile() && marker.glob.test(e.name),
363
+ );
364
+ if (hit) return { name: hit.name, kind: marker.kind, type: 'file' };
365
+ continue;
366
+ }
367
+ const hit = entries.find((e) => e.name === marker.name);
368
+ if (!hit) continue;
369
+ if (marker.type === 'dir' && hit.isDirectory()) {
370
+ return { name: marker.name, kind: marker.kind, type: 'dir' };
371
+ }
372
+ if (marker.type === 'file' && hit.isFile()) {
373
+ return { name: marker.name, kind: marker.kind, type: 'file' };
374
+ }
375
+ }
376
+ return null;
279
377
  }
@@ -0,0 +1,44 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import {
4
+ CONFIG_DIR,
5
+ CONFIG_SEED_FILES,
6
+ PLUGIN_SOURCE_DIR,
7
+ } from './constants.mjs';
8
+
9
+ /**
10
+ * Seed <dest>/config/ with user-editable config files on FIRST INSTALL ONLY.
11
+ *
12
+ * Rules:
13
+ * - If <dest>/config/<file> already exists → skip (never overwrite).
14
+ * - Behavior is identical regardless of --force; config/ is user territory.
15
+ * - Returns a per-file result so the installer can summarize what happened.
16
+ *
17
+ * @param {string} sourcePkgDir – root of the npm package
18
+ * @param {string} destAbsolute – absolute install destination (e.g. <workspace>/.kushi)
19
+ * @returns {{ seeded: string[], preserved: string[] }}
20
+ */
21
+ export function seedConfig(sourcePkgDir, destAbsolute) {
22
+ const configDir = path.join(destAbsolute, CONFIG_DIR);
23
+ fs.mkdirSync(configDir, { recursive: true });
24
+
25
+ const seeded = [];
26
+ const preserved = [];
27
+
28
+ for (const { template, target } of CONFIG_SEED_FILES) {
29
+ const src = path.join(sourcePkgDir, PLUGIN_SOURCE_DIR, 'templates', template);
30
+ const dst = path.join(configDir, target);
31
+
32
+ if (!fs.existsSync(src)) continue;
33
+
34
+ if (fs.existsSync(dst)) {
35
+ preserved.push(target);
36
+ continue;
37
+ }
38
+
39
+ fs.cpSync(src, dst);
40
+ seeded.push(target);
41
+ }
42
+
43
+ return { seeded, preserved };
44
+ }